Rework CLI, add list-domains
diff --git a/reclass_tools/__init__.py b/reclass_tools/__init__.py
index 0418604..e69de29 100644
--- a/reclass_tools/__init__.py
+++ b/reclass_tools/__init__.py
@@ -1,38 +0,0 @@
-import os
-import time
-import logging.config
-
-
-LOGGER_SETTINGS = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'loggers': {
- 'reclass_tools': {
- 'level': 'DEBUG',
- 'handlers': ['console_output'],
- },
- 'paramiko': {'level': 'WARNING'},
- 'iso8601': {'level': 'WARNING'},
- 'keystoneauth': {'level': 'WARNING'},
- },
- 'handlers': {
- 'console_output': {
- 'class': 'logging.StreamHandler',
- 'level': 'INFO',
- 'formatter': 'default',
- 'stream': 'ext://sys.stdout',
- },
- },
- 'formatters': {
- 'default': {
- 'format': '%(asctime)s - %(levelname)s - %(filename)s:'
- '%(lineno)d -- %(message)s',
- 'datefmt': '%Y-%m-%d %H:%M:%S',
- },
- },
-}
-
-logging.config.dictConfig(LOGGER_SETTINGS)
-# set logging timezone to GMT
-logging.Formatter.converter = time.gmtime
-logger = logging.getLogger(__name__)
diff --git a/reclass_tools/cli.py b/reclass_tools/cli.py
index becff4e..c696400 100644
--- a/reclass_tools/cli.py
+++ b/reclass_tools/cli.py
@@ -1,4 +1,4 @@
-# Copyright 2013 - 2016 Mirantis, Inc.
+# Copyright 2013 - 2017 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
@@ -22,157 +22,206 @@
from reclass_tools import walk_models
-def execute(params):
+class Shell(object):
+ def __init__(self, args):
+ self.args = args
+ self.params = self.get_params()
- results = walk_models.get_all_reclass_params(
- params.paths,
- verbose=params.verbose)
+ def execute(self):
+ command_name = 'do_{}'.format(self.params.command.replace('-', '_'))
+ command_method = getattr(self, command_name)
+ command_method()
- print(yaml.dump(results))
+ def do_get_key(self):
+ results = walk_models.remove_reclass_parameter(
+ self.params.path,
+ self.params.key_name,
+ verbose=self.params.verbose,
+ pretend=True)
+
+ def do_del_key(self):
+ results = walk_models.remove_reclass_parameter(
+ self.params.path,
+ self.params.key_name,
+ verbose=self.params.verbose,
+ pretend=False)
+
+ def do_list_params(self):
+ results = walk_models.get_all_reclass_params(
+ self.params.path,
+ verbose=self.params.verbose)
+ print(yaml.dump(results))
+
+ def do_list_domains(self):
+ try:
+ from reclass_tools import reclass_models
+ except ImportError:
+ sys.exit("Please run this tool on the salt-master node "
+ "with installed 'reclass'")
+ inventory = reclass_models.inventory_list()
+ reclass_storage = reclass_models.reclass_storage(inventory=inventory)
+ print('\n'.join(sorted(reclass_storage.keys())))
-def dump_params(args=None):
+ def do_list_nodes(self):
+ try:
+ from reclass_tools import reclass_models
+ except ImportError:
+ sys.exit("Please run this tool on the salt-master node "
+ "with installed 'reclass'")
+
+ inventory = reclass_models.inventory_list(domain=self.params.domain)
+ vcp_nodes = reclass_models.vcp_list(domain=self.params.domain,
+ inventory=inventory)
+ vcp_node_names = ['{0}.{1}'.format(name, domain)
+ for name, domain in vcp_nodes]
+
+ if self.params.vcp_only:
+ print('\n'.join(sorted(vcp_node_names)))
+ elif self.params.non_vcp_only:
+ print('\n'.join(sorted((node_name for node_name in inventory.keys()
+ if node_name not in vcp_node_names))))
+ else:
+ print('\n'.join(sorted(inventory.keys())))
+
+ def do_show_context(self):
+ try:
+ from reclass_tools import create_inventory
+ except ImportError:
+ sys.exit("Please run this tool on the salt-master node "
+ "with installed 'reclass'")
+
+ current_underlay_context = create_inventory.create_inventory_context(
+ domain=self.params.domain, keys=self.params.keys)
+
+ print(yaml.dump(current_underlay_context, default_flow_style=False))
+
+ def do_render(self):
+ try:
+ from reclass_tools import create_inventory
+ except ImportError:
+ sys.exit("Please run this tool on the salt-master node "
+ "with installed 'reclass'")
+
+ if not self.params.template_dir or not self.params.output_dir \
+ or not self.params.contexts:
+ sys.exit("Missing parameters, see: reclass-tools render -h")
+
+ create_inventory.render_dir(template_dir=self.params.template_dir,
+ output_dir=self.params.output_dir,
+ contexts=self.params.contexts)
+
+ def get_params(self):
+
+ verbose_parser = argparse.ArgumentParser(add_help=False)
+ verbose_parser.add_argument('--verbose', dest='verbose',
+ action='store_const', const=True,
+ help='Show verbosed output', default=False)
+
+ key_parser = argparse.ArgumentParser(add_help=False)
+ key_parser_help = (
+ 'Key name to find in reclass model files, for example:'
+ ' parameters.linux.network.interface')
+ key_parser.add_argument('key_name', help=key_parser_help, default=None)
+
+ keys_parser = argparse.ArgumentParser(add_help=False)
+ keys_parser.add_argument(
+ 'keys',
+ help='Key names to find in reclass model files', nargs='*')
+
+ path_parser = argparse.ArgumentParser(add_help=False)
+ path_parser.add_argument(
+ 'path',
+ help='Path to search for *.yml files.', nargs='+')
+
+ domain_parser = argparse.ArgumentParser(add_help=False)
+ domain_parser.add_argument(
+ '--domain', '-d', dest='domain',
+ help=('Show only the nodes which names are ended with the '
+ 'specified domain, for example: example.local'))
+
+ vcp_only_parser = argparse.ArgumentParser(add_help=False)
+ vcp_only_parser.add_argument(
+ '--vcp-only', dest='vcp_only',
+ action='store_const', const=True,
+ help=('Show only VCP nodes (present in '
+ 'parameters.salt.control.cluster.internal.node)'),
+ default=False)
+
+ non_vcp_only_parser = argparse.ArgumentParser(add_help=False)
+ non_vcp_only_parser.add_argument(
+ '--non-vcp-only', dest='non_vcp_only',
+ action='store_const', const=True, default=False,
+ help=('Show only non-VCP nodes (absent in '
+ 'parameters.salt.control.cluster.internal.node)'))
+
+ render_parser = argparse.ArgumentParser(add_help=False)
+ render_parser.add_argument(
+ '--template-dir', '-t', dest='template_dir',
+ help=('Coockiecutter-based template directory'))
+ render_parser.add_argument(
+ '--output-dir', '-o', dest='output_dir',
+ help=('Path to the directory where the rendered '
+ 'template will be placed'))
+ render_parser.add_argument(
+ '--context', '-c', dest='contexts', nargs='+',
+ help=('YAML/JSON files with context data to render '
+ 'the template'))
+
+
+
+ parser = argparse.ArgumentParser(
+ description="Manage virtual environments. "
+ "For additional help, use with -h/--help option")
+ subparsers = parser.add_subparsers(title="Operation commands",
+ help='available commands',
+ dest='command')
+
+ # TODO: add-class NNN [to] MMM.yml # can be used with 'render'
+ subparsers.add_parser('get-key',
+ parents=[key_parser, path_parser,
+ verbose_parser],
+ help="Find a key in YAMLs found in <path>",
+ description=("Get a key collected from "
+ "different YAMLs"))
+ subparsers.add_parser('del-key',
+ parents=[key_parser, path_parser,
+ verbose_parser],
+ help="Delete a key from YAMLs found in <path>",
+ description="Delete a key from different YAMLs")
+ subparsers.add_parser('list-params',
+ parents=[path_parser, verbose_parser],
+ help=("Collect all options for "
+ "'parameters._params' keys from YAMLs "
+ "found in <path>"))
+ subparsers.add_parser('list-nodes',
+ parents=[domain_parser, vcp_only_parser,
+ non_vcp_only_parser],
+ help=("List nodes that are available for "
+ "reclass. Use on salt-master node only!"))
+ subparsers.add_parser('list-domains',
+ help=("List domains that are available from "
+ "reclass models. Use on salt-master "
+ "node only!"))
+ subparsers.add_parser('show-context',
+ parents=[domain_parser, keys_parser],
+ help=("Show domain nodes with rendered content "
+ "for specified keys. Use on salt-master "
+ "node for already generated inventory "
+ "only!"))
+ subparsers.add_parser('render',
+ parents=[render_parser],
+ help=("Render cookiecutter template using "
+ "multiple metadata sources"))
+
+ if len(self.args) == 0:
+ self.args = ['-h']
+ return parser.parse_args(self.args)
+
+
+def main(args=None):
if args is None:
args = sys.argv[1:]
- parser = argparse.ArgumentParser(
- formatter_class=argparse.RawTextHelpFormatter,
- description="")
- parser.add_argument('--verbose', dest='verbose', action='store_const', const=True,
- help='Show verbosed output.', default=False)
- parser.add_argument('paths', help='Paths to search for *.yml files.', nargs='+')
-
- if len(args) == 0:
- args = ['-h']
-
- params = parser.parse_args(args)
- results = walk_models.get_all_reclass_params(
- params.paths,
- verbose=params.verbose)
-
- print(yaml.dump(results))
-
-
-def show_key(args=None):
- remove_key(args=args, pretend=True)
-
-
-def remove_key(args=None, pretend=False):
- if args is None:
- args = sys.argv[1:]
-
- key_parser = argparse.ArgumentParser(add_help=False)
- if pretend:
- key_parser_help = (
- 'Key name to find in reclass model files, for example:'
- ' reclass-show-key parameters.linux.network.interface'
- ' /path/to/model/')
- else:
- key_parser_help = (
- 'Key name to remove from reclass model files, for example:'
- ' reclass-remove-key parameters.linux.network.interface'
- ' /path/to/model/')
- key_parser.add_argument('key_name', help=key_parser_help)
-
- parser = argparse.ArgumentParser(parents=[key_parser],
- formatter_class=argparse.RawTextHelpFormatter,
- description="")
- parser.add_argument('--verbose', dest='verbose', action='store_const', const=True,
- help='Show verbosed output.', default=False)
- parser.add_argument('paths', help='Paths to search for *.yml files.', nargs='+')
-
- if len(args) == 0:
- args = ['-h']
-
- params = parser.parse_args(args)
- results = walk_models.remove_reclass_parameter(
- params.paths,
- params.key_name,
- verbose=params.verbose,
- pretend=pretend)
-
-
-def inventory_list(args=None):
- try:
- from reclass_tools import reclass_models
- except ImportError:
- print("Please run this tool on the salt-master node with installed 'reclass'")
- return
-
- parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
- description="")
- parser.add_argument('--domain', '-d', dest='domain',
- help=('Show only the nodes which names are ended with the specified domain, for example:'
- ' reclass-inventory-list -d example.local'))
-
-
- params = parser.parse_args(args)
-
- inventory = reclass_models.inventory_list(domain=params.domain)
-
- print('\n'.join(sorted(inventory.keys())))
-
-def vcp_list(args=None):
- try:
- from reclass_tools import reclass_models
- except ImportError:
- print("Please run this tool on the salt-master node with installed 'reclass'")
- return
-
- parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
- description="")
- parser.add_argument('--domain', '-d', dest='domain',
- help=('Show only the nodes which names are ended with the specified domain, for example:'
- ' reclass-inventory-list -d example.local'))
-
- params = parser.parse_args(args)
-
- vcp_node_names = reclass_models.vcp_list(domain=params.domain)
- #print('\n'.join(sorted(vcp_node_names)))
- print('\n'.join(sorted(('{0}.{1}'.format(name, domain) for name, domain in vcp_node_names))))
-
-
-def create_inventory_context(args=None):
- try:
- from reclass_tools import create_inventory
- except ImportError:
- print("Please run this tool on the salt-master node with installed 'reclass'")
- return
-
- parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
- description="Dumps nodes and specified node parameters from reclass, for example: create_inventory_context -d example.local parameters.linux.network.interface parameters.linux.storage")
- parser.add_argument('--domain', '-d', dest='domain',
- help=('Show only the nodes which names are ended with the specified domain, for example:'
- ' reclass-inventory-list -d example.local'))
- parser.add_argument('keys', help=(
- 'Reclass key names to dump with nodes'), nargs='*')
-
- params = parser.parse_args(args)
-
- current_underlay_context = create_inventory.create_inventory_context(domain=params.domain, keys=params.keys)
-
- print(yaml.dump(current_underlay_context, default_flow_style=False))
-
-
-def render_dir(args=None):
- try:
- from reclass_tools import create_inventory
- except ImportError:
- print("Please run this tool on the salt-master node with installed 'reclass'")
- return
-
- parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
- description="Render a coockiecutter-based template directory using several different context files")
- parser.add_argument('--template-dir', '-t', dest='template_dir',
- help=('Coockiecutter-based template directory'))
- parser.add_argument('--output-dir', '-o', dest='output_dir',
- help=('Path to the directory where the rendered template will be placed'))
- parser.add_argument('--context', '-c', dest='contexts',
- help=('Path to the directory where the rendered template will be placed'),
- nargs='+')
-
- params = parser.parse_args(args)
-
- create_inventory.render_dir(template_dir=params.template_dir, output_dir=params.output_dir, contexts=params.contexts)
-
-
+ shell = Shell(args)
+ shell.execute()
diff --git a/reclass_tools/create_inventory.py b/reclass_tools/create_inventory.py
index ce0d7a6..9b03632 100644
--- a/reclass_tools/create_inventory.py
+++ b/reclass_tools/create_inventory.py
@@ -1,9 +1,6 @@
import yaml
import json
-
-from cookiecutter import __version__
-#from cookiecutter.log import configure_logger
-#from cookiecutter.main import cookiecutter
+import sys
from cookiecutter import generate
from cookiecutter.exceptions import UndefinedVariableInTemplate
@@ -41,12 +38,7 @@
reclass_storage = reclass_models.reclass_storage(domain=domain, inventory=inventory)
if domain is None:
- raise Exception("Please specify a domain name from: \n{}".format('\n'.join(reclass_storage.keys())))
-
- #current_underlay_context = {
- # 'current_clusters': {
- # }
- #}
+ sys.exit("Error: please specify a domain name from: \n{}".format('\n'.join(reclass_storage.keys())))
for storage_domain, storage_nodes in reclass_storage.items():
if storage_domain != domain:
@@ -78,9 +70,6 @@
if reclass_key:
helpers.create_nested_key(current_cluster_nodes[inventory_node_name], path=key_path, value=reclass_key)
- #current_underlay_context['current_clusters'][domain] = {
- # 'nodes': current_cluster_nodes
- #}
current_underlay_context = {
'cookiecutter': {
'cluster_name': storage_domain,
@@ -91,32 +80,6 @@
return current_underlay_context
- #1. Generate jinga interfaces / hw details based on node information provided to jinja
-
- #2. Generate appropriate includes to reclass.storate model in config node
- #configure_logger(
- # stream_level='DEBUG' if verbose else 'INFO',
- # debug_file=debug_file,
- #)
-
-#current_clusters:
-# <cluster_names>:
-# nodes:
-# <node_names>:
-# name: ctl01
-# reclass_storage_name: openstack_control_node01
-# # if classes - then classes
-# roles:
-# - vcp # to select wich interface type to use
-# #- openstack_controller # Don't forget to map the roles to corresponded classes if needed
-# parameters: # there is just a DUMP of the existing model,
-# # which could be re-used complete or particulary for rendering new model
-# linux:
-# network:
-# interfaces:
-# ..
-
-
def render_dir(template_dir, output_dir, contexts):
"""Coockiecutter echancement to use several source JSON files
@@ -128,23 +91,7 @@
dict is in the same order as files in the list.
"""
-#ipdb> repo_dir
-#u'/root/cookiecutter-templates/cluster_product/openstack'
-#ipdb> context
-#{u'cookiecutter': {u'openstack_telemetry_node02_hostname': u'mdb02', ... }}
-#ipdb> overwrite_if_exists
-#False
-#ipdb> output_dir
-#'/root/my_new_deployment/'
-
- print(template_dir)
- print(output_dir)
- print(contexts)
- #return
- #repo_dir = '/root/cookiecutter-templates/cluster_product/openstack'
overwrite_if_exists = True
- #output_dir = '/root/my_new_deployment/'
- #context = {'cookiecutter': {'openstack_telemetry_node02_hostname': 'mdb02' }}
merged_context = {}
for fcon in contexts:
@@ -153,16 +100,10 @@
elif fcon.endswith('.json'):
context = helpers.json_read(fcon)
else:
- print("Error: Please use YAML or JSON files for contexts")
- return # should be exit 1
+ sys.exit("Error: Please use YAML or JSON files for contexts")
-
- #merged_context.update(context)
- #merged_context = dict(chain(merged_context.items(), context.items()))
merged_context = helpers.merge_nested_objects(merged_context, context)
- #print(yaml.dump(merged_context, default_flow_style=False))
-
try:
generate.generate_files(
repo_dir=template_dir,
@@ -171,14 +112,11 @@
output_dir=output_dir
)
-
except UndefinedVariableInTemplate as undefined_err:
- print('>>> {}'.format(undefined_err.message))
- print('>>> Error message: {}'.format(undefined_err.error.message))
-
context_str = yaml.dump(
undefined_err.context,
default_flow_style=False
)
print('='*15 + ' Context: '+ '='*15 + '\n{}'.format(context_str) + '='*40)
- return
+ print('>>> {}'.format(undefined_err.message))
+ sys.exit('>>> Error message: {}'.format(undefined_err.error.message))
diff --git a/reclass_tools/walk_models.py b/reclass_tools/walk_models.py
index 7017722..8b4d91f 100644
--- a/reclass_tools/walk_models.py
+++ b/reclass_tools/walk_models.py
@@ -148,11 +148,11 @@
if nested_key:
found_keys[fyml.fname] = copy.deepcopy(nested_key)
if pretend:
- print("\nFound {0} in {1}".format('.'.join(remove_key),
+ print("\n---\n# Found {0} in {1}".format('.'.join(remove_key),
fyml.fname))
print(yaml.dump(nested_key, default_flow_style=False))
else:
- print("\nRemoving {0} from {1}".format('.'.join(remove_key),
+ print("\n---\n# Removing {0} from {1}".format('.'.join(remove_key),
fyml.fname))
print(yaml.dump(nested_key, default_flow_style=False))