Merge "Add version to software-api and software client"
This commit is contained in:
commit
6c96e5d766
|
@ -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
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
@ -321,7 +321,7 @@ def software_command_not_implemented_yet(args):
|
||||||
def release_is_available_req(args):
|
def release_is_available_req(args):
|
||||||
|
|
||||||
releases = "/".join(args.release)
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -345,7 +345,7 @@ def release_is_available_req(args):
|
||||||
def release_is_deployed_req(args):
|
def release_is_deployed_req(args):
|
||||||
|
|
||||||
releases = "/".join(args.release)
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -369,7 +369,7 @@ def release_is_deployed_req(args):
|
||||||
def release_is_committed_req(args):
|
def release_is_committed_req(args):
|
||||||
|
|
||||||
releases = "/".join(args.release)
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -434,7 +434,7 @@ def release_upload_req(args):
|
||||||
encoder = MultipartEncoder(fields=to_upload_files)
|
encoder = MultipartEncoder(fields=to_upload_files)
|
||||||
headers = {'Content-Type': encoder.content_type}
|
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)
|
append_auth_token_if_required(headers)
|
||||||
req = requests.post(url,
|
req = requests.post(url,
|
||||||
data=to_upload_filenames if is_local else encoder,
|
data=to_upload_filenames if is_local else encoder,
|
||||||
|
@ -466,7 +466,7 @@ def release_delete_req(args):
|
||||||
# Ignore interrupts during this function
|
# Ignore interrupts during this function
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -498,7 +498,7 @@ def commit_patch_req(args):
|
||||||
elif args.all:
|
elif args.all:
|
||||||
# Get a list of all patches
|
# Get a list of all patches
|
||||||
extra_opts = "&release=%s" % relopt
|
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)
|
req = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
@ -527,7 +527,7 @@ def commit_patch_req(args):
|
||||||
patches = "/".join(args.patch)
|
patches = "/".join(args.patch)
|
||||||
|
|
||||||
# First, get a list of dependencies and ask for confirmation
|
# 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)
|
req = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
@ -548,7 +548,7 @@ def commit_patch_req(args):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Run dry-run
|
# 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)
|
req = requests.post(url, headers=headers)
|
||||||
print_software_op_result(req)
|
print_software_op_result(req)
|
||||||
|
@ -571,7 +571,7 @@ def commit_patch_req(args):
|
||||||
print("Aborting...")
|
print("Aborting...")
|
||||||
return 1
|
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)
|
req = requests.post(url, headers=headers)
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
|
@ -587,7 +587,7 @@ def release_list_req(args):
|
||||||
extra_opts = ""
|
extra_opts = ""
|
||||||
if args.release:
|
if args.release:
|
||||||
extra_opts = "&release=%s" % 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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
req = requests.get(url, headers=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):
|
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)
|
req = requests.get(url)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print_result_debug(req)
|
print_result_debug(req)
|
||||||
|
@ -676,7 +676,7 @@ def release_show_req(args):
|
||||||
# arg.release is a list
|
# arg.release is a list
|
||||||
releases = "/".join(args.release)
|
releases = "/".join(args.release)
|
||||||
|
|
||||||
url = "http://%s/software/show/%s" % (api_addr, releases)
|
url = "http://%s/v1/software/show/%s" % (api_addr, releases)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -692,7 +692,7 @@ def release_show_req(args):
|
||||||
|
|
||||||
|
|
||||||
def wait_for_install_complete(agent_ip):
|
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
|
rc = 0
|
||||||
|
|
||||||
max_retries = 4
|
max_retries = 4
|
||||||
|
@ -788,7 +788,7 @@ def host_install(args):
|
||||||
agent_ip = args.agent
|
agent_ip = args.agent
|
||||||
|
|
||||||
# Issue deploy_host request and poll for results
|
# 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:
|
if args.force:
|
||||||
url += "/force"
|
url += "/force"
|
||||||
|
@ -821,7 +821,7 @@ def host_install(args):
|
||||||
def drop_host(args):
|
def drop_host(args):
|
||||||
host_ip = args.host
|
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)
|
req = requests.post(url)
|
||||||
|
|
||||||
|
@ -837,7 +837,7 @@ def install_local(args): # pylint: disable=unused-argument
|
||||||
# Ignore interrupts during this function
|
# Ignore interrupts during this function
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(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'))
|
to_upload_files[software_file] = (software_file, open(software_file, 'rb'))
|
||||||
|
|
||||||
encoder = MultipartEncoder(fields=to_upload_files)
|
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}
|
headers = {'Content-Type': encoder.content_type}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
req = requests.post(url,
|
req = requests.post(url,
|
||||||
|
@ -896,7 +896,7 @@ def deploy_precheck_req(args):
|
||||||
region_name = args.region_name
|
region_name = args.region_name
|
||||||
|
|
||||||
# Issue deploy_precheck request
|
# 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:
|
if args.force:
|
||||||
url += "/force"
|
url += "/force"
|
||||||
url += "?region_name=%s" % region_name
|
url += "?region_name=%s" % region_name
|
||||||
|
@ -922,9 +922,9 @@ def deploy_start_req(args):
|
||||||
|
|
||||||
# Issue deploy_start request
|
# Issue deploy_start request
|
||||||
if args.force:
|
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:
|
else:
|
||||||
url = "http://%s/software/deploy_start/%s" % (api_addr, deployment)
|
url = "http://%s/v1/software/deploy_start/%s" % (api_addr, deployment)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -946,7 +946,7 @@ def deploy_activate_req(args):
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
# Issue deploy_start request
|
# 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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -968,7 +968,7 @@ def deploy_complete_req(args):
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
# Issue deploy_complete request
|
# 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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
@ -983,7 +983,7 @@ def deploy_complete_req(args):
|
||||||
|
|
||||||
|
|
||||||
def deploy_show_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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
req = requests.get(url, headers=headers)
|
req = requests.get(url, headers=headers)
|
||||||
|
@ -1025,7 +1025,7 @@ def deploy_host_req(args):
|
||||||
agent_ip = args.agent
|
agent_ip = args.agent
|
||||||
|
|
||||||
# Issue deploy_host request and poll for results
|
# 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:
|
if args.force:
|
||||||
url += "/force"
|
url += "/force"
|
||||||
|
@ -1060,7 +1060,7 @@ def patch_init_release(args):
|
||||||
|
|
||||||
release = args.release
|
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)
|
req = requests.post(url)
|
||||||
|
|
||||||
|
@ -1078,7 +1078,7 @@ def patch_del_release(args):
|
||||||
|
|
||||||
release = args.release
|
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)
|
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)
|
extra_opts_str = '?%s' % '&'.join(extra_opts)
|
||||||
|
|
||||||
patches = "/".join(args)
|
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)
|
% (api_addr, patches, extra_opts_str)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
|
@ -1111,7 +1111,7 @@ def patch_report_app_dependencies_req(args): # pylint: disable=unused-argument
|
||||||
|
|
||||||
|
|
||||||
def patch_query_app_dependencies_req():
|
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 = {}
|
headers = {}
|
||||||
append_auth_token_if_required(headers)
|
append_auth_token_if_required(headers)
|
||||||
|
|
|
@ -466,12 +466,14 @@ confidence=HIGH,
|
||||||
# W1505 deprecated-method
|
# W1505 deprecated-method
|
||||||
# W1514 unspecified-encoding
|
# W1514 unspecified-encoding
|
||||||
# W3101 missing-timeout
|
# 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,
|
C0301,C0302,C0325,C0411,C0413,C0415,
|
||||||
R0205,R0402,R0801,R0902,R0903,R0904,R0911,
|
R0205,R0402,R0801,R0902,R0903,R0904,R0911,
|
||||||
R0912,R0913,R0914,R0915,R1702,R1705,R1714,
|
R0912,R0913,R0914,R0915,R1702,R1705,R1714,
|
||||||
R1715,R1722,R1724,R1725,R1732,R1735,
|
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
|
# 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
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
|
|
@ -14,3 +14,4 @@ PyGObject
|
||||||
requests_toolbelt
|
requests_toolbelt
|
||||||
sh
|
sh
|
||||||
WebOb
|
WebOb
|
||||||
|
WSME>=0.5b2
|
||||||
|
|
|
@ -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
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import cgi
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from pecan import expose
|
import pecan
|
||||||
from pecan import request
|
from pecan import rest
|
||||||
import shutil
|
|
||||||
|
|
||||||
from software.exceptions import SoftwareError
|
from software.api.controllers import v1
|
||||||
from software.software_controller import sc
|
from software.api.controllers.v1 import base
|
||||||
import software.utils as utils
|
from software.api.controllers.v1 import link
|
||||||
import software.constants as constants
|
|
||||||
|
from wsme import types as wtypes
|
||||||
|
import wsmeext.pecan as wsme_pecan
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SoftwareAPIController(object):
|
class Version(base.APIBase):
|
||||||
|
"""An API version representation."""
|
||||||
|
|
||||||
@expose('json')
|
id = wtypes.text
|
||||||
def commit_patch(self, *args):
|
"The ID of the version, also acts as the release number"
|
||||||
try:
|
|
||||||
result = sc.patch_commit(list(args))
|
|
||||||
except SoftwareError as e:
|
|
||||||
return dict(error=str(e))
|
|
||||||
|
|
||||||
sc.software_sync()
|
links = [link.Link]
|
||||||
|
"A Link that point to a specific version of the API"
|
||||||
|
|
||||||
return result
|
@classmethod
|
||||||
|
def convert(self, id):
|
||||||
@expose('json')
|
version = Version()
|
||||||
def commit_dry_run(self, *args):
|
version.id = id
|
||||||
try:
|
version.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
result = sc.patch_commit(list(args), dry_run=True)
|
id, '', bookmark=True)]
|
||||||
except SoftwareError as e:
|
return version
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class RootController:
|
class Root(base.APIBase):
|
||||||
"""pecan REST API root"""
|
|
||||||
|
|
||||||
@expose()
|
name = wtypes.text
|
||||||
@expose('json')
|
"The name of the API"
|
||||||
def index(self):
|
|
||||||
"""index for the root"""
|
|
||||||
return "Unified Software Management API, Available versions: /v1"
|
|
||||||
|
|
||||||
software = SoftwareAPIController()
|
description = wtypes.text
|
||||||
v1 = SoftwareAPIController()
|
"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()
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue