feat(validation): treat any <placeholder> sub-option as a keyed collection
This commit is contained in:
@@ -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 ``<name>`` 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 ``<name>`` 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 = "<name>"
|
||||
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:
|
||||
|
||||
@@ -36,7 +36,7 @@ argument_specs:
|
||||
users:
|
||||
type: dict
|
||||
options:
|
||||
"<name>":
|
||||
"<user>":
|
||||
type: dict
|
||||
options:
|
||||
password:
|
||||
|
||||
Reference in New Issue
Block a user