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
|
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
|
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
|
option whose only sub-option is a placeholder such as ``<name>`` is a collection keyed by name; each
|
||||||
against that sub-option, and a null entry means the entry is not managed. Messages name the key path
|
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.
|
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.parsing.vault import EncryptedString
|
||||||
from ansible.plugins.action import ActionBase
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
KEYED = "<name>"
|
|
||||||
SPEC_KEYS = {"type", "required", "default", "choices", "elements", "options", "description", "no_log"}
|
SPEC_KEYS = {"type", "required", "default", "choices", "elements", "options", "description", "no_log"}
|
||||||
UNRESOLVED = object()
|
UNRESOLVED = object()
|
||||||
|
|
||||||
@@ -91,10 +90,11 @@ class _Validator:
|
|||||||
self.check(element, item, f"{path}[{index}]")
|
self.check(element, item, f"{path}[{index}]")
|
||||||
|
|
||||||
def check_options(self, options, value, path):
|
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():
|
for name, entry in value.items():
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
self.check(options[KEYED], entry, f"{path}.{name}")
|
self.check(entry_spec, entry, f"{path}.{name}")
|
||||||
return
|
return
|
||||||
for key in value:
|
for key in value:
|
||||||
if key not in options:
|
if key not in options:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ argument_specs:
|
|||||||
users:
|
users:
|
||||||
type: dict
|
type: dict
|
||||||
options:
|
options:
|
||||||
"<name>":
|
"<user>":
|
||||||
type: dict
|
type: dict
|
||||||
options:
|
options:
|
||||||
password:
|
password:
|
||||||
|
|||||||
Reference in New Issue
Block a user