def _check_mutually_exclusive(self, spec, param=None): if param is None: param = self.params try: check_mutually_exclusive(spec, param) except TypeError as e: msg = to_native(e) if self._options_context: msg += " found in %s" % " -> ".join(self._options_context) self.fail_json(msg=msg)
def test_check_mutually_exclusive_found(mutually_exclusive_terms): params = { 'string1': 'cat', 'string2': 'hat', 'fox': 'red', 'socks': 'blue', } expected = "TypeError('parameters are mutually exclusive: string1|string2, box|fox|socks',)" with pytest.raises(TypeError) as e: check_mutually_exclusive(mutually_exclusive_terms, params) assert e.value == expected
def test_check_mutually_exclusive_none(): terms = None params = { 'string1': 'cat', 'fox': 'hat', } assert check_mutually_exclusive(terms, params) == []
def main(): result = {} module = AnsibleModule(argument_spec=dict(category=dict(required=True), command=dict(required=True, type='list', elements='str'), baseuri=dict(required=True), username=dict(), password=dict(no_log=True), auth_token=dict(no_log=True), manager_attributes=dict( type='dict', default={}), timeout=dict(type='int', default=10), resource_id=dict()), required_together=[ ('username', 'password'), ], required_one_of=[ ('username', 'auth_token'), ], mutually_exclusive=[ ('username', 'auth_token'), ], supports_check_mode=False) category = module.params['category'] command_list = module.params['command'] # admin credentials used for authentication creds = { 'user': module.params['username'], 'pswd': module.params['password'], 'token': module.params['auth_token'] } # timeout timeout = module.params['timeout'] # System, Manager or Chassis ID to modify resource_id = module.params['resource_id'] # Build root URI root_uri = "https://" + module.params['baseuri'] rf_utils = IdracRedfishUtils(creds, root_uri, timeout, module, resource_id=resource_id, data_modification=True) # Check that Category is valid if category not in CATEGORY_COMMANDS_ALL: module.fail_json( msg=to_native("Invalid Category '%s'. Valid Categories = %s" % (category, list(CATEGORY_COMMANDS_ALL.keys())))) # Check that all commands are valid for cmd in command_list: # Fail if even one command given is invalid if cmd not in CATEGORY_COMMANDS_ALL[category]: module.fail_json( msg=to_native("Invalid Command '%s'. Valid Commands = %s" % (cmd, CATEGORY_COMMANDS_ALL[category]))) # check for mutually exclusive commands try: # check_mutually_exclusive accepts a single list or list of lists that # are groups of terms that should be mutually exclusive with one another # and checks that against a dictionary check_mutually_exclusive( CATEGORY_COMMANDS_MUTUALLY_EXCLUSIVE[category], dict.fromkeys(command_list, True)) except TypeError as e: module.fail_json(msg=to_native(e)) # Organize by Categories / Commands if category == "Manager": # execute only if we find a Manager resource result = rf_utils._find_managers_resource() if result['ret'] is False: module.fail_json(msg=to_native(result['msg'])) for command in command_list: if command in [ "SetManagerAttributes", "SetLifecycleControllerAttributes", "SetSystemAttributes" ]: result = rf_utils.set_manager_attributes(command) # Return data back or fail with proper message if result['ret'] is True: module.exit_json(changed=result['changed'], msg=to_native(result['msg'])) else: module.fail_json(msg=to_native(result['msg']))
def validate(self, parameters, *args, **kwargs): """Validate ``parameters`` against argument spec. Error messages in the :class:`ValidationResult` may contain no_log values and should be sanitized with :func:`~ansible.module_utils.common.parameters.sanitize_keys` before logging or displaying. :arg parameters: Parameters to validate against the argument spec :type parameters: dict[str, dict] :return: :class:`ValidationResult` containing validated parameters. :Simple Example: .. code-block:: text argument_spec = { 'name': {'type': 'str'}, 'age': {'type': 'int'}, } parameters = { 'name': 'bo', 'age': '42', } validator = ArgumentSpecValidator(argument_spec) result = validator.validate(parameters) if result.error_messages: sys.exit("Validation failed: {0}".format(", ".join(result.error_messages)) valid_params = result.validated_parameters """ result = ValidationResult(parameters) result._no_log_values.update( set_fallbacks(self.argument_spec, result._validated_parameters)) alias_warnings = [] alias_deprecations = [] try: result._aliases.update( _handle_aliases(self.argument_spec, result._validated_parameters, alias_warnings, alias_deprecations)) except (TypeError, ValueError) as e: result.errors.append(AliasError(to_native(e))) legal_inputs = _get_legal_inputs(self.argument_spec, result._validated_parameters, result._aliases) for option, alias in alias_warnings: result._warnings.append({'option': option, 'alias': alias}) for deprecation in alias_deprecations: result._deprecations.append({ 'name': deprecation['name'], 'version': deprecation.get('version'), 'date': deprecation.get('date'), 'collection_name': deprecation.get('collection_name'), }) try: result._no_log_values.update( _list_no_log_values(self.argument_spec, result._validated_parameters)) except TypeError as te: result.errors.append(NoLogError(to_native(te))) try: result._unsupported_parameters.update( _get_unsupported_parameters(self.argument_spec, result._validated_parameters, legal_inputs)) except TypeError as te: result.errors.append(RequiredDefaultError(to_native(te))) except ValueError as ve: result.errors.append(AliasError(to_native(ve))) try: check_mutually_exclusive(self._mutually_exclusive, result._validated_parameters) except TypeError as te: result.errors.append(MutuallyExclusiveError(to_native(te))) result._no_log_values.update( _set_defaults(self.argument_spec, result._validated_parameters, False)) try: check_required_arguments(self.argument_spec, result._validated_parameters) except TypeError as e: result.errors.append(RequiredError(to_native(e))) _validate_argument_types(self.argument_spec, result._validated_parameters, errors=result.errors) _validate_argument_values(self.argument_spec, result._validated_parameters, errors=result.errors) for check in _ADDITIONAL_CHECKS: try: check['func'](getattr(self, "_{attr}".format(attr=check['attr'])), result._validated_parameters) except TypeError as te: result.errors.append(check['err'](to_native(te))) result._no_log_values.update( _set_defaults(self.argument_spec, result._validated_parameters)) _validate_sub_spec( self.argument_spec, result._validated_parameters, errors=result.errors, no_log_values=result._no_log_values, unsupported_parameters=result._unsupported_parameters) if result._unsupported_parameters: flattened_names = [] for item in result._unsupported_parameters: if isinstance(item, tuple): flattened_names.append(".".join(item)) else: flattened_names.append(item) unsupported_string = ", ".join(sorted(list(flattened_names))) supported_string = ", ".join(self._valid_parameter_names) result.errors.append( UnsupportedError( "{0}. Supported parameters include: {1}.".format( unsupported_string, supported_string))) return result
def _validate_sub_spec(argument_spec, parameters, prefix='', options_context=None, errors=None, no_log_values=None, unsupported_parameters=None): """Validate sub argument spec. This function is recursive. """ if options_context is None: options_context = [] if errors is None: errors = AnsibleValidationErrorMultiple() if no_log_values is None: no_log_values = set() if unsupported_parameters is None: unsupported_parameters = set() for param, value in argument_spec.items(): wanted = value.get('type') if wanted == 'dict' or (wanted == 'list' and value.get('elements', '') == 'dict'): sub_spec = value.get('options') if value.get('apply_defaults', False): if sub_spec is not None: if parameters.get(param) is None: parameters[param] = {} else: continue elif sub_spec is None or param not in parameters or parameters[ param] is None: continue # Keep track of context for warning messages options_context.append(param) # Make sure we can iterate over the elements if not isinstance(parameters[param], Sequence) or isinstance( parameters[param], string_types): elements = [parameters[param]] else: elements = parameters[param] for idx, sub_parameters in enumerate(elements): no_log_values.update(set_fallbacks(sub_spec, sub_parameters)) if not isinstance(sub_parameters, dict): errors.append( SubParameterTypeError( "value of '%s' must be of type dict or list of dicts" % param)) continue # Set prefix for warning messages new_prefix = prefix + param if wanted == 'list': new_prefix += '[%d]' % idx new_prefix += '.' alias_warnings = [] alias_deprecations = [] try: options_aliases = _handle_aliases(sub_spec, sub_parameters, alias_warnings, alias_deprecations) except (TypeError, ValueError) as e: options_aliases = {} errors.append(AliasError(to_native(e))) for option, alias in alias_warnings: warn('Both option %s and its alias %s are set.' % (option, alias)) try: no_log_values.update( _list_no_log_values(sub_spec, sub_parameters)) except TypeError as te: errors.append(NoLogError(to_native(te))) legal_inputs = _get_legal_inputs(sub_spec, sub_parameters, options_aliases) unsupported_parameters.update( _get_unsupported_parameters(sub_spec, sub_parameters, legal_inputs, options_context)) try: check_mutually_exclusive(value.get('mutually_exclusive'), sub_parameters, options_context) except TypeError as e: errors.append(MutuallyExclusiveError(to_native(e))) no_log_values.update( _set_defaults(sub_spec, sub_parameters, False)) try: check_required_arguments(sub_spec, sub_parameters, options_context) except TypeError as e: errors.append(RequiredError(to_native(e))) _validate_argument_types(sub_spec, sub_parameters, new_prefix, options_context, errors=errors) _validate_argument_values(sub_spec, sub_parameters, options_context, errors=errors) for check in _ADDITIONAL_CHECKS: try: check['func'](value.get(check['attr']), sub_parameters, options_context) except TypeError as e: errors.append(check['err'](to_native(e))) no_log_values.update(_set_defaults(sub_spec, sub_parameters)) # Handle nested specs _validate_sub_spec(sub_spec, sub_parameters, new_prefix, options_context, errors, no_log_values, unsupported_parameters) options_context.pop()
def validate_sub_spec(argument_spec, parameters, prefix='', options_context=None, errors=None, no_log_values=None, unsupported_parameters=None): """Validate sub argument spec. This function is recursive.""" if options_context is None: options_context = [] if errors is None: errors = [] if no_log_values is None: no_log_values = set() if unsupported_parameters is None: unsupported_parameters = set() for param, value in argument_spec.items(): wanted = value.get('type') if wanted == 'dict' or (wanted == 'list' and value.get('elements', '') == dict): sub_spec = value.get('options') if value.get('apply_defaults', False): if sub_spec is not None: if parameters.get(value) is None: parameters[param] = {} else: continue elif sub_spec is None or param not in parameters or parameters[param] is None: continue # Keep track of context for warning messages options_context.append(param) # Make sure we can iterate over the elements if isinstance(parameters[param], dict): elements = [parameters[param]] else: elements = parameters[param] for idx, sub_parameters in enumerate(elements): if not isinstance(sub_parameters, dict): errors.append("value of '%s' must be of type dict or list of dicts" % param) # Set prefix for warning messages new_prefix = prefix + param if wanted == 'list': new_prefix += '[%d]' % idx new_prefix += '.' no_log_values.update(set_fallbacks(sub_spec, sub_parameters)) alias_warnings = [] try: options_aliases, legal_inputs = handle_aliases(sub_spec, sub_parameters, alias_warnings) except (TypeError, ValueError) as e: options_aliases = {} legal_inputs = None errors.append(to_native(e)) for option, alias in alias_warnings: warn('Both option %s and its alias %s are set.' % (option, alias)) no_log_values.update(list_no_log_values(sub_spec, sub_parameters)) if legal_inputs is None: legal_inputs = list(options_aliases.keys()) + list(sub_spec.keys()) unsupported_parameters.update(get_unsupported_parameters(sub_spec, sub_parameters, legal_inputs)) try: check_mutually_exclusive(value.get('mutually_exclusive'), sub_parameters) except TypeError as e: errors.append(to_native(e)) no_log_values.update(set_defaults(sub_spec, sub_parameters, False)) try: check_required_arguments(sub_spec, sub_parameters) except TypeError as e: errors.append(to_native(e)) validate_argument_types(sub_spec, sub_parameters, new_prefix, options_context, errors=errors) validate_argument_values(sub_spec, sub_parameters, options_context, errors=errors) checks = [ (check_required_together, 'required_together'), (check_required_one_of, 'required_one_of'), (check_required_if, 'required_if'), (check_required_by, 'required_by'), ] for check in checks: try: check[0](value.get(check[1]), parameters) except TypeError as e: errors.append(to_native(e)) no_log_values.update(set_defaults(sub_spec, sub_parameters)) # Handle nested specs validate_sub_spec(sub_spec, sub_parameters, new_prefix, options_context, errors, no_log_values, unsupported_parameters) options_context.pop()
def validate(self, parameters, *args, **kwargs): """Validate module parameters against argument spec. Returns a ValidationResult object. Error messages in the ValidationResult may contain no_log values and should be sanitized before logging or displaying. :Example: validator = ArgumentSpecValidator(argument_spec) result = validator.validate(parameters) if result.error_messages: sys.exit("Validation failed: {0}".format(", ".join(result.error_messages)) valid_params = result.validated_parameters :param argument_spec: Specification of parameters, type, and valid values :type argument_spec: dict :param parameters: Parameters provided to the role :type parameters: dict :return: Object containing validated parameters. :rtype: ValidationResult """ result = ValidationResult(parameters) result._no_log_values.update( set_fallbacks(self.argument_spec, result._validated_parameters)) alias_warnings = [] alias_deprecations = [] try: aliases = _handle_aliases(self.argument_spec, result._validated_parameters, alias_warnings, alias_deprecations) except (TypeError, ValueError) as e: aliases = {} result.errors.append(AliasError(to_native(e))) legal_inputs = _get_legal_inputs(self.argument_spec, result._validated_parameters, aliases) for option, alias in alias_warnings: result._warnings.append({'option': option, 'alias': alias}) for deprecation in alias_deprecations: result._deprecations.append({ 'name': deprecation['name'], 'version': deprecation.get('version'), 'date': deprecation.get('date'), 'collection_name': deprecation.get('collection_name'), }) try: result._no_log_values.update( _list_no_log_values(self.argument_spec, result._validated_parameters)) except TypeError as te: result.errors.append(NoLogError(to_native(te))) try: result._unsupported_parameters.update( _get_unsupported_parameters(self.argument_spec, result._validated_parameters, legal_inputs)) except TypeError as te: result.errors.append(RequiredDefaultError(to_native(te))) except ValueError as ve: result.errors.append(AliasError(to_native(ve))) try: check_mutually_exclusive(self._mutually_exclusive, result._validated_parameters) except TypeError as te: result.errors.append(MutuallyExclusiveError(to_native(te))) result._no_log_values.update( _set_defaults(self.argument_spec, result._validated_parameters, False)) try: check_required_arguments(self.argument_spec, result._validated_parameters) except TypeError as e: result.errors.append(RequiredError(to_native(e))) _validate_argument_types(self.argument_spec, result._validated_parameters, errors=result.errors) _validate_argument_values(self.argument_spec, result._validated_parameters, errors=result.errors) for check in _ADDITIONAL_CHECKS: try: check['func'](getattr(self, "_{attr}".format(attr=check['attr'])), result._validated_parameters) except TypeError as te: result.errors.append(check['err'](to_native(te))) result._no_log_values.update( _set_defaults(self.argument_spec, result._validated_parameters)) _validate_sub_spec( self.argument_spec, result._validated_parameters, errors=result.errors, no_log_values=result._no_log_values, unsupported_parameters=result._unsupported_parameters) if result._unsupported_parameters: flattened_names = [] for item in result._unsupported_parameters: if isinstance(item, tuple): flattened_names.append(".".join(item)) else: flattened_names.append(item) unsupported_string = ", ".join(sorted(list(flattened_names))) supported_string = ", ".join(self._valid_parameter_names) result.errors.append( UnsupportedError( "{0}. Supported parameters include: {1}.".format( unsupported_string, supported_string))) return result
def run_module(): module_args = dict(cert_not_after=dict(type="str"), cert_not_before=dict(type="str"), force=dict(type="bool"), host=dict(type="bool"), k8ssa_token_path=dict(type="path"), key=dict(type="path"), kid=dict(type="str"), name=dict(aliases=["subject"], type="str", required=True), not_after=dict(type="str"), not_before=dict(type="str"), output_file=dict(type="path"), principal=dict(type="list", elements="str"), provisioner=dict(type="str", aliases=["issuer"]), provisioner_password_file=dict(type="path", no_log=False), return_token=dict(type="bool"), revoke=dict(type="bool"), renew=dict(type="bool"), rekey=dict(type="bool"), san=dict(type="list", elements="str"), ssh=dict(type="bool"), sshpop_cert=dict(type="str"), sshpop_key=dict(type="path"), x5c_cert=dict(type="str"), x5c_key=dict(type="path"), step_cli_executable=dict(type="path", default="step-cli")) result = dict(changed=False, stdout="", stderr="", msg="") module = AnsibleModule(argument_spec={ **module_args, **connection_argspec }, supports_check_mode=True) check_mutually_exclusive(["return_token", "output_file"], module.params) check_required_one_of(["return_token", "output_file"], module.params) check_step_cli_install(module, module.params["step_cli_executable"], result) # Positional Parameters params = ["ca", "token", module.params["name"]] # Regular args args = [ "cert_not_after", "cert_not_before", "force", "host", "k8ssa_token_path", "key", "kid", "not_after", "not_before", "output_file", "principal", "provisioner", "provisioner_password_file", "revoke", "renew", "rekey", "san", "ssh", "sshpop_cert", "sshpop_key", "x5c_cert", "x5c_key" ] # All parameters can be converted to a mapping by just appending -- and replacing the underscores args = {arg: "--{a}".format(a=arg.replace("_", "-")) for arg in args} result = run_step_cli_command(module.params["step_cli_executable"], params, module, result, { **args, **connection_run_args }) result["changed"] = True if module.params["return_token"]: result["token"] = result["stdout"] result["stdout"] = "" result["stdout_lines"] = "" module.exit_json(**result)
def test_check_mutually_exclusive_no_params(mutually_exclusive_terms): with pytest.raises(TypeError) as te: check_mutually_exclusive(mutually_exclusive_terms, None) assert "TypeError: 'NoneType' object is not iterable" in to_native( te.error)
def test_check_mutually_exclusive(mutually_exclusive_terms): params = { 'string1': 'cat', 'fox': 'hat', } assert check_mutually_exclusive(mutually_exclusive_terms, params) == []