mirror of https://github.com/ansible/ansible.git
Add feature preview play argument spec validation (#85763)
* Add new play keyword validate_argspec
Set to True to use the play name as the argument spec identifier. A play name is required (i.e. a host pattern is not supported as an argument spec name).
Alternatively, set to a specific argument spec name.
A valid argument spec for the play is required. Example:
# playbook.meta.yml
argument_specs:
name of the play:
options: {}
* Play argument spec validation runs after fact gathering
Play keywords like tags are inherited and work similarly to fact gathering
pull/85889/head
parent
c87dc6ed7d
commit
27a56a34df
@ -0,0 +1,11 @@
|
||||
minor_changes:
|
||||
- >-
|
||||
Add tech preview play argument spec validation, which can be
|
||||
enabled by setting the play keyword ``validate_argspec`` to ``True``
|
||||
or the name of an argument spec.
|
||||
When ``validate_argspec`` is set to ``True``, a play ``name`` is
|
||||
required and used as the argument spec name.
|
||||
When enabled, the argument spec is loaded from a file matching the
|
||||
pattern <playbook_name>.meta.yml.
|
||||
At minimum, this file should contain ``{"argument_specs": {"name": {"options": {}}}}``,
|
||||
where "name" is the name of the play or configured argument spec.
|
||||
@ -0,0 +1,2 @@
|
||||
shippable/posix/group3
|
||||
context/controller
|
||||
@ -0,0 +1,4 @@
|
||||
playbook: "{{ role_path }}/playbooks/{{ playbook_name }}.yml"
|
||||
playbook_meta: "{{ role_path }}/playbooks/{{ playbook_name }}.meta.yml"
|
||||
playbook_name: play_argspec
|
||||
play_name: Test play spec
|
||||
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
from __future__ import annotations
|
||||
import json
|
||||
print(json.dumps({"ansible_facts": {"custom_fact": "required"}}))
|
||||
@ -0,0 +1,3 @@
|
||||
- name: Test play argspec
|
||||
hosts: localhost
|
||||
validate_argspec: True
|
||||
@ -0,0 +1,5 @@
|
||||
argument_specs:
|
||||
Test validate_argspec and module_defaults interaction:
|
||||
options:
|
||||
valid_argument:
|
||||
type: str
|
||||
@ -0,0 +1,15 @@
|
||||
- name: "Test validate_argspec and module_defaults interaction"
|
||||
hosts: localhost
|
||||
module_defaults:
|
||||
validate_argument_spec:
|
||||
provided_arguments: |
|
||||
{% set defined_vars = {} %}
|
||||
{% set ignore = ['group_names', 'groups', 'inventory_hostname', 'inventory_hostname_short', 'playbook_dir'] %}
|
||||
{% for key, value in hostvars[inventory_hostname].items() %}
|
||||
{% if value is defined and not (key is search("^ansible_.*$") or key in ignore) %}
|
||||
{% set _ = defined_vars.update({key: value}) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ defined_vars }}
|
||||
gather_facts: no
|
||||
validate_argspec: True
|
||||
@ -0,0 +1,2 @@
|
||||
- hosts: localhost
|
||||
validate_argspec: True
|
||||
@ -0,0 +1,11 @@
|
||||
argument_specs:
|
||||
Test play spec:
|
||||
options:
|
||||
required_str:
|
||||
required: True
|
||||
type: str
|
||||
optional:
|
||||
type: str
|
||||
choices:
|
||||
- option1
|
||||
- option2
|
||||
@ -0,0 +1,6 @@
|
||||
- name: "{{ play_name | default(_play_name) }}"
|
||||
vars:
|
||||
_play_name: "{{ play_name_from_vars }}"
|
||||
hosts: localhost
|
||||
gather_facts: no
|
||||
validate_argspec: True
|
||||
@ -0,0 +1,6 @@
|
||||
argument_specs:
|
||||
Tagged Play:
|
||||
options:
|
||||
required_str:
|
||||
required: True
|
||||
type: str
|
||||
@ -0,0 +1,5 @@
|
||||
- name: Tagged Play
|
||||
hosts: localhost
|
||||
tags:
|
||||
- play_level_tag
|
||||
validate_argspec: True
|
||||
@ -0,0 +1,3 @@
|
||||
argument_specs:
|
||||
Argument Spec Name:
|
||||
options: {}
|
||||
@ -0,0 +1,3 @@
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
validate_argspec: "{{ test_variable }}"
|
||||
@ -0,0 +1,3 @@
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
validate_argspec: no
|
||||
@ -0,0 +1,3 @@
|
||||
argument_specs:
|
||||
"no":
|
||||
options: {}
|
||||
@ -0,0 +1,3 @@
|
||||
- hosts: localhost
|
||||
gather_facts: no
|
||||
validate_argspec: "no"
|
||||
@ -0,0 +1,6 @@
|
||||
argument_specs:
|
||||
Gather Facts:
|
||||
options:
|
||||
custom_fact:
|
||||
required: True
|
||||
type: str
|
||||
@ -0,0 +1,7 @@
|
||||
- name: Gather Facts
|
||||
hosts: localhost
|
||||
gather_facts: True
|
||||
validate_argspec: True
|
||||
vars:
|
||||
ansible_facts_modules:
|
||||
- custom_facts
|
||||
@ -0,0 +1,197 @@
|
||||
- name: Test missing playbook meta file
|
||||
command: ansible-playbook {{ playbook }}
|
||||
vars:
|
||||
playbook_name: missing_metadata
|
||||
register: result
|
||||
ignore_errors: True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.stderr is search("A playbook meta file is required. Considered:")
|
||||
- result.stderr is search(playbook_meta)
|
||||
vars:
|
||||
playbook_name: missing_metadata
|
||||
|
||||
- name: Test configuring validate_argspec as no (False)
|
||||
command: ansible-playbook {{ playbook }}
|
||||
vars:
|
||||
playbook_name: test_keyword_bool_no
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is not search('Validating arguments against arg spec') }}"
|
||||
|
||||
- name: Test configuring validate_argspec as "no" (string)
|
||||
command: ansible-playbook {{ playbook }}
|
||||
vars:
|
||||
playbook_name: test_keyword_str_no
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is search('Validating arguments against arg spec no') }}"
|
||||
|
||||
- name: Test configuring argument spec validation
|
||||
vars:
|
||||
playbook_name: test_keyword
|
||||
block:
|
||||
- name: Test configuring argument spec validation
|
||||
command: ansible-playbook {{ playbook }} -e 'test_variable="{{ spec_name }}"'
|
||||
vars:
|
||||
spec_name: !unsafe '{{ "Argument Spec Name" }}'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is search('Validating arguments against arg spec Argument Spec Name') }}"
|
||||
|
||||
- name: Test turning off argument spec validation
|
||||
command: ansible-playbook {{ playbook }} -e 'test_variable="{{ bool_var }}"'
|
||||
vars:
|
||||
bool_var: !unsafe '{{ False }}'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is not search('Validating arguments against arg spec') }}"
|
||||
|
||||
- name: Test undefined configuration
|
||||
command: ansible-playbook {{ playbook }} -e 'test_variable="{{ undef_var }}"'
|
||||
vars:
|
||||
undef_var: !unsafe "{{ inventory_hostname != 'localhost' }}"
|
||||
ignore_errors: True
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- >-
|
||||
result.stderr is search("Error processing keyword 'validate_argspec': .* is undefined")
|
||||
|
||||
- name: Test omitted configuration
|
||||
command: ansible-playbook {{ playbook }} -e 'test_variable="{{ omitted }}"'
|
||||
vars:
|
||||
omitted: !unsafe "{{ omit }}"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is not search('Validating arguments against arg spec') }}"
|
||||
|
||||
- name: Test configuring unknown argument spec name
|
||||
command: ansible-playbook {{ playbook }} -e 'test_variable="No argument spec"'
|
||||
ignore_errors: True
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.stderr is search("No argument spec named 'No argument spec' in " ~ playbook_meta)
|
||||
|
||||
- name: Validate missing required argument
|
||||
command: ansible-playbook {{ playbook }} -e 'play_name="{{ play_name }}"'
|
||||
register: result
|
||||
ignore_errors: True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- 'result.stdout is search("missing required arguments: required_str")'
|
||||
|
||||
- name: Validate incorrect argument choice
|
||||
command: ansible-playbook {{ playbook }} -e 'play_name="{{ play_name }}" required_str="" optional="fail"'
|
||||
register: result
|
||||
ignore_errors: True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- 'result.stdout is search("value of optional must be one of: option1, option2, got: fail")'
|
||||
|
||||
# Test compatibility with play level fact gathering
|
||||
|
||||
- name: Test validating play level fact gathering
|
||||
command: ansible-playbook {{ playbook }}
|
||||
vars:
|
||||
playbook_name: validate_facts
|
||||
environment:
|
||||
ANSIBLE_LIBRARY: "{{ role_path }}/library"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is search('Validating arguments against arg spec Gather Facts') }}"
|
||||
|
||||
# Test compatibility with the play name keyword
|
||||
|
||||
- name: Test validating a vars variable play name argument spec
|
||||
command: ansible-playbook {{ playbook }} -e 'play_name_from_vars="{{ play_name }}" required_str=""'
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ result.stdout is search('Validating arguments against arg spec ' ~ play_name) }}"
|
||||
|
||||
- name: Test a play name is required
|
||||
command: ansible-playbook {{ playbook }} -e 'play_name="{{ undef_var }}"'
|
||||
register: result
|
||||
ignore_errors: True
|
||||
loop_control:
|
||||
loop_var: undef_var
|
||||
loop:
|
||||
- !unsafe "{{ inventory_hostname }}"
|
||||
- !unsafe "{{ omit }}"
|
||||
- ""
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.results[0] is failed
|
||||
- >-
|
||||
result.results[0].stderr is search("Error processing keyword 'name': .* is undefined")
|
||||
- result.results[1] is failed
|
||||
- result.results[1].stderr is search("A play name is required when validate_argspec is True.")
|
||||
- result.results[2] is failed
|
||||
- result.results[2].stderr is search("A play name is required when validate_argspec is True.")
|
||||
|
||||
- name: Test host pattern is not used as an argument spec name
|
||||
command: ansible-playbook {{ playbook }}
|
||||
vars:
|
||||
playbook_name: no_play_name
|
||||
register: result
|
||||
ignore_errors: True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result.stderr is search(err)
|
||||
vars:
|
||||
err: >-
|
||||
A play name is required when validate_argspec is True.
|
||||
Alternatively, set validate_argspec to the name of an argument spec.
|
||||
A playbook meta file is required. Considered:
|
||||
|
||||
# Test compatibility with the play module_defaults keyword
|
||||
|
||||
- name: Test using module_defaults to validate arbitrary variables
|
||||
command: ansible-playbook {{ playbook }} -e 'nodoc="fail"'
|
||||
vars:
|
||||
playbook_name: module_defaults
|
||||
register: result
|
||||
ignore_errors: True
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- 'result.stdout is search("nodoc. Supported parameters include: valid_argument.")'
|
||||
|
||||
# Test compatibility with the play tags keyword
|
||||
|
||||
- name: Test skipping the whole play, including argspec validation
|
||||
command: ansible-playbook {{ playbook }} --skip-tags play_level_tag
|
||||
vars:
|
||||
playbook_name: tagged_play
|
||||
|
||||
- name: Test validation always runs otherwise
|
||||
command: ansible-playbook {{ playbook }} --tags task_level_tag -e 'required_str="success"'
|
||||
vars:
|
||||
playbook_name: tagged_play
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result.stdout is search("Validating arguments against arg spec Tagged Play")
|
||||
Loading…
Reference in New Issue