feat(validation): add a strict validate_inputs action for user input

This commit is contained in:
2026-10-05 18:05:05 +02:00
parent 8d4bc2a207
commit f0b580cd3d
8 changed files with 333 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
---
- name: "Validate the fixture ({{ inf.name }})"
block:
- name: "Run the validator ({{ inf.name }})"
validate_inputs:
specs:
- "{{ playbook_dir }}/fixture_spec.yml"
vars:
fixture: "{{ inf.value }}"
register: inputs_result
rescue:
- name: "Keep the failed result ({{ inf.name }})"
ansible.builtin.set_fact:
inputs_result: "{{ ansible_failed_result }}"
- name: "Assert the reported violations ({{ inf.name }})"
ansible.builtin.assert:
that:
- (inputs_result.errors | default([])) | sort == inf.expect | sort
- "'topsecret-value' not in (inputs_result | to_json)"
fail_msg: "[inputs {{ inf.name }}] expected {{ inf.expect }}, got {{ inputs_result.errors | default([]) }}"
success_msg: "[inputs {{ inf.name }}] OK"
+42
View File
@@ -0,0 +1,42 @@
---
argument_specs:
inputs:
options:
fixture:
type: dict
options:
name:
type: str
required: true
enabled:
type: bool
count:
type: int
servers:
type: list
elements: str
kind:
type: str
choices: [a, b]
secret:
type: str
no_log: true
disks:
type: list
elements: dict
options:
size:
type: int
required: true
users:
type: dict
options:
"<name>":
type: dict
options:
password:
type: str
no_log: true
keys:
type: list
elements: str
+18
View File
@@ -0,0 +1,18 @@
---
# Vaulted with a discarded password: the validator must handle a value it cannot decrypt.
fixture:
name: web01
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
62363835313830363035333361613236373662303039376639396136336438616331336565393631
6637376163346430386663393661386561316537306662350a663563326164376164396230616361
33646564346532643735396239626133333162353839666135343030643836313938363466383439
3130353636666365310a353064373139323836373163336563353861356333316534386238333664
3663
servers: !vault |
$ANSIBLE_VAULT;1.1;AES256
65626131373433633432363261356534613135386461626535393539656631323531346661643233
3961366535643630386462303038636231373239343130310a343565336461633532316136643139
30306461616232663763633630353661376130323536346637633964373239343664303533396236
6165623262633131330a303965643439626335663130376237393362363231346537643334633163
3835
+88
View File
@@ -0,0 +1,88 @@
---
inputs_fixtures:
- name: valid
value:
name: web01
enabled: true
count: "3"
servers: [10.0.0.1, 10.0.0.2]
kind: a
disks:
- size: 30
users:
alice:
password: pw
keys: [ssh-ed25519 AAAA alice]
retired: ~
expect: []
- name: list-for-dict
value:
name: web01
users:
- name: alice
expect: ["fixture.users: expected dict, got list"]
- name: comma-string-for-list
value:
name: web01
servers: 10.0.0.1,10.0.0.2
expect: ["fixture.servers: expected list, got str"]
- name: yes-string-for-bool
value:
name: web01
enabled: "yes"
expect: ["fixture.enabled: expected bool, got str"]
- name: typo-key
value:
name: web01
userz: {}
expect: ["fixture.userz: unsupported key, fixture accepts count, disks, enabled, kind, name, secret, servers, users"]
- name: wrong-element-type
value:
name: web01
disks:
- size: 30
- size: big
expect: ["fixture.disks[1].size: expected int, got str"]
- name: missing-required
value:
enabled: false
expect: ["fixture.name: required key is missing"]
- name: choice-mismatch
value:
name: web01
kind: c
expect: ["fixture.kind: must be one of a, b, got 'c'"]
- name: secrets-never-echoed
value:
name: web01
secret: [topsecret-value]
users:
alice:
password: [topsecret-value]
expect:
- "fixture.secret: expected str, got list"
- "fixture.users.alice.password: expected str, got list"
- name: keyed-entry-not-a-dict
value:
name: web01
users:
alice: pw
expect: ["fixture.users.alice: expected dict, got str"]
- name: two-violations-at-once
value:
name: web01
enabled: "true"
userz: {}
expect:
- "fixture.userz: unsupported key, fixture accepts count, disks, enabled, kind, name, secret, servers, users"
- "fixture.enabled: expected bool, got str"
+35
View File
@@ -0,0 +1,35 @@
---
# Run: ansible-playbook tests/inputs/test_inputs.yml
- name: Strict input validator fixture tests
hosts: localhost
gather_facts: false
connection: local
vars_files:
- inputs_fixtures.yml
tasks:
- name: Run each input fixture
ansible.builtin.include_tasks: _assert_inputs.yml
loop: "{{ inputs_fixtures }}"
loop_control:
loop_var: inf
label: "{{ inf.name }}"
- name: Validate a host var holding values that cannot be decrypted
block:
- name: Run the validator on the vaulted host var
validate_inputs:
specs:
- "{{ playbook_dir }}/fixture_spec.yml"
register: inputs_vault_result
rescue:
- name: Keep the failed vault result
ansible.builtin.set_fact:
inputs_vault_result: "{{ ansible_failed_result }}"
- name: Assert a vaulted string passes and a vaulted non-list is reported
ansible.builtin.assert:
that:
- >-
inputs_vault_result.errors | default([]) == ['fixture.servers: expected list, got str']
fail_msg: "[inputs vault] got {{ inputs_vault_result.errors | default([]) }}"
success_msg: "[inputs vault] OK"