diff --git a/ansible_taskrunner/cli.py b/ansible_taskrunner/cli.py index bc23bbf..2cb056f 100644 --- a/ansible_taskrunner/cli.py +++ b/ansible_taskrunner/cli.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Import builtins from __future__ import print_function +from collections import OrderedDict import logging import logging.handlers import os @@ -318,6 +319,30 @@ def run(args=None, **kwargs): prefix = 'echo' if kwargs.get('_echo') else '' # Gather variables from commandline for interpolation cli_vars = '' + # python 2.x + # Make sure kwargs adhere to the order in + # which they were called + if sys.version_info[0] < 3: + # First we build a mapping of cli variables to corresponding yaml variables + req_parameters = yaml_vars.get('required_parameters', {}) or {} + opt_parameters = yaml_vars.get('optional_parameters', {}) or {} + if req_parameters: + parameter_mapping = dict(opt_parameters).update(dict(req_parameters)) + else: + parameter_mapping = dict(opt_parameters) + # Next, we create a dictionary that holds cli arguments + # in the order they were called, as per the parameter mapping + ordered_args = {} + for k, v in parameter_mapping.items(): + for a in sys.argv: + if re.search(k, a): + for o in k.split('|'): + if o in sys.argv: + i = sys.argv.index(o) + ordered_args[k] = i + # Lastly, we convert our kwargs object to + # an ordered dictionary object as per the above + kwargs = OrderedDict([(parameter_mapping[k],kwargs.get(parameter_mapping[k])) for k, v in sorted(ordered_args.items(), key=lambda item: item[1])]) for key, value in kwargs.items(): if key.startswith('_'): cli_vars += '{k}="{v}"\n'.format(k=key, v=value) @@ -341,7 +366,10 @@ def run(args=None, **kwargs): vars_list.append((var[0],var[1])) else: vars_list.append((var[0], kwargs_dict_filtered[var[0]])) - default_vars = dict(vars_list) + if sys.version_info[0] < 3: + default_vars = OrderedDict(vars_list) + else: + default_vars = dict(vars_list) # List-type variables list_vars = [] for var in default_vars: @@ -363,30 +391,29 @@ def run(args=None, **kwargs): # Short-circuit the task runner # if we're calling functions from the commandline cli_functions = ['{k} {v}'.format( - k=key, v=value) for key, value in kwargs.items() if + k=key, v='' if value in [True, False] else value) for key, value in kwargs.items() if value and key in internal_functions.keys()] if cli_functions: - for cli_function in cli_functions: - command = '''{clv} + command = '''{clv} {dsv} {psv} {dlv} {bfn} {clf} {arg} {raw} - '''.format( - dsv='\n'.join(defaults_string_vars), - psv=paramset_var, - dlv='\n'.join(list_vars), - clv=cli_vars, - bfn='\n'.join(bash_functions), - clf=cli_function, - arg=args, - raw=raw_args - ) - if prefix == 'echo': - print(command) - else: - yamlcli.call(command) + '''.format( + dsv='\n'.join(defaults_string_vars), + psv=paramset_var, + dlv='\n'.join(list_vars), + clv=cli_vars, + bfn='\n'.join(bash_functions), + clf='\n'.join(cli_functions), + arg=args, + raw=raw_args + ) + if prefix == 'echo': + print(command) + else: + yamlcli.call(command) else: # Invoke the cli provider provider_cli.invocation( diff --git a/ansible_taskrunner/lib/superduperconfig/__init__.py b/ansible_taskrunner/lib/superduperconfig/__init__.py index 368cba2..5223ca3 100644 --- a/ansible_taskrunner/lib/superduperconfig/__init__.py +++ b/ansible_taskrunner/lib/superduperconfig/__init__.py @@ -1,6 +1,7 @@ import logging import os import sys +from collections import OrderedDict import yaml # Logging @@ -15,6 +16,18 @@ def __init__(self, prog_name): self.logger = logger pass + def ordered_load(self, stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict): + class OrderedLoader(Loader): + pass + def construct_mapping(loader, node): + loader.flatten_mapping(node) + return object_pairs_hook(loader.construct_pairs(node)) + OrderedLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + construct_mapping) + return yaml.load(stream, OrderedLoader) + + def load_config(self, config_file, req_keys=[], failfast=False, data_key=None, debug=False): """ Load config file """ @@ -33,7 +46,12 @@ def load_config(self, config_file, req_keys=[], failfast=False, data_key=None, d config_found = True try: with open(config_path, 'r') as ymlfile: - cfg = yaml.load(ymlfile, yaml.Loader) + # Preserve dictionary order for python 2 + # https://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts + if sys.version_info[0] < 3: + cfg = self.ordered_load(ymlfile, yaml.Loader) + else: + cfg = yaml.load(ymlfile, yaml.Loader) config_dict = cfg[data_key] if data_key is not None else cfg config_is_valid = all([m[m.keys()[0]].get(k) for k in req_keys for m in config_dict])