0c88b5e7fe
fuel snapshot simplified fixed progress bars api version now explicit Change-Id: I62a7202fe6e7914895604be1b3fb38de5a7a3141
1317 lines
39 KiB
Python
Executable File
1317 lines
39 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2013 Mirantis, Inc.
|
|
#
|
|
# 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.
|
|
|
|
import argparse
|
|
import curses
|
|
from functools import partial
|
|
from itertools import chain
|
|
import json
|
|
import math
|
|
import os
|
|
import shutil
|
|
import sys
|
|
from time import sleep
|
|
import urllib2
|
|
|
|
ROOT = "http://127.0.0.1:8000"
|
|
API_ROOT = ROOT + "/api/v1/"
|
|
DEBUG = False
|
|
JSON = False
|
|
passive_arguments = ["rel", "env", "action", "debug", "json"]
|
|
|
|
|
|
class SetAction(argparse.Action):
|
|
"""Custom argparse.Action subclass to store distinct values
|
|
|
|
:returns: Set of arguments
|
|
"""
|
|
def __call__(self, _parser, namespace, values, option_string=None):
|
|
try:
|
|
getattr(namespace, self.dest).update(values)
|
|
except AttributeError:
|
|
setattr(namespace, self.dest, set(values))
|
|
|
|
|
|
def recur_get(multi_level_dict, key_chain):
|
|
"""Method accesses some field in nested dictionaries
|
|
|
|
:returns: value for last key in key_chain in last dictionary
|
|
"""
|
|
if not isinstance(multi_level_dict[key_chain[0]], dict):
|
|
return multi_level_dict[key_chain[0]]
|
|
else:
|
|
return recur_get(multi_level_dict[key_chain[0]], key_chain[1:])
|
|
|
|
|
|
def format_table(data, acceptable_keys=None, subdict_keys=None):
|
|
"""Format list of dicts to ascii table
|
|
|
|
:acceptable_keys list(str): list of keys for which to create table
|
|
also specifies their order
|
|
:subdict_keys list(tuple(str)): list of key chains (tuples of key strings)
|
|
which are applied to dictionaries
|
|
to extract values
|
|
"""
|
|
if JSON:
|
|
print(json.dumps(data, indent=4))
|
|
exit(0)
|
|
if subdict_keys:
|
|
for key_chain in subdict_keys:
|
|
for data_dict in data:
|
|
data_dict[key_chain[0]] = recur_get(data_dict, key_chain)
|
|
if acceptable_keys:
|
|
rows = [tuple([value[key] for key in acceptable_keys])
|
|
for value in data]
|
|
header = tuple(acceptable_keys)
|
|
else:
|
|
rows = [tuple(x.values()) for x in data]
|
|
header = tuple(data[0].keys())
|
|
number_of_columns = len(header)
|
|
column_widths = dict(
|
|
zip(
|
|
range(number_of_columns),
|
|
(len(str(x)) for x in header)
|
|
)
|
|
)
|
|
|
|
for row in rows:
|
|
column_widths.update(
|
|
(index, max(column_widths[index], len(str(element))))
|
|
for index, element in enumerate(row)
|
|
)
|
|
row_template = ' | '.join(
|
|
'%%-%ss' % column_widths[i] for i in range(number_of_columns)
|
|
)
|
|
|
|
return '\n'.join(
|
|
(row_template % header,
|
|
'-|-'.join(column_widths[column_index]*'-'
|
|
for column_index in range(number_of_columns)),
|
|
'\n'.join(row_template % x for x in rows))
|
|
)
|
|
|
|
|
|
def json_api_delete_request(api):
|
|
"""Make DELETE request to specific API with some data
|
|
"""
|
|
print_debug(
|
|
"DELETE {0}".format(API_ROOT + api)
|
|
)
|
|
opener = urllib2.build_opener(urllib2.HTTPHandler)
|
|
request = urllib2.Request(API_ROOT + api)
|
|
request.add_header('Content-Type', ' application/json')
|
|
request.get_method = lambda: 'DELETE'
|
|
try:
|
|
return json.loads(
|
|
opener.open(request).read()
|
|
)
|
|
except ValueError:
|
|
return {}
|
|
except urllib2.HTTPError as e:
|
|
print_error(str(e) + "\n")
|
|
|
|
|
|
def json_api_put_request(api, data):
|
|
"""Make PUT request to specific API with some data
|
|
"""
|
|
data_json = json.dumps(data)
|
|
print_debug(
|
|
"PUT {0} data={1}"
|
|
.format(API_ROOT + api, data_json)
|
|
)
|
|
opener = urllib2.build_opener(urllib2.HTTPHandler)
|
|
request = urllib2.Request(API_ROOT + api, data=data_json)
|
|
request.add_header('Content-Type', ' application/json')
|
|
request.get_method = lambda: 'PUT'
|
|
try:
|
|
return json.loads(
|
|
opener.open(request).read()
|
|
)
|
|
except urllib2.HTTPError as e:
|
|
print_error(str(e) + "\n")
|
|
|
|
|
|
def json_api_get_request(api):
|
|
"""Make GET request to specific API
|
|
"""
|
|
print_debug(
|
|
"GET {0}"
|
|
.format(API_ROOT + api)
|
|
)
|
|
try:
|
|
request = urllib2.urlopen(API_ROOT + api)
|
|
return json.loads(
|
|
request.read()
|
|
)
|
|
except urllib2.HTTPError as e:
|
|
print_error(str(e) + "\n")
|
|
|
|
|
|
def json_api_post_request(api, data):
|
|
"""Make POST request to specific API with some data
|
|
"""
|
|
data_json = json.dumps(data)
|
|
print_debug(
|
|
"POST {0} data={1}"
|
|
.format(API_ROOT + api, data_json)
|
|
)
|
|
try:
|
|
return json.loads(
|
|
urllib2.urlopen(
|
|
url=API_ROOT + api,
|
|
data=data_json
|
|
).read()
|
|
)
|
|
except urllib2.HTTPError as e:
|
|
print_error(str(e) + "\n")
|
|
|
|
|
|
def has_arguments(params):
|
|
current_arguments = [v for k, v in params.__dict__.iteritems()
|
|
if k not in passive_arguments]
|
|
return any(current_arguments)
|
|
|
|
|
|
def print_error(message):
|
|
sys.stderr.write(message + "\n")
|
|
exit(1)
|
|
|
|
|
|
def print_debug(message):
|
|
if DEBUG:
|
|
print(message)
|
|
|
|
|
|
def check_for_attributes(params, attributes):
|
|
not_paseed_checks = [attribute for attribute in attributes
|
|
if not getattr(params, attribute)]
|
|
if len(not_paseed_checks):
|
|
print_error(
|
|
"{0} required!".format(
|
|
", ".join(
|
|
map(
|
|
lambda attr: "--" + attr,
|
|
not_paseed_checks
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def check_for_one_attribute(params, attributes):
|
|
if not any(map(
|
|
lambda attr: getattr(params, attr),
|
|
attributes
|
|
)):
|
|
print_error(
|
|
"At least one of {0} is required!".format(
|
|
", ".join(
|
|
map(
|
|
lambda attr: "--" + attr,
|
|
attributes)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def release(params):
|
|
"""List and modify currently available releases
|
|
"""
|
|
acceptable_keys = ["id", "name", "state", "operating_system", "version"]
|
|
if not has_arguments(params) or params.list:
|
|
if params.rel:
|
|
data = [json_api_get_request(
|
|
"releases/{0}/"
|
|
.format(params.rel)
|
|
)]
|
|
else:
|
|
data = json_api_get_request("releases/")
|
|
if params.json:
|
|
print(json.dumps(data, indent=4))
|
|
else:
|
|
print(format_table(data, acceptable_keys=acceptable_keys))
|
|
elif params.config:
|
|
check_for_attributes(params, ["rel", "username", "password"])
|
|
data = {
|
|
"release_id": params.rel,
|
|
"license_type": "rhsm",
|
|
"username": params.username,
|
|
"password": params.password,
|
|
"satellite": "",
|
|
"activation_key": ""
|
|
}
|
|
release_response = json_api_post_request(
|
|
"redhat/setup/",
|
|
data
|
|
)
|
|
if params.json:
|
|
print(json.dumps(release_response, indent=4))
|
|
else:
|
|
print(
|
|
"Credentials for release with id={0}"
|
|
" were successfully modified!"
|
|
.format(params.rel)
|
|
)
|
|
|
|
|
|
def environment(params):
|
|
"""Create, list and modify currently existing environments(clusters)
|
|
"""
|
|
if not has_arguments(params) or params.list:
|
|
acceptable_keys = ["id", "status", "name", "mode",
|
|
"release", "changes"]
|
|
data = json_api_get_request("clusters/")
|
|
if params.env:
|
|
data = filter(
|
|
lambda x: x[u"id"] == int(params.env),
|
|
data
|
|
)
|
|
print(format_table(data, acceptable_keys=acceptable_keys,
|
|
subdict_keys=[("release", u"id")]))
|
|
elif params.create:
|
|
check_for_attributes(params, ["ename", "rel"])
|
|
data = {
|
|
"nodes": [],
|
|
"tasks": [],
|
|
"name": params.ename,
|
|
"release": int(params.rel)
|
|
}
|
|
cluster_response = json_api_post_request("clusters/", data)
|
|
cluster_id = cluster_response[u"id"]
|
|
if params.mode:
|
|
data = {"mode": params.mode}
|
|
cluster_response = json_api_put_request(
|
|
"clusters/{0}/".format(cluster_id),
|
|
data
|
|
)
|
|
if params.json:
|
|
print(json.dumps(cluster_response, indent=4))
|
|
else:
|
|
print(
|
|
"Environment with id={id} in mode={mode}"
|
|
" was successfully created!"
|
|
.format(**cluster_response)
|
|
)
|
|
elif params.set:
|
|
check_for_attributes(params, ["env"])
|
|
if params.mode or params.ename:
|
|
data = {}
|
|
if params.mode:
|
|
data["mode"] = params.mode
|
|
if params.ename:
|
|
data["name"] = params.ename
|
|
put_response = json_api_put_request(
|
|
"clusters/{0}/".format(params.env),
|
|
data
|
|
)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
if params.ename:
|
|
print(
|
|
"Environment with id={0} was "
|
|
"successfully renamed to {1}"
|
|
.format(
|
|
params.env,
|
|
params.ename
|
|
)
|
|
)
|
|
if params.mode:
|
|
print(
|
|
"Mode of environment with id={0} was "
|
|
"successfully set to {1}."
|
|
.format(
|
|
params.env,
|
|
params.mode
|
|
)
|
|
)
|
|
elif params.delete:
|
|
check_for_attributes(params, ["env"])
|
|
delete_response = json_api_delete_request(
|
|
"clusters/{0}/".format(params.env)
|
|
)
|
|
if params.json:
|
|
print(json.dumps(delete_response, indent=4))
|
|
else:
|
|
print(
|
|
"Environment with id={0} was "
|
|
"successfully deleted."
|
|
.format(params.env)
|
|
)
|
|
|
|
|
|
def node(params):
|
|
"""List and assign available nodes to environments
|
|
"""
|
|
if not has_arguments(params) or params.list:
|
|
acceptable_keys = ["id", "status", "name", "cluster",
|
|
"mac", "roles", "pending_roles", "online"]
|
|
data = json_api_get_request("nodes/")
|
|
if params.env:
|
|
data = filter(
|
|
lambda x: x[u"cluster"] == int(params.env),
|
|
data
|
|
)
|
|
print(
|
|
format_table(data, acceptable_keys=acceptable_keys)
|
|
)
|
|
elif params.set:
|
|
check_for_attributes(params, ["node", "role", "env"])
|
|
node_ids = list(chain(*params.node))
|
|
roles = map(str.lower, params.role)
|
|
data = map(
|
|
lambda _node_id: {
|
|
"id": _node_id,
|
|
"cluster_id": params.env,
|
|
"pending_roles": roles,
|
|
"pending_addition": True,
|
|
"pending_deletion": False
|
|
},
|
|
node_ids
|
|
)
|
|
put_response = json_api_put_request("nodes/", data)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print(
|
|
"Nodes {0} with roles {1} "
|
|
"were successfully added to environment {2}"
|
|
.format(node_ids, roles, params.env)
|
|
)
|
|
elif params.delete:
|
|
check_for_one_attribute(params, ["env", "node"])
|
|
nodes_clusters = dict((_node["id"], _node["cluster"])
|
|
for _node in json_api_get_request("nodes/"))
|
|
if not params.node and params.env:
|
|
node_ids = [k for k, v in nodes_clusters.iteritems()
|
|
if v == int(params.env)]
|
|
else:
|
|
node_ids = list(chain(*params.node))
|
|
data = map(
|
|
lambda _node_id: {
|
|
"id": _node_id,
|
|
"cluster_id": None,
|
|
"pending_roles": [],
|
|
"pending_addition": False,
|
|
"pending_deletion": True
|
|
},
|
|
node_ids
|
|
)
|
|
put_response = json_api_put_request("nodes/", data)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print(
|
|
"Nodes with ids {0} were removed from environment with id {1}."
|
|
.format(
|
|
node_ids,
|
|
nodes_clusters[node_ids[0]]
|
|
)
|
|
)
|
|
elif params.network or params.disk:
|
|
check_for_one_attribute(params, ["default", "download", "upload"])
|
|
check_for_attributes(params, ["node"])
|
|
node_ids = list(chain(*params.node))
|
|
for node_id in node_ids:
|
|
if params.network:
|
|
get_node_attribute(
|
|
params,
|
|
node_id,
|
|
"interfaces",
|
|
[("interfaces", "default_assignment")]
|
|
)
|
|
elif params.disk:
|
|
get_node_attribute(
|
|
params,
|
|
node_id,
|
|
"disks",
|
|
[("disks", "defaults")]
|
|
)
|
|
|
|
|
|
def get_node_attribute(params, node_id, upload_attribute, attributes):
|
|
default_url_template = "nodes/{0}/{1}/{2}".format(node_id, "{0}", "{1}")
|
|
dir_path = os.path.join(
|
|
os.path.abspath(params.dir or os.path.curdir),
|
|
"node_{0}".format(node_id)
|
|
)
|
|
if params.upload:
|
|
dir_path = folder_or_one_up(dir_path)
|
|
if not os.path.exists(dir_path):
|
|
print_error(
|
|
"Folder {0} doesn't contain node folder '{1}'"
|
|
.format(dir_path, "node_{0}".format(node_id))
|
|
)
|
|
upload_node_attribute(
|
|
default_url_template.format(upload_attribute, ""),
|
|
node_id,
|
|
dir_path,
|
|
upload_attribute
|
|
)
|
|
elif params.default or params.download:
|
|
if not os.path.exists(dir_path):
|
|
os.makedirs(dir_path)
|
|
for attribute, default_tail in attributes:
|
|
write_node_attribute(
|
|
default_url_template.format(
|
|
attribute,
|
|
default_tail if params.default else ""
|
|
),
|
|
dir_path,
|
|
attribute
|
|
)
|
|
|
|
|
|
def upload_node_attribute(url, node_id, node_dir_path, attribute):
|
|
data = read_json(
|
|
os.path.join(
|
|
node_dir_path,
|
|
attribute + ".json"
|
|
)
|
|
)
|
|
if attribute == "interfaces":
|
|
url = "nodes/interfaces"
|
|
data = [{
|
|
"interfaces": data,
|
|
"id": node_id
|
|
}]
|
|
put_response = json_api_put_request(
|
|
url,
|
|
data
|
|
)
|
|
if JSON:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print("{0} configuration uploaded successfully.".format(attribute))
|
|
|
|
|
|
def write_node_attribute(url, node_dir_path, attribute):
|
|
attribute_path = os.path.join(
|
|
node_dir_path,
|
|
attribute + ".json"
|
|
)
|
|
get_response = json_api_get_request(url)
|
|
if os.path.exists(attribute_path):
|
|
os.remove(attribute_path)
|
|
write_json(
|
|
attribute_path,
|
|
get_response
|
|
)
|
|
if JSON:
|
|
print(json.dumps(get_response, indent=4))
|
|
else:
|
|
print(
|
|
"{0} configuration successfully downloaded to {1}."
|
|
.format(attribute, attribute_path)
|
|
)
|
|
|
|
|
|
def network(params):
|
|
"""Show or modify network settings of specific environments
|
|
"""
|
|
check_for_attributes(params, ["env"])
|
|
|
|
network_url = "clusters/{0}/network_configuration".format(params.env)
|
|
network_data = json_api_get_request(network_url)
|
|
network_file_path = os.path.join(
|
|
os.path.abspath(params.dir or os.path.curdir),
|
|
"network_{0}.json".format(params.env)
|
|
)
|
|
if params.upload:
|
|
put_response = json_api_put_request(
|
|
network_url,
|
|
read_json(network_file_path)
|
|
)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print("Network configuration uploaded successfully.")
|
|
elif params.verify:
|
|
verify_url = network_url + "/verify"
|
|
put_response = json_api_put_request(verify_url, network_data)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print(
|
|
"Verification status is '{status}'. message: {message}"
|
|
.format(**put_response)
|
|
)
|
|
else:
|
|
if params.download:
|
|
write_json(network_file_path, network_data)
|
|
print(
|
|
"Network configuration for environment with id={0}"
|
|
" downloaded to {1} successfully."
|
|
.format(params.env, network_file_path)
|
|
)
|
|
else:
|
|
if params.json:
|
|
print(json.dumps(network_data, indent=4))
|
|
|
|
|
|
def settings(params):
|
|
"""Show or modify environment settings
|
|
"""
|
|
check_for_attributes(params, ["env"])
|
|
|
|
settings_url = "clusters/{0}/attributes".format(params.env)
|
|
settings_data = json_api_get_request(settings_url)
|
|
settings_file_path = os.path.join(
|
|
os.path.abspath(params.dir or os.path.curdir),
|
|
"settings_{0}.json".format(params.env)
|
|
)
|
|
if params.upload:
|
|
put_response = json_api_put_request(
|
|
settings_url,
|
|
read_json(settings_file_path)
|
|
)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print("Settings configuration uploaded successfully.")
|
|
elif params.default:
|
|
default_url = settings_url + "/defaults"
|
|
get_response = json_api_get_request(default_url)
|
|
write_json(settings_file_path, get_response)
|
|
if params.json:
|
|
print(json.dumps(get_response, indent=4))
|
|
else:
|
|
print(
|
|
"Default settings configuration downloaded successfully."
|
|
)
|
|
else:
|
|
if params.download:
|
|
write_json(settings_file_path, settings_data)
|
|
print(
|
|
"Settings configuration for environment with id={0}"
|
|
" downloaded to {1} successfully."
|
|
.format(params.env, settings_file_path)
|
|
)
|
|
else:
|
|
if params.json:
|
|
print(json.dumps(settings_data, indent=4))
|
|
|
|
|
|
def task(params):
|
|
"""Show tasks
|
|
"""
|
|
if params.delete:
|
|
check_for_attributes(params, ["tid"])
|
|
task_ids = list(chain(*params.tid))
|
|
delete_response = map(
|
|
lambda tid: json_api_delete_request("tasks/{0}/".format(tid)),
|
|
task_ids
|
|
)
|
|
if params.json:
|
|
print(json.dumps(delete_response, indent=4))
|
|
else:
|
|
print(
|
|
"Tasks with id's {0} deleted successfully!"
|
|
.format(','.join(map(str, task_ids)))
|
|
)
|
|
else:
|
|
acceptable_keys = ["id", "status", "name", "cluster", "progress"]
|
|
tasks = json_api_get_request("tasks/")
|
|
if params.json:
|
|
print(json.dumps(tasks, indent=4))
|
|
else:
|
|
print(
|
|
format_table(tasks, acceptable_keys=acceptable_keys)
|
|
)
|
|
|
|
|
|
def snapshot(params):
|
|
"""Generate and download snapshot.
|
|
"""
|
|
if params.download:
|
|
dump_task = json_api_put_request(
|
|
"logs/package",
|
|
"{}"
|
|
)
|
|
task_id = dump_task["id"]
|
|
print("Generating dump...")
|
|
while dump_task["progress"] < 100:
|
|
dump_task = json_api_get_request("tasks/{0}/".format(task_id))
|
|
sleep(0.5)
|
|
download_snapshot_with_progress_bar(
|
|
ROOT + dump_task["message"],
|
|
params.dir
|
|
)
|
|
|
|
|
|
def download_snapshot_with_progress_bar(url, directory):
|
|
directory = directory or os.path.curdir
|
|
if not os.path.exists(directory):
|
|
print_error("Folder {0} doesn't exist.".format(directory))
|
|
file_name = os.path.join(
|
|
os.path.abspath(directory),
|
|
url.split('/')[-1]
|
|
)
|
|
download_handle = urllib2.urlopen(url)
|
|
with open(file_name, 'wb') as file_handle:
|
|
meta = download_handle.info()
|
|
file_size = int(meta.getheaders("Content-Length")[0])
|
|
print("Downloading: {0} Bytes: {1}".format(url, file_size))
|
|
file_size_dl = 0
|
|
block_size = 8192
|
|
bar = partial(get_bar_for_progress, 80)
|
|
while True:
|
|
data_buffer = download_handle.read(block_size)
|
|
if not data_buffer:
|
|
break
|
|
file_size_dl += len(data_buffer)
|
|
file_handle.write(data_buffer)
|
|
progress = int(100 * float(file_size_dl) / file_size)
|
|
sys.stdout.write("\r{0}".format(
|
|
bar(progress)
|
|
))
|
|
sys.stdout.flush()
|
|
sleep(1/10)
|
|
print
|
|
|
|
|
|
def deploy(params):
|
|
"""Deploy changes to environments
|
|
"""
|
|
check_for_attributes(params, ["env"])
|
|
put_response = json_api_put_request(
|
|
"clusters/{0}/changes".format(params.env),
|
|
{}
|
|
)
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print_deploy_progress(params.env)
|
|
|
|
|
|
def get_bar_for_progress(full_width, progress):
|
|
number_of_equal_signs = int(
|
|
math.ceil(progress * float(full_width-2)/100)
|
|
)
|
|
return "[{0}{1}{2}]".format(
|
|
"=" * number_of_equal_signs,
|
|
">" if number_of_equal_signs < full_width-2 else "",
|
|
" " * (full_width-3-number_of_equal_signs)
|
|
)
|
|
|
|
|
|
def print_deploy_progress(env):
|
|
stdscr = curses.initscr()
|
|
scr_width = stdscr.getmaxyx()[1]
|
|
curses.noecho()
|
|
curses.cbreak()
|
|
tasks = json_api_get_request("tasks?cluster_id={0}".format(env))
|
|
deploy_task = filter(
|
|
lambda x: x.get("name", "") == "deploy",
|
|
tasks
|
|
)[0]
|
|
deploy_task_id = deploy_task["id"]
|
|
progress = 0
|
|
total_progress_bar = partial(get_bar_for_progress, scr_width - 17)
|
|
node_bar = partial(get_bar_for_progress, scr_width - 28)
|
|
nodes = []
|
|
try:
|
|
while progress != 100 or \
|
|
any(map(lambda _node: _node["progress"] != 100, nodes)):
|
|
task_response = json_api_get_request(
|
|
"tasks/{0}/".format(deploy_task_id)
|
|
)
|
|
progress = task_response["progress"]
|
|
nodes = json_api_get_request(
|
|
"nodes?cluster_id={0}"
|
|
.format(env)
|
|
)
|
|
nodes.sort(key=lambda _node: _node.get("id"))
|
|
stdscr.addstr(0, 0,
|
|
"Deploying changes to environment with id={0}"
|
|
.format(env)
|
|
)
|
|
stdscr.addstr(1, 0,
|
|
"Deployment: {0} {1:4.0%}".format(
|
|
total_progress_bar(progress),
|
|
progress / 100.0
|
|
))
|
|
for index, node in enumerate(nodes):
|
|
stdscr.addstr(index + 2, 0,
|
|
"Node{0:3} {1:13}: {2} {3:4.0%}".format(
|
|
node["id"],
|
|
node["status"],
|
|
node_bar(node["progress"]),
|
|
node["progress"] / 100.0
|
|
)
|
|
)
|
|
sleep(0.25)
|
|
stdscr.refresh()
|
|
sleep(1)
|
|
finally:
|
|
curses.echo()
|
|
curses.nocbreak()
|
|
curses.endwin()
|
|
|
|
|
|
def provisioning(params):
|
|
"""Show computed provisioning facts for orchestrator
|
|
"""
|
|
fact(params, "provisioning")
|
|
|
|
|
|
def deployment(params):
|
|
"""Show computed deployment facts for orchestrator
|
|
"""
|
|
fact(params, "deployment")
|
|
|
|
|
|
def fact(params, info_type):
|
|
check_for_attributes(params, ["env"])
|
|
|
|
dir_name = os.path.join(
|
|
os.path.abspath(params.dir or os.path.curdir),
|
|
"{0}_{1}".format(info_type, params.env)
|
|
)
|
|
facts_default_url = "clusters/{0}/orchestrator/{1}/defaults".format(
|
|
params.env,
|
|
info_type
|
|
)
|
|
facts_url = "clusters/{0}/orchestrator/{1}/".format(
|
|
params.env,
|
|
info_type
|
|
)
|
|
if params.default:
|
|
facts = json_api_get_request(facts_default_url)
|
|
write_facts_to_dir(facts, dir_name)
|
|
elif params.upload:
|
|
put_response = json_api_put_request(
|
|
facts_url,
|
|
read_deployment_info(dir_name)
|
|
if info_type == "deployment" else
|
|
read_provisioning_info(dir_name)
|
|
)
|
|
|
|
if params.json:
|
|
print(json.dumps(put_response, indent=4))
|
|
else:
|
|
print("{0} facts uploaded successfully.".format(info_type))
|
|
elif params.delete:
|
|
json_api_delete_request(facts_url)
|
|
print("{0} facts deleted successfully.".format(info_type))
|
|
else:
|
|
facts = json_api_get_request(facts_url)
|
|
if not facts:
|
|
print(
|
|
"Environment with id={0} has no {1} info."
|
|
.format(params.env, info_type)
|
|
)
|
|
return
|
|
if params.download:
|
|
write_facts_to_dir(facts, dir_name)
|
|
else:
|
|
if params.json:
|
|
print(json.dumps(facts, indent=4))
|
|
|
|
|
|
def write_json(path, data):
|
|
with open(path, "w+") as json_file:
|
|
json_file.write(json.dumps(data, indent=4))
|
|
|
|
|
|
def read_json(path):
|
|
with open(path, "r") as json_file:
|
|
return json.loads(json_file.read())
|
|
|
|
|
|
def folder_or_one_up(dir_path):
|
|
if not os.path.exists(dir_path):
|
|
path_to_folder = dir_path.split(os.sep)
|
|
one_folder_up = path_to_folder[:-2] + path_to_folder[-2:-1]
|
|
dir_path = os.sep.join(one_folder_up)
|
|
return dir_path
|
|
|
|
|
|
def read_provisioning_info(dir_name):
|
|
dir_name = folder_or_one_up(dir_name)
|
|
if "engine.json" not in os.listdir(dir_name):
|
|
print_error(
|
|
"engine.json was not found in {0}"
|
|
.format(dir_name)
|
|
)
|
|
try:
|
|
node_facts = map(
|
|
read_json,
|
|
[os.path.join(dir_name, json_file)
|
|
for json_file in os.listdir(dir_name)
|
|
if json_file.endswith('.json') and json_file != "engine.json"]
|
|
)
|
|
engine = read_json(os.path.join(dir_name, "engine.json"))
|
|
return {
|
|
"engine": engine,
|
|
"nodes": node_facts
|
|
}
|
|
except OSError:
|
|
print_error(
|
|
"Directory {0} doesn't exist."
|
|
.format(dir_name)
|
|
)
|
|
|
|
|
|
def read_deployment_info(dir_name):
|
|
dir_name = folder_or_one_up(dir_name)
|
|
try:
|
|
return map(
|
|
read_json,
|
|
[os.path.join(dir_name, json_file)
|
|
for json_file in os.listdir(dir_name)
|
|
if json_file.endswith('.json')]
|
|
)
|
|
except OSError:
|
|
print_error(
|
|
"Directory {0} doesn't exist."
|
|
.format(dir_name)
|
|
)
|
|
|
|
|
|
def write_facts_to_dir(facts, dir_name):
|
|
if os.path.exists(dir_name):
|
|
shutil.rmtree(dir_name)
|
|
print("old directory {0} was removed".format(dir_name))
|
|
os.makedirs(dir_name)
|
|
print("directory {0} was created".format(dir_name))
|
|
if isinstance(facts, dict):
|
|
engine_fd = open(os.path.join(dir_name, "engine.json"), "w+")
|
|
engine_fd.write(json.dumps(facts["engine"], indent=4))
|
|
print("Created {0}".format(engine_fd.name))
|
|
engine_fd.close()
|
|
facts = facts["nodes"]
|
|
name_template = "{name}.json"
|
|
else:
|
|
name_template = "{role}_{uid}.json"
|
|
for fact in facts:
|
|
fd = open(
|
|
os.path.join(
|
|
dir_name,
|
|
name_template.format(**fact)
|
|
),
|
|
"w+"
|
|
)
|
|
fd.write(json.dumps(fact, indent=4))
|
|
print("Created {0}".format(fd.name))
|
|
fd.close()
|
|
|
|
|
|
def parse_node_ids(x):
|
|
"""Parse arguments with commas and spaces
|
|
|
|
:returns: list of lists with numbers
|
|
"""
|
|
filtered = [y for y in x.split(",") if y.strip() != '']
|
|
if len(filtered) > 1:
|
|
return map(int, filtered)
|
|
elif len(filtered) == 1:
|
|
return [int(filtered[0])]
|
|
else:
|
|
return None
|
|
|
|
|
|
def get_download_arg(help_msg):
|
|
return {
|
|
"args": ["-d", "--download"],
|
|
"params": {
|
|
"dest": "download",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_list_arg(help_msg):
|
|
return {
|
|
"args": ["-l", "--list"],
|
|
"params": {
|
|
"dest": "list",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_dir_arg(help_msg):
|
|
return {
|
|
"args": ["--dir"],
|
|
"params": {
|
|
"dest": "dir",
|
|
"action": "store",
|
|
"help": help_msg,
|
|
"default": None
|
|
}
|
|
}
|
|
|
|
|
|
def get_verify_arg(help_msg):
|
|
return {
|
|
"args": ["-v", "--verify"],
|
|
"params": {
|
|
"dest": "verify",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_upload_arg(help_msg):
|
|
return {
|
|
"args": ["-u", "--upload"],
|
|
"params": {
|
|
"dest": "upload",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_default_arg(help_msg):
|
|
return {
|
|
"args": ["--default"],
|
|
"params": {
|
|
"dest": "default",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_set_arg(help_msg):
|
|
return {
|
|
"args": ["-s", "--set"],
|
|
"params": {
|
|
"dest": "set",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_delete_arg(help_msg):
|
|
return {
|
|
"args": ["--delete"],
|
|
"params": {
|
|
"dest": "delete",
|
|
"action": "store_true",
|
|
"help": help_msg,
|
|
"default": False
|
|
}
|
|
}
|
|
|
|
|
|
def get_release_arg(help_msg):
|
|
return {
|
|
"args": ["--rel", "--release"],
|
|
"params": {
|
|
"dest": "rel",
|
|
"action": "store",
|
|
"type": str,
|
|
"help": help_msg,
|
|
"default": None
|
|
}
|
|
}
|
|
|
|
|
|
actions = {
|
|
"release": {
|
|
"action": release,
|
|
"args": [
|
|
get_list_arg("List all available releases."),
|
|
get_release_arg("Specify release id to configure"),
|
|
{
|
|
"args": ["-c", "--config"],
|
|
"params": {
|
|
"dest": "config",
|
|
"action": "store_true",
|
|
"help": "Configure release with --release",
|
|
"default": False
|
|
}
|
|
}, {
|
|
"args": ["-U", "--user", "--user-name"],
|
|
"params": {
|
|
"dest": "username",
|
|
"action": "store",
|
|
"type": str,
|
|
"help": "Username for release credentials",
|
|
"default": None
|
|
}
|
|
}, {
|
|
"args": ["-P", "--pass", "--password"],
|
|
"params": {
|
|
"dest": "password",
|
|
"action": "store",
|
|
"type": str,
|
|
"help": "Password for release credentials",
|
|
"default": None
|
|
}
|
|
}]
|
|
},
|
|
"environment": {
|
|
"action": environment,
|
|
"args": [
|
|
get_list_arg("List all available environments."),
|
|
get_set_arg("Set environment parameters (e.g name, deployment mode)"),
|
|
get_delete_arg("Delete environment with specific env or name"),
|
|
get_release_arg("Release id"),
|
|
{
|
|
"args": ["-c", "--env-create", "--create"],
|
|
"params": {
|
|
"dest": "create",
|
|
"action": "store_true",
|
|
"help": "Create a new environment with specific "
|
|
"release id and name.",
|
|
"default": False
|
|
}
|
|
}, {
|
|
"args": ["--name", "--env-name"],
|
|
"params": {
|
|
"dest": "ename",
|
|
"action": "store",
|
|
"type": str,
|
|
"help": "environment name",
|
|
"default": None
|
|
}
|
|
}, {
|
|
"args": ["-m", "--mode", "--deployment-mode"],
|
|
"params": {
|
|
"dest": "mode",
|
|
"action": "store",
|
|
"choices": ["multinode", "ha_compact"],
|
|
"help": "Set deployment mode for specific environment.",
|
|
"default": False
|
|
}
|
|
}]
|
|
},
|
|
"node": {
|
|
"action": node,
|
|
"args": [
|
|
get_list_arg("List all nodes."),
|
|
get_set_arg("Set role for specific node."),
|
|
get_delete_arg("Delete specific node from environment."),
|
|
get_default_arg("Get default network configuration of some node"),
|
|
get_download_arg("Download configuration of specific node"),
|
|
get_upload_arg("Upload configuration to specific node"),
|
|
get_dir_arg("Select directory to which download node attributes"),
|
|
{
|
|
"args": ["--node", "--node-id"],
|
|
"params": {
|
|
"dest": "node",
|
|
"action": "store",
|
|
"nargs": '+',
|
|
"type": parse_node_ids,
|
|
"help": "Node id.",
|
|
"default": None
|
|
}
|
|
}, {
|
|
"args": ["-r", "--role"],
|
|
"params": {
|
|
"dest": "role",
|
|
"type": lambda v: v.split(','),
|
|
"action": SetAction,
|
|
"help": "Role to assign for node.",
|
|
"default": None
|
|
}
|
|
}, {
|
|
"args": ["--net", "--network"],
|
|
"params": {
|
|
"dest": "network",
|
|
"action": "store_true",
|
|
"help": "Node network configuration.",
|
|
"default": False
|
|
}
|
|
}, {
|
|
"args": ["--disk"],
|
|
"params": {
|
|
"dest": "disk",
|
|
"action": "store_true",
|
|
"help": "Node disk configuration.",
|
|
"default": False
|
|
}
|
|
}]
|
|
},
|
|
"network": {
|
|
"action": network,
|
|
"args": [
|
|
get_download_arg("Download current network configuration."),
|
|
get_dir_arg("Directory with network data."),
|
|
get_verify_arg("Verify current network configuration."),
|
|
get_upload_arg("Upload changed network configuration.")
|
|
]
|
|
},
|
|
"settings": {
|
|
"action": settings,
|
|
"args": [
|
|
get_download_arg("Modify current configuration."),
|
|
get_default_arg("Open default configuration."),
|
|
get_upload_arg("Save current changes in configuration."),
|
|
get_dir_arg("Directory with configuration data.")
|
|
]
|
|
},
|
|
"task": {
|
|
"action": task,
|
|
"args": [{
|
|
"args": ["-d", "--delete"],
|
|
"params": {
|
|
"dest": "delete",
|
|
"action": "store_true",
|
|
"help": "delete",
|
|
"default": False
|
|
}
|
|
}, {
|
|
"args": ["--id", "--task-id"],
|
|
"params": {
|
|
"dest": "tid",
|
|
"action": "store",
|
|
"nargs": '+',
|
|
"type": parse_node_ids,
|
|
"help": "Task id.",
|
|
"default": None
|
|
}
|
|
}]
|
|
},
|
|
"snapshot": {
|
|
"action": snapshot,
|
|
"args": [
|
|
get_dir_arg("Directory to which download snapshot."),
|
|
get_download_arg("Download snapshot.")
|
|
]
|
|
},
|
|
"deploy": {
|
|
"action": deploy,
|
|
"args": []
|
|
}
|
|
}
|
|
|
|
|
|
def get_args_for_facts(fact_type):
|
|
return [
|
|
get_delete_arg("Delete current {0} data.".format(fact_type)),
|
|
get_download_arg("Download current {0} data.".format(fact_type)),
|
|
get_upload_arg("Upload current {0} data.".format(fact_type)),
|
|
get_default_arg("Download default {0} data.".format(fact_type)),
|
|
get_dir_arg("Directory with {0} data.".format(fact_type))
|
|
]
|
|
|
|
|
|
substitutions = {
|
|
#replace from: to
|
|
"env": "environment",
|
|
"nodes": "node",
|
|
"net": "network",
|
|
"rel": "release",
|
|
"list": "--list",
|
|
"set": "--set",
|
|
"delete": "--delete",
|
|
"download": "--download",
|
|
"upload": "--upload",
|
|
"default": "--default",
|
|
"create": "--create",
|
|
"remove": "--delete",
|
|
"config": "--config",
|
|
"--roles": "--role"
|
|
}
|
|
|
|
|
|
def prepare_args():
|
|
# replace some args from dict substitutions
|
|
sys.argv = map(
|
|
lambda x: substitutions.get(x, x),
|
|
sys.argv
|
|
)
|
|
# move --json and --debug flags before any action
|
|
for argument in ["--json", "--debug"]:
|
|
if argument in sys.argv:
|
|
sys.argv.remove(argument)
|
|
sys.argv.insert(1, argument)
|
|
|
|
# move --env or --env-id flags to beginning if declared after action
|
|
for arg in sys.argv:
|
|
if "--env" in arg:
|
|
# if declaration with '=' sign (e.g. --env-id=1)
|
|
if "=" in arg:
|
|
index_of_env = sys.argv.index(arg)
|
|
env = sys.argv.pop(index_of_env)
|
|
sys.argv.insert(1, env)
|
|
else:
|
|
index_of_env = sys.argv.index(arg)
|
|
sys.argv.pop(index_of_env)
|
|
env = sys.argv.pop(index_of_env)
|
|
sys.argv.insert(1, env)
|
|
sys.argv.insert(1, arg)
|
|
break
|
|
|
|
for fact_type in [deployment, provisioning]:
|
|
actions[fact_type.__name__] = {
|
|
"action": fact_type,
|
|
"args": get_args_for_facts(fact_type.__name__)
|
|
}
|
|
|
|
|
|
if __name__ == '__main__':
|
|
prepare_args()
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--env", "--env-id",
|
|
dest="env",
|
|
action="store",
|
|
type=str,
|
|
help="environment id",
|
|
default=None
|
|
)
|
|
parser.add_argument("--json",
|
|
dest="json",
|
|
action="store_true",
|
|
help="prints to only json to stdout",
|
|
default=False
|
|
)
|
|
parser.add_argument("--debug",
|
|
dest="debug",
|
|
action="store_true",
|
|
help="prints details of all HTTP request",
|
|
default=False
|
|
)
|
|
subparsers = parser.add_subparsers(
|
|
dest="action", help='actions'
|
|
)
|
|
|
|
for action, parameters in actions.iteritems():
|
|
action_parser = subparsers.add_parser(
|
|
action, help=parameters["action"].__doc__
|
|
)
|
|
for arg in parameters.get("args", []):
|
|
action_parser.add_argument(
|
|
*arg["args"],
|
|
**arg["params"]
|
|
)
|
|
|
|
parsed_params, other_params = parser.parse_known_args()
|
|
sys.argv.pop(1)
|
|
DEBUG = parsed_params.debug
|
|
JSON = parsed_params.json
|
|
if parsed_params.action not in actions:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
current_action = getattr(parsed_params, "action")
|
|
delattr(parsed_params, "action")
|
|
actions[current_action]["action"](parsed_params)
|