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