
When the valid files are being uploaded, the error messages were mistakenly displayed due to missing absolute file path. The fix is to ensure the absolute file path is added when filtering invalid files. Test Plan: PASS: uploaded valid files and no error message displayed. PASS: uploaded invalid files and error message displayed. Task: 49380 Story: 2010676 Change-Id: I6e5a68056892cbfdc3377e511974d7bfdb50b240 Signed-off-by: junfeng-li <junfeng.li@windriver.com>
1533 lines
51 KiB
Python
1533 lines
51 KiB
Python
"""
|
|
Copyright (c) 2023 Wind River Systems, Inc.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""
|
|
# PYTHON_ARGCOMPLETE_OK
|
|
import argcomplete
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import requests
|
|
import signal
|
|
import software_client.constants as constants
|
|
import subprocess
|
|
import sys
|
|
import textwrap
|
|
import time
|
|
|
|
from requests_toolbelt import MultipartEncoder
|
|
from urllib.parse import urlparse
|
|
|
|
from tsconfig.tsconfig import SW_VERSION as RUNNING_SW_VERSION
|
|
|
|
api_addr = "127.0.0.1:5493"
|
|
auth_token = None
|
|
|
|
TERM_WIDTH = 72
|
|
VIRTUAL_REGION = 'SystemController'
|
|
IPV6_FAMILY = 6
|
|
|
|
|
|
def set_term_width():
|
|
global TERM_WIDTH
|
|
|
|
try:
|
|
with open(os.devnull, 'w') as NULL:
|
|
output = subprocess.check_output(["tput", "cols"], stderr=NULL)
|
|
width = int(output)
|
|
if width > 60:
|
|
TERM_WIDTH = width - 4
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def check_rc(req):
|
|
rc = 0
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'error' in data and data["error"] != "":
|
|
rc = 1
|
|
else:
|
|
rc = 1
|
|
|
|
return rc
|
|
|
|
|
|
def print_result_debug(req):
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'sd' in data:
|
|
print(json.dumps(data['sd'],
|
|
sort_keys=True,
|
|
indent=4,
|
|
separators=(',', ': ')))
|
|
elif 'data' in data:
|
|
print(json.dumps(data['data'],
|
|
sort_keys=True,
|
|
indent=4,
|
|
separators=(',', ': ')))
|
|
else:
|
|
print(json.dumps(data,
|
|
sort_keys=True,
|
|
indent=4,
|
|
separators=(',', ': ')))
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
else:
|
|
m = re.search("(Error message:.*)", req.text, re.MULTILINE)
|
|
if m:
|
|
print(m.group(0))
|
|
else:
|
|
print("%s %s" % (req.status_code, req.reason))
|
|
|
|
|
|
def print_software_op_result(req):
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
|
|
if 'sd' in data:
|
|
sd = data['sd']
|
|
|
|
# Calculate column widths
|
|
hdr_release = "Release"
|
|
hdr_version = "Version"
|
|
hdr_rr = "RR"
|
|
hdr_state = "State"
|
|
|
|
width_release = len(hdr_release)
|
|
width_version = len(hdr_version)
|
|
width_rr = len(hdr_rr)
|
|
width_state = len(hdr_state)
|
|
|
|
show_all = False
|
|
|
|
for release_id in list(sd):
|
|
width_release = max(len(release_id), width_release)
|
|
width_state = max(len(sd[release_id]["state"]), width_state)
|
|
if "sw_version" in sd[release_id]:
|
|
show_all = True
|
|
width_version = max(len(sd[release_id]["sw_version"]), width_version)
|
|
|
|
if show_all:
|
|
print("{0:^{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}}".format(
|
|
hdr_release, hdr_rr, hdr_version, hdr_state,
|
|
width_release=width_release, width_rr=width_rr,
|
|
width_version=width_version, width_state=width_state))
|
|
|
|
print("{0} {1} {2} {3}".format(
|
|
'=' * width_release, '=' * width_rr, '=' * width_version, '=' * width_state))
|
|
|
|
for release_id in sorted(list(sd)):
|
|
if "reboot_required" in sd[release_id]:
|
|
rr = sd[release_id]["reboot_required"]
|
|
else:
|
|
rr = "Y"
|
|
|
|
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_version}} {3:^{width_state}}".format(
|
|
release_id,
|
|
rr,
|
|
sd[release_id]["sw_version"],
|
|
sd[release_id]["state"],
|
|
width_release=width_release, width_rr=width_rr,
|
|
width_version=width_version, width_state=width_state))
|
|
else:
|
|
print("{0:^{width_release}} {1:^{width_state}}".format(
|
|
hdr_release, hdr_state,
|
|
width_release=width_release, width_state=width_state))
|
|
|
|
print("{0} {1}".format(
|
|
'=' * width_release, '=' * width_state))
|
|
|
|
for release_id in sorted(list(sd)):
|
|
if "reboot_required" in sd[release_id]:
|
|
rr = sd[release_id]["reboot_required"]
|
|
else:
|
|
rr = "Y"
|
|
|
|
print("{0:<{width_release}} {1:^{width_rr}} {2:^{width_state}}".format(
|
|
release_id,
|
|
rr,
|
|
sd[release_id]["state"],
|
|
width_release=width_release, width_rr=width_rr,
|
|
width_state=width_state))
|
|
|
|
print("")
|
|
|
|
if 'info' in data and data["info"] != "":
|
|
print(data["info"])
|
|
|
|
if 'warning' in data and data["warning"] != "":
|
|
print("Warning:")
|
|
print(data["warning"])
|
|
|
|
if 'error' in data and data["error"] != "":
|
|
print("Error:")
|
|
print(data["error"])
|
|
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
else:
|
|
print("Error: %s has occurred. %s" % (req.status_code, req.reason))
|
|
|
|
|
|
def print_release_show_result(req):
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
|
|
if 'metadata' in data:
|
|
sd = data['metadata']
|
|
contents = data['contents']
|
|
for release_id in sorted(list(sd)):
|
|
print("%s:" % release_id)
|
|
|
|
if "sw_version" in sd[release_id] and sd[release_id]["sw_version"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Version:") + sd[release_id]["sw_version"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "state" in sd[release_id] and sd[release_id]["state"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("State:") + sd[release_id]["state"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "status" in sd[release_id] and sd[release_id]["status"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Status:") + sd[release_id]["status"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "unremovable" in sd[release_id] and sd[release_id]["unremovable"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Unremovable:") + sd[release_id]["unremovable"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "reboot_required" in sd[release_id] and sd[release_id]["reboot_required"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("RR:") + sd[release_id]["reboot_required"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "apply_active_release_only" in sd[release_id] and sd[release_id]["apply_active_release_only"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Apply Active Release Only:") + sd[release_id]["apply_active_release_only"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "summary" in sd[release_id] and sd[release_id]["summary"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Summary:") + sd[release_id]["summary"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
if "description" in sd[release_id] and sd[release_id]["description"] != "":
|
|
first_line = True
|
|
for line in sd[release_id]["description"].split('\n'):
|
|
if first_line:
|
|
print(textwrap.fill(" {0:<15} ".format("Description:") + line,
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
first_line = False
|
|
else:
|
|
print(textwrap.fill(line,
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
initial_indent=' ' * 20))
|
|
|
|
if "install_instructions" in sd[release_id] and sd[release_id]["install_instructions"] != "":
|
|
print(" Install Instructions:")
|
|
for line in sd[release_id]["install_instructions"].split('\n'):
|
|
print(textwrap.fill(line,
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
initial_indent=' ' * 20))
|
|
|
|
if "warnings" in sd[release_id] and sd[release_id]["warnings"] != "":
|
|
first_line = True
|
|
for line in sd[release_id]["warnings"].split('\n'):
|
|
if first_line:
|
|
print(textwrap.fill(" {0:<15} ".format("Warnings:") + line,
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
first_line = False
|
|
else:
|
|
print(textwrap.fill(line,
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
initial_indent=' ' * 20))
|
|
|
|
if "requires" in sd[release_id] and len(sd[release_id]["requires"]) > 0:
|
|
print(" Requires:")
|
|
for req_patch in sorted(sd[release_id]["requires"]):
|
|
print(' ' * 20 + req_patch)
|
|
|
|
if "contents" in data and release_id in data["contents"]:
|
|
print(" Contents:\n")
|
|
if "number_of_commits" in contents[release_id] and \
|
|
contents[release_id]["number_of_commits"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("No. of commits:") +
|
|
contents[release_id]["number_of_commits"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
if "base" in contents[release_id] and \
|
|
contents[release_id]["base"]["commit"] != "":
|
|
print(textwrap.fill(" {0:<15} ".format("Base commit:") +
|
|
contents[release_id]["base"]["commit"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
if "number_of_commits" in contents[release_id] and \
|
|
contents[release_id]["number_of_commits"] != "":
|
|
for i in range(int(contents[release_id]["number_of_commits"])):
|
|
print(textwrap.fill(" {0:<15} ".format("Commit%s:" % (i + 1)) +
|
|
contents[release_id]["commit%s" % (i + 1)]["commit"],
|
|
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
|
|
print("\n")
|
|
|
|
if 'info' in data and data["info"] != "":
|
|
print(data["info"])
|
|
|
|
if 'warning' in data and data["warning"] != "":
|
|
print("Warning:")
|
|
print(data["warning"])
|
|
|
|
if 'error' in data and data["error"] != "":
|
|
print("Error:")
|
|
print(data["error"])
|
|
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
|
|
|
|
def software_command_not_implemented_yet(args):
|
|
print("NOT IMPLEMENTED %s" % args)
|
|
return 1
|
|
|
|
|
|
def release_is_available_req(args):
|
|
|
|
releases = "/".join(args.release)
|
|
url = "http://%s/software/is_available/%s" % (api_addr, releases)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
rc = 1
|
|
|
|
if req.status_code == 200:
|
|
result = json.loads(req.text)
|
|
print(result)
|
|
if result is True:
|
|
rc = 0
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
else:
|
|
print("Error: %s has occurred. %s" % (req.status_code, req.reason))
|
|
|
|
return rc
|
|
|
|
|
|
def release_is_deployed_req(args):
|
|
|
|
releases = "/".join(args.release)
|
|
url = "http://%s/software/is_deployed/%s" % (api_addr, releases)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
rc = 1
|
|
|
|
if req.status_code == 200:
|
|
result = json.loads(req.text)
|
|
print(result)
|
|
if result is True:
|
|
rc = 0
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
else:
|
|
print("Error: %s has occurred. %s" % (req.status_code, req.reason))
|
|
|
|
return rc
|
|
|
|
|
|
def release_is_committed_req(args):
|
|
|
|
releases = "/".join(args.release)
|
|
url = "http://%s/software/is_committed/%s" % (api_addr, releases)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
rc = 1
|
|
|
|
if req.status_code == 200:
|
|
result = json.loads(req.text)
|
|
print(result)
|
|
if result is True:
|
|
rc = 0
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
else:
|
|
print("Error: %s has occurred. %s" % (req.status_code, req.reason))
|
|
|
|
return rc
|
|
|
|
|
|
def release_upload_req(args):
|
|
rc = 0
|
|
|
|
# arg.release is a list
|
|
releases = args.release
|
|
is_local = args.local # defaults to False
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
to_upload_files = {}
|
|
valid_files = []
|
|
invalid_files = []
|
|
|
|
# Validate all the files
|
|
valid_files = [os.path.abspath(software_file) for software_file in releases if os.path.isfile(
|
|
software_file) and os.path.splitext(software_file)[1] in constants.SUPPORTED_UPLOAD_FILE_EXT]
|
|
invalid_files = [os.path.abspath(software_file) for software_file in releases
|
|
if os.path.abspath(software_file) not in valid_files]
|
|
|
|
for software_file in invalid_files:
|
|
if os.path.isdir(software_file):
|
|
print("Error: %s is a directory. Please use upload-dir" % software_file)
|
|
elif os.path.isfile(software_file):
|
|
print("Error: %s has the unsupported file extension." % software_file)
|
|
else:
|
|
print("Error: File does not exist: %s" % software_file)
|
|
|
|
if len(valid_files) == 0:
|
|
print("No file to be uploaded.")
|
|
return rc
|
|
|
|
if is_local:
|
|
to_upload_filenames = json.dumps(valid_files)
|
|
headers = {'Content-Type': 'text/plain'}
|
|
else:
|
|
for software_file in valid_files:
|
|
with open(software_file, 'rb') as file:
|
|
data_content = file.read()
|
|
to_upload_files[software_file] = (software_file, data_content)
|
|
|
|
encoder = MultipartEncoder(fields=to_upload_files)
|
|
headers = {'Content-Type': encoder.content_type}
|
|
|
|
url = "http://%s/software/upload" % api_addr
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url,
|
|
data=to_upload_filenames if is_local else encoder,
|
|
headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
data = json.loads(req.text)
|
|
data_list = [(k, v["id"], v["sw_version"])
|
|
for d in data["upload_info"] for k, v in d.items()]
|
|
|
|
header_data_list = ["Uploaded File", "Id", "SW Version"]
|
|
|
|
# Find the longest header string in each column
|
|
header_lengths = [len(str(x)) for x in header_data_list]
|
|
# Find the longest content string in each column
|
|
content_lengths = [max(len(str(x[i])) for x in data_list)
|
|
for i in range(len(header_data_list))]
|
|
# Find the max of the two for each column
|
|
col_lengths = [(x if x > y else y) for x, y in zip(header_lengths, content_lengths)]
|
|
|
|
print(' '.join(f"{x.center(col_lengths[i])}" for i, x in enumerate(header_data_list)))
|
|
print(' '.join('=' * length for length in col_lengths))
|
|
for item in data_list:
|
|
print(' '.join(f"{str(x).center(col_lengths[i])}" for i, x in enumerate(item)))
|
|
print("\n")
|
|
|
|
if check_rc(req) != 0:
|
|
# We hit a failure. Update rc but keep looping
|
|
rc = 1
|
|
|
|
return rc
|
|
|
|
|
|
def release_delete_req(args):
|
|
# arg.release is a list
|
|
releases = "/".join(args.release)
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
url = "http://%s/software/delete/%s" % (api_addr, releases)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def commit_patch_req(args):
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
# Default to running release
|
|
# this all needs to be changed
|
|
relopt = RUNNING_SW_VERSION
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
if args.sw_version and not args.all:
|
|
# Disallow
|
|
print("Use of --sw-version option requires --all")
|
|
return 1
|
|
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)
|
|
|
|
req = requests.get(url, headers=headers)
|
|
|
|
patch_list = []
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
|
|
if 'sd' in data:
|
|
patch_list = sorted(list(data['sd']))
|
|
elif req.status_code == 500:
|
|
print("Failed to get patch list. Aborting...")
|
|
return 1
|
|
|
|
if len(patch_list) == 0:
|
|
print("There are no %s patches to commit." % relopt)
|
|
return 0
|
|
|
|
print("The following patches will be committed:")
|
|
for patch_id in patch_list:
|
|
print(" %s" % patch_id)
|
|
print()
|
|
|
|
patches = "/".join(patch_list)
|
|
else:
|
|
# args.patch is a list
|
|
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)
|
|
|
|
req = requests.get(url, headers=headers)
|
|
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
|
|
if 'patches' in data:
|
|
print("The following patches will be committed:")
|
|
for release_id in sorted(data['patches']):
|
|
print(" %s" % release_id)
|
|
print()
|
|
else:
|
|
print("No patches found to commit")
|
|
return 1
|
|
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
return 1
|
|
|
|
# Run dry-run
|
|
url = "http://%s/software/commit_dry_run/%s" % (api_addr, patches)
|
|
|
|
req = requests.post(url, headers=headers)
|
|
print_software_op_result(req)
|
|
|
|
if check_rc(req) != 0:
|
|
print("Aborting...")
|
|
return 1
|
|
|
|
if args.dry_run:
|
|
return 0
|
|
|
|
print()
|
|
commit_warning = "WARNING: Committing a patch is an irreversible operation. " + \
|
|
"Committed patches cannot be removed."
|
|
print(textwrap.fill(commit_warning, width=TERM_WIDTH, subsequent_indent=' ' * 9))
|
|
print()
|
|
|
|
user_input = input("Would you like to continue? [y/N]: ")
|
|
if user_input.lower() != 'y':
|
|
print("Aborting...")
|
|
return 1
|
|
|
|
url = "http://%s/software/commit_patch/%s" % (api_addr, patches)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def release_list_req(args):
|
|
state = args.state # defaults to "all"
|
|
extra_opts = ""
|
|
if args.release:
|
|
extra_opts = "&release=%s" % args.release
|
|
url = "http://%s/software/query?show=%s%s" % (api_addr, state, extra_opts)
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.get(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def print_software_deploy_host_list_result(req):
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'data' not in data:
|
|
print("Invalid data returned:")
|
|
print_result_debug(req)
|
|
return
|
|
|
|
agents = data['data']
|
|
|
|
# Calculate column widths
|
|
hdr_hn = "Hostname"
|
|
hdr_rel = "Software Release"
|
|
hdr_tg_rel = "Target Release"
|
|
hdr_rr = "Reboot Required"
|
|
hdr_state = "Host State"
|
|
|
|
width_hn = len(hdr_hn)
|
|
width_rel = len(hdr_rel)
|
|
width_tg_rel = len(hdr_tg_rel)
|
|
width_rr = len(hdr_rr)
|
|
width_state = len(hdr_state)
|
|
|
|
for agent in sorted(agents, key=lambda a: a["hostname"]):
|
|
if agent.get("deploy_host_state") is None:
|
|
agent["deploy_host_state"] = "No active deployment"
|
|
if agent.get("to_release") is None:
|
|
agent["to_release"] = "N/A"
|
|
if len(agent["hostname"]) > width_hn:
|
|
width_hn = len(agent["hostname"])
|
|
if len(agent["sw_version"]) > width_rel:
|
|
width_rel = len(agent["sw_version"])
|
|
if len(agent["to_release"]) > width_tg_rel:
|
|
width_tg_rel = len(agent["to_release"])
|
|
if len(agent["deploy_host_state"]) > width_state:
|
|
width_state = len(agent["deploy_host_state"])
|
|
|
|
print("{0:^{width_hn}} {1:^{width_rel}} {2:^{width_tg_rel}} {3:^{width_rr}} {4:^{width_state}}".format(
|
|
hdr_hn, hdr_rel, hdr_tg_rel, hdr_rr, hdr_state,
|
|
width_hn=width_hn, width_rel=width_rel, width_tg_rel=width_tg_rel, width_rr=width_rr, width_state=width_state))
|
|
|
|
print("{0} {1} {2} {3} {4}".format(
|
|
'=' * width_hn, '=' * width_rel, '=' * width_tg_rel, '=' * width_rr, '=' * width_state))
|
|
|
|
for agent in sorted(agents, key=lambda a: a["hostname"]):
|
|
print("{0:<{width_hn}} {1:^{width_rel}} {2:^{width_tg_rel}} {3:^{width_rr}} {4:^{width_state}}".format(
|
|
agent["hostname"],
|
|
agent["sw_version"],
|
|
agent["to_release"],
|
|
"Yes" if agent.get("reboot_required", None) else "No",
|
|
agent["deploy_host_state"],
|
|
width_hn=width_hn, width_rel=width_rel, width_tg_rel=width_tg_rel, width_rr=width_rr, width_state=width_state))
|
|
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
|
|
|
|
def deploy_host_list_req(args):
|
|
url = "http://%s/software/host_list" % api_addr
|
|
req = requests.get(url)
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_deploy_host_list_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def release_show_req(args):
|
|
# arg.release is a list
|
|
releases = "/".join(args.release)
|
|
|
|
url = "http://%s/software/show/%s" % (api_addr, releases)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
# todo(abailey): convert this to a GET
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_release_show_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def wait_for_install_complete(agent_ip):
|
|
url = "http://%s/software/host_list" % api_addr
|
|
rc = 0
|
|
|
|
max_retries = 4
|
|
retriable_count = 0
|
|
|
|
while True:
|
|
# Sleep on the first pass as well, to allow time for the
|
|
# agent to respond
|
|
time.sleep(5)
|
|
|
|
try:
|
|
req = requests.get(url)
|
|
except requests.exceptions.ConnectionError:
|
|
# The local software-controller may have restarted.
|
|
retriable_count += 1
|
|
if retriable_count <= max_retries:
|
|
continue
|
|
else:
|
|
print("Lost communications with the software controller")
|
|
rc = 1
|
|
break
|
|
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'data' not in data:
|
|
print("Invalid host-list data returned:")
|
|
print_result_debug(req)
|
|
rc = 1
|
|
break
|
|
|
|
state = None
|
|
agents = data['data']
|
|
interim_state = None
|
|
|
|
for agent in agents:
|
|
if agent['hostname'] == agent_ip \
|
|
or agent['ip'] == agent_ip:
|
|
state = agent.get('state')
|
|
interim_state = agent.get('interim_state')
|
|
|
|
if state is None:
|
|
# If the software daemons have restarted, there's a
|
|
# window after the software-controller restart that the
|
|
# hosts table will be empty.
|
|
retriable_count += 1
|
|
if retriable_count <= max_retries:
|
|
continue
|
|
else:
|
|
print("%s agent has timed out." % agent_ip)
|
|
rc = 1
|
|
break
|
|
|
|
if state == constants.PATCH_AGENT_STATE_INSTALLING or \
|
|
interim_state is True:
|
|
# Still installing
|
|
sys.stdout.write(".")
|
|
sys.stdout.flush()
|
|
elif state == constants.PATCH_AGENT_STATE_INSTALL_REJECTED:
|
|
print("\nInstallation rejected. Node must be locked")
|
|
rc = 1
|
|
break
|
|
elif state == constants.PATCH_AGENT_STATE_INSTALL_FAILED:
|
|
print("\nInstallation failed. Please check logs for details.")
|
|
rc = 1
|
|
break
|
|
elif state == constants.PATCH_AGENT_STATE_IDLE:
|
|
print("\nInstallation was successful.")
|
|
rc = 0
|
|
break
|
|
else:
|
|
print("\nPatch agent is reporting unknown state: %s" % state)
|
|
rc = 1
|
|
break
|
|
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
rc = 1
|
|
break
|
|
else:
|
|
m = re.search("(Error message:.*)", req.text, re.MULTILINE)
|
|
if m:
|
|
print(m.group(0))
|
|
else:
|
|
print(vars(req))
|
|
rc = 1
|
|
break
|
|
|
|
return rc
|
|
|
|
|
|
def host_install(args):
|
|
rc = 0
|
|
agent_ip = args.agent
|
|
|
|
# Issue deploy_host request and poll for results
|
|
url = "http://%s/software/deploy_host/%s" % (api_addr, agent_ip)
|
|
|
|
if args.force:
|
|
url += "/force"
|
|
|
|
req = requests.post(url)
|
|
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'error' in data and data["error"] != "":
|
|
print("Error:")
|
|
print(data["error"])
|
|
rc = 1
|
|
else:
|
|
rc = wait_for_install_complete(agent_ip)
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. "
|
|
"Please check /var/log/software.log for details")
|
|
rc = 1
|
|
else:
|
|
m = re.search("(Error message:.*)", req.text, re.MULTILINE)
|
|
if m:
|
|
print(m.group(0))
|
|
else:
|
|
print("%s %s" % (req.status_code, req.reason))
|
|
rc = 1
|
|
|
|
return rc
|
|
|
|
|
|
def drop_host(args):
|
|
host_ip = args.host
|
|
|
|
url = "http://%s/software/drop_host/%s" % (api_addr, host_ip)
|
|
|
|
req = requests.post(url)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
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)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.get(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def release_upload_dir_req(args):
|
|
# arg.release is a list
|
|
release_dirs = args.release
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
to_upload_files = {}
|
|
raw_files = []
|
|
|
|
# Find all files that need to be uploaded in given directories
|
|
for release_dir in release_dirs:
|
|
raw_files = [f for f in os.listdir(release_dir)
|
|
if os.path.isfile(os.path.join(release_dir, f))]
|
|
|
|
# Get absolute path of files
|
|
raw_files = [os.path.abspath(os.path.join(release_dir, f)) for f in raw_files]
|
|
|
|
for software_file in sorted(set(raw_files)):
|
|
_, ext = os.path.splitext(software_file)
|
|
if ext in constants.SUPPORTED_UPLOAD_FILE_EXT:
|
|
to_upload_files[software_file] = (software_file, open(software_file, 'rb'))
|
|
|
|
encoder = MultipartEncoder(fields=to_upload_files)
|
|
url = "http://%s/software/upload" % api_addr
|
|
headers = {'Content-Type': encoder.content_type}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url,
|
|
data=encoder,
|
|
headers=headers)
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
return check_rc(req)
|
|
|
|
|
|
def deploy_precheck_req(args):
|
|
# args.deployment is a string
|
|
deployment = args.deployment
|
|
|
|
# args.region is a string
|
|
region_name = args.region_name
|
|
|
|
# Issue deploy_precheck request
|
|
url = "http://%s/software/deploy_precheck/%s" % (api_addr, deployment)
|
|
if args.force:
|
|
url += "/force"
|
|
url += "?region_name=%s" % region_name
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def deploy_start_req(args):
|
|
# args.deployment is a string
|
|
deployment = args.deployment
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
# Issue deploy_start request
|
|
url = "http://%s/software/deploy_start/%s" % (api_addr, deployment)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def deploy_activate_req(args):
|
|
# args.deployment is a string
|
|
deployment = args.deployment
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
# Issue deploy_start request
|
|
url = "http://%s/software/deploy_activate/%s" % (api_addr, deployment)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def deploy_complete_req(args):
|
|
# args.deployment is a string
|
|
deployment = args.deployment
|
|
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
# Issue deploy_complete request
|
|
url = "http://%s/software/deploy_complete/%s" % (api_addr, deployment)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def deploy_show_req(args):
|
|
url = "http://%s/software/deploy_show" % api_addr
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.get(url, headers=headers)
|
|
|
|
if req.status_code >= 500:
|
|
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
return 1
|
|
elif req.status_code >= 400:
|
|
print("Respond code %d. Error: %s" % (req.status_code, req.reason))
|
|
return 1
|
|
|
|
data = json.loads(req.text)
|
|
if not data:
|
|
print("No deploy in progress.\n")
|
|
else:
|
|
data["reboot_required"] = "Yes" if data.get("reboot_required") else "No"
|
|
data_list = [[k, v] for k, v in data.items()]
|
|
transposed_data_list = list(zip(*data_list))
|
|
|
|
transposed_data_list[0] = [s.title().replace('_', ' ') for s in transposed_data_list[0]]
|
|
# Find the longest header string in each column
|
|
header_lengths = [len(str(x)) for x in transposed_data_list[0]]
|
|
# Find the longest content string in each column
|
|
content_lengths = [len(str(x)) for x in transposed_data_list[1]]
|
|
# Find the max of the two for each column
|
|
col_lengths = [(x if x > y else y) for x, y in zip(header_lengths, content_lengths)]
|
|
|
|
print(' '.join(f"{x.center(col_lengths[i])}" for i, x in enumerate(transposed_data_list[0])))
|
|
print(' '.join('=' * length for length in col_lengths))
|
|
print(' '.join(f"{x.center(col_lengths[i])}" for i, x in enumerate(transposed_data_list[1])))
|
|
|
|
return 0
|
|
|
|
|
|
def deploy_host_req(args):
|
|
rc = 0
|
|
agent_ip = args.agent
|
|
|
|
# Issue deploy_host request and poll for results
|
|
url = "http://%s/software/deploy_host/%s" % (api_addr, agent_ip)
|
|
|
|
if args.force:
|
|
url += "/force"
|
|
|
|
req = requests.post(url)
|
|
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if 'error' in data and data["error"] != "":
|
|
print("Error:")
|
|
print(data["error"])
|
|
rc = 1
|
|
else:
|
|
rc = wait_for_install_complete(agent_ip)
|
|
elif req.status_code == 500:
|
|
print("An internal error has occurred. "
|
|
"Please check /var/log/software.log for details")
|
|
rc = 1
|
|
else:
|
|
m = re.search("(Error message:.*)", req.text, re.MULTILINE)
|
|
if m:
|
|
print(m.group(0))
|
|
else:
|
|
print("%s %s" % (req.status_code, req.reason))
|
|
rc = 1
|
|
return rc
|
|
|
|
|
|
def patch_init_release(args):
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
release = args.release
|
|
|
|
url = "http://%s/software/init_release/%s" % (api_addr, release)
|
|
|
|
req = requests.post(url)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def patch_del_release(args):
|
|
# Ignore interrupts during this function
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
release = args.release
|
|
|
|
url = "http://%s/software/del_release/%s" % (api_addr, release)
|
|
|
|
req = requests.post(url)
|
|
|
|
if args.debug:
|
|
print_result_debug(req)
|
|
else:
|
|
print_software_op_result(req)
|
|
|
|
return check_rc(req)
|
|
|
|
|
|
def patch_report_app_dependencies_req(args): # pylint: disable=unused-argument
|
|
extra_opts = [args.app]
|
|
extra_opts_str = '?%s' % '&'.join(extra_opts)
|
|
|
|
patches = "/".join(args)
|
|
url = "http://%s/software/report_app_dependencies/%s%s" \
|
|
% (api_addr, patches, extra_opts_str)
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if req.status_code == 200:
|
|
return 0
|
|
else:
|
|
print("An internal error has occurred. "
|
|
"Please check /var/log/software.log for details.")
|
|
return 1
|
|
|
|
|
|
def patch_query_app_dependencies_req():
|
|
url = "http://%s/software/query_app_dependencies" % api_addr
|
|
|
|
headers = {}
|
|
append_auth_token_if_required(headers)
|
|
req = requests.post(url, headers=headers)
|
|
|
|
if req.status_code == 200:
|
|
data = json.loads(req.text)
|
|
if len(data) == 0:
|
|
print("There are no application dependencies.")
|
|
else:
|
|
hdr_app = "Application"
|
|
hdr_list = "Required Patches"
|
|
width_app = len(hdr_app)
|
|
width_list = len(hdr_list)
|
|
|
|
for app, patch_list in data.items():
|
|
width_app = max(width_app, len(app))
|
|
width_list = max(width_list, len(', '.join(patch_list)))
|
|
|
|
print("{0:<{width_app}} {1:<{width_list}}".format(
|
|
hdr_app, hdr_list,
|
|
width_app=width_app, width_list=width_list))
|
|
|
|
print("{0} {1}".format(
|
|
'=' * width_app, '=' * width_list))
|
|
|
|
for app, patch_list in sorted(data.items()):
|
|
print("{0:<{width_app}} {1:<{width_list}}".format(
|
|
app, ', '.join(patch_list),
|
|
width_app=width_app, width_list=width_list))
|
|
|
|
return 0
|
|
else:
|
|
print("An internal error has occurred. "
|
|
"Please check /var/log/software.log for details.")
|
|
return 1
|
|
|
|
|
|
def check_env(env, var):
|
|
if env not in os.environ:
|
|
print("You must provide a %s via env[%s]" % (var, env))
|
|
exit(-1)
|
|
|
|
|
|
def get_auth_token_and_endpoint(region_name):
|
|
from keystoneauth1 import exceptions
|
|
from keystoneauth1 import identity
|
|
from keystoneauth1 import session
|
|
|
|
user_env_map = {'OS_USERNAME': 'username',
|
|
'OS_PASSWORD': 'password',
|
|
'OS_PROJECT_NAME': 'project_name',
|
|
'OS_AUTH_URL': 'auth_url',
|
|
'OS_USER_DOMAIN_NAME': 'user_domain_name',
|
|
'OS_PROJECT_DOMAIN_NAME': 'project_domain_name'}
|
|
|
|
for k, v in user_env_map.items():
|
|
check_env(k, v)
|
|
|
|
user = dict()
|
|
for k, v in user_env_map.items():
|
|
user[v] = os.environ.get(k)
|
|
|
|
auth = identity.V3Password(**user)
|
|
sess = session.Session(auth=auth)
|
|
try:
|
|
token = auth.get_token(sess)
|
|
endpoint = auth.get_endpoint(sess, service_type='usm',
|
|
interface='internal',
|
|
region_name=region_name)
|
|
except (exceptions.http.Unauthorized, exceptions.EndpointNotFound) as e:
|
|
print(str(e))
|
|
exit(-1)
|
|
|
|
return token, endpoint
|
|
|
|
|
|
def append_auth_token_if_required(headers):
|
|
global auth_token
|
|
if auth_token is not None:
|
|
headers['X-Auth-Token'] = auth_token
|
|
|
|
|
|
def format_url_address(address):
|
|
import netaddr
|
|
try:
|
|
ip_addr = netaddr.IPAddress(address)
|
|
if ip_addr.version == IPV6_FAMILY:
|
|
return "[%s]" % address
|
|
else:
|
|
return address
|
|
except netaddr.AddrFormatError:
|
|
return address
|
|
|
|
|
|
def check_for_os_region_name(args):
|
|
# argparse converts os-region-name to os_region_name
|
|
region = args.os_region_name
|
|
if region is None:
|
|
return False
|
|
|
|
global VIRTUAL_REGION
|
|
if region != VIRTUAL_REGION:
|
|
print("Unsupported region name: %s" % region)
|
|
exit(1)
|
|
|
|
# check it is running on the active controller
|
|
# not able to use sm-query due to it requires sudo
|
|
try:
|
|
subprocess.check_output("pgrep -f dcorch-api-proxy", shell=True)
|
|
except subprocess.CalledProcessError:
|
|
print("Command must be run from the active controller.")
|
|
exit(1)
|
|
|
|
# get a token and fetch the internal endpoint in SystemController
|
|
global auth_token
|
|
auth_token, endpoint = get_auth_token_and_endpoint(region)
|
|
if endpoint is not None:
|
|
global api_addr
|
|
url = urlparse(endpoint)
|
|
address = format_url_address(url.hostname)
|
|
api_addr = '{}:{}'.format(address, url.port)
|
|
|
|
return True
|
|
|
|
|
|
def register_deploy_commands(commands):
|
|
"""deploy commands
|
|
- precheck
|
|
- start
|
|
- host
|
|
- activate
|
|
- complete
|
|
non root/sudo users can run:
|
|
- host-list
|
|
- show
|
|
Deploy commands are region_restricted, which means
|
|
that they are not permitted to be run in DC
|
|
"""
|
|
|
|
cmd_area = 'deploy'
|
|
cmd_parser = commands.add_parser(
|
|
cmd_area,
|
|
help='Software Deploy',
|
|
epilog="StarlingX Unified Software Deployment"
|
|
)
|
|
cmd_parser.set_defaults(cmd_area=cmd_area)
|
|
|
|
# Deploy commands are region_restricted, which means
|
|
# that they are not permitted to be run in DC
|
|
cmd_parser.set_defaults(region_restricted=True)
|
|
|
|
sub_cmds = cmd_parser.add_subparsers(
|
|
title='Software Deploy Commands',
|
|
metavar=''
|
|
)
|
|
sub_cmds.required = True
|
|
|
|
# --- software deploy precheck -----------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'precheck',
|
|
help='Verify whether prerequisites for installing the software deployment are satisfied'
|
|
)
|
|
cmd.set_defaults(cmd='precheck')
|
|
cmd.set_defaults(func=deploy_precheck_req)
|
|
cmd.add_argument('deployment',
|
|
help='Verify if prerequisites are met for this Deployment ID')
|
|
cmd.add_argument('-f',
|
|
'--force',
|
|
action='store_true',
|
|
required=False,
|
|
help='Allow bypassing non-critical checks')
|
|
cmd.add_argument('--region_name',
|
|
default='RegionOne',
|
|
required=False,
|
|
help='Run precheck against a subcloud')
|
|
|
|
# --- software deploy start --------------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'start',
|
|
help='Start the software deployment'
|
|
)
|
|
cmd.set_defaults(cmd='start')
|
|
cmd.set_defaults(func=deploy_start_req)
|
|
cmd.add_argument('deployment',
|
|
help='Deployment ID to start')
|
|
|
|
# --- software deploy host ---------------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'host',
|
|
help='Deploy prestaged software deployment to the host'
|
|
)
|
|
cmd.set_defaults(cmd='host')
|
|
cmd.set_defaults(func=deploy_host_req)
|
|
cmd.add_argument('agent',
|
|
help="Agent on which host deploy is triggered")
|
|
cmd.add_argument('-f',
|
|
'--force',
|
|
action='store_true',
|
|
required=False,
|
|
help="Force deploy host")
|
|
|
|
# --- software deploy activate -----------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'activate',
|
|
help='Activate the software deployment'
|
|
)
|
|
cmd.set_defaults(cmd='activate')
|
|
cmd.set_defaults(func=deploy_activate_req)
|
|
cmd.add_argument('deployment',
|
|
help='Deployment ID to activate')
|
|
|
|
# --- software deploy complete -----------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'complete',
|
|
help='Complete the software deployment'
|
|
)
|
|
cmd.set_defaults(cmd='complete')
|
|
cmd.set_defaults(func=deploy_complete_req)
|
|
cmd.add_argument('deployment',
|
|
help='Deployment ID to complete')
|
|
|
|
# --- software deploy show ---------------------------
|
|
cmd = sub_cmds.add_parser(
|
|
'show',
|
|
help='Show the software deployments states'
|
|
)
|
|
cmd.set_defaults(cmd='show')
|
|
cmd.set_defaults(func=deploy_show_req)
|
|
cmd.set_defaults(restricted=False) # can run non root
|
|
# --deployment is an optional argument
|
|
cmd.add_argument('--deployment',
|
|
required=False,
|
|
help='List the deployment specified')
|
|
# --state is an optional argument.
|
|
# default: "all"
|
|
# acceptable values: inactive, active, prestaging, prestaged, all
|
|
cmd.add_argument('--state',
|
|
default="all",
|
|
required=False,
|
|
help='List all deployments that have this state')
|
|
|
|
# --- software deploy host-list -------------
|
|
cmd = sub_cmds.add_parser(
|
|
'host-list',
|
|
help='List of hosts for software deployment'
|
|
)
|
|
cmd.set_defaults(cmd='host-list')
|
|
cmd.set_defaults(func=deploy_host_list_req)
|
|
cmd.set_defaults(restricted=False) # can run non root
|
|
|
|
|
|
def setup_argparse():
|
|
parser = argparse.ArgumentParser(prog="software",
|
|
description="Unified Software Management",
|
|
epilog="Used for patching and upgrading")
|
|
parser.add_argument('--debug', action='store_true', help="Enable debug output")
|
|
# parser.add_argument('--os-auth-url', default=None)
|
|
# parser.add_argument('--os-project-name', default=None)
|
|
# parser.add_argument('--os-project-domain-name', default=None)
|
|
# parser.add_argument('--os-username', default=None)
|
|
# parser.add_argument('--os-password', default=None)
|
|
# parser.add_argument('--os-user-domain-name', default=None)
|
|
parser.add_argument('--os-region-name', default=None)
|
|
# parser.add_argument('--os-interface', default=None)
|
|
|
|
# All commands are considered restricted, unless explicitly set to False
|
|
parser.set_defaults(restricted=True)
|
|
# All functions are initially defined as 'not implemented yet'
|
|
# The func will be overridden by the command definition as they are completed
|
|
parser.set_defaults(func=software_command_not_implemented_yet)
|
|
|
|
# No commands are region restricted, unless explicitly set to True
|
|
parser.set_defaults(region_restricted=False)
|
|
|
|
commands = parser.add_subparsers(title='Commands', metavar='')
|
|
commands.required = True
|
|
|
|
# -- software commit-patch <release> ---------------
|
|
cmd = commands.add_parser(
|
|
'commit-patch',
|
|
help='Commit patches to free disk space. WARNING: This action is irreversible!'
|
|
)
|
|
cmd.set_defaults(cmd='commit-patch')
|
|
cmd.set_defaults(func=commit_patch_req)
|
|
cmd.add_argument('patch',
|
|
nargs="+", # accepts a list
|
|
help='Patch ID/s to commit')
|
|
# --dry-run is an optional argument
|
|
cmd.add_argument('--dry-run',
|
|
action='store_true',
|
|
required=False,
|
|
help='Check the space savings without committing the patch')
|
|
# --all is an optional argument
|
|
cmd.add_argument('--all',
|
|
action='store_true',
|
|
required=False,
|
|
help='Commit all the applied patches')
|
|
# --sw-version is an optional argument
|
|
cmd.add_argument('--sw-version',
|
|
required=False,
|
|
help='Software release version')
|
|
|
|
# -- software delete <release> ---------------
|
|
cmd = commands.add_parser(
|
|
'delete',
|
|
help='Delete the software release'
|
|
)
|
|
cmd.set_defaults(cmd='delete')
|
|
cmd.set_defaults(func=release_delete_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='Release ID to delete')
|
|
|
|
# -- software install-local ---------------
|
|
cmd = commands.add_parser(
|
|
'install-local',
|
|
help='Trigger patch install/remove on the local host. ' +
|
|
'This command can only be used for patch installation ' +
|
|
'prior to initial configuration.'
|
|
)
|
|
cmd.set_defaults(cmd='install-local')
|
|
cmd.set_defaults(func=install_local)
|
|
|
|
# --- software is-available <release> ------
|
|
cmd = commands.add_parser(
|
|
'is-available',
|
|
help='Query Available state for list of releases. Returns True if all are Available, False otherwise.'
|
|
)
|
|
cmd.set_defaults(cmd='is-available')
|
|
cmd.set_defaults(func=release_is_available_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='List of releases')
|
|
|
|
# --- software is-committed <release> ------
|
|
cmd = commands.add_parser(
|
|
'is-committed',
|
|
help='Query Committed state for list of releases. Returns True if all are Committed, False otherwise.'
|
|
)
|
|
cmd.set_defaults(cmd='is-committed')
|
|
cmd.set_defaults(func=release_is_committed_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='List of releases')
|
|
|
|
# --- software is-deployed <release> ------
|
|
cmd = commands.add_parser(
|
|
'is-deployed',
|
|
help='Query Deployed state for list of releases. Returns True if all are Deployed, False otherwise.'
|
|
)
|
|
cmd.set_defaults(cmd='is-deployed')
|
|
cmd.set_defaults(func=release_is_deployed_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='List of releases')
|
|
|
|
# --- software list ---------------------------
|
|
cmd = commands.add_parser(
|
|
'list',
|
|
help='List the software releases'
|
|
)
|
|
cmd.set_defaults(cmd='list')
|
|
cmd.set_defaults(func=release_list_req)
|
|
cmd.set_defaults(restricted=False) # can run non root
|
|
# --release is an optional argument
|
|
cmd.add_argument('--release',
|
|
required=False,
|
|
help='filter against a release ID')
|
|
# --state is an optional argument. default: "all"
|
|
cmd.add_argument('--state',
|
|
default="all",
|
|
required=False,
|
|
help='filter against a release state')
|
|
|
|
# --- software show <release> -----------------
|
|
cmd = commands.add_parser(
|
|
'show',
|
|
help='Show the software release'
|
|
)
|
|
cmd.set_defaults(cmd='show')
|
|
cmd.set_defaults(func=release_show_req)
|
|
cmd.set_defaults(restricted=False) # can run non root
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='Release ID to show')
|
|
|
|
# --- software upload <release> ---------------
|
|
cmd = commands.add_parser(
|
|
'upload',
|
|
help='Upload a software release'
|
|
)
|
|
cmd.set_defaults(cmd='upload')
|
|
cmd.set_defaults(func=release_upload_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='software releases to upload')
|
|
cmd.add_argument('--local',
|
|
required=False,
|
|
default=False,
|
|
action='store_true',
|
|
help='Upload files from active controller')
|
|
|
|
# --- software upload-dir <release dir> ------
|
|
cmd = commands.add_parser(
|
|
'upload-dir',
|
|
help='Upload a software release dir'
|
|
)
|
|
cmd.set_defaults(cmd='upload-dir')
|
|
cmd.set_defaults(func=release_upload_dir_req)
|
|
cmd.add_argument('release',
|
|
nargs="+", # accepts a list
|
|
help='directory containing software releases to upload')
|
|
|
|
register_deploy_commands(commands)
|
|
return parser
|
|
|
|
|
|
def main():
|
|
set_term_width()
|
|
|
|
rc = 0
|
|
parser = setup_argparse()
|
|
argcomplete.autocomplete(parser)
|
|
args = parser.parse_args()
|
|
dc_request = check_for_os_region_name(args)
|
|
|
|
# Reject the commands that are not supported in the virtual region
|
|
if dc_request and args.region_restricted:
|
|
global VIRTUAL_REGION
|
|
print("\n%s %s command is not allowed in %s region" % (args.cmd_area,
|
|
args.cmd,
|
|
VIRTUAL_REGION))
|
|
rc = 1
|
|
exit(rc)
|
|
|
|
if auth_token is None and os.geteuid() != 0:
|
|
if args.restricted:
|
|
print("Error: Command must be run as sudo or root", file=sys.stderr)
|
|
rc = 1
|
|
exit(rc)
|
|
|
|
# Call the function registered with argparse, and pass the 'args' to it
|
|
rc = args.func(args)
|
|
exit(rc)
|