Merge "Add version to software-api and software client"

This commit is contained in:
Zuul 2024-02-08 18:21:16 +00:00 committed by Gerrit Code Review
commit 6c96e5d766
8 changed files with 515 additions and 266 deletions

View File

@ -1,5 +1,5 @@
"""
Copyright (c) 2023 Wind River Systems, Inc.
Copyright (c) 2023-2024 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
@ -321,7 +321,7 @@ def software_command_not_implemented_yet(args):
def release_is_available_req(args):
releases = "/".join(args.release)
url = "http://%s/software/is_available/%s" % (api_addr, releases)
url = "http://%s/v1/software/is_available/%s" % (api_addr, releases)
headers = {}
append_auth_token_if_required(headers)
@ -345,7 +345,7 @@ def release_is_available_req(args):
def release_is_deployed_req(args):
releases = "/".join(args.release)
url = "http://%s/software/is_deployed/%s" % (api_addr, releases)
url = "http://%s/v1/software/is_deployed/%s" % (api_addr, releases)
headers = {}
append_auth_token_if_required(headers)
@ -369,7 +369,7 @@ def release_is_deployed_req(args):
def release_is_committed_req(args):
releases = "/".join(args.release)
url = "http://%s/software/is_committed/%s" % (api_addr, releases)
url = "http://%s/v1/software/is_committed/%s" % (api_addr, releases)
headers = {}
append_auth_token_if_required(headers)
@ -434,7 +434,7 @@ def release_upload_req(args):
encoder = MultipartEncoder(fields=to_upload_files)
headers = {'Content-Type': encoder.content_type}
url = "http://%s/software/upload" % api_addr
url = "http://%s/v1/software/upload" % api_addr
append_auth_token_if_required(headers)
req = requests.post(url,
data=to_upload_filenames if is_local else encoder,
@ -466,7 +466,7 @@ def release_delete_req(args):
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
url = "http://%s/software/delete/%s" % (api_addr, releases)
url = "http://%s/v1/software/delete/%s" % (api_addr, releases)
headers = {}
append_auth_token_if_required(headers)
@ -498,7 +498,7 @@ def commit_patch_req(args):
elif args.all:
# Get a list of all patches
extra_opts = "&release=%s" % relopt
url = "http://%s/software/query?show=patch%s" % (api_addr, extra_opts)
url = "http://%s/v1/software/query?show=patch%s" % (api_addr, extra_opts)
req = requests.get(url, headers=headers)
@ -527,7 +527,7 @@ def commit_patch_req(args):
patches = "/".join(args.patch)
# First, get a list of dependencies and ask for confirmation
url = "http://%s/software/query_dependencies/%s?recursive=yes" % (api_addr, patches)
url = "http://%s/v1/software/query_dependencies/%s?recursive=yes" % (api_addr, patches)
req = requests.get(url, headers=headers)
@ -548,7 +548,7 @@ def commit_patch_req(args):
return 1
# Run dry-run
url = "http://%s/software/commit_dry_run/%s" % (api_addr, patches)
url = "http://%s/v1/software/commit_dry_run/%s" % (api_addr, patches)
req = requests.post(url, headers=headers)
print_software_op_result(req)
@ -571,7 +571,7 @@ def commit_patch_req(args):
print("Aborting...")
return 1
url = "http://%s/software/commit_patch/%s" % (api_addr, patches)
url = "http://%s/v1/software/commit_patch/%s" % (api_addr, patches)
req = requests.post(url, headers=headers)
if args.debug:
@ -587,7 +587,7 @@ def release_list_req(args):
extra_opts = ""
if args.release:
extra_opts = "&release=%s" % args.release
url = "http://%s/software/query?show=%s%s" % (api_addr, state, extra_opts)
url = "http://%s/v1/software/query?show=%s%s" % (api_addr, state, extra_opts)
headers = {}
append_auth_token_if_required(headers)
req = requests.get(url, headers=headers)
@ -662,7 +662,7 @@ def print_software_deploy_host_list_result(req):
def deploy_host_list_req(args):
url = "http://%s/software/host_list" % api_addr
url = "http://%s/v1/software/host_list" % api_addr
req = requests.get(url)
if args.debug:
print_result_debug(req)
@ -676,7 +676,7 @@ def release_show_req(args):
# arg.release is a list
releases = "/".join(args.release)
url = "http://%s/software/show/%s" % (api_addr, releases)
url = "http://%s/v1/software/show/%s" % (api_addr, releases)
headers = {}
append_auth_token_if_required(headers)
@ -692,7 +692,7 @@ def release_show_req(args):
def wait_for_install_complete(agent_ip):
url = "http://%s/software/host_list" % api_addr
url = "http://%s/v1/software/host_list" % api_addr
rc = 0
max_retries = 4
@ -788,7 +788,7 @@ def host_install(args):
agent_ip = args.agent
# Issue deploy_host request and poll for results
url = "http://%s/software/deploy_host/%s" % (api_addr, agent_ip)
url = "http://%s/v1/software/deploy_host/%s" % (api_addr, agent_ip)
if args.force:
url += "/force"
@ -821,7 +821,7 @@ def host_install(args):
def drop_host(args):
host_ip = args.host
url = "http://%s/software/drop_host/%s" % (api_addr, host_ip)
url = "http://%s/v1/software/drop_host/%s" % (api_addr, host_ip)
req = requests.post(url)
@ -837,7 +837,7 @@ def install_local(args): # pylint: disable=unused-argument
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
url = "http://%s/software/install_local" % (api_addr)
url = "http://%s/v1/software/install_local" % (api_addr)
headers = {}
append_auth_token_if_required(headers)
@ -875,7 +875,7 @@ def release_upload_dir_req(args):
to_upload_files[software_file] = (software_file, open(software_file, 'rb'))
encoder = MultipartEncoder(fields=to_upload_files)
url = "http://%s/software/upload" % api_addr
url = "http://%s/v1/software/upload" % api_addr
headers = {'Content-Type': encoder.content_type}
append_auth_token_if_required(headers)
req = requests.post(url,
@ -896,7 +896,7 @@ def deploy_precheck_req(args):
region_name = args.region_name
# Issue deploy_precheck request
url = "http://%s/software/deploy_precheck/%s" % (api_addr, deployment)
url = "http://%s/v1/software/deploy_precheck/%s" % (api_addr, deployment)
if args.force:
url += "/force"
url += "?region_name=%s" % region_name
@ -922,9 +922,9 @@ def deploy_start_req(args):
# Issue deploy_start request
if args.force:
url = "http://%s/software/deploy_start/%s/force" % (api_addr, deployment)
url = "http://%s/v1/software/deploy_start/%s/force" % (api_addr, deployment)
else:
url = "http://%s/software/deploy_start/%s" % (api_addr, deployment)
url = "http://%s/v1/software/deploy_start/%s" % (api_addr, deployment)
headers = {}
append_auth_token_if_required(headers)
@ -946,7 +946,7 @@ def deploy_activate_req(args):
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_start request
url = "http://%s/software/deploy_activate/%s" % (api_addr, deployment)
url = "http://%s/v1/software/deploy_activate/%s" % (api_addr, deployment)
headers = {}
append_auth_token_if_required(headers)
@ -968,7 +968,7 @@ def deploy_complete_req(args):
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Issue deploy_complete request
url = "http://%s/software/deploy_complete/%s" % (api_addr, deployment)
url = "http://%s/v1/software/deploy_complete/%s" % (api_addr, deployment)
headers = {}
append_auth_token_if_required(headers)
@ -983,7 +983,7 @@ def deploy_complete_req(args):
def deploy_show_req(args):
url = "http://%s/software/deploy_show" % api_addr
url = "http://%s/v1/software/deploy_show" % api_addr
headers = {}
append_auth_token_if_required(headers)
req = requests.get(url, headers=headers)
@ -1025,7 +1025,7 @@ def deploy_host_req(args):
agent_ip = args.agent
# Issue deploy_host request and poll for results
url = "http://%s/software/deploy_host/%s" % (api_addr, agent_ip)
url = "http://%s/v1/software/deploy_host/%s" % (api_addr, agent_ip)
if args.force:
url += "/force"
@ -1060,7 +1060,7 @@ def patch_init_release(args):
release = args.release
url = "http://%s/software/init_release/%s" % (api_addr, release)
url = "http://%s/v1/software/init_release/%s" % (api_addr, release)
req = requests.post(url)
@ -1078,7 +1078,7 @@ def patch_del_release(args):
release = args.release
url = "http://%s/software/del_release/%s" % (api_addr, release)
url = "http://%s/v1/software/del_release/%s" % (api_addr, release)
req = requests.post(url)
@ -1095,7 +1095,7 @@ def patch_report_app_dependencies_req(args): # pylint: disable=unused-argument
extra_opts_str = '?%s' % '&'.join(extra_opts)
patches = "/".join(args)
url = "http://%s/software/report_app_dependencies/%s%s" \
url = "http://%s/v1/software/report_app_dependencies/%s%s" \
% (api_addr, patches, extra_opts_str)
headers = {}
@ -1111,7 +1111,7 @@ def patch_report_app_dependencies_req(args): # pylint: disable=unused-argument
def patch_query_app_dependencies_req():
url = "http://%s/software/query_app_dependencies" % api_addr
url = "http://%s/v1/software/query_app_dependencies" % api_addr
headers = {}
append_auth_token_if_required(headers)

View File

@ -466,12 +466,14 @@ confidence=HIGH,
# W1505 deprecated-method
# W1514 unspecified-encoding
# W3101 missing-timeout
disable= C0103,C0114,C0115,C0116,C0201,C0206,C0209,C2801,
disable= C0103,C0114,C0115,C0116,C0201,C0202,C0206,C0209,C2801,
C0301,C0302,C0325,C0411,C0413,C0415,
R0205,R0402,R0801,R0902,R0903,R0904,R0911,
R0912,R0913,R0914,R0915,R1702,R1705,R1714,
R1715,R1722,R1724,R1725,R1732,R1735,
W0107,W0602,W0603,W0703,W0707,W0719,W1201,W1514,W3101
W0107,W0231,W0602,W0603,W0621,W0622,
W0703,W0707,W0719,W1201,W1514,W3101,
E0605
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option

View File

@ -14,3 +14,4 @@ PyGObject
requests_toolbelt
sh
WebOb
WSME>=0.5b2

View File

@ -1,253 +1,75 @@
"""
Copyright (c) 2023 Wind River Systems, Inc.
Copyright (c) 2023-2024 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import cgi
import json
import os
from oslo_log import log
from pecan import expose
from pecan import request
import shutil
import pecan
from pecan import rest
from software.exceptions import SoftwareError
from software.software_controller import sc
import software.utils as utils
import software.constants as constants
from software.api.controllers import v1
from software.api.controllers.v1 import base
from software.api.controllers.v1 import link
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
LOG = log.getLogger(__name__)
class SoftwareAPIController(object):
class Version(base.APIBase):
"""An API version representation."""
@expose('json')
def commit_patch(self, *args):
try:
result = sc.patch_commit(list(args))
except SoftwareError as e:
return dict(error=str(e))
id = wtypes.text
"The ID of the version, also acts as the release number"
sc.software_sync()
links = [link.Link]
"A Link that point to a specific version of the API"
return result
@expose('json')
def commit_dry_run(self, *args):
try:
result = sc.patch_commit(list(args), dry_run=True)
except SoftwareError as e:
return dict(error=str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def delete(self, *args):
try:
result = sc.software_release_delete_api(list(args))
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_activate(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
try:
result = sc.software_deploy_activate_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_complete(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
try:
result = sc.software_deploy_complete_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_host(self, *args):
if len(list(args)) == 0:
return dict(error="Host must be specified for install")
force = False
if len(list(args)) > 1 and 'force' in list(args)[1:]:
force = True
try:
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_precheck(self, *args, **kwargs):
force = False
if 'force' in list(args):
force = True
try:
result = sc.software_deploy_precheck_api(list(args)[0], force, **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_start(self, *args, **kwargs):
# if --force is provided
force = 'force' in list(args)
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing releases.")
try:
result = sc.software_deploy_start_api(list(args)[0], force, **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.send_latest_feed_commit_to_agent()
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_show(self):
try:
result = sc.software_deploy_show_api()
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def install_local(self):
try:
result = sc.software_install_local_api()
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
def is_available(self, *args):
return sc.is_available(list(args))
@expose('json')
def is_committed(self, *args):
return sc.is_committed(list(args))
@expose('json')
def is_deployed(self, *args):
return sc.is_deployed(list(args))
@expose('json')
@expose('show.xml', content_type='application/xml')
def show(self, *args):
try:
result = sc.software_release_query_specific_cached(list(args))
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def upload(self):
is_local = False
temp_dir = None
uploaded_files = []
request_data = []
local_files = []
# --local option only sends a list of file names
if (request.content_type == "text/plain"):
local_files = list(json.loads(request.body))
is_local = True
else:
request_data = list(request.POST.items())
temp_dir = os.path.join(constants.SCRATCH_DIR, 'upload_files')
try:
if len(request_data) == 0 and len(local_files) == 0:
raise SoftwareError("No files uploaded")
if is_local:
uploaded_files = local_files
LOG.info("Uploaded local files: %s", uploaded_files)
else:
# Protect against duplications
uploaded_files = sorted(set(request_data))
# Save all uploaded files to /scratch/upload_files dir
for file_item in uploaded_files:
assert isinstance(file_item[1], cgi.FieldStorage)
utils.save_temp_file(file_item[1], temp_dir)
# Get all uploaded files from /scratch dir
uploaded_files = utils.get_all_files(temp_dir)
LOG.info("Uploaded files: %s", uploaded_files)
# Process uploaded files
return sc.software_release_upload(uploaded_files)
except Exception as e:
return dict(error=str(e))
finally:
# Remove all uploaded files from /scratch dir
sc.software_sync()
if temp_dir:
shutil.rmtree(temp_dir, ignore_errors=True)
@expose('json')
@expose('query.xml', content_type='application/xml')
def query(self, **kwargs):
try:
sd = sc.software_release_query_cached(**kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return dict(sd=sd)
@expose('json')
@expose('query_hosts.xml', content_type='application/xml')
def host_list(self, *args): # pylint: disable=unused-argument
try:
query_hosts = sc.deploy_host_list()
except Exception as e:
return dict(error=str(e))
return dict(data=query_hosts)
@classmethod
def convert(self, id):
version = Version()
version.id = id
version.links = [link.Link.make_link('self', pecan.request.host_url,
id, '', bookmark=True)]
return version
class RootController:
"""pecan REST API root"""
class Root(base.APIBase):
@expose()
@expose('json')
def index(self):
"""index for the root"""
return "Unified Software Management API, Available versions: /v1"
name = wtypes.text
"The name of the API"
software = SoftwareAPIController()
v1 = SoftwareAPIController()
description = wtypes.text
"Some information about this API"
versions = [Version]
"Links to all the versions available in this API"
default_version = Version
"A link to the default version of the API"
@classmethod
def convert(self):
root = Root()
root.name = "StarlingX USM API"
root.description = ("Unified Software Management API allows for a "
"single REST API / CLI and single procedure for updating "
"the StarlingX software on a Standalone Cloud or Distributed Cloud."
)
root.versions = [Version.convert('v1')]
root.default_version = Version.convert('v1')
return root
class RootController(rest.RestController):
v1 = v1.Controller()
@wsme_pecan.wsexpose(Root)
def get(self):
# NOTE: The reason why convert() it's being called for every
# request is because we need to get the host url from
# the request object to make the links.
return Root.convert()

View File

@ -0,0 +1,88 @@
#
# Copyright (c) 2013-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# All Rights Reserved.
#
"""
Version 1 of the USM API
Specification can be found in code repo.
"""
import pecan
import wsmeext.pecan as wsme_pecan
from pecan import rest
from wsme import types as wtypes
from software.api.controllers.v1 import base
from software.api.controllers.v1 import link
from software.api.controllers.v1 import software
class MediaType(base.APIBase):
"""A media type representation."""
base = wtypes.text
type = wtypes.text
def __init__(self, base, type):
self.base = base
self.type = type
class V1(base.APIBase):
"""The representation of the version 1 of the API."""
id = wtypes.text
"The ID of the version, also acts as the release number"
media_types = [MediaType]
"An array of supported media types for this version"
links = [link.Link]
"Links that point to a specific URL for this version and documentation"
software = [link.Link]
"Links to the software resource"
@classmethod
def convert(self):
v1 = V1()
v1.id = "v1"
v1.links = [link.Link.make_link('self', pecan.request.host_url,
'v1', '', bookmark=True),
]
v1.media_types = [MediaType('application/json',
'application/vnd.openstack.software.v1+json')]
v1.software = [link.Link.make_link('self', pecan.request.host_url,
'software', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'software', '',
bookmark=True)
]
return v1
class Controller(rest.RestController):
"""Version 1 API controller root."""
software = software.SoftwareAPIController()
@wsme_pecan.wsexpose(V1)
def get(self):
# NOTE: The reason why convert() it's being called for every
# request is because we need to get the host url from
# the request object to make the links.
return V1.convert()
__all__ = (Controller)

View File

@ -0,0 +1,52 @@
"""
Copyright (c) 2013-2024 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import datetime
import wsme
from wsme import types as wtypes
class APIBase(wtypes.Base):
created_at = datetime.datetime
"The time in UTC at which the object is created"
updated_at = datetime.datetime
"The time in UTC at which the object is updated"
def as_dict(self):
"""Render this object as a dict of its fields."""
return dict((k, getattr(self, k))
for k in self.fields # pylint: disable=no-member
if hasattr(self, k) and
getattr(self, k) != wsme.Unset)
def unset_fields_except(self, except_list=None):
"""Unset fields so they don't appear in the message body.
:param except_list: A list of fields that won't be touched.
"""
if except_list is None:
except_list = []
for k in self.as_dict():
if k not in except_list:
setattr(self, k, wsme.Unset)
@classmethod
def from_rpc_object(cls, m, fields=None):
"""Convert a RPC object to an API object."""
obj_dict = m.as_dict()
# Unset non-required fields so they do not appear
# in the message body
obj_dict.update(dict((k, wsme.Unset)
for k in obj_dict.keys()
if fields and k not in fields))
return cls(**obj_dict)

View File

@ -0,0 +1,43 @@
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2024 Wind River Systems, Inc.
#
from wsme import types as wtypes
from software.api.controllers.v1 import base
class Link(base.APIBase):
"""A link representation."""
href = wtypes.text
"The url of a link."
rel = wtypes.text
"The name of a link."
type = wtypes.text
"Indicates the type of document/link."
@classmethod
def make_link(cls, rel_name, url, resource, resource_args,
bookmark=False, type=wtypes.Unset):
template = '%s/%s' if bookmark else '%s/v1/%s'
template += '%s' if resource_args.startswith('?') else '/%s'
return Link(href=(template) % (url, resource, resource_args),
rel=rel_name, type=type)

View File

@ -0,0 +1,241 @@
"""
Copyright (c) 2023-2024 Wind River Systems, Inc.
SPDX-License-Identifier: Apache-2.0
"""
import cgi
import json
import os
from oslo_log import log
from pecan import expose
from pecan import request
import shutil
from software.exceptions import SoftwareError
from software.software_controller import sc
import software.utils as utils
import software.constants as constants
LOG = log.getLogger(__name__)
class SoftwareAPIController(object):
@expose('json')
def commit_patch(self, *args):
try:
result = sc.patch_commit(list(args))
except SoftwareError as e:
return dict(error=str(e))
sc.software_sync()
return result
@expose('json')
def commit_dry_run(self, *args):
try:
result = sc.patch_commit(list(args), dry_run=True)
except SoftwareError as e:
return dict(error=str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def delete(self, *args):
try:
result = sc.software_release_delete_api(list(args))
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_activate(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
try:
result = sc.software_deploy_activate_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_complete(self, *args):
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing a release.")
try:
result = sc.software_deploy_complete_api(list(args)[0])
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_host(self, *args):
if len(list(args)) == 0:
return dict(error="Host must be specified for install")
force = False
if len(list(args)) > 1 and 'force' in list(args)[1:]:
force = True
try:
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_precheck(self, *args, **kwargs):
force = False
if 'force' in list(args):
force = True
try:
result = sc.software_deploy_precheck_api(list(args)[0], force, **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_start(self, *args, **kwargs):
# if --force is provided
force = 'force' in list(args)
if sc.any_patch_host_installing():
return dict(error="Rejected: One or more nodes are installing releases.")
try:
result = sc.software_deploy_start_api(list(args)[0], force, **kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
sc.send_latest_feed_commit_to_agent()
sc.software_sync()
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def deploy_show(self):
try:
result = sc.software_deploy_show_api()
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def install_local(self):
try:
result = sc.software_install_local_api()
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
def is_available(self, *args):
return sc.is_available(list(args))
@expose('json')
def is_committed(self, *args):
return sc.is_committed(list(args))
@expose('json')
def is_deployed(self, *args):
return sc.is_deployed(list(args))
@expose('json')
@expose('show.xml', content_type='application/xml')
def show(self, *args):
try:
result = sc.software_release_query_specific_cached(list(args))
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def upload(self):
is_local = False
temp_dir = None
uploaded_files = []
request_data = []
local_files = []
# --local option only sends a list of file names
if (request.content_type == "text/plain"):
local_files = list(json.loads(request.body))
is_local = True
else:
request_data = list(request.POST.items())
temp_dir = os.path.join(constants.SCRATCH_DIR, 'upload_files')
try:
if len(request_data) == 0 and len(local_files) == 0:
raise SoftwareError("No files uploaded")
if is_local:
uploaded_files = local_files
LOG.info("Uploaded local files: %s", uploaded_files)
else:
# Protect against duplications
uploaded_files = sorted(set(request_data))
# Save all uploaded files to /scratch/upload_files dir
for file_item in uploaded_files:
assert isinstance(file_item[1], cgi.FieldStorage)
utils.save_temp_file(file_item[1], temp_dir)
# Get all uploaded files from /scratch dir
uploaded_files = utils.get_all_files(temp_dir)
LOG.info("Uploaded files: %s", uploaded_files)
# Process uploaded files
return sc.software_release_upload(uploaded_files)
except Exception as e:
return dict(error=str(e))
finally:
# Remove all uploaded files from /scratch dir
sc.software_sync()
if temp_dir:
shutil.rmtree(temp_dir, ignore_errors=True)
@expose('json')
@expose('query.xml', content_type='application/xml')
def query(self, **kwargs):
try:
sd = sc.software_release_query_cached(**kwargs)
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return dict(sd=sd)
@expose('json')
@expose('query_hosts.xml', content_type='application/xml')
def host_list(self, *args): # pylint: disable=unused-argument
try:
query_hosts = sc.deploy_host_list()
except Exception as e:
return dict(error=str(e))
return dict(data=query_hosts)