diff --git a/plugins/action/validate_inputs.py b/plugins/action/validate_inputs.py index b2b19d2..2c24e3b 100644 --- a/plugins/action/validate_inputs.py +++ b/plugins/action/validate_inputs.py @@ -2,8 +2,8 @@ Each spec file's ``argument_specs.inputs.options`` declares top-level variables. Every one the host defines is checked without Ansible's lenient coercion: a string is never a list, bool or dict. A dict -option whose only sub-option is named ```` is a collection keyed by name; each entry is checked -against that sub-option, and a null entry means the entry is not managed. Messages name the key path +option whose only sub-option is a placeholder such as ```` is a collection keyed by name; each +entry is checked against that sub-option, and a null entry means the entry is not managed. Messages name the key path and the types involved but never a value, so secrets cannot leak into job output. """ @@ -14,7 +14,6 @@ from ansible.errors import AnsibleError from ansible.parsing.vault import EncryptedString from ansible.plugins.action import ActionBase -KEYED = "" SPEC_KEYS = {"type", "required", "default", "choices", "elements", "options", "description", "no_log"} UNRESOLVED = object() @@ -91,10 +90,11 @@ class _Validator: self.check(element, item, f"{path}[{index}]") def check_options(self, options, value, path): - if set(options) == {KEYED}: + if len(options) == 1 and next(iter(options)).startswith("<"): + entry_spec = next(iter(options.values())) for name, entry in value.items(): if entry is not None: - self.check(options[KEYED], entry, f"{path}.{name}") + self.check(entry_spec, entry, f"{path}.{name}") return for key in value: if key not in options: diff --git a/tests/inputs/fixture_spec.yml b/tests/inputs/fixture_spec.yml index 5b8938d..99e4e35 100644 --- a/tests/inputs/fixture_spec.yml +++ b/tests/inputs/fixture_spec.yml @@ -36,7 +36,7 @@ argument_specs: users: type: dict options: - "": + "": type: dict options: password: