1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
---
- name: "Running include_role test1"
hosts: localhost
gather_facts: false
vars:
ansible_unicode_type_match: "<type 'ansible.parsing.yaml.objects.AnsibleUnicode'>"
unicode_type_match: "<type 'unicode'>"
string_type_match: "<type 'str'>"
float_type_match: "<type 'float'>"
list_type_match: "<type 'list'>"
ansible_list_type_match: "<type 'ansible.parsing.yaml.objects.AnsibleSequence'>"
dict_type_match: "<type 'dict'>"
ansible_dict_type_match: "<type 'ansible.parsing.yaml.objects.AnsibleMapping'>"
ansible_unicode_class_match: "<class 'ansible.parsing.yaml.objects.AnsibleUnicode'>"
unicode_class_match: "<class 'unicode'>"
string_class_match: "<class 'str'>"
bytes_class_match: "<class 'bytes'>"
float_class_match: "<class 'float'>"
list_class_match: "<class 'list'>"
ansible_list_class_match: "<class 'ansible.parsing.yaml.objects.AnsibleSequence'>"
dict_class_match: "<class 'dict'>"
ansible_dict_class_match: "<class 'ansible.parsing.yaml.objects.AnsibleMapping'>"
expected:
test1_1:
argument_errors: [
"argument 'tidy_expected' is of type <class 'ansible.parsing.yaml.objects.AnsibleMapping'> and we were unable to convert to list: <class 'ansible.parsing.yaml.objects.AnsibleMapping'> cannot be converted to a list",
"argument 'bust_some_stuff' is of type <class 'str'> and we were unable to convert to int: <class 'str'> cannot be converted to an int",
"argument 'some_list' is of type <class 'ansible.parsing.yaml.objects.AnsibleMapping'> and we were unable to convert to list: <class 'ansible.parsing.yaml.objects.AnsibleMapping'> cannot be converted to a list",
"argument 'some_dict' is of type <class 'ansible.parsing.yaml.objects.AnsibleSequence'> and we were unable to convert to dict: <class 'ansible.parsing.yaml.objects.AnsibleSequence'> cannot be converted to a dict",
"argument 'some_int' is of type <class 'float'> and we were unable to convert to int: <class 'float'> cannot be converted to an int",
"argument 'some_float' is of type <class 'str'> and we were unable to convert to float: <class 'str'> cannot be converted to a float",
"argument 'some_bytes' is of type <class 'bytes'> and we were unable to convert to bytes: <class 'bytes'> cannot be converted to a Byte value",
"argument 'some_bits' is of type <class 'str'> and we were unable to convert to bits: <class 'str'> cannot be converted to a Bit value",
"value of test1_choices must be one of: this paddle game, the astray, this remote control, the chair, got: My dog",
"value of some_choices must be one of: choice1, choice2, got: choice4",
"argument 'some_second_level' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> found in 'some_dict_options'. and we were unable to convert to bool: The value 'not-a-bool' is not a valid boolean. ",
"argument 'third_level' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> found in 'multi_level_option -> second_level'. and we were unable to convert to int: <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> cannot be converted to an int",
"argument 'some_more_dict_options' is of type <class 'ansible.parsing.yaml.objects.AnsibleUnicode'> and we were unable to convert to dict: dictionary requested, could not parse JSON or key=value",
"value of 'some_more_dict_options' must be of type dict or list of dicts",
"dictionary requested, could not parse JSON or key=value",
]
tasks:
- name: include_role test1 since it has a arg_spec.yml
block:
- include_role:
name: test1
vars:
tidy_expected:
some_key: some_value
test1_var1: 37.4
test1_choices: "My dog"
bust_some_stuff: "some_string_that_is_not_an_int"
some_choices: "choice4"
some_str: 37.5
some_list: {'a': false}
some_dict:
- "foo"
- "bar"
some_int: 37.
some_float: "notafloatisit"
some_path: "anything_is_a_valid_path"
some_raw: {"anything_can_be": "a_raw_type"}
# not sure what would be an invalid jsonarg
# some_jsonarg: "not sure what this does yet"
some_json: |
'{[1, 3, 3] 345345|45v<#!}'
some_jsonarg: |
{"foo": [1, 3, 3]}
# not sure we can load binary in safe_load
some_bytes: !!binary |
R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
+f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
some_bits: "foo"
# some_str_nicknames: []
# some_str_akas: {}
some_str_removed_in: "foo"
some_dict_options:
some_second_level: "not-a-bool"
some_more_dict_options: "not-a-dict"
multi_level_option:
second_level:
third_level: "should_be_int"
- fail:
msg: "Should not get here"
rescue:
- debug:
var: ansible_failed_result
- name: replace py version specific types with generic names so tests work on py2 and py3
set_fact:
# We want to compare if the actual failure messages and the expected failure messages
# are the same. But to compare and do set differences, we have to handle some
# differences between py2/py3.
# The validation failure messages include python type and class reprs, which are
# different between py2 and py3. For ex, "<type 'str'>" vs "<class 'str'>". Plus
# the usual py2/py3 unicode/str/bytes type shenanigans. The 'THE_FLOAT_REPR' is
# because py3 quotes the value in the error while py2 does not, so we just ignore
# the rest of the line.
actual_generic: "{{ ansible_failed_result.argument_errors|
map('replace', ansible_unicode_type_match, 'STR')|
map('replace', unicode_type_match, 'STR')|
map('replace', string_type_match, 'STR')|
map('replace', float_type_match, 'FLOAT')|
map('replace', list_type_match, 'LIST')|
map('replace', ansible_list_type_match, 'LIST')|
map('replace', dict_type_match, 'DICT')|
map('replace', ansible_dict_type_match, 'DICT')|
map('replace', ansible_unicode_class_match, 'STR')|
map('replace', unicode_class_match, 'STR')|
map('replace', string_class_match, 'STR')|
map('replace', bytes_class_match, 'STR')|
map('replace', float_class_match, 'FLOAT')|
map('replace', list_class_match, 'LIST')|
map('replace', ansible_list_class_match, 'LIST')|
map('replace', dict_class_match, 'DICT')|
map('replace', ansible_dict_class_match, 'DICT')|
map('regex_replace', '''float:.*$''', 'THE_FLOAT_REPR')|
map('regex_replace', 'Valid booleans include.*$', '')|
list }}"
expected_generic: "{{ expected.test1_1.argument_errors|
map('replace', ansible_unicode_type_match, 'STR')|
map('replace', unicode_type_match, 'STR')|
map('replace', string_type_match, 'STR')|
map('replace', float_type_match, 'FLOAT')|
map('replace', list_type_match, 'LIST')|
map('replace', ansible_list_type_match, 'LIST')|
map('replace', dict_type_match, 'DICT')|
map('replace', ansible_dict_type_match, 'DICT')|
map('replace', ansible_unicode_class_match, 'STR')|
map('replace', unicode_class_match, 'STR')|
map('replace', string_class_match, 'STR')|
map('replace', bytes_class_match, 'STR')|
map('replace', float_class_match, 'FLOAT')|
map('replace', list_class_match, 'LIST')|
map('replace', ansible_list_class_match, 'LIST')|
map('replace', dict_class_match, 'DICT')|
map('replace', ansible_dict_class_match, 'DICT')|
map('regex_replace', '''float:.*$''', 'THE_FLOAT_REPR')|
map('regex_replace', 'Valid booleans include.*$', '')|
list }}"
- name: figure out the difference between expected and actual validate_argument_spec failures
set_fact:
actual_not_in_expected: "{{ actual_generic| difference(expected_generic) | sort() }}"
expected_not_in_actual: "{{ expected_generic | difference(actual_generic) | sort() }}"
- name: assert that all actual validate_argument_spec failures were in expected
assert:
that:
- actual_not_in_expected | length == 0
msg: "Actual validate_argument_spec failures that were not expected: {{ actual_not_in_expected }}"
- name: assert that all expected validate_argument_spec failures were in expected
assert:
that:
- expected_not_in_actual | length == 0
msg: "Expected validate_argument_spec failures that were not in actual results: {{ expected_not_in_actual }}"
- name: assert that `validate_args_context` return value has what we expect
assert:
that:
- ansible_failed_result.validate_args_context.argument_spec_name == "main"
- ansible_failed_result.validate_args_context.name == "test1"
- ansible_failed_result.validate_args_context.type == "role"
- "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/test1')"
- name: test message for missing required parameters and invalid suboptions
block:
- include_role:
name: test1
vars:
some_json: '{}'
some_jsonarg: '{}'
multi_level_option:
second_level:
not_a_supported_suboption: true
- fail:
msg: "Should not get here"
rescue:
- debug:
var: ansible_failed_result
- assert:
that:
- ansible_failed_result.argument_errors | length == 2
- missing_required in ansible_failed_result.argument_errors
- got_unexpected in ansible_failed_result.argument_errors
vars:
missing_required: "missing required arguments: third_level found in multi_level_option -> second_level"
got_unexpected: "multi_level_option.second_level.not_a_supported_suboption. Supported parameters include: third_level."
|