diff --git a/plugins/action/validate_inputs.py b/plugins/action/validate_inputs.py index 86dd5bd..5687ed0 100644 --- a/plugins/action/validate_inputs.py +++ b/plugins/action/validate_inputs.py @@ -3,7 +3,9 @@ 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 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 +entry is checked against that sub-option, and a null entry means the entry is not managed. A ``raw`` +option that declares ``options`` is a role toggle: ``true``, ``false``, or the dict those options +describe. Messages name the key path and the types involved but never a value, so secrets cannot leak into job output. """ @@ -72,6 +74,9 @@ class _Validator: if value is UNRESOLVED: return kind = spec.get("type", "str") + if kind == "raw" and "options" in spec and not isinstance(value, (bool, Mapping)): + self.errors.append(f"{path}: expected bool or dict, got {_kind(value)}") + return if not TYPES[kind](value): self.errors.append(f"{path}: expected {kind}, got {_kind(value)}") return diff --git a/tests/inputs/inputs_fixtures.yml b/tests/inputs/inputs_fixtures.yml index 6af2cec..0590fbb 100644 --- a/tests/inputs/inputs_fixtures.yml +++ b/tests/inputs/inputs_fixtures.yml @@ -102,3 +102,15 @@ inputs_fixtures: expect: - "fixture.gate.portz: unsupported key, fixture.gate accepts port" - "fixture.gate.port: expected int, got str" + + - name: raw-gate-true + value: + name: web01 + gate: true + expect: [] + + - name: raw-gate-string-rejected + value: + name: web01 + gate: "yes" + expect: ["fixture.gate: expected bool or dict, got str"]