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
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,3 +14,4 @@ PyGObject
|
|||
requests_toolbelt
|
||||
sh
|
||||
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
|
||||
|
||||
"""
|
||||
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()
|
||||
|
|
|
@ -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