From 8a754e0858d922e955e71b253c139e071ecec432 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 18:04:21 +0200 Subject: Adding upstream version 2.14.3. Signed-off-by: Daniel Baumann --- test/integration/targets/roles_arg_spec/aliases | 2 + .../ansible_collections/foo/bar/MANIFEST.json | 0 .../foo/bar/roles/blah/meta/argument_specs.yml | 8 + .../foo/bar/roles/blah/tasks/main.yml | 3 + .../roles_arg_spec/roles/a/meta/argument_specs.yml | 17 + .../targets/roles_arg_spec/roles/a/meta/main.yml | 13 + .../roles_arg_spec/roles/a/tasks/alternate.yml | 3 + .../targets/roles_arg_spec/roles/a/tasks/main.yml | 3 + .../roles/a/tasks/no_spec_entrypoint.yml | 3 + .../roles/b/meta/argument_specs.yaml | 13 + .../targets/roles_arg_spec/roles/b/tasks/main.yml | 9 + .../targets/roles_arg_spec/roles/c/meta/main.yml | 7 + .../targets/roles_arg_spec/roles/c/tasks/main.yml | 12 + .../roles/empty_argspec/meta/argument_specs.yml | 2 + .../roles/empty_argspec/tasks/main.yml | 3 + .../roles/empty_file/meta/argument_specs.yml | 1 + .../roles_arg_spec/roles/empty_file/tasks/main.yml | 3 + .../role_with_no_tasks/meta/argument_specs.yml | 7 + .../roles_arg_spec/roles/test1/defaults/main.yml | 3 + .../roles/test1/meta/argument_specs.yml | 112 +++++++ .../roles_arg_spec/roles/test1/tasks/main.yml | 11 + .../roles_arg_spec/roles/test1/tasks/other.yml | 11 + .../roles/test1/tasks/test1_other.yml | 11 + .../roles_arg_spec/roles/test1/vars/main.yml | 4 + .../roles_arg_spec/roles/test1/vars/other.yml | 4 + test/integration/targets/roles_arg_spec/runme.sh | 32 ++ test/integration/targets/roles_arg_spec/test.yml | 356 +++++++++++++++++++++ .../roles_arg_spec/test_complex_role_fails.yml | 197 ++++++++++++ .../roles_arg_spec/test_play_level_role_fails.yml | 5 + .../targets/roles_arg_spec/test_tags.yml | 11 + 30 files changed, 866 insertions(+) create mode 100644 test/integration/targets/roles_arg_spec/aliases create mode 100644 test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/MANIFEST.json create mode 100644 test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/a/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/a/meta/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/a/tasks/alternate.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/a/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/a/tasks/no_spec_entrypoint.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/b/meta/argument_specs.yaml create mode 100644 test/integration/targets/roles_arg_spec/roles/b/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/c/meta/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/c/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/empty_argspec/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/empty_argspec/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/empty_file/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/empty_file/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/role_with_no_tasks/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/defaults/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/meta/argument_specs.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/tasks/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/tasks/other.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/tasks/test1_other.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/vars/main.yml create mode 100644 test/integration/targets/roles_arg_spec/roles/test1/vars/other.yml create mode 100755 test/integration/targets/roles_arg_spec/runme.sh create mode 100644 test/integration/targets/roles_arg_spec/test.yml create mode 100644 test/integration/targets/roles_arg_spec/test_complex_role_fails.yml create mode 100644 test/integration/targets/roles_arg_spec/test_play_level_role_fails.yml create mode 100644 test/integration/targets/roles_arg_spec/test_tags.yml (limited to 'test/integration/targets/roles_arg_spec') diff --git a/test/integration/targets/roles_arg_spec/aliases b/test/integration/targets/roles_arg_spec/aliases new file mode 100644 index 0000000..498fedd --- /dev/null +++ b/test/integration/targets/roles_arg_spec/aliases @@ -0,0 +1,2 @@ +shippable/posix/group4 +context/controller diff --git a/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/MANIFEST.json b/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/MANIFEST.json new file mode 100644 index 0000000..e69de29 diff --git a/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/meta/argument_specs.yml new file mode 100644 index 0000000..3cb0a87 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/meta/argument_specs.yml @@ -0,0 +1,8 @@ +argument_specs: + main: + short_description: "The foo.bar.blah role" + options: + blah_str: + type: "str" + required: true + description: "A string value" diff --git a/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/tasks/main.yml b/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/tasks/main.yml new file mode 100644 index 0000000..ecb4dac --- /dev/null +++ b/test/integration/targets/roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah/tasks/main.yml @@ -0,0 +1,3 @@ +- name: "First task of blah role" + debug: + msg: "The string is {{ blah_str }}" diff --git a/test/integration/targets/roles_arg_spec/roles/a/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/roles/a/meta/argument_specs.yml new file mode 100644 index 0000000..cfc1a37 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/a/meta/argument_specs.yml @@ -0,0 +1,17 @@ +argument_specs: + main: + short_description: Main entry point for role A. + options: + a_str: + type: "str" + required: true + + alternate: + short_description: Alternate entry point for role A. + options: + a_int: + type: "int" + required: true + + no_spec_entrypoint: + short_description: An entry point with no spec diff --git a/test/integration/targets/roles_arg_spec/roles/a/meta/main.yml b/test/integration/targets/roles_arg_spec/roles/a/meta/main.yml new file mode 100644 index 0000000..90920db --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/a/meta/main.yml @@ -0,0 +1,13 @@ +# This meta/main.yml exists to test that it is NOT read, with preference being +# given to the meta/argument_specs.yml file. This spec contains an extra required +# parameter, a_something, that argument_specs.yml does not. +argument_specs: + main: + short_description: Main entry point for role A. + options: + a_str: + type: "str" + required: true + a_something: + type: "str" + required: true diff --git a/test/integration/targets/roles_arg_spec/roles/a/tasks/alternate.yml b/test/integration/targets/roles_arg_spec/roles/a/tasks/alternate.yml new file mode 100644 index 0000000..4d688be --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/a/tasks/alternate.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "Role A (alternate) with {{ a_int }}" diff --git a/test/integration/targets/roles_arg_spec/roles/a/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/a/tasks/main.yml new file mode 100644 index 0000000..a74f37b --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/a/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "Role A with {{ a_str }}" diff --git a/test/integration/targets/roles_arg_spec/roles/a/tasks/no_spec_entrypoint.yml b/test/integration/targets/roles_arg_spec/roles/a/tasks/no_spec_entrypoint.yml new file mode 100644 index 0000000..f1e600b --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/a/tasks/no_spec_entrypoint.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "Role A no_spec_entrypoint" diff --git a/test/integration/targets/roles_arg_spec/roles/b/meta/argument_specs.yaml b/test/integration/targets/roles_arg_spec/roles/b/meta/argument_specs.yaml new file mode 100644 index 0000000..93663e9 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/b/meta/argument_specs.yaml @@ -0,0 +1,13 @@ +argument_specs: + main: + short_description: Main entry point for role B. + options: + b_str: + type: "str" + required: true + b_int: + type: "int" + required: true + b_bool: + type: "bool" + required: true diff --git a/test/integration/targets/roles_arg_spec/roles/b/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/b/tasks/main.yml new file mode 100644 index 0000000..b7e15cc --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/b/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- debug: + msg: "Role B" +- debug: + var: b_str +- debug: + var: b_int +- debug: + var: b_bool diff --git a/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml b/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml new file mode 100644 index 0000000..1a1ccbe --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml @@ -0,0 +1,7 @@ +argument_specs: + main: + short_description: Main entry point for role C. + options: + c_int: + type: "int" + required: true diff --git a/test/integration/targets/roles_arg_spec/roles/c/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/c/tasks/main.yml new file mode 100644 index 0000000..78282be --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/c/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- debug: + msg: "Role C that includes Role A with var {{ c_int }}" + +- name: "Role C import_role A with a_str {{ a_str }}" + import_role: + name: a + +- name: "Role C include_role A with a_int {{ a_int }}" + include_role: + name: a + tasks_from: "alternate" diff --git a/test/integration/targets/roles_arg_spec/roles/empty_argspec/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/roles/empty_argspec/meta/argument_specs.yml new file mode 100644 index 0000000..b592aa0 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/empty_argspec/meta/argument_specs.yml @@ -0,0 +1,2 @@ +--- +argument_specs: diff --git a/test/integration/targets/roles_arg_spec/roles/empty_argspec/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/empty_argspec/tasks/main.yml new file mode 100644 index 0000000..90aab0e --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/empty_argspec/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "Role with empty argument_specs key" diff --git a/test/integration/targets/roles_arg_spec/roles/empty_file/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/roles/empty_file/meta/argument_specs.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/empty_file/meta/argument_specs.yml @@ -0,0 +1 @@ +--- diff --git a/test/integration/targets/roles_arg_spec/roles/empty_file/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/empty_file/tasks/main.yml new file mode 100644 index 0000000..b77b835 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/empty_file/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- debug: + msg: "Role with empty argument_specs.yml" diff --git a/test/integration/targets/roles_arg_spec/roles/role_with_no_tasks/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/roles/role_with_no_tasks/meta/argument_specs.yml new file mode 100644 index 0000000..55e4800 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/role_with_no_tasks/meta/argument_specs.yml @@ -0,0 +1,7 @@ +argument_specs: + main: + short_description: Main entry point for role role_with_no_tasks. + options: + a_str: + type: "str" + required: true diff --git a/test/integration/targets/roles_arg_spec/roles/test1/defaults/main.yml b/test/integration/targets/roles_arg_spec/roles/test1/defaults/main.yml new file mode 100644 index 0000000..5255f93 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/defaults/main.yml @@ -0,0 +1,3 @@ +--- +# defaults file for test1 +test1_var1: 'THE_TEST1_VAR1_DEFAULT_VALUE' diff --git a/test/integration/targets/roles_arg_spec/roles/test1/meta/argument_specs.yml b/test/integration/targets/roles_arg_spec/roles/test1/meta/argument_specs.yml new file mode 100644 index 0000000..427946e --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/meta/argument_specs.yml @@ -0,0 +1,112 @@ +--- +argument_specs: + main: + short_description: "EXPECTED FAILURE Validate the argument spec for the 'test1' role" + options: + test1_choices: + required: false + # required: true + choices: + - "this paddle game" + - "the astray" + - "this remote control" + - "the chair" + type: "str" + default: "this paddle game" + tidy_expected: + # required: false + # default: none + type: "list" + test1_var1: + # required: true + default: "THIS IS THE DEFAULT SURVEY ANSWER FOR test1_survey_test1_var1" + type: "str" + test1_var2: + required: false + default: "This IS THE DEFAULT fake band name / test1_var2 answer from survey_spec.yml" + type: "str" + bust_some_stuff: + # required: false + type: "int" + some_choices: + choices: + - "choice1" + - "choice2" + required: false + type: "str" + some_str: + type: "str" + some_list: + type: "list" + elements: "float" + some_dict: + type: "dict" + some_bool: + type: "bool" + some_int: + type: "int" + some_float: + type: "float" + some_path: + type: "path" + some_raw: + type: "raw" + some_jsonarg: + type: "jsonarg" + required: true + some_json: + type: "json" + required: true + some_bytes: + type: "bytes" + some_bits: + type: "bits" + some_str_aliases: + type: "str" + aliases: + - "some_str_nicknames" + - "some_str_akas" + - "some_str_formerly_known_as" + some_dict_options: + type: "dict" + options: + some_second_level: + type: "bool" + default: true + some_more_dict_options: + type: "dict" + options: + some_second_level: + type: "str" + some_str_removed_in: + type: "str" + removed_in: 2.10 + some_tmp_path: + type: "path" + multi_level_option: + type: "dict" + options: + second_level: + type: "dict" + options: + third_level: + type: "int" + required: true + + other: + short_description: "test1_simple_preset_arg_spec_other" + description: "A simpler set of required args for other tasks" + options: + test1_var1: + default: "This the default value for the other set of arg specs for test1 test1_var1" + type: "str" + + test1_other: + description: "test1_other for role_that_includes_role" + options: + some_test1_other_arg: + default: "The some_test1_other_arg default value" + type: str + some_required_str: + type: str + required: true diff --git a/test/integration/targets/roles_arg_spec/roles/test1/tasks/main.yml b/test/integration/targets/roles_arg_spec/roles/test1/tasks/main.yml new file mode 100644 index 0000000..9ecf8b0 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/tasks/main.yml @@ -0,0 +1,11 @@ +--- +# tasks file for test1 +- name: debug for task1 show test1_var1 + debug: + var: test1_var1 + tags: ["runme"] + +- name: debug for task1 show test1_var2 + debug: + var: test1_var2 + tags: ["runme"] diff --git a/test/integration/targets/roles_arg_spec/roles/test1/tasks/other.yml b/test/integration/targets/roles_arg_spec/roles/test1/tasks/other.yml new file mode 100644 index 0000000..b045813 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/tasks/other.yml @@ -0,0 +1,11 @@ +--- +# "other" tasks file for test1 +- name: other tasks debug for task1 show test1_var1 + debug: + var: test1_var1 + tags: ["runme"] + +- name: other tasks debug for task1 show test1_var2 + debug: + var: test1_var2 + tags: ["runme"] diff --git a/test/integration/targets/roles_arg_spec/roles/test1/tasks/test1_other.yml b/test/integration/targets/roles_arg_spec/roles/test1/tasks/test1_other.yml new file mode 100644 index 0000000..8b1ec13 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/tasks/test1_other.yml @@ -0,0 +1,11 @@ +--- +# "test1_other" tasks file for test1 +- name: "test1_other BLIPPY test1_other tasks debug for task1 show test1_var1" + debug: + var: test1_var1 + tags: ["runme"] + +- name: "BLIPPY FOO test1_other tasks debug for task1 show test1_var2" + debug: + var: test1_var2 + tags: ["runme"] diff --git a/test/integration/targets/roles_arg_spec/roles/test1/vars/main.yml b/test/integration/targets/roles_arg_spec/roles/test1/vars/main.yml new file mode 100644 index 0000000..3e72dd6 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/vars/main.yml @@ -0,0 +1,4 @@ +--- +# vars file for test1 +test1_var1: 'THE_TEST1_VAR1_VARS_VALUE' +test1_var2: 'THE_TEST1_VAR2_VARS_VALUE' diff --git a/test/integration/targets/roles_arg_spec/roles/test1/vars/other.yml b/test/integration/targets/roles_arg_spec/roles/test1/vars/other.yml new file mode 100644 index 0000000..a397bdc --- /dev/null +++ b/test/integration/targets/roles_arg_spec/roles/test1/vars/other.yml @@ -0,0 +1,4 @@ +--- +# vars file for test1 +test1_var1: 'other_THE_TEST1_VAR1_VARS_VALUE' +test1_var2: 'other_THE_TEST1_VAR2_VARS_VALUE' diff --git a/test/integration/targets/roles_arg_spec/runme.sh b/test/integration/targets/roles_arg_spec/runme.sh new file mode 100755 index 0000000..209a34e --- /dev/null +++ b/test/integration/targets/roles_arg_spec/runme.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -eux + +# This effectively disables junit callback output by directing the output to +# a directory ansible-test will not look at. +# +# Since the failures in these tests are on the role arg spec validation and the +# name for those tasks is fixed (we cannot add "EXPECTED FAILURE" to the name), +# disabling the junit callback output is the easiest way to prevent these from +# showing up in test run output. +# +# Longer term, an option can be added to the junit callback allowing a custom +# regexp to be supplied rather than the hard coded "EXPECTED FAILURE". +export JUNIT_OUTPUT_DIR="${OUTPUT_DIR}" + +# Various simple role scenarios +ansible-playbook test.yml -i ../../inventory "$@" + +# More complex role test +ansible-playbook test_complex_role_fails.yml -i ../../inventory "$@" + +# Test play level role will fail +set +e +ansible-playbook test_play_level_role_fails.yml -i ../../inventory "$@" +test $? -ne 0 +set -e + +# Test the validation task is tagged with 'always' by specifying an unused tag. +# The task is tagged with 'foo' but we use 'bar' in the call below and expect +# the validation task to run anyway since it is tagged 'always'. +ansible-playbook test_tags.yml -i ../../inventory "$@" --tags bar | grep "a : Validating arguments against arg spec 'main' - Main entry point for role A." diff --git a/test/integration/targets/roles_arg_spec/test.yml b/test/integration/targets/roles_arg_spec/test.yml new file mode 100644 index 0000000..5eca7c7 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/test.yml @@ -0,0 +1,356 @@ +--- +- hosts: localhost + gather_facts: false + roles: + - { role: a, a_str: "roles" } + + vars: + INT_VALUE: 42 + + tasks: + + - name: "Valid simple role usage with include_role" + include_role: + name: a + vars: + a_str: "include_role" + + - name: "Valid simple role usage with import_role" + import_role: + name: a + vars: + a_str: "import_role" + + - name: "Valid role usage (more args)" + include_role: + name: b + vars: + b_str: "xyz" + b_int: 5 + b_bool: true + + - name: "Valid simple role usage with include_role of different entry point" + include_role: + name: a + tasks_from: "alternate" + vars: + a_int: 256 + + - name: "Valid simple role usage with import_role of different entry point" + import_role: + name: a + tasks_from: "alternate" + vars: + a_int: 512 + + - name: "Valid simple role usage with a templated value" + import_role: + name: a + vars: + a_int: "{{ INT_VALUE }}" + + - name: "Call role entry point that is defined, but has no spec data" + import_role: + name: a + tasks_from: "no_spec_entrypoint" + +- name: "New play to reset vars: Test include_role fails" + hosts: localhost + gather_facts: false + vars: + expected_returned_spec: + b_bool: + required: true + type: "bool" + b_int: + required: true + type: "int" + b_str: + required: true + type: "str" + + tasks: + - block: + - name: "Invalid role usage" + include_role: + name: b + vars: + b_bool: 7 + + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + + - name: "Validate failure" + assert: + that: + - ansible_failed_task.name == "Validating arguments against arg spec 'main' - Main entry point for role B." + - ansible_failed_result.argument_errors | length == 2 + - "'missing required arguments: b_int, b_str' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "main" + - ansible_failed_result.validate_args_context.name == "b" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/b')" + - ansible_failed_result.argument_spec_data == expected_returned_spec + + +- name: "New play to reset vars: Test import_role fails" + hosts: localhost + gather_facts: false + vars: + expected_returned_spec: + b_bool: + required: true + type: "bool" + b_int: + required: true + type: "int" + b_str: + required: true + type: "str" + + tasks: + - block: + - name: "Invalid role usage" + import_role: + name: b + vars: + b_bool: 7 + + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + + - name: "Validate failure" + assert: + that: + - ansible_failed_task.name == "Validating arguments against arg spec 'main' - Main entry point for role B." + - ansible_failed_result.argument_errors | length == 2 + - "'missing required arguments: b_int, b_str' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "main" + - ansible_failed_result.validate_args_context.name == "b" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/b')" + - ansible_failed_result.argument_spec_data == expected_returned_spec + + +- name: "New play to reset vars: Test nested role including/importing role succeeds" + hosts: localhost + gather_facts: false + vars: + c_int: 1 + a_str: "some string" + a_int: 42 + tasks: + - name: "Test import_role of role C" + import_role: + name: c + + - name: "Test include_role of role C" + include_role: + name: c + + +- name: "New play to reset vars: Test nested role including/importing role fails" + hosts: localhost + gather_facts: false + vars: + main_expected_returned_spec: + a_str: + required: true + type: "str" + alternate_expected_returned_spec: + a_int: + required: true + type: "int" + + tasks: + - block: + - name: "Test import_role of role C (missing a_str)" + import_role: + name: c + vars: + c_int: 100 + + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + - name: "Validate import_role failure" + assert: + that: + # NOTE: a bug here that prevents us from getting ansible_failed_task + - ansible_failed_result.argument_errors | length == 1 + - "'missing required arguments: a_str' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "main" + - ansible_failed_result.validate_args_context.name == "a" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/a')" + - ansible_failed_result.argument_spec_data == main_expected_returned_spec + + - block: + - name: "Test include_role of role C (missing a_int from `alternate` entry point)" + include_role: + name: c + vars: + c_int: 200 + a_str: "some string" + + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + - name: "Validate include_role failure" + assert: + that: + # NOTE: a bug here that prevents us from getting ansible_failed_task + - ansible_failed_result.argument_errors | length == 1 + - "'missing required arguments: a_int' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "alternate" + - ansible_failed_result.validate_args_context.name == "a" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/a')" + - ansible_failed_result.argument_spec_data == alternate_expected_returned_spec + +- name: "New play to reset vars: Test role with no tasks can fail" + hosts: localhost + gather_facts: false + tasks: + - block: + - name: "Test import_role of role role_with_no_tasks (missing a_str)" + import_role: + name: role_with_no_tasks + + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + - name: "Validate import_role failure" + assert: + that: + # NOTE: a bug here that prevents us from getting ansible_failed_task + - ansible_failed_result.argument_errors | length == 1 + - "'missing required arguments: a_str' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "main" + - ansible_failed_result.validate_args_context.name == "role_with_no_tasks" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/roles/role_with_no_tasks')" + +- name: "New play to reset vars: Test disabling role validation with rolespec_validate=False" + hosts: localhost + gather_facts: false + tasks: + - block: + - name: "Test import_role of role C (missing a_str), but validation turned off" + import_role: + name: c + rolespec_validate: False + - fail: + msg: "Should not get here" + + rescue: + - debug: + var: ansible_failed_result + - name: "Validate import_role failure" + assert: + that: + # We expect the role to actually run, but will fail because an undefined variable was referenced + # and validation wasn't performed up front (thus not returning 'argument_errors'). + - "'argument_errors' not in ansible_failed_result" + - "'The task includes an option with an undefined variable.' in ansible_failed_result.msg" + +- name: "New play to reset vars: Test collection-based role" + hosts: localhost + gather_facts: false + tasks: + - name: "Valid collection-based role usage" + import_role: + name: "foo.bar.blah" + vars: + blah_str: "some string" + + +- name: "New play to reset vars: Test collection-based role will fail" + hosts: localhost + gather_facts: false + tasks: + - block: + - name: "Invalid collection-based role usage" + import_role: + name: "foo.bar.blah" + - fail: + msg: "Should not get here" + rescue: + - debug: var=ansible_failed_result + - name: "Validate import_role failure for collection-based role" + assert: + that: + - ansible_failed_result.argument_errors | length == 1 + - "'missing required arguments: blah_str' in ansible_failed_result.argument_errors" + - ansible_failed_result.validate_args_context.argument_spec_name == "main" + - ansible_failed_result.validate_args_context.name == "blah" + - ansible_failed_result.validate_args_context.type == "role" + - "ansible_failed_result.validate_args_context.path is search('roles_arg_spec/collections/ansible_collections/foo/bar/roles/blah')" + +- name: "New play to reset vars: Test templating succeeds" + hosts: localhost + gather_facts: false + vars: + value_some_choices: "choice2" + value_some_list: [1.5] + value_some_dict: {"some_key": "some_value"} + value_some_int: 1 + value_some_float: 1.5 + value_some_json: '{[1, 3, 3] 345345|45v<#!}' + value_some_jsonarg: {"foo": [1, 3, 3]} + value_some_second_level: True + value_third_level: 5 + tasks: + - block: + - include_role: + name: test1 + vars: + some_choices: "{{ value_some_choices }}" + some_list: "{{ value_some_list }}" + some_dict: "{{ value_some_dict }}" + some_int: "{{ value_some_int }}" + some_float: "{{ value_some_float }}" + some_json: "{{ value_some_json }}" + some_jsonarg: "{{ value_some_jsonarg }}" + some_dict_options: + some_second_level: "{{ value_some_second_level }}" + multi_level_option: + second_level: + third_level: "{{ value_third_level }}" + rescue: + - debug: var=ansible_failed_result + - fail: + msg: "Should not get here" + +- name: "New play to reset vars: Test empty argument_specs.yml" + hosts: localhost + gather_facts: false + tasks: + - name: Import role with an empty argument_specs.yml + import_role: + name: empty_file + +- name: "New play to reset vars: Test empty argument_specs key" + hosts: localhost + gather_facts: false + tasks: + - name: Import role with an empty argument_specs key + import_role: + name: empty_argspec diff --git a/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml b/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml new file mode 100644 index 0000000..81abdaa --- /dev/null +++ b/test/integration/targets/roles_arg_spec/test_complex_role_fails.yml @@ -0,0 +1,197 @@ +--- +- name: "Running include_role test1" + hosts: localhost + gather_facts: false + vars: + ansible_unicode_type_match: "" + unicode_type_match: "" + string_type_match: "" + float_type_match: "" + list_type_match: "" + ansible_list_type_match: "" + dict_type_match: "" + ansible_dict_type_match: "" + ansible_unicode_class_match: "" + unicode_class_match: "" + string_class_match: "" + bytes_class_match: "" + float_class_match: "" + list_class_match: "" + ansible_list_class_match: "" + dict_class_match: "" + ansible_dict_class_match: "" + expected: + test1_1: + argument_errors: [ + "argument 'tidy_expected' is of type and we were unable to convert to list: cannot be converted to a list", + "argument 'bust_some_stuff' is of type and we were unable to convert to int: cannot be converted to an int", + "argument 'some_list' is of type and we were unable to convert to list: cannot be converted to a list", + "argument 'some_dict' is of type and we were unable to convert to dict: cannot be converted to a dict", + "argument 'some_int' is of type and we were unable to convert to int: cannot be converted to an int", + "argument 'some_float' is of type and we were unable to convert to float: cannot be converted to a float", + "argument 'some_bytes' is of type and we were unable to convert to bytes: cannot be converted to a Byte value", + "argument 'some_bits' is of type and we were unable to convert to bits: 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 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 found in 'multi_level_option -> second_level'. and we were unable to convert to int: cannot be converted to an int", + "argument 'some_more_dict_options' is of type 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, "" vs "". 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." diff --git a/test/integration/targets/roles_arg_spec/test_play_level_role_fails.yml b/test/integration/targets/roles_arg_spec/test_play_level_role_fails.yml new file mode 100644 index 0000000..6c79569 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/test_play_level_role_fails.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + gather_facts: false + roles: + - { role: a, invalid_str: "roles" } diff --git a/test/integration/targets/roles_arg_spec/test_tags.yml b/test/integration/targets/roles_arg_spec/test_tags.yml new file mode 100644 index 0000000..b4ea188 --- /dev/null +++ b/test/integration/targets/roles_arg_spec/test_tags.yml @@ -0,0 +1,11 @@ +--- +- hosts: localhost + gather_facts: false + tasks: + - name: "Tag test #1" + import_role: + name: a + vars: + a_str: "tag test #1" + tags: + - foo -- cgit v1.2.3