diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:55:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:55:42 +0000 |
commit | 62d9962ec7d01c95bf5732169320d3857a41446e (patch) | |
tree | f60d8fc63ff738e5f5afec48a84cf41480ee1315 /test/integration/targets | |
parent | Releasing progress-linux version 2.14.13-1~progress7.99u1. (diff) | |
download | ansible-core-62d9962ec7d01c95bf5732169320d3857a41446e.tar.xz ansible-core-62d9962ec7d01c95bf5732169320d3857a41446e.zip |
Merging upstream version 2.16.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/integration/targets')
508 files changed, 6934 insertions, 1160 deletions
diff --git a/test/integration/targets/ansible-config/aliases b/test/integration/targets/ansible-config/aliases new file mode 100644 index 0000000..1d28bdb --- /dev/null +++ b/test/integration/targets/ansible-config/aliases @@ -0,0 +1,2 @@ +shippable/posix/group5 +context/controller diff --git a/test/integration/targets/ansible-config/files/ini_dupes.py b/test/integration/targets/ansible-config/files/ini_dupes.py new file mode 100755 index 0000000..ed42e6a --- /dev/null +++ b/test/integration/targets/ansible-config/files/ini_dupes.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +import configparser +import sys + + +ini_file = sys.argv[1] +c = configparser.ConfigParser(strict=True, inline_comment_prefixes=(';',)) +c.read_file(open(ini_file)) diff --git a/test/integration/targets/ansible-config/tasks/main.yml b/test/integration/targets/ansible-config/tasks/main.yml new file mode 100644 index 0000000..a894dd4 --- /dev/null +++ b/test/integration/targets/ansible-config/tasks/main.yml @@ -0,0 +1,14 @@ +- name: test ansible-config for valid output and no dupes + block: + - name: Create temporary file + tempfile: + path: '{{output_dir}}' + state: file + suffix: temp.ini + register: ini_tempfile + + - name: run config full dump + shell: ansible-config init -t all > {{ini_tempfile.path}} + + - name: run ini tester, for correctness and dupes + shell: "{{ansible_playbook_python}} '{{role_path}}/files/ini_dupes.py' '{{ini_tempfile.path}}'" diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json index 243a5e4..36f402f 100644 --- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json +++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/MANIFEST.json @@ -17,7 +17,7 @@ "version": "0.1.1231", "readme": "README.md", "license_file": "COPYING", - "homepage": "", + "homepage": "" }, "file_manifest_file": { "format": 1, diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py index caec2ed..dfc1271 100644 --- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py +++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py @@ -20,7 +20,6 @@ DOCUMENTATION = ''' required: True ''' -from ansible.errors import AnsibleParserError from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable diff --git a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py index d456986..639d3c6 100644 --- a/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py +++ b/test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py @@ -32,7 +32,8 @@ RETURN = """ version_added: 1.0.0 """ -from ansible.module_utils.common._collections_compat import Sequence +from collections.abc import Sequence + from ansible.plugins.lookup import LookupBase from ansible.errors import AnsibleError diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json index 243a5e4..36f402f 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json @@ -17,7 +17,7 @@ "version": "0.1.1231", "readme": "README.md", "license_file": "COPYING", - "homepage": "", + "homepage": "" }, "file_manifest_file": { "format": 1, diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py index cbb8f0f..1870b8e 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py @@ -19,7 +19,6 @@ DOCUMENTATION = ''' required: True ''' -from ansible.errors import AnsibleParserError from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py index 79b7a70..aaaecb8 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py @@ -3,12 +3,17 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' +DOCUMENTATION = r''' --- module: randommodule short_description: A random module description: - A random module. + - See O(foo.bar.baz#role:main:foo=bar) for how this is used in the P(foo.bar.baz#role)'s C(main) entrypoint. + - See L(the docsite,https://docs.ansible.com/ansible-core/devel/) for more information on ansible-core. + - This module is not related to the M(ansible.builtin.copy) module. HORIZONTALLINE You might also be interested + in R(ansible_python_interpreter, ansible_python_interpreter). + - Sometimes you have M(broken markup) that will result in error messages. author: - Ansible Core Team version_added: 1.0.0 @@ -18,22 +23,22 @@ deprecated: removed_in: '3.0.0' options: test: - description: Some text. + description: Some text. Consider not using O(ignore:foo=bar). type: str version_added: 1.2.0 sub: - description: Suboptions. + description: Suboptions. Contains O(sub.subtest), which can be set to V(123). You can use E(TEST_ENV) to set this. type: dict suboptions: subtest: - description: A suboption. + description: A suboption. Not compatible to O(ansible.builtin.copy#module:path=c:\\foo\(1\).txt). type: int version_added: 1.1.0 # The following is the wrong syntax, and should not get processed # by add_collection_to_versions_and_dates() options: subtest2: - description: Another suboption. + description: Another suboption. Useful when P(ansible.builtin.shuffle#filter) is used with value V([a,b,\),d\\]). type: float version_added: 1.1.0 # The following is not supported in modules, and should not get processed @@ -65,7 +70,7 @@ seealso: EXAMPLES = ''' ''' -RETURN = ''' +RETURN = r''' z_last: description: A last result. type: str @@ -75,7 +80,8 @@ z_last: m_middle: description: - This should be in the middle. - - Has some more data + - Has some more data. + - Check out RV(m_middle.suboption) and compare it to RV(a_first=foo) and RV(community.general.foo#lookup:value). type: dict returned: success and 1st of month contains: @@ -86,7 +92,7 @@ m_middle: version_added: 1.4.0 a_first: - description: A first result. + description: A first result. Use RV(a_first=foo\(bar\\baz\)bam). type: str returned: success ''' diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml index cc60945..ebfea2a 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/test/yolo.yml @@ -8,6 +8,25 @@ DOCUMENTATION: description: does not matter type: raw required: true + seealso: + - module: ansible.builtin.test + - module: testns.testcol.fakemodule + description: A fake module + - plugin: testns.testcol.noop + plugin_type: lookup + - plugin: testns.testcol.grouped + plugin_type: filter + description: A grouped filter. + - plugin: ansible.builtin.combine + plugin_type: filter + - plugin: ansible.builtin.file + plugin_type: lookup + description: Read a file on the controller. + - link: https://docs.ansible.com + name: Ansible docsite + description: See also the Ansible docsite. + - ref: foo_bar + description: Some foo bar. EXAMPLES: | {{ 'anything' is yolo }} diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json index 02ec289..e930d7d 100644 --- a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol2/MANIFEST.json @@ -17,7 +17,7 @@ "version": "1.2.0", "readme": "README.md", "license_file": "COPYING", - "homepage": "", + "homepage": "" }, "file_manifest_file": { "format": 1, diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/galaxy.yml b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/galaxy.yml new file mode 100644 index 0000000..bd6c15a --- /dev/null +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/galaxy.yml @@ -0,0 +1,6 @@ +namespace: testns +name: testcol3 +version: 0.1.0 +readme: README.md +authors: + - me diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/plugins/modules/test1.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/plugins/modules/test1.py new file mode 100644 index 0000000..02dfb89 --- /dev/null +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol3/plugins/modules/test1.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = """ +module: test1 +short_description: Foo module in testcol3 +description: + - This is a foo module. +author: + - me +""" + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec=dict(), + ) + + module.exit_json() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/galaxy.yml b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/galaxy.yml new file mode 100644 index 0000000..7894d39 --- /dev/null +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/galaxy.yml @@ -0,0 +1,6 @@ +namespace: testns +name: testcol4 +version: 1.0.0 +readme: README.md +authors: + - me diff --git a/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/plugins/modules/test2.py b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/plugins/modules/test2.py new file mode 100644 index 0000000..ddb0c11 --- /dev/null +++ b/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol4/plugins/modules/test2.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = """ +module: test2 +short_description: Foo module in testcol4 +description: + - This is a foo module. +author: + - me +""" + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule( + argument_spec=dict(), + ) + + module.exit_json() + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/ansible-doc/randommodule-text.output b/test/integration/targets/ansible-doc/randommodule-text.output index 602d66e..ca36134 100644 --- a/test/integration/targets/ansible-doc/randommodule-text.output +++ b/test/integration/targets/ansible-doc/randommodule-text.output @@ -1,6 +1,13 @@ > TESTNS.TESTCOL.RANDOMMODULE (./collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py) - A random module. + A random module. See `foo=bar' (of role foo.bar.baz, main + entrypoint) for how this is used in the [foo.bar.baz]'s `main' + entrypoint. See the docsite <https://docs.ansible.com/ansible- + core/devel/> for more information on ansible-core. This module + is not related to the [ansible.builtin.copy] module. + ------------- You might also be interested in + ansible_python_interpreter. Sometimes you have [broken markup] + that will result in error messages. ADDED IN: version 1.0.0 of testns.testcol @@ -14,7 +21,8 @@ DEPRECATED: OPTIONS (= is mandatory): - sub - Suboptions. + Suboptions. Contains `sub.subtest', which can be set to `123'. + You can use `TEST_ENV' to set this. set_via: env: - deprecated: @@ -29,7 +37,8 @@ OPTIONS (= is mandatory): OPTIONS: - subtest2 - Another suboption. + Another suboption. Useful when [ansible.builtin.shuffle] + is used with value `[a,b,),d\]'. default: null type: float added in: version 1.1.0 @@ -39,14 +48,15 @@ OPTIONS (= is mandatory): SUBOPTIONS: - subtest - A suboption. + A suboption. Not compatible to `path=c:\foo(1).txt' (of + module ansible.builtin.copy). default: null type: int added in: version 1.1.0 of testns.testcol - test - Some text. + Some text. Consider not using `foo=bar'. default: null type: str added in: version 1.2.0 of testns.testcol @@ -93,13 +103,15 @@ EXAMPLES: RETURN VALUES: - a_first - A first result. + A first result. Use `a_first=foo(bar\baz)bam'. returned: success type: str - m_middle This should be in the middle. - Has some more data + Has some more data. + Check out `m_middle.suboption' and compare it to `a_first=foo' + and `value' (of lookup plugin community.general.foo). returned: success and 1st of month type: dict diff --git a/test/integration/targets/ansible-doc/randommodule.output b/test/integration/targets/ansible-doc/randommodule.output index cf03600..f40202a 100644 --- a/test/integration/targets/ansible-doc/randommodule.output +++ b/test/integration/targets/ansible-doc/randommodule.output @@ -12,14 +12,18 @@ "why": "Test deprecation" }, "description": [ - "A random module." + "A random module.", + "See O(foo.bar.baz#role:main:foo=bar) for how this is used in the P(foo.bar.baz#role)'s C(main) entrypoint.", + "See L(the docsite,https://docs.ansible.com/ansible-core/devel/) for more information on ansible-core.", + "This module is not related to the M(ansible.builtin.copy) module. HORIZONTALLINE You might also be interested in R(ansible_python_interpreter, ansible_python_interpreter).", + "Sometimes you have M(broken markup) that will result in error messages." ], "filename": "./collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py", "has_action": false, "module": "randommodule", "options": { "sub": { - "description": "Suboptions.", + "description": "Suboptions. Contains O(sub.subtest), which can be set to V(123). You can use E(TEST_ENV) to set this.", "env": [ { "deprecated": { @@ -34,14 +38,14 @@ ], "options": { "subtest2": { - "description": "Another suboption.", + "description": "Another suboption. Useful when P(ansible.builtin.shuffle#filter) is used with value V([a,b,\\),d\\\\]).", "type": "float", "version_added": "1.1.0" } }, "suboptions": { "subtest": { - "description": "A suboption.", + "description": "A suboption. Not compatible to O(ansible.builtin.copy#module:path=c:\\\\foo\\(1\\).txt).", "type": "int", "version_added": "1.1.0", "version_added_collection": "testns.testcol" @@ -50,7 +54,7 @@ "type": "dict" }, "test": { - "description": "Some text.", + "description": "Some text. Consider not using O(ignore:foo=bar).", "type": "str", "version_added": "1.2.0", "version_added_collection": "testns.testcol" @@ -103,7 +107,7 @@ "metadata": null, "return": { "a_first": { - "description": "A first result.", + "description": "A first result. Use RV(a_first=foo\\(bar\\\\baz\\)bam).", "returned": "success", "type": "str" }, @@ -123,7 +127,8 @@ }, "description": [ "This should be in the middle.", - "Has some more data" + "Has some more data.", + "Check out RV(m_middle.suboption) and compare it to RV(a_first=foo) and RV(community.general.foo#lookup:value)." ], "returned": "success and 1st of month", "type": "dict" diff --git a/test/integration/targets/ansible-doc/runme.sh b/test/integration/targets/ansible-doc/runme.sh index f51fa8a..b525766 100755 --- a/test/integration/targets/ansible-doc/runme.sh +++ b/test/integration/targets/ansible-doc/runme.sh @@ -1,36 +1,74 @@ #!/usr/bin/env bash -set -eux +# always set sane error behaviors, enable execution tracing later if sufficient verbosity requested +set -eu + +verbosity=0 + +# default to silent output for naked grep; -vvv+ will adjust this +export GREP_OPTS=-q + +# shell tracing output is very large from this script; only enable if >= -vvv was passed +while getopts :v opt +do case "$opt" in + v) ((verbosity+=1)) ;; + *) ;; + esac +done + +if (( verbosity >= 3 )); +then + set -x; + export GREP_OPTS= ; +fi + +echo "running playbook-backed docs tests" ansible-playbook test.yml -i inventory "$@" # test keyword docs -ansible-doc -t keyword -l | grep 'vars_prompt: list of variables to prompt for.' -ansible-doc -t keyword vars_prompt | grep 'description: list of variables to prompt for.' -ansible-doc -t keyword asldkfjaslidfhals 2>&1 | grep 'Skipping Invalid keyword' +ansible-doc -t keyword -l | grep $GREP_OPTS 'vars_prompt: list of variables to prompt for.' +ansible-doc -t keyword vars_prompt | grep $GREP_OPTS 'description: list of variables to prompt for.' +ansible-doc -t keyword asldkfjaslidfhals 2>&1 | grep $GREP_OPTS 'Skipping Invalid keyword' # collections testing ( unset ANSIBLE_PLAYBOOK_DIR cd "$(dirname "$0")" -# test module docs from collection + +echo "test fakemodule docs from collection" # we use sed to strip the module path from the first line current_out="$(ansible-doc --playbook-dir ./ testns.testcol.fakemodule | sed '1 s/\(^> TESTNS\.TESTCOL\.FAKEMODULE\).*(.*)$/\1/')" expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.FAKEMODULE\).*(.*)$/\1/' fakemodule.output)" test "$current_out" == "$expected_out" +echo "test randommodule docs from collection" # we use sed to strip the plugin path from the first line, and fix-urls.py to unbreak and replace URLs from stable-X branches current_out="$(ansible-doc --playbook-dir ./ testns.testcol.randommodule | sed '1 s/\(^> TESTNS\.TESTCOL\.RANDOMMODULE\).*(.*)$/\1/' | python fix-urls.py)" expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.RANDOMMODULE\).*(.*)$/\1/' randommodule-text.output)" test "$current_out" == "$expected_out" -# ensure we do work with valid collection name for list -ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep -v "Invalid collection name" +echo "test yolo filter docs from collection" +# we use sed to strip the plugin path from the first line, and fix-urls.py to unbreak and replace URLs from stable-X branches +current_out="$(ansible-doc --playbook-dir ./ testns.testcol.yolo --type test | sed '1 s/\(^> TESTNS\.TESTCOL\.YOLO\).*(.*)$/\1/' | python fix-urls.py)" +expected_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.YOLO\).*(.*)$/\1/' yolo-text.output)" +test "$current_out" == "$expected_out" + +echo "ensure we do work with valid collection name for list" +ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep $GREP_OPTS -v "Invalid collection name" -# ensure we dont break on invalid collection name for list -ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep "Invalid collection name" +echo "ensure we dont break on invalid collection name for list" +ansible-doc --list testns.testcol.fakemodule --playbook-dir ./ 2>&1 | grep $GREP_OPTS "Invalid collection name" -# test listing diff plugin types from collection +echo "filter list with more than one collection (1/2)" +output=$(ansible-doc --list testns.testcol3 testns.testcol4 --playbook-dir ./ 2>&1 | wc -l) +test "$output" -eq 2 + +echo "filter list with more than one collection (2/2)" +output=$(ansible-doc --list testns.testcol testns.testcol4 --playbook-dir ./ 2>&1 | wc -l) +test "$output" -eq 5 + +echo "testing ansible-doc output for various plugin types" for ptype in cache inventory lookup vars filter module do # each plugin type adds 1 from collection @@ -50,20 +88,20 @@ do elif [ "${ptype}" == "lookup" ]; then expected_names=("noop"); elif [ "${ptype}" == "vars" ]; then expected_names=("noop_vars_plugin"); fi fi - # ensure we ONLY list from the collection + echo "testing collection-filtered list for plugin ${ptype}" justcol=$(ansible-doc -l -t ${ptype} --playbook-dir ./ testns.testcol|wc -l) test "$justcol" -eq "$expected" - # ensure the right names are displayed + echo "validate collection plugin name display for plugin ${ptype}" list_result=$(ansible-doc -l -t ${ptype} --playbook-dir ./ testns.testcol) metadata_result=$(ansible-doc --metadata-dump --no-fail-on-errors -t ${ptype} --playbook-dir ./ testns.testcol) for name in "${expected_names[@]}"; do - echo "${list_result}" | grep "testns.testcol.${name}" - echo "${metadata_result}" | grep "testns.testcol.${name}" + echo "${list_result}" | grep $GREP_OPTS "testns.testcol.${name}" + echo "${metadata_result}" | grep $GREP_OPTS "testns.testcol.${name}" done - # ensure we get error if passinginvalid collection, much less any plugins - ansible-doc -l -t ${ptype} testns.testcol 2>&1 | grep "unable to locate collection" + # ensure we get error if passing invalid collection, much less any plugins + ansible-doc -l -t ${ptype} bogus.boguscoll 2>&1 | grep $GREP_OPTS "unable to locate collection" # TODO: do we want per namespace? # ensure we get 1 plugins when restricting namespace @@ -73,20 +111,28 @@ done #### test role functionality -# Test role text output +echo "testing role text output" # we use sed to strip the role path from the first line current_role_out="$(ansible-doc -t role -r ./roles test_role1 | sed '1 s/\(^> TEST_ROLE1\).*(.*)$/\1/')" expected_role_out="$(sed '1 s/\(^> TEST_ROLE1\).*(.*)$/\1/' fakerole.output)" test "$current_role_out" == "$expected_role_out" +echo "testing multiple role entrypoints" # Two collection roles are defined, but only 1 has a role arg spec with 2 entry points output=$(ansible-doc -t role -l --playbook-dir . testns.testcol | wc -l) test "$output" -eq 2 +echo "test listing roles with multiple collection filters" +# Two collection roles are defined, but only 1 has a role arg spec with 2 entry points +output=$(ansible-doc -t role -l --playbook-dir . testns.testcol2 testns.testcol | wc -l) +test "$output" -eq 2 + +echo "testing standalone roles" # Include normal roles (no collection filter) output=$(ansible-doc -t role -l --playbook-dir . | wc -l) test "$output" -eq 3 +echo "testing role precedence" # Test that a role in the playbook dir with the same name as a role in the # 'roles' subdir of the playbook dir does not appear (lower precedence). output=$(ansible-doc -t role -l --playbook-dir . | grep -c "test_role1 from roles subdir") @@ -94,7 +140,7 @@ test "$output" -eq 1 output=$(ansible-doc -t role -l --playbook-dir . | grep -c "test_role1 from playbook dir" || true) test "$output" -eq 0 -# Test entry point filter +echo "testing role entrypoint filter" current_role_out="$(ansible-doc -t role --playbook-dir . testns.testcol.testrole -e alternate| sed '1 s/\(^> TESTNS\.TESTCOL\.TESTROLE\).*(.*)$/\1/')" expected_role_out="$(sed '1 s/\(^> TESTNS\.TESTCOL\.TESTROLE\).*(.*)$/\1/' fakecollrole.output)" test "$current_role_out" == "$expected_role_out" @@ -103,10 +149,16 @@ test "$current_role_out" == "$expected_role_out" #### test add_collection_to_versions_and_dates() +echo "testing json output" current_out="$(ansible-doc --json --playbook-dir ./ testns.testcol.randommodule | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')" expected_out="$(sed 's/ *"filename": "[^"]*",$//' randommodule.output)" test "$current_out" == "$expected_out" +echo "testing json output 2" +current_out="$(ansible-doc --json --playbook-dir ./ testns.testcol.yolo --type test | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')" +expected_out="$(sed 's/ *"filename": "[^"]*",$//' yolo.output)" +test "$current_out" == "$expected_out" + current_out="$(ansible-doc --json --playbook-dir ./ -t cache testns.testcol.notjsonfile | sed 's/ *$//' | sed 's/ *"filename": "[^"]*",$//')" expected_out="$(sed 's/ *"filename": "[^"]*",$//' notjsonfile.output)" test "$current_out" == "$expected_out" @@ -119,8 +171,9 @@ current_out="$(ansible-doc --json --playbook-dir ./ -t vars testns.testcol.noop_ expected_out="$(sed 's/ *"filename": "[^"]*",$//' noop_vars_plugin.output)" test "$current_out" == "$expected_out" +echo "testing metadata dump" # just ensure it runs -ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir /dev/null >/dev/null +ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir /dev/null 1>/dev/null 2>&1 # create broken role argument spec mkdir -p broken-docs/collections/ansible_collections/testns/testcol/roles/testrole/meta @@ -144,71 +197,72 @@ argument_specs: EOF # ensure that --metadata-dump does not fail when --no-fail-on-errors is supplied -ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --no-fail-on-errors --playbook-dir broken-docs testns.testcol >/dev/null +ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --no-fail-on-errors --playbook-dir broken-docs testns.testcol 1>/dev/null 2>&1 # ensure that --metadata-dump does fail when --no-fail-on-errors is not supplied output=$(ANSIBLE_LIBRARY='./nolibrary' ansible-doc --metadata-dump --playbook-dir broken-docs testns.testcol 2>&1 | grep -c 'ERROR!' || true) test "${output}" -eq 1 -# ensure we list the 'legacy plugins' + +echo "testing legacy plugin listing" [ "$(ansible-doc -M ./library -l ansible.legacy |wc -l)" -gt "0" ] -# playbook dir should work the same +echo "testing legacy plugin list via --playbook-dir" [ "$(ansible-doc -l ansible.legacy --playbook-dir ./|wc -l)" -gt "0" ] -# see that we show undocumented when missing docs +echo "testing undocumented plugin output" [ "$(ansible-doc -M ./library -l ansible.legacy |grep -c UNDOCUMENTED)" == "6" ] -# ensure filtering works and does not include any 'test_' modules +echo "testing filtering does not include any 'test_' modules" [ "$(ansible-doc -M ./library -l ansible.builtin |grep -c test_)" == 0 ] [ "$(ansible-doc --playbook-dir ./ -l ansible.builtin |grep -c test_)" == 0 ] -# ensure filtering still shows modules +echo "testing filtering still shows modules" count=$(ANSIBLE_LIBRARY='./nolibrary' ansible-doc -l ansible.builtin |wc -l) [ "${count}" -gt "0" ] [ "$(ansible-doc -M ./library -l ansible.builtin |wc -l)" == "${count}" ] [ "$(ansible-doc --playbook-dir ./ -l ansible.builtin |wc -l)" == "${count}" ] -# produce 'sidecar' docs for test +echo "testing sidecar docs for jinja plugins" [ "$(ansible-doc -t test --playbook-dir ./ testns.testcol.yolo| wc -l)" -gt "0" ] [ "$(ansible-doc -t filter --playbook-dir ./ donothing| wc -l)" -gt "0" ] [ "$(ansible-doc -t filter --playbook-dir ./ ansible.legacy.donothing| wc -l)" -gt "0" ] -# no docs and no sidecar -ansible-doc -t filter --playbook-dir ./ nodocs 2>&1| grep -c 'missing documentation' || true +echo "testing no docs and no sidecar" +ansible-doc -t filter --playbook-dir ./ nodocs 2>&1| grep $GREP_OPTS -c 'missing documentation' || true -# produce 'sidecar' docs for module +echo "testing sidecar docs for module" [ "$(ansible-doc -M ./library test_win_module| wc -l)" -gt "0" ] [ "$(ansible-doc --playbook-dir ./ test_win_module| wc -l)" -gt "0" ] -# test 'double DOCUMENTATION' use +echo "testing duplicate DOCUMENTATION" [ "$(ansible-doc --playbook-dir ./ double_doc| wc -l)" -gt "0" ] -# don't break on module dir +echo "testing don't break on module dir" ansible-doc --list --module-path ./modules > /dev/null -# ensure we dedupe by fqcn and not base name +echo "testing dedupe by fqcn and not base name" [ "$(ansible-doc -l -t filter --playbook-dir ./ |grep -c 'b64decode')" -eq "3" ] -# ensure we don't show duplicates for plugins that only exist in ansible.builtin when listing ansible.legacy plugins +echo "testing no duplicates for plugins that only exist in ansible.builtin when listing ansible.legacy plugins" [ "$(ansible-doc -l -t filter --playbook-dir ./ |grep -c 'b64encode')" -eq "1" ] -# with playbook dir, legacy should override -ansible-doc -t filter split --playbook-dir ./ |grep histerical +echo "testing with playbook dir, legacy should override" +ansible-doc -t filter split --playbook-dir ./ |grep $GREP_OPTS histerical pyc_src="$(pwd)/filter_plugins/other.py" pyc_1="$(pwd)/filter_plugins/split.pyc" pyc_2="$(pwd)/library/notaplugin.pyc" trap 'rm -rf "$pyc_1" "$pyc_2"' EXIT -# test pyc files are not used as adjacent documentation +echo "testing pyc files are not used as adjacent documentation" python -c "import py_compile; py_compile.compile('$pyc_src', cfile='$pyc_1')" -ansible-doc -t filter split --playbook-dir ./ |grep histerical +ansible-doc -t filter split --playbook-dir ./ |grep $GREP_OPTS histerical -# test pyc files are not listed as plugins +echo "testing pyc files are not listed as plugins" python -c "import py_compile; py_compile.compile('$pyc_src', cfile='$pyc_2')" test "$(ansible-doc -l -t module --playbook-dir ./ 2>&1 1>/dev/null |grep -c "notaplugin")" == 0 -# without playbook dir, builtin should return -ansible-doc -t filter split |grep -v histerical +echo "testing without playbook dir, builtin should return" +ansible-doc -t filter split 2>&1 |grep $GREP_OPTS -v histerical diff --git a/test/integration/targets/ansible-doc/yolo-text.output b/test/integration/targets/ansible-doc/yolo-text.output new file mode 100644 index 0000000..647a4f6 --- /dev/null +++ b/test/integration/targets/ansible-doc/yolo-text.output @@ -0,0 +1,47 @@ +> TESTNS.TESTCOL.YOLO (./collections/ansible_collections/testns/testcol/plugins/test/yolo.yml) + + This is always true + +OPTIONS (= is mandatory): + += _input + does not matter + type: raw + + +SEE ALSO: + * Module ansible.builtin.test + The official documentation on the + ansible.builtin.test module. + https://docs.ansible.com/ansible-core/devel/collections/ansible/builtin/test_module.html + * Module testns.testcol.fakemodule + A fake module + * Lookup plugin testns.testcol.noop + * Filter plugin testns.testcol.grouped + A grouped filter. + * Filter plugin ansible.builtin.combine + The official documentation on the + ansible.builtin.combine filter plugin. + https://docs.ansible.com/ansible-core/devel/collections/ansible/builtin/combine_filter.html + * Lookup plugin ansible.builtin.file + Read a file on the controller. + https://docs.ansible.com/ansible-core/devel/collections/ansible/builtin/file_lookup.html + * Ansible docsite + See also the Ansible docsite. + https://docs.ansible.com + * Ansible documentation [foo_bar] + Some foo bar. + https://docs.ansible.com/ansible-core/devel/#stq=foo_bar&stp=1 + + +NAME: yolo + +EXAMPLES: + +{{ 'anything' is yolo }} + + +RETURN VALUES: +- output + always true + type: boolean diff --git a/test/integration/targets/ansible-doc/yolo.output b/test/integration/targets/ansible-doc/yolo.output new file mode 100644 index 0000000..b54cc2d --- /dev/null +++ b/test/integration/targets/ansible-doc/yolo.output @@ -0,0 +1,64 @@ +{ + "testns.testcol.yolo": { + "doc": { + "collection": "testns.testcol", + "description": [ + "This is always true" + ], + "filename": "./collections/ansible_collections/testns/testcol/plugins/test/yolo.yml", + "name": "yolo", + "options": { + "_input": { + "description": "does not matter", + "required": true, + "type": "raw" + } + }, + "seealso": [ + { + "module": "ansible.builtin.test" + }, + { + "description": "A fake module", + "module": "testns.testcol.fakemodule" + }, + { + "plugin": "testns.testcol.noop", + "plugin_type": "lookup" + }, + { + "description": "A grouped filter.", + "plugin": "testns.testcol.grouped", + "plugin_type": "filter" + }, + { + "plugin": "ansible.builtin.combine", + "plugin_type": "filter" + }, + { + "description": "Read a file on the controller.", + "plugin": "ansible.builtin.file", + "plugin_type": "lookup" + }, + { + "description": "See also the Ansible docsite.", + "link": "https://docs.ansible.com", + "name": "Ansible docsite" + }, + { + "description": "Some foo bar.", + "ref": "foo_bar" + } + ], + "short_description": "you only live once" + }, + "examples": "{{ 'anything' is yolo }}\n", + "metadata": null, + "return": { + "output": { + "description": "always true", + "type": "boolean" + } + } + } +} diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt b/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt index 110009e..6921829 100644 --- a/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt +++ b/test/integration/targets/ansible-galaxy-collection-cli/files/expected.txt @@ -1,6 +1,11 @@ MANIFEST.json FILES.json README.rst +GPL +LICENSES/ +LICENSES/MIT.txt +.reuse/ +.reuse/dep5 changelogs/ docs/ playbooks/ @@ -88,6 +93,7 @@ plugins/test/bar.yml plugins/test/baz.yaml plugins/test/test.py plugins/vars/bar.yml +plugins/vars/bar.yml.license plugins/vars/baz.yaml plugins/vars/test.py roles/foo/ diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml b/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml index 8f0ada0..140bf2a 100644 --- a/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml +++ b/test/integration/targets/ansible-galaxy-collection-cli/files/galaxy.yml @@ -2,6 +2,7 @@ namespace: ns name: col version: 1.0.0 readme: README.rst +license_file: GPL authors: - Ansible manifest: diff --git a/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py b/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py index 913a6f7..60c43cc 100644 --- a/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py +++ b/test/integration/targets/ansible-galaxy-collection-cli/files/make_collection_dir.py @@ -5,8 +5,12 @@ paths = [ 'ns-col-1.0.0.tar.gz', 'foo.txt', 'README.rst', + 'GPL', + 'LICENSES/MIT.txt', + '.reuse/dep5', 'artifacts/.gitkeep', 'plugins/vars/bar.yml', + 'plugins/vars/bar.yml.license', 'plugins/vars/baz.yaml', 'plugins/vars/test.py', 'plugins/vars/docs.md', diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml index dab599b..f0e78ca 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/main.yml @@ -5,7 +5,7 @@ - name: Test installing collections from git repositories environment: - ANSIBLE_COLLECTIONS_PATHS: "{{ galaxy_dir }}/collections" + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/collections" vars: cleanup: True galaxy_dir: "{{ galaxy_dir }}" diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml index f22f984..91ed912 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/multi_collection_repo_all.yml @@ -14,6 +14,8 @@ command: 'ansible-galaxy collection install {{ artifact_path }} -p {{ alt_install_path }} --no-deps' vars: artifact_path: "{{ galaxy_dir }}/ansible_test-collection_1-1.0.0.tar.gz" + environment: + ANSIBLE_COLLECTIONS_PATH: "" - name: check if the files and folders in build_ignore were respected stat: diff --git a/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml b/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml index dd307d7..520dbe5 100644 --- a/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml +++ b/test/integration/targets/ansible-galaxy-collection-scm/tasks/setup_recursive_scm_dependency.yml @@ -22,7 +22,12 @@ lineinfile: path: '{{ scm_path }}/namespace_2/collection_2/galaxy.yml' regexp: '^dependencies' - line: "dependencies: {'git+file://{{ scm_path }}/namespace_1/.git#collection_1/': 'master'}" + # NOTE: The committish is set to `HEAD` here because Git's default has + # NOTE: changed to `main` and it behaves differently in + # NOTE: different envs with different Git versions. + line: >- + dependencies: + {'git+file://{{ scm_path }}/namespace_1/.git#collection_1/': 'HEAD'} - name: Commit the changes shell: git add ./; git commit -m 'add collection' diff --git a/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py b/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py index 53c29f7..c1f5e1d 100644 --- a/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py +++ b/test/integration/targets/ansible-galaxy-collection/library/reset_pulp.py @@ -84,7 +84,8 @@ def invoke_api(module, url, method='GET', data=None, status_codes=None): resp, info = fetch_url(module, url, method=method, data=data, headers=headers) if info['status'] not in status_codes: - module.fail_json(url=url, **info) + info['url'] = url + module.fail_json(**info) data = to_text(resp.read()) if data: @@ -105,7 +106,7 @@ def delete_pulp_distribution(distribution, module): def delete_pulp_orphans(module): """ Deletes any orphaned pulp objects. """ - orphan_uri = module.params['pulp_api'] + '/pulp/api/v3/orphans/' + orphan_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/orphans/' task_info = invoke_api(module, orphan_uri, method='DELETE', status_codes=[202]) wait_pulp_task(task_info['task'], module) @@ -125,25 +126,39 @@ def get_galaxy_namespaces(module): return [n['name'] for n in ns_info['data']] -def get_pulp_distributions(module): +def get_pulp_distributions(module, distribution): """ Gets a list of all the pulp distributions. """ - distro_uri = module.params['pulp_api'] + '/pulp/api/v3/distributions/ansible/ansible/' - distro_info = invoke_api(module, distro_uri) + distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/' + distro_info = invoke_api(module, distro_uri + '?name=' + distribution) return [module.params['pulp_api'] + r['pulp_href'] for r in distro_info['results']] -def get_pulp_repositories(module): +def get_pulp_repositories(module, repository): """ Gets a list of all the pulp repositories. """ - repo_uri = module.params['pulp_api'] + '/pulp/api/v3/repositories/ansible/ansible/' - repo_info = invoke_api(module, repo_uri) + repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/' + repo_info = invoke_api(module, repo_uri + '?name=' + repository) return [module.params['pulp_api'] + r['pulp_href'] for r in repo_info['results']] +def get_repo_collections(repository, module): + collections_uri = module.params['galaxy_ng_server'] + 'v3/plugin/ansible/content/' + repository + '/collections/index/' + # status code 500 isn't really expected, an unhandled exception is causing this instead of a 404 + # See https://issues.redhat.com/browse/AAH-2329 + info = invoke_api(module, collections_uri + '?limit=100&offset=0', status_codes=[200, 500]) + if not info: + return [] + return [module.params['pulp_api'] + c['href'] for c in info['data']] + + +def delete_repo_collection(collection, module): + task_info = invoke_api(module, collection, method='DELETE', status_codes=[202]) + wait_pulp_task(task_info['task'], module) + + def new_galaxy_namespace(name, module): """ Creates a new namespace in Galaxy NG. """ - ns_uri = module.params['galaxy_ng_server'] + 'v3/_ui/namespaces/' - data = {'name': name, 'groups': [{'name': 'system:partner-engineers', 'object_permissions': - ['add_namespace', 'change_namespace', 'upload_to_namespace']}]} + ns_uri = module.params['galaxy_ng_server'] + 'v3/namespaces/ ' + data = {'name': name, 'groups': []} ns_info = invoke_api(module, ns_uri, method='POST', data=data, status_codes=[201]) return ns_info['id'] @@ -151,16 +166,17 @@ def new_galaxy_namespace(name, module): def new_pulp_repository(name, module): """ Creates a new pulp repository. """ - repo_uri = module.params['pulp_api'] + '/pulp/api/v3/repositories/ansible/ansible/' - data = {'name': name} + repo_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/repositories/ansible/ansible/' + # retain_repo_versions to work around https://issues.redhat.com/browse/AAH-2332 + data = {'name': name, 'retain_repo_versions': '1024'} repo_info = invoke_api(module, repo_uri, method='POST', data=data, status_codes=[201]) - return module.params['pulp_api'] + repo_info['pulp_href'] + return repo_info['pulp_href'] def new_pulp_distribution(name, base_path, repository, module): """ Creates a new pulp distribution for a repository. """ - distro_uri = module.params['pulp_api'] + '/pulp/api/v3/distributions/ansible/ansible/' + distro_uri = module.params['galaxy_ng_server'] + 'pulp/api/v3/distributions/ansible/ansible/' data = {'name': name, 'base_path': base_path, 'repository': repository} task_info = invoke_api(module, distro_uri, method='POST', data=data, status_codes=[202]) task_info = wait_pulp_task(task_info['task'], module) @@ -194,8 +210,15 @@ def main(): ) module.params['force_basic_auth'] = True - [delete_pulp_distribution(d, module) for d in get_pulp_distributions(module)] - [delete_pulp_repository(r, module) for r in get_pulp_repositories(module)] + # It may be due to the process of cleaning up orphans, but we cannot delete the namespace + # while a collection still exists, so this is just a new safety to nuke all collections + # first + for repository in module.params['repositories']: + [delete_repo_collection(c, module) for c in get_repo_collections(repository, module)] + + for repository in module.params['repositories']: + [delete_pulp_distribution(d, module) for d in get_pulp_distributions(module, repository)] + [delete_pulp_repository(r, module) for r in get_pulp_repositories(module, repository)] delete_pulp_orphans(module) [delete_galaxy_namespace(n, module) for n in get_galaxy_namespaces(module)] diff --git a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py index f4a51c4..423edd9 100644 --- a/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py +++ b/test/integration/targets/ansible-galaxy-collection/library/setup_collections.py @@ -77,6 +77,7 @@ RETURN = ''' # ''' +import datetime import os import subprocess import tarfile @@ -84,13 +85,13 @@ import tempfile import yaml from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_bytes +from ansible.module_utils.common.text.converters import to_bytes from functools import partial from multiprocessing import dummy as threading from multiprocessing import TimeoutError -COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT = 120 +COLLECTIONS_BUILD_AND_PUBLISH_TIMEOUT = 180 def publish_collection(module, collection): @@ -104,6 +105,7 @@ def publish_collection(module, collection): collection_dir = os.path.join(module.tmpdir, "%s-%s-%s" % (namespace, name, version)) b_collection_dir = to_bytes(collection_dir, errors='surrogate_or_strict') os.mkdir(b_collection_dir) + os.mkdir(os.path.join(b_collection_dir, b'meta')) with open(os.path.join(b_collection_dir, b'README.md'), mode='wb') as fd: fd.write(b"Collection readme") @@ -120,6 +122,8 @@ def publish_collection(module, collection): } with open(os.path.join(b_collection_dir, b'galaxy.yml'), mode='wb') as fd: fd.write(to_bytes(yaml.safe_dump(galaxy_meta), errors='surrogate_or_strict')) + with open(os.path.join(b_collection_dir, b'meta/runtime.yml'), mode='wb') as fd: + fd.write(b'requires_ansible: ">=1.0.0"') with tempfile.NamedTemporaryFile(mode='wb') as temp_fd: temp_fd.write(b"data") @@ -246,7 +250,8 @@ def run_module(): supports_check_mode=False ) - result = dict(changed=True, results=[]) + start = datetime.datetime.now() + result = dict(changed=True, results=[], start=str(start)) pool = threading.Pool(4) publish_func = partial(publish_collection, module) @@ -263,7 +268,9 @@ def run_module(): r['build']['rc'] + r['publish']['rc'] for r in result['results'] )) - module.exit_json(failed=failed, **result) + end = datetime.datetime.now() + delta = end - start + module.exit_json(failed=failed, end=str(end), delta=str(delta), **result) def main(): diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/build.yml b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml index 8140d46..83e9acc 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/build.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml @@ -1,4 +1,29 @@ --- +- name: create a dangling symbolic link inside collection directory + ansible.builtin.file: + src: '/non-existent-path/README.md' + dest: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/docs/README.md' + state: link + force: yes + +- name: build basic collection based on current directory with dangling symlink + command: ansible-galaxy collection build {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/scratch/ansible_test/my_collection' + register: fail_symlink_build + ignore_errors: yes + +- name: assert that build fails due to dangling symlink + assert: + that: + - fail_symlink_build.failed + - '"Failed to find the target path" in fail_symlink_build.stderr' + +- name: remove dangling symbolic link + ansible.builtin.file: + path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/docs/README.md' + state: absent + - name: build basic collection based on current directory command: ansible-galaxy collection build {{ galaxy_verbosity }} args: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/download.yml b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml index b651a73..a554c27 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/download.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml @@ -5,7 +5,7 @@ state: directory - name: download collection with multiple dependencies with --no-deps - command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 --no-deps -s pulp_v2 {{ galaxy_verbosity }} + command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 --no-deps -s galaxy_ng {{ galaxy_verbosity }} register: download_collection args: chdir: '{{ galaxy_dir }}/download' @@ -34,7 +34,7 @@ - (download_collection_actual.files[1].path | basename) in ['requirements.yml', 'parent_dep-parent_collection-1.0.0.tar.gz'] - name: download collection with multiple dependencies - command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 -s pulp_v2 {{ galaxy_verbosity }} + command: ansible-galaxy collection download parent_dep.parent_collection:1.0.0 -s galaxy_ng {{ galaxy_verbosity }} register: download_collection args: chdir: '{{ galaxy_dir }}/download' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml index eb471f8..d861cb4 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml @@ -1,5 +1,5 @@ # resolvelib>=0.6.0 added an 'incompatibilities' parameter to find_matches -# If incompatibilities aren't removed from the viable candidates, this example causes infinite resursion +# If incompatibilities aren't removed from the viable candidates, this example causes infinite recursion - name: test resolvelib removes incompatibilites in find_matches and errors quickly (prevent infinite recursion) block: - name: create collection dir diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/init.yml b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml index 17a000d..46198fe 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/init.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml @@ -5,6 +5,12 @@ chdir: '{{ galaxy_dir }}/scratch' register: init_relative +- name: create required runtime.yml + copy: + content: | + requires_ansible: '>=1.0.0' + dest: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/meta/runtime.yml' + - name: get result of create default skeleton find: path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection' @@ -92,6 +98,65 @@ - (init_custom_path_actual.files | map(attribute='path') | list)[2] | basename in ['docs', 'plugins', 'roles', 'meta'] - (init_custom_path_actual.files | map(attribute='path') | list)[3] | basename in ['docs', 'plugins', 'roles', 'meta'] +- name: test using a custom skeleton for collection init + block: + - name: create skeleton directories + file: + path: "{{ galaxy_dir }}/scratch/skeleton/{{ item }}" + state: directory + loop: + - custom_skeleton + - custom_skeleton/plugins + - inventory + + - name: create files + file: + path: "{{ galaxy_dir }}/scratch/skeleton/{{ item }}" + state: touch + loop: + - inventory/foo.py + - galaxy.yml + + - name: create symlinks + file: + path: "{{ galaxy_dir }}/scratch/skeleton/{{ item.link }}" + src: "{{ galaxy_dir }}/scratch/skeleton/{{ item.source }}" + state: link + loop: + - link: custom_skeleton/plugins/inventory + source: inventory + - link: custom_skeleton/galaxy.yml + source: galaxy.yml + + - name: initialize a collection using the skeleton + command: ansible-galaxy collection init ansible_test.my_collection {{ init_path }} {{ skeleton }} + vars: + init_path: '--init-path {{ galaxy_dir }}/scratch/skeleton' + skeleton: '--collection-skeleton {{ galaxy_dir }}/scratch/skeleton/custom_skeleton' + + - name: stat expected collection contents + stat: + path: "{{ galaxy_dir }}/scratch/skeleton/ansible_test/my_collection/{{ item }}" + register: stat_result + loop: + - plugins + - plugins/inventory + - galaxy.yml + - plugins/inventory/foo.py + + - assert: + that: + - stat_result.results[0].stat.isdir + - stat_result.results[1].stat.islnk + - stat_result.results[2].stat.islnk + - stat_result.results[3].stat.isreg + + always: + - name: cleanup + file: + path: "{{ galaxy_dir }}/scratch/skeleton" + state: absent + - name: create collection for ignored files and folders command: ansible-galaxy collection init ansible_test.ignore args: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml index cca83c7..9237826 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml @@ -165,10 +165,13 @@ failed_when: - '"Could not satisfy the following requirements" not in fail_dep_mismatch.stderr' - '" fail_dep2.name:<0.0.5 (dependency of fail_namespace.fail_collection:2.1.2)" not in fail_dep_mismatch.stderr' + - 'pre_release_hint not in fail_dep_mismatch.stderr' + vars: + pre_release_hint: 'Hint: Pre-releases are not installed by default unless the specific version is given. To enable pre-releases, use --pre.' - name: Find artifact url for namespace3.name uri: - url: '{{ test_server }}{{ vX }}collections/namespace3/name/versions/1.0.0/' + url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace3/name/versions/1.0.0/' user: '{{ pulp_user }}' password: '{{ pulp_password }}' force_basic_auth: true @@ -218,7 +221,7 @@ state: absent - assert: - that: error == expected_error + that: expected_error in error vars: error: "{{ result.stderr | regex_replace('\\n', ' ') }}" expected_error: >- @@ -258,12 +261,14 @@ ignore_errors: yes register: result - - debug: msg="Actual - {{ error }}" + - debug: + msg: "Actual - {{ error }}" - - debug: msg="Expected - {{ expected_error }}" + - debug: + msg: "Expected - {{ expected_error }}" - assert: - that: error == expected_error + that: expected_error in error always: - name: clean up collection skeleton and artifact file: @@ -295,7 +300,7 @@ - name: Find artifact url for namespace4.name uri: - url: '{{ test_server }}{{ vX }}collections/namespace4/name/versions/1.0.0/' + url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/namespace4/name/versions/1.0.0/' user: '{{ pulp_user }}' password: '{{ pulp_password }}' force_basic_auth: true @@ -325,10 +330,11 @@ environment: ANSIBLE_GALAXY_SERVER_LIST: undefined -- when: not requires_auth +# pulp_v2 doesn't require auth +- when: v2|default(false) block: - name: install a collection with an empty server list - {{ test_id }} - command: ansible-galaxy collection install namespace5.name -s '{{ test_server }}' {{ galaxy_verbosity }} + command: ansible-galaxy collection install namespace5.name -s '{{ test_server }}' --api-version 2 {{ galaxy_verbosity }} register: install_empty_server_list environment: ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' @@ -571,7 +577,6 @@ - namespace8 - namespace9 -# SIVEL - name: assert invalid signature is not fatal with ansible-galaxy install --ignore-errors - {{ test_id }} assert: that: @@ -646,6 +651,7 @@ - namespace8 - namespace9 +# test --ignore-signature-status-code extends ANSIBLE_GALAXY_IGNORE_SIGNATURE_STATUS_CODES env var - name: install collections with only one valid signature by ignoring the other errors command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} --ignore-signature-status-code FAILURE register: install_req @@ -686,6 +692,60 @@ vars: install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" +# test --ignore-signature-status-code passed multiple times +- name: reinstall collections with only one valid signature by ignoring the other errors + command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} {{ ignore_errors }} + register: install_req + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + cli_opts: "-s {{ test_name }} --keyring {{ keyring }} --force" + keyring: "{{ gpg_homedir }}/pubring.kbx" + ignore_errors: "--ignore-signature-status-code BADSIG --ignore-signature-status-code FAILURE" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: assert invalid signature is not fatal with ansible-galaxy install - {{ test_name }} + assert: + that: + - install_req is success + - '"Installing ''namespace7.name:1.0.0'' to" in install_req.stdout' + - '"Signature verification failed for ''namespace7.name'' (return code 1)" not in install_req.stdout' + - '"Not installing namespace7.name because GnuPG signature verification failed." not in install_stderr' + - '"Installing ''namespace8.name:1.0.0'' to" in install_req.stdout' + - '"Installing ''namespace9.name:1.0.0'' to" in install_req.stdout' + vars: + install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" + +# test --ignore-signature-status-code passed once with a list +- name: reinstall collections with only one valid signature by ignoring the other errors + command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} {{ ignore_errors }} + register: install_req + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + cli_opts: "-s {{ test_name }} --keyring {{ keyring }} --force" + keyring: "{{ gpg_homedir }}/pubring.kbx" + ignore_errors: "--ignore-signature-status-codes BADSIG FAILURE" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: assert invalid signature is not fatal with ansible-galaxy install - {{ test_name }} + assert: + that: + - install_req is success + - '"Installing ''namespace7.name:1.0.0'' to" in install_req.stdout' + - '"Signature verification failed for ''namespace7.name'' (return code 1)" not in install_req.stdout' + - '"Not installing namespace7.name because GnuPG signature verification failed." not in install_stderr' + - '"Installing ''namespace8.name:1.0.0'' to" in install_req.stdout' + - '"Installing ''namespace9.name:1.0.0'' to" in install_req.stdout' + vars: + install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" + - name: clean up collections from last test file: path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name' @@ -697,44 +757,45 @@ - namespace8 - namespace9 -# Uncomment once pulp container is at pulp>=0.5.0 -#- name: install cache.cache at the current latest version -# command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' -vvv -# environment: -# ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' -# -#- set_fact: -# cache_version_build: '{{ (cache_version_build | int) + 1 }}' -# -#- name: publish update for cache.cache test -# setup_collections: -# server: galaxy_ng -# collections: -# - namespace: cache -# name: cache -# version: 1.0.{{ cache_version_build }} -# -#- name: make sure the cache version list is ignored on a collection version change - {{ test_id }} -# command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' --force -vvv -# register: install_cached_update -# environment: -# ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' -# -#- name: get result of cache version list is ignored on a collection version change - {{ test_id }} -# slurp: -# path: '{{ galaxy_dir }}/ansible_collections/cache/cache/MANIFEST.json' -# register: install_cached_update_actual -# -#- name: assert cache version list is ignored on a collection version change - {{ test_id }} -# assert: -# that: -# - '"Installing ''cache.cache:1.0.{{ cache_version_build }}'' to" in install_cached_update.stdout' -# - (install_cached_update_actual.content | b64decode | from_json).collection_info.version == '1.0.' ~ cache_version_build +- when: not v2|default(false) + block: + - name: install cache.cache at the current latest version + command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' -vvv + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + + - set_fact: + cache_version_build: '{{ (cache_version_build | int) + 1 }}' + + - name: publish update for cache.cache test + setup_collections: + server: galaxy_ng + collections: + - namespace: cache + name: cache + version: 1.0.{{ cache_version_build }} + + - name: make sure the cache version list is ignored on a collection version change - {{ test_id }} + command: ansible-galaxy collection install cache.cache -s '{{ test_name }}' --force -vvv + register: install_cached_update + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + + - name: get result of cache version list is ignored on a collection version change - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/cache/cache/MANIFEST.json' + register: install_cached_update_actual + + - name: assert cache version list is ignored on a collection version change - {{ test_id }} + assert: + that: + - '"Installing ''cache.cache:1.0.{{ cache_version_build }}'' to" in install_cached_update.stdout' + - (install_cached_update_actual.content | b64decode | from_json).collection_info.version == '1.0.' ~ cache_version_build - name: install collection with symlink - {{ test_id }} command: ansible-galaxy collection install symlink.symlink -s '{{ test_name }}' {{ galaxy_verbosity }} environment: - ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' register: install_symlink - find: @@ -772,6 +833,56 @@ - install_symlink_actual.results[5].stat.islnk - install_symlink_actual.results[5].stat.lnk_target == '../REÅDMÈ.md' + +# Testing an install from source to check that symlinks to directories +# are preserved (see issue https://github.com/ansible/ansible/issues/78442) +- name: symlink_dirs collection install from source test + block: + + - name: create symlink_dirs collection + command: ansible-galaxy collection init symlink_dirs.symlink_dirs --init-path "{{ galaxy_dir }}/scratch" + + - name: create directory in collection + file: + path: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderA" + state: directory + + - name: create symlink to folderA + file: + dest: "{{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/folderB" + src: ./folderA + state: link + force: yes + + - name: install symlink_dirs collection from source + command: ansible-galaxy collection install {{ galaxy_dir }}/scratch/symlink_dirs/symlink_dirs/ + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + register: install_symlink_dirs + + - name: get result of install collection with symlink_dirs - {{ test_id }} + stat: + path: '{{ galaxy_dir }}/ansible_collections/symlink_dirs/symlink_dirs/{{ path }}' + register: install_symlink_dirs_actual + loop_control: + loop_var: path + loop: + - folderA + - folderB + + - name: assert install collection with symlink_dirs - {{ test_id }} + assert: + that: + - '"Installing ''symlink_dirs.symlink_dirs:1.0.0'' to" in install_symlink_dirs.stdout' + - install_symlink_dirs_actual.results[0].stat.isdir + - install_symlink_dirs_actual.results[1].stat.islnk + - install_symlink_dirs_actual.results[1].stat.lnk_target == './folderA' + always: + - name: clean up symlink_dirs collection directory + file: + path: "{{ galaxy_dir }}/scratch/symlink_dirs" + state: absent + - name: remove install directory for the next test because parent_dep.parent_collection was installed - {{ test_id }} file: path: '{{ galaxy_dir }}/ansible_collections' @@ -780,7 +891,7 @@ - name: install collection and dep compatible with multiple requirements - {{ test_id }} command: ansible-galaxy collection install parent_dep.parent_collection parent_dep2.parent_collection environment: - ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' register: install_req - name: assert install collections with ansible-galaxy install - {{ test_id }} @@ -802,7 +913,7 @@ - name: install a collection to the same installation directory - {{ test_id }} command: ansible-galaxy collection install namespace1.name1 environment: - ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' register: install_req - name: assert installed collections with ansible-galaxy install - {{ test_id }} @@ -1009,7 +1120,7 @@ args: chdir: '{{ galaxy_dir }}/scratch' environment: - ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' register: install_concrete_pre - name: get result of install collections with concrete pre-release dep - {{ test_id }} diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml index 74c9983..f3b9777 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml @@ -25,6 +25,14 @@ regexp: "^dependencies:*" line: "dependencies: {'ns.coll2': '>=1.0.0'}" + - name: create required runtime.yml + copy: + dest: "{{ galaxy_dir }}/offline/setup/ns/{{ item }}/meta/runtime.yml" + content: "requires_ansible: '>=1.0.0'" + loop: + - coll1 + - coll2 + - name: build both collections command: ansible-galaxy collection build {{ init_dir }}/ns/{{ item }} args: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml index b8d6349..1c93d54 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml @@ -1,4 +1,4 @@ -- name: initialize collection structure +- name: initialize dev collection structure command: ansible-galaxy collection init {{ item }} --init-path "{{ galaxy_dir }}/dev/ansible_collections" {{ galaxy_verbosity }} loop: - 'dev.collection1' @@ -8,6 +8,13 @@ - 'dev.collection5' - 'dev.collection6' +- name: initialize prod collection structure + command: ansible-galaxy collection init {{ item }} --init-path "{{ galaxy_dir }}/prod/ansible_collections" {{ galaxy_verbosity }} + loop: + - 'prod.collection1' + - 'prod.collection2' + - 'prod.collection3' + - name: replace the default version of the collections lineinfile: path: "{{ galaxy_dir }}/dev/ansible_collections/dev/{{ item.name }}/galaxy.yml" @@ -53,13 +60,13 @@ - assert: that: - - "'dev.collection1 *' in list_result.stdout" + - "'dev.collection1 *' in list_result.stdout" # Note the version displayed is the 'placeholder' string rather than "*" since it is not falsey - - "'dev.collection2 placeholder' in list_result.stdout" - - "'dev.collection3 *' in list_result.stdout" - - "'dev.collection4 *' in list_result.stdout" - - "'dev.collection5 *' in list_result.stdout" - - "'dev.collection6 *' in list_result.stdout" + - "'dev.collection2 placeholder' in list_result.stdout" + - "'dev.collection3 *' in list_result.stdout" + - "'dev.collection4 *' in list_result.stdout" + - "'dev.collection5 *' in list_result.stdout" + - "'dev.collection6 *' in list_result.stdout" - name: list collections in human format command: ansible-galaxy collection list --format human @@ -69,12 +76,12 @@ - assert: that: - - "'dev.collection1 *' in list_result_human.stdout" + - "'dev.collection1 *' in list_result_human.stdout" # Note the version displayed is the 'placeholder' string rather than "*" since it is not falsey - - "'dev.collection2 placeholder' in list_result_human.stdout" - - "'dev.collection3 *' in list_result_human.stdout" - - "'dev.collection5 *' in list_result.stdout" - - "'dev.collection6 *' in list_result.stdout" + - "'dev.collection2 placeholder' in list_result_human.stdout" + - "'dev.collection3 *' in list_result_human.stdout" + - "'dev.collection5 *' in list_result.stdout" + - "'dev.collection6 *' in list_result.stdout" - name: list collections in yaml format command: ansible-galaxy collection list --format yaml @@ -84,6 +91,12 @@ - assert: that: + - yaml_result[galaxy_dir ~ '/dev/ansible_collections'] != yaml_result[galaxy_dir ~ '/prod/ansible_collections'] + vars: + yaml_result: '{{ list_result_yaml.stdout | from_yaml }}' + +- assert: + that: - "item.value | length == 6" - "item.value['dev.collection1'].version == '*'" - "item.value['dev.collection2'].version == 'placeholder'" @@ -91,6 +104,7 @@ - "item.value['dev.collection5'].version == '*'" - "item.value['dev.collection6'].version == '*'" with_dict: "{{ list_result_yaml.stdout | from_yaml }}" + when: "'dev' in item.key" - name: list collections in json format command: ansible-galaxy collection list --format json @@ -107,6 +121,7 @@ - "item.value['dev.collection5'].version == '*'" - "item.value['dev.collection6'].version == '*'" with_dict: "{{ list_result_json.stdout | from_json }}" + when: "'dev' in item.key" - name: list single collection in json format command: "ansible-galaxy collection list {{ item.key }} --format json" @@ -137,7 +152,7 @@ register: list_result_error ignore_errors: True environment: - ANSIBLE_COLLECTIONS_PATH: "" + ANSIBLE_COLLECTIONS_PATH: "i_dont_exist" - assert: that: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml index 724c861..e17d6aa 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml @@ -72,13 +72,12 @@ vars: test_name: '{{ item.name }}' test_server: '{{ item.server }}' - vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}' + test_api_server: '{{ item.api_server|default(item.server) }}' loop: - name: pulp_v2 - server: '{{ pulp_server }}published/api/' - - name: pulp_v3 - server: '{{ pulp_server }}published/api/' - v3: true + api_server: '{{ galaxy_ng_server }}' + server: '{{ pulp_server }}primary/api/' + v2: true - name: galaxy_ng server: '{{ galaxy_ng_server }}' v3: true @@ -108,8 +107,9 @@ test_id: '{{ item.name }}' test_name: '{{ item.name }}' test_server: '{{ item.server }}' - vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}' + test_api_server: '{{ item.api_server|default(item.server) }}' requires_auth: '{{ item.requires_auth|default(false) }}' + v2: '{{ item.v2|default(false) }}' args: apply: environment: @@ -120,10 +120,9 @@ v3: true requires_auth: true - name: pulp_v2 - server: '{{ pulp_server }}published/api/' - - name: pulp_v3 - server: '{{ pulp_server }}published/api/' - v3: true + server: '{{ pulp_server }}primary/api/' + api_server: '{{ galaxy_ng_server }}' + v2: true - name: test installing and downloading collections with the range of supported resolvelib versions include_tasks: supported_resolvelib.yml @@ -135,6 +134,17 @@ loop_control: loop_var: resolvelib_version +- name: test choosing pinned pre-releases anywhere in the dependency tree + # This is a regression test for the case when the end-user does not + # explicitly allow installing pre-release collection versions, but their + # precise pins are still selected if met among the dependencies, either + # direct or transitive. + include_tasks: pinned_pre_releases_in_deptree.yml + +- name: test installing prereleases via scm direct requests + # In this test suite because the bug relies on the dep having versions on a Galaxy server + include_tasks: virtual_direct_requests.yml + - name: publish collection with a dep on another server setup_collections: server: secondary @@ -176,13 +186,13 @@ in install_cross_dep.stdout # pulp_v2 is highest in the list so it will find it there first - >- - "'parent_dep.parent_collection:1.0.0' obtained from server pulp_v2" + "'parent_dep.parent_collection:1.0.0' obtained from server galaxy_ng" in install_cross_dep.stdout - >- - "'child_dep.child_collection:0.9.9' obtained from server pulp_v2" + "'child_dep.child_collection:0.9.9' obtained from server galaxy_ng" in install_cross_dep.stdout - >- - "'child_dep.child_dep2:1.2.2' obtained from server pulp_v2" + "'child_dep.child_dep2:1.2.2' obtained from server galaxy_ng" in install_cross_dep.stdout - (install_cross_dep_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' - (install_cross_dep_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' @@ -204,10 +214,9 @@ ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' vars: - test_api_fallback: 'pulp_v2' - test_api_fallback_versions: 'v1, v2' - test_name: 'galaxy_ng' - test_server: '{{ galaxy_ng_server }}' + test_api_fallback: 'galaxy_ng' + test_api_fallback_versions: 'v3, pulp-v3, v1' + test_name: 'pulp_v2' - name: run ansible-galaxy collection list tests include_tasks: list.yml diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/pinned_pre_releases_in_deptree.yml b/test/integration/targets/ansible-galaxy-collection/tasks/pinned_pre_releases_in_deptree.yml new file mode 100644 index 0000000..3745fa3 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/pinned_pre_releases_in_deptree.yml @@ -0,0 +1,79 @@ +--- + +- name: >- + test that the dependency resolver chooses pre-releases if they are pinned + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + block: + - name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + + - name: >- + install collections with pre-release versions in the dependency tree + command: >- + ansible-galaxy collection install + meta_ns_with_transitive_wildcard_dep.meta_name_with_transitive_wildcard_dep + rc_meta_ns_with_transitive_dev_dep.rc_meta_name_with_transitive_dev_dep:=2.4.5-rc5 + {{ galaxy_verbosity }} + register: prioritize_direct_req + - assert: + that: + - >- + "rc_meta_ns_with_transitive_dev_dep.rc_meta_name_with_transitive_dev_dep:2.4.5-rc5 was installed successfully" + in prioritize_direct_req.stdout + - >- + "meta_ns_with_transitive_wildcard_dep.meta_name_with_transitive_wildcard_dep:4.5.6 was installed successfully" + in prioritize_direct_req.stdout + - >- + "ns_with_dev_dep.name_with_dev_dep:6.7.8 was installed successfully" + in prioritize_direct_req.stdout + - >- + "ns_with_wildcard_dep.name_with_wildcard_dep:5.6.7-beta.3 was installed successfully" + in prioritize_direct_req.stdout + - >- + "dev_and_stables_ns.dev_and_stables_name:1.2.3-dev0 was installed successfully" + in prioritize_direct_req.stdout + + - name: cleanup + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + + - name: >- + install collection that only has pre-release versions published + to the index + command: >- + ansible-galaxy collection install + rc_meta_ns_with_transitive_dev_dep.rc_meta_name_with_transitive_dev_dep:* + {{ galaxy_verbosity }} + register: select_pre_release_if_no_stable + - assert: + that: + - >- + "rc_meta_ns_with_transitive_dev_dep.rc_meta_name_with_transitive_dev_dep:2.4.5-rc5 was installed successfully" + in select_pre_release_if_no_stable.stdout + - >- + "ns_with_dev_dep.name_with_dev_dep:6.7.8 was installed successfully" + in select_pre_release_if_no_stable.stdout + - >- + "dev_and_stables_ns.dev_and_stables_name:1.2.3-dev0 was installed successfully" + in select_pre_release_if_no_stable.stdout + always: + - name: cleanup + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +... diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml index 241eae6..1be16ae 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml @@ -5,9 +5,12 @@ chdir: '{{ galaxy_dir }}' register: publish_collection +- name: ensure we can download the published collection - {{ test_name }} + command: ansible-galaxy collection install -s {{ test_name }} -p "{{ remote_tmp_dir }}/publish/{{ test_name }}" ansible_test.my_collection==1.0.0 {{ galaxy_verbosity }} + - name: get result of publish collection - {{ test_name }} uri: - url: '{{ test_server }}{{ vX }}collections/ansible_test/my_collection/versions/1.0.0/' + url: '{{ test_api_server }}v3/plugin/ansible/content/primary/collections/index/ansible_test/my_collection/versions/1.0.0/' return_content: yes user: '{{ pulp_user }}' password: '{{ pulp_password }}' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml index 763c5a1..bff3689 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml @@ -20,11 +20,11 @@ - include_tasks: install.yml vars: - test_name: pulp_v3 + test_name: galaxy_ng test_id: '{{ test_name }} (resolvelib {{ resolvelib_version }})' - test_server: '{{ pulp_server }}published/api/' - vX: "v3/" - requires_auth: false + test_server: '{{ galaxy_ng_server }}' + test_api_server: '{{ galaxy_ng_server }}' + requires_auth: true args: apply: environment: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml index 893ea80..debd70b 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml @@ -142,7 +142,7 @@ - directory - name: install a collection - command: ansible-galaxy collection install namespace1.name1:0.0.1 {{ galaxy_verbosity }} + command: ansible-galaxy collection install namespace1.name1==0.0.1 {{ galaxy_verbosity }} register: result failed_when: - '"namespace1.name1:0.0.1 was installed successfully" not in result.stdout_lines' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml index dfe3d0f..0fe2f82 100644 --- a/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml +++ b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml @@ -3,6 +3,11 @@ args: chdir: '{{ galaxy_dir }}/scratch' +- name: created required runtime.yml + copy: + content: 'requires_ansible: ">=1.0.0"' + dest: '{{ galaxy_dir }}/scratch/ansible_test/verify/meta/runtime.yml' + - name: build the collection command: ansible-galaxy collection build scratch/ansible_test/verify args: @@ -31,6 +36,9 @@ - name: verify the collection against the first valid server command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -vvvv {{ galaxy_verbosity }} register: verify + vars: + # This sets a specific precedence that the tests are expecting + ANSIBLE_GALAXY_SERVER_LIST: offline,secondary,pulp_v2,galaxy_ng - assert: that: diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/virtual_direct_requests.yml b/test/integration/targets/ansible-galaxy-collection/tasks/virtual_direct_requests.yml new file mode 100644 index 0000000..7b1931f --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/virtual_direct_requests.yml @@ -0,0 +1,77 @@ +- environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + vars: + scm_path: "{{ galaxy_dir }}/scms" + metadata: + collection1: |- + name: collection1 + version: "1.0.0" + dependencies: + test_prereleases.collection2: '*' + collection2: | + name: collection2 + version: "1.0.0-dev0" + dependencies: {} + namespace_boilerplate: |- + namespace: test_prereleases + readme: README.md + authors: + - "ansible-core" + description: test prerelease priority with virtual collections + license: + - GPL-2.0-or-later + license_file: '' + tags: [] + repository: https://github.com/ansible/ansible + documentation: https://github.com/ansible/ansible + homepage: https://github.com/ansible/ansible + issues: https://github.com/ansible/ansible + build_ignore: [] + block: + - name: Initialize git repository + command: 'git init {{ scm_path }}/test_prereleases' + + - name: Configure commiter for the repo + shell: git config user.email ansible-test@ansible.com && git config user.name ansible-test + args: + chdir: "{{ scm_path }}/test_prereleases" + + - name: Add collections to the repo + file: + path: "{{ scm_path }}/test_prereleases/{{ item }}" + state: directory + loop: + - collection1 + - collection2 + + - name: Add collection metadata + copy: + dest: "{{ scm_path }}/test_prereleases/{{ item }}/galaxy.yml" + content: "{{ metadata[item] + '\n' + metadata['namespace_boilerplate'] }}" + loop: + - collection1 + - collection2 + + - name: Save the changes + shell: git add . && git commit -m "Add collections to test installing a git repo directly takes priority over indirect Galaxy dep" + args: + chdir: '{{ scm_path }}/test_prereleases' + + - name: Validate the dependency also exists on Galaxy before test + command: "ansible-galaxy collection install test_prereleases.collection2" + register: prereq + failed_when: '"test_prereleases.collection2:1.0.0 was installed successfully" not in prereq.stdout' + + - name: Install collections from source + command: "ansible-galaxy collection install git+file://{{ scm_path }}/test_prereleases" + register: prioritize_direct_req + + - assert: + that: + - '"test_prereleases.collection2:1.0.0-dev0 was installed successfully" in prioritize_direct_req.stdout' + + always: + - name: Clean up test repos + file: + path: "{{ scm_path }}" + state: absent diff --git a/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2 b/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2 index 9bff527..a242979 100644 --- a/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2 +++ b/test/integration/targets/ansible-galaxy-collection/templates/ansible.cfg.j2 @@ -1,28 +1,22 @@ [galaxy] # Ensures subsequent unstable reruns don't use the cached information causing another failure cache_dir={{ remote_tmp_dir }}/galaxy_cache -server_list=offline,pulp_v2,pulp_v3,galaxy_ng,secondary +server_list=offline,galaxy_ng,secondary,pulp_v2 [galaxy_server.offline] url={{ offline_server }} [galaxy_server.pulp_v2] -url={{ pulp_server }}published/api/ -username={{ pulp_user }} -password={{ pulp_password }} - -[galaxy_server.pulp_v3] -url={{ pulp_server }}published/api/ -v3=true +url={{ pulp_server }}primary/api/ username={{ pulp_user }} password={{ pulp_password }} +api_version=2 [galaxy_server.galaxy_ng] -url={{ galaxy_ng_server }} +url={{ galaxy_ng_server }}content/primary/ token={{ galaxy_ng_token.json.token }} [galaxy_server.secondary] -url={{ pulp_server }}secondary/api/ -v3=true +url={{ galaxy_ng_server }}content/secondary/ username={{ pulp_user }} password={{ pulp_password }} diff --git a/test/integration/targets/ansible-galaxy-collection/vars/main.yml b/test/integration/targets/ansible-galaxy-collection/vars/main.yml index 175d669..066d267 100644 --- a/test/integration/targets/ansible-galaxy-collection/vars/main.yml +++ b/test/integration/targets/ansible-galaxy-collection/vars/main.yml @@ -9,17 +9,20 @@ supported_resolvelib_versions: - "0.6.0" - "0.7.0" - "0.8.0" + - "0.9.0" + - "1.0.1" unsupported_resolvelib_versions: - "0.2.0" # Fails on import - "0.5.1" pulp_repositories: - - published + - primary - secondary publish_namespaces: - ansible_test + - secondary collection_list: # Scenario to test out pre-release being ignored unless explicitly set and version pagination. @@ -162,3 +165,41 @@ collection_list: name: parent dependencies: namespace1.name1: '*' + + # non-prerelease is published to test that installing + # the pre-release from SCM doesn't accidentally prefer indirect + # dependencies from Galaxy + - namespace: test_prereleases + name: collection2 + version: 1.0.0 + + - namespace: dev_and_stables_ns + name: dev_and_stables_name + version: 1.2.3-dev0 + - namespace: dev_and_stables_ns + name: dev_and_stables_name + version: 1.2.4 + + - namespace: ns_with_wildcard_dep + name: name_with_wildcard_dep + version: 5.6.7-beta.3 + dependencies: + dev_and_stables_ns.dev_and_stables_name: >- + * + - namespace: ns_with_dev_dep + name: name_with_dev_dep + version: 6.7.8 + dependencies: + dev_and_stables_ns.dev_and_stables_name: 1.2.3-dev0 + + - namespace: rc_meta_ns_with_transitive_dev_dep + name: rc_meta_name_with_transitive_dev_dep + version: 2.4.5-rc5 + dependencies: + ns_with_dev_dep.name_with_dev_dep: >- + * + - namespace: meta_ns_with_transitive_wildcard_dep + name: meta_name_with_transitive_wildcard_dep + version: 4.5.6 + dependencies: + ns_with_wildcard_dep.name_with_wildcard_dep: 5.6.7-beta.3 diff --git a/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py index cfd908c..4876663 100755 --- a/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py +++ b/test/integration/targets/ansible-galaxy-role/files/create-role-archive.py @@ -2,6 +2,7 @@ """Create a role archive which overwrites an arbitrary file.""" import argparse +import os import pathlib import tarfile import tempfile @@ -18,6 +19,15 @@ def main() -> None: create_archive(args.archive, args.content, args.target) +def generate_files_from_path(path): + if os.path.isdir(path): + for subpath in os.listdir(path): + _path = os.path.join(path, subpath) + yield from generate_files_from_path(_path) + elif os.path.isfile(path): + yield pathlib.Path(path) + + def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, target_path: pathlib.Path) -> None: with ( tarfile.open(name=archive_path, mode='w') as role_archive, @@ -35,10 +45,15 @@ def create_archive(archive_path: pathlib.Path, content_path: pathlib.Path, targe role_archive.add(meta_main_path) role_archive.add(symlink_path) - content_tarinfo = role_archive.gettarinfo(content_path, str(symlink_path)) + for path in generate_files_from_path(content_path): + if path == content_path: + arcname = str(symlink_path) + else: + arcname = os.path.join(temp_dir_path, path) - with content_path.open('rb') as content_file: - role_archive.addfile(content_tarinfo, content_file) + content_tarinfo = role_archive.gettarinfo(path, arcname) + with path.open('rb') as file_content: + role_archive.addfile(content_tarinfo, file_content) if __name__ == '__main__': diff --git a/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml index c70e899..1c17daf 100644 --- a/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml +++ b/test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml @@ -23,6 +23,9 @@ command: cmd: ansible-galaxy role install --roles-path '{{ remote_tmp_dir }}/dir-traversal/roles' dangerous.tar chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False ignore_errors: true register: galaxy_install_dangerous @@ -42,3 +45,86 @@ - dangerous_overwrite_content.content|default('')|b64decode == '' - not dangerous_overwrite_stat.stat.exists - galaxy_install_dangerous is failed + - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))" + +- name: remove tarfile for next test + file: + path: '{{ item }}' + state: absent + loop: + - '{{ remote_tmp_dir }}/dir-traversal/source/dangerous.tar' + - '{{ remote_tmp_dir }}/dir-traversal/roles/dangerous.tar' + +- name: build dangerous dir traversal role that includes .. in the symlink path + script: + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + cmd: create-role-archive.py dangerous.tar content.txt {{ remote_tmp_dir }}/dir-traversal/source/../target/target-file-to-overwrite.txt + executable: '{{ ansible_playbook_python }}' + +- name: install dangerous role + command: + cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles dangerous.tar' + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + ignore_errors: true + register: galaxy_install_dangerous + +- name: check for overwritten file + stat: + path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt' + register: dangerous_overwrite_stat + +- name: get overwritten content + slurp: + path: '{{ remote_tmp_dir }}/dir-traversal/target/target-file-to-overwrite.txt' + register: dangerous_overwrite_content + when: dangerous_overwrite_stat.stat.exists + +- assert: + that: + - dangerous_overwrite_content.content|default('')|b64decode == '' + - not dangerous_overwrite_stat.stat.exists + - galaxy_install_dangerous is failed + - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))" + +- name: remove tarfile for next test + file: + path: '{{ remote_tmp_dir }}/dir-traversal/source/dangerous.tar' + state: absent + +- name: build dangerous dir traversal role that includes .. in the relative symlink path + script: + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + cmd: create-role-archive.py dangerous_rel.tar content.txt ../context.txt + +- name: install dangerous role with relative symlink + command: + cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles dangerous_rel.tar' + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + ignore_errors: true + register: galaxy_install_dangerous + +- name: check for symlink outside role + stat: + path: "{{ remote_tmp_dir | realpath }}/dir-traversal/roles/symlink" + register: symlink_outside_role + +- assert: + that: + - not symlink_outside_role.stat.exists + - galaxy_install_dangerous is failed + - "'is not a subpath of the role' in (galaxy_install_dangerous.stderr | regex_replace('\n', ' '))" + +- name: remove test directories + file: + path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}' + state: absent + loop: + - source + - target + - roles diff --git a/test/integration/targets/ansible-galaxy-role/tasks/main.yml b/test/integration/targets/ansible-galaxy-role/tasks/main.yml index b39df11..5f88a55 100644 --- a/test/integration/targets/ansible-galaxy-role/tasks/main.yml +++ b/test/integration/targets/ansible-galaxy-role/tasks/main.yml @@ -25,10 +25,18 @@ - name: Valid role archive command: "tar cf {{ remote_tmp_dir }}/valid-role.tar {{ remote_tmp_dir }}/role.d" -- name: Invalid file - copy: - content: "" +- name: Add invalid symlink + file: + state: link + src: "~/invalid" dest: "{{ remote_tmp_dir }}/role.d/tasks/~invalid.yml" + force: yes + +- name: Add another invalid symlink + file: + state: link + src: "/" + dest: "{{ remote_tmp_dir }}/role.d/tasks/invalid$name.yml" - name: Valid requirements file copy: @@ -61,3 +69,4 @@ command: ansible-galaxy role remove invalid-testrole - import_tasks: dir-traversal.yml +- import_tasks: valid-role-symlinks.yml diff --git a/test/integration/targets/ansible-galaxy-role/tasks/valid-role-symlinks.yml b/test/integration/targets/ansible-galaxy-role/tasks/valid-role-symlinks.yml new file mode 100644 index 0000000..8a60b2e --- /dev/null +++ b/test/integration/targets/ansible-galaxy-role/tasks/valid-role-symlinks.yml @@ -0,0 +1,78 @@ +- name: create test directories + file: + path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}' + state: directory + loop: + - source + - target + - roles + +- name: create subdir in the role content to test relative symlinks + file: + dest: '{{ remote_tmp_dir }}/dir-traversal/source/role_subdir' + state: directory + +- copy: + dest: '{{ remote_tmp_dir }}/dir-traversal/source/role_subdir/.keep' + content: '' + +- set_fact: + installed_roles: "{{ remote_tmp_dir | realpath }}/dir-traversal/roles" + +- name: build role with symlink to a directory in the role + script: + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + cmd: create-role-archive.py safe-link-dir.tar ./ role_subdir/.. + executable: '{{ ansible_playbook_python }}' + +- name: install role successfully + command: + cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles safe-link-dir.tar' + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + register: galaxy_install_ok + +- name: check for the directory symlink in the role + stat: + path: "{{ installed_roles }}/safe-link-dir.tar/symlink" + register: symlink_in_role + +- assert: + that: + - symlink_in_role.stat.exists + - symlink_in_role.stat.lnk_source == installed_roles + '/safe-link-dir.tar' + +- name: remove tarfile for next test + file: + path: '{{ remote_tmp_dir }}/dir-traversal/source/safe-link-dir.tar' + state: absent + +- name: build role with safe relative symlink + script: + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + cmd: create-role-archive.py safe.tar ./ role_subdir/../context.txt + executable: '{{ ansible_playbook_python }}' + +- name: install role successfully + command: + cmd: 'ansible-galaxy role install --roles-path {{ remote_tmp_dir }}/dir-traversal/roles safe.tar' + chdir: '{{ remote_tmp_dir }}/dir-traversal/source' + register: galaxy_install_ok + +- name: check for symlink in role + stat: + path: "{{ installed_roles }}/safe.tar/symlink" + register: symlink_in_role + +- assert: + that: + - symlink_in_role.stat.exists + - symlink_in_role.stat.lnk_source == installed_roles + '/safe.tar/context.txt' + +- name: remove test directories + file: + path: '{{ remote_tmp_dir }}/dir-traversal/{{ item }}' + state: absent + loop: + - source + - target + - roles diff --git a/test/integration/targets/ansible-galaxy/files/testserver.py b/test/integration/targets/ansible-galaxy/files/testserver.py index 1359850..8cca6a8 100644 --- a/test/integration/targets/ansible-galaxy/files/testserver.py +++ b/test/integration/targets/ansible-galaxy/files/testserver.py @@ -1,20 +1,15 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import sys +import http.server +import socketserver import ssl if __name__ == '__main__': - if sys.version_info[0] >= 3: - import http.server - import socketserver - Handler = http.server.SimpleHTTPRequestHandler - httpd = socketserver.TCPServer(("", 4443), Handler) - else: - import BaseHTTPServer - import SimpleHTTPServer - Handler = SimpleHTTPServer.SimpleHTTPRequestHandler - httpd = BaseHTTPServer.HTTPServer(("", 4443), Handler) + Handler = http.server.SimpleHTTPRequestHandler + context = ssl.SSLContext() + context.load_cert_chain(certfile='./cert.pem', keyfile='./key.pem') + httpd = socketserver.TCPServer(("", 4443), Handler) + httpd.socket = context.wrap_socket(httpd.socket, server_side=True) - httpd.socket = ssl.wrap_socket(httpd.socket, certfile='./cert.pem', keyfile='./key.pem', server_side=True) httpd.serve_forever() diff --git a/test/integration/targets/ansible-galaxy/runme.sh b/test/integration/targets/ansible-galaxy/runme.sh index 7d966e2..fcd826c 100755 --- a/test/integration/targets/ansible-galaxy/runme.sh +++ b/test/integration/targets/ansible-galaxy/runme.sh @@ -61,10 +61,13 @@ f_ansible_galaxy_create_role_repo_post() git add . git commit -m "local testing ansible galaxy role" + # NOTE: `HEAD` is used because the newer Git versions create + # NOTE: `main` by default and the older ones differ. We + # NOTE: want to avoid hardcoding them. git archive \ --format=tar \ --prefix="${repo_name}/" \ - master > "${repo_tar}" + HEAD > "${repo_tar}" # Configure basic (insecure) HTTPS-accessible repository galaxy_local_test_role_http_repo="${galaxy_webserver_root}/${galaxy_local_test_role}.git" if [[ ! -d "${galaxy_local_test_role_http_repo}" ]]; then @@ -354,7 +357,7 @@ pushd "${galaxy_testdir}" popd # ${galaxy_testdir} f_ansible_galaxy_status \ - "role info non-existant role" + "role info non-existent role" mkdir -p "${role_testdir}" pushd "${role_testdir}" diff --git a/test/integration/targets/ansible-inventory/files/complex.ini b/test/integration/targets/ansible-inventory/files/complex.ini new file mode 100644 index 0000000..227d9ea --- /dev/null +++ b/test/integration/targets/ansible-inventory/files/complex.ini @@ -0,0 +1,35 @@ +ihavenogroup + +[all] +hostinall + +[all:vars] +ansible_connection=local + +[test_group1] +test1 myvar=something +test2 myvar=something2 +test3 + +[test_group2] +test1 +test4 +test5 + +[test_group3] +test2 othervar=stuff +test3 +test6 + +[parent_1:children] +test_group1 + +[parent_2:children] +test_group1 + +[parent_3:children] +test_group2 +test_group3 + +[parent_3] +test2 diff --git a/test/integration/targets/ansible-inventory/files/valid_sample.yml b/test/integration/targets/ansible-inventory/files/valid_sample.yml index 477f82f..b8e7b88 100644 --- a/test/integration/targets/ansible-inventory/files/valid_sample.yml +++ b/test/integration/targets/ansible-inventory/files/valid_sample.yml @@ -4,4 +4,4 @@ all: hosts: something: foo: bar - ungrouped: {}
\ No newline at end of file + ungrouped: {} diff --git a/test/integration/targets/ansible-inventory/filter_plugins/toml.py b/test/integration/targets/ansible-inventory/filter_plugins/toml.py new file mode 100644 index 0000000..997173c --- /dev/null +++ b/test/integration/targets/ansible-inventory/filter_plugins/toml.py @@ -0,0 +1,50 @@ +# (c) 2017, Matt Martz <matt@sivel.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import functools + +from ansible.plugins.inventory.toml import HAS_TOML, toml_dumps +try: + from ansible.plugins.inventory.toml import toml +except ImportError: + pass + +from ansible.errors import AnsibleFilterError +from ansible.module_utils.common.text.converters import to_text +from ansible.module_utils.common._collections_compat import MutableMapping +from ansible.module_utils.six import string_types + + +def _check_toml(func): + @functools.wraps(func) + def inner(o): + if not HAS_TOML: + raise AnsibleFilterError('The %s filter plugin requires the python "toml" library' % func.__name__) + return func(o) + return inner + + +@_check_toml +def from_toml(o): + if not isinstance(o, string_types): + raise AnsibleFilterError('from_toml requires a string, got %s' % type(o)) + return toml.loads(to_text(o, errors='surrogate_or_strict')) + + +@_check_toml +def to_toml(o): + if not isinstance(o, MutableMapping): + raise AnsibleFilterError('to_toml requires a dict, got %s' % type(o)) + return to_text(toml_dumps(o), errors='surrogate_or_strict') + + +class FilterModule(object): + def filters(self): + return { + 'to_toml': to_toml, + 'from_toml': from_toml + } diff --git a/test/integration/targets/ansible-inventory/tasks/json_output.yml b/test/integration/targets/ansible-inventory/tasks/json_output.yml new file mode 100644 index 0000000..2652061 --- /dev/null +++ b/test/integration/targets/ansible-inventory/tasks/json_output.yml @@ -0,0 +1,33 @@ +- block: + - name: check baseline + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --list + register: limited + + - name: ensure non empty host list + assert: + that: + - "'something' in inv['_meta']['hostvars']" + + - name: check that limit removes host + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --limit '!something' --list + register: limited + + - name: ensure empty host list + assert: + that: + - "'something' not in inv['_meta']['hostvars']" + + - name: check dupes + command: ansible-inventory -i '{{ role_path }}/files/complex.ini' --list + register: limited + + - name: ensure host only appears on directly assigned + assert: + that: + - "'hosts' not in inv['parent_1']" + - "'hosts' not in inv['parent_2']" + - "'hosts' in inv['parent_3']" + - "'test1' in inv['test_group1']['hosts']" + vars: + inv: '{{limited.stdout|from_json }}' + delegate_to: localhost diff --git a/test/integration/targets/ansible-inventory/tasks/main.yml b/test/integration/targets/ansible-inventory/tasks/main.yml index 84ac2c3..c3459c1 100644 --- a/test/integration/targets/ansible-inventory/tasks/main.yml +++ b/test/integration/targets/ansible-inventory/tasks/main.yml @@ -145,3 +145,10 @@ loop_control: loop_var: toml_package when: toml_package is not contains 'tomllib' or (toml_package is contains 'tomllib' and ansible_facts.python.version_info >= [3, 11]) + + +- include_tasks: "{{item}}_output.yml" + loop: + - json + - yaml + - toml diff --git a/test/integration/targets/ansible-inventory/tasks/toml_output.yml b/test/integration/targets/ansible-inventory/tasks/toml_output.yml new file mode 100644 index 0000000..1e5df9a --- /dev/null +++ b/test/integration/targets/ansible-inventory/tasks/toml_output.yml @@ -0,0 +1,43 @@ +- name: only test if have toml in python + command: "{{ansible_playbook_python}} -c 'import toml'" + ignore_errors: true + delegate_to: localhost + register: has_toml + +- block: + - name: check baseline + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --list --toml + register: limited + + - name: ensure non empty host list + assert: + that: + - "'something' in inv['somegroup']['hosts']" + + - name: check that limit removes host + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --limit '!something' --list --toml + register: limited + ignore_errors: true + + - name: ensure empty host list + assert: + that: + - limited is failed + + - name: check dupes + command: ansible-inventory -i '{{ role_path }}/files/complex.ini' --list --toml + register: limited + + - debug: var=inv + + - name: ensure host only appears on directly assigned + assert: + that: + - "'hosts' not in inv['parent_1']" + - "'hosts' not in inv['parent_2']" + - "'hosts' in inv['parent_3']" + - "'test1' in inv['test_group1']['hosts']" + vars: + inv: '{{limited.stdout|from_toml}}' + when: has_toml is success + delegate_to: localhost diff --git a/test/integration/targets/ansible-inventory/tasks/yaml_output.yml b/test/integration/targets/ansible-inventory/tasks/yaml_output.yml new file mode 100644 index 0000000..d41a8d0 --- /dev/null +++ b/test/integration/targets/ansible-inventory/tasks/yaml_output.yml @@ -0,0 +1,34 @@ +- block: + - name: check baseline + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --list --yaml + register: limited + + - name: ensure something in host list + assert: + that: + - "'something' in inv['all']['children']['somegroup']['hosts']" + + - name: check that limit removes host + command: ansible-inventory -i '{{ role_path }}/files/valid_sample.yml' --limit '!something' --list --yaml + register: limited + + - name: ensure empty host list + assert: + that: + - not inv + + - name: check dupes + command: ansible-inventory -i '{{ role_path }}/files/complex.ini' --list --yaml + register: limited + + - name: ensure host only appears on directly assigned + assert: + that: + - "'hosts' not in inv['all']['children']['parent_1']" + - "'hosts' not in inv['all']['children']['parent_2']" + - "'hosts' in inv['all']['children']['parent_3']" + - "'test1' in inv['all']['children']['parent_1']['children']['test_group1']['hosts']" + - "'hosts' not in inv['all']['children']['parent_2']['children']['test_group1']" + vars: + inv: '{{limited.stdout|from_yaml}}' + delegate_to: localhost diff --git a/test/integration/targets/ansible-playbook-callbacks/aliases b/test/integration/targets/ansible-playbook-callbacks/aliases new file mode 100644 index 0000000..d88a7fa --- /dev/null +++ b/test/integration/targets/ansible-playbook-callbacks/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 +context/controller +needs/target/setup_remote_tmp_dir +needs/target/support-callback_plugins diff --git a/test/integration/targets/ansible-playbook-callbacks/all-callbacks.yml b/test/integration/targets/ansible-playbook-callbacks/all-callbacks.yml new file mode 100644 index 0000000..85a53c7 --- /dev/null +++ b/test/integration/targets/ansible-playbook-callbacks/all-callbacks.yml @@ -0,0 +1,123 @@ +- hosts: localhost + gather_facts: false + vars_prompt: + name: vars_prompt_var + default: hamsandwich + handlers: + - name: handler1 + debug: + msg: handler1 + + - debug: + msg: listen1 + listen: + - listen1 + roles: + - setup_remote_tmp_dir + tasks: + - name: ok + debug: + msg: ok + + - name: changed + debug: + msg: changed + changed_when: true + + - name: skipped + debug: + msg: skipped + when: false + + - name: failed + debug: + msg: failed + failed_when: true + ignore_errors: true + + - name: unreachable + ping: + delegate_to: example.org + ignore_unreachable: true + vars: + ansible_timeout: 1 + + - name: loop + debug: + ignore_errors: true + changed_when: '{{ item.changed }}' + failed_when: '{{ item.failed }}' + when: '{{ item.when }}' + loop: + # ok + - changed: false + failed: false + when: true + # changed + - changed: true + failed: false + when: true + # failed + - changed: false + failed: true + when: true + # skipped + - changed: false + failed: false + when: false + + - name: notify handler1 + debug: + msg: notify handler1 + changed_when: true + notify: + - handler1 + + - name: notify listen1 + debug: + msg: notify listen1 + changed_when: true + notify: + - listen1 + + - name: retry ok + debug: + register: result + until: result.attempts == 2 + retries: 1 + delay: 0 + + - name: retry failed + debug: + register: result + until: result.attempts == 3 + retries: 1 + delay: 0 + ignore_errors: true + + - name: async poll ok + command: sleep 3 + async: 5 + poll: 2 + + - name: async poll failed + shell: sleep 3; false + async: 5 + poll: 2 + ignore_errors: true + + - include_tasks: include_me.yml + + - name: diff + copy: + content: diff + dest: '{{ remote_tmp_dir }}/diff.txt' + diff: true + +- hosts: i_dont_exist + +- hosts: localhost + gather_facts: false + max_fail_percentage: 0 + tasks: + - fail: diff --git a/test/integration/targets/ansible-playbook-callbacks/callbacks_list.expected b/test/integration/targets/ansible-playbook-callbacks/callbacks_list.expected new file mode 100644 index 0000000..1d064a2 --- /dev/null +++ b/test/integration/targets/ansible-playbook-callbacks/callbacks_list.expected @@ -0,0 +1,24 @@ + 1 __init__ +92 v2_on_any + 1 v2_on_file_diff + 4 v2_playbook_on_handler_task_start + 2 v2_playbook_on_include + 1 v2_playbook_on_no_hosts_matched + 3 v2_playbook_on_notify + 3 v2_playbook_on_play_start + 1 v2_playbook_on_start + 1 v2_playbook_on_stats +19 v2_playbook_on_task_start + 1 v2_playbook_on_vars_prompt + 1 v2_runner_item_on_failed + 2 v2_runner_item_on_ok + 1 v2_runner_item_on_skipped + 1 v2_runner_on_async_failed + 1 v2_runner_on_async_ok + 2 v2_runner_on_async_poll + 5 v2_runner_on_failed +16 v2_runner_on_ok + 1 v2_runner_on_skipped +23 v2_runner_on_start + 1 v2_runner_on_unreachable + 2 v2_runner_retry diff --git a/test/integration/targets/collections/testcoll2/MANIFEST.json b/test/integration/targets/ansible-playbook-callbacks/include_me.yml index e69de29..e69de29 100644 --- a/test/integration/targets/collections/testcoll2/MANIFEST.json +++ b/test/integration/targets/ansible-playbook-callbacks/include_me.yml diff --git a/test/integration/targets/ansible-playbook-callbacks/runme.sh b/test/integration/targets/ansible-playbook-callbacks/runme.sh new file mode 100755 index 0000000..933863e --- /dev/null +++ b/test/integration/targets/ansible-playbook-callbacks/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eux + +export ANSIBLE_CALLBACK_PLUGINS=../support-callback_plugins/callback_plugins +export ANSIBLE_ROLES_PATH=../ +export ANSIBLE_STDOUT_CALLBACK=callback_debug +export ANSIBLE_HOST_PATTERN_MISMATCH=warning + +ansible-playbook all-callbacks.yml 2>/dev/null | sort | uniq -c | tee callbacks_list.out + +diff -w callbacks_list.out callbacks_list.expected diff --git a/test/integration/targets/ansible-pull/pull-integration-test/conn_secret.yml b/test/integration/targets/ansible-pull/pull-integration-test/conn_secret.yml new file mode 100644 index 0000000..f884973 --- /dev/null +++ b/test/integration/targets/ansible-pull/pull-integration-test/conn_secret.yml @@ -0,0 +1,12 @@ +- hosts: localhost + gather_facts: false + tasks: + - ping: data='{{ansible_password}}' + register: dumb + vars: + ansible_python_interpreter: '{{ansible_playbook_python}}' + + - name: If we got here, password was passed! + assert: + that: + - "dumb.ping == 'Testing123'" diff --git a/test/integration/targets/ansible-pull/pull-integration-test/secret_connection_password b/test/integration/targets/ansible-pull/pull-integration-test/secret_connection_password new file mode 100644 index 0000000..44e6a2c --- /dev/null +++ b/test/integration/targets/ansible-pull/pull-integration-test/secret_connection_password @@ -0,0 +1 @@ +Testing123 diff --git a/test/integration/targets/ansible-pull/runme.sh b/test/integration/targets/ansible-pull/runme.sh index 582e809..b591b28 100755 --- a/test/integration/targets/ansible-pull/runme.sh +++ b/test/integration/targets/ansible-pull/runme.sh @@ -36,7 +36,8 @@ function pass_tests { fi # test for https://github.com/ansible/ansible/issues/13681 - if grep -E '127\.0\.0\.1.*ok' "${temp_log}"; then + # match play default output stats, was matching limit + docker + if grep -E '127\.0\.0\.1\s*: ok=' "${temp_log}"; then cat "${temp_log}" echo "Found host 127.0.0.1 in output. Only localhost should be present." exit 1 @@ -86,5 +87,7 @@ ANSIBLE_CONFIG='' ansible-pull -d "${pull_dir}" -U "${repo_dir}" "$@" multi_play pass_tests_multi +ANSIBLE_CONFIG='' ansible-pull -d "${pull_dir}" -U "${repo_dir}" conn_secret.yml --connection-password-file "${repo_dir}/secret_connection_password" "$@" + # fail if we try do delete /var/tmp ANSIBLE_CONFIG='' ansible-pull -d var/tmp -U "${repo_dir}" --purge "$@" diff --git a/test/integration/targets/ansible-runner/aliases b/test/integration/targets/ansible-runner/aliases index 13e7d78..f4caffd 100644 --- a/test/integration/targets/ansible-runner/aliases +++ b/test/integration/targets/ansible-runner/aliases @@ -1,5 +1,4 @@ shippable/posix/group5 context/controller -skip/osx skip/macos skip/freebsd diff --git a/test/integration/targets/ansible-runner/files/adhoc_example1.py b/test/integration/targets/ansible-runner/files/adhoc_example1.py index ab24bca..fe7f944 100644 --- a/test/integration/targets/ansible-runner/files/adhoc_example1.py +++ b/test/integration/targets/ansible-runner/files/adhoc_example1.py @@ -2,7 +2,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import json -import os import sys import ansible_runner diff --git a/test/integration/targets/ansible-test-cloud-foreman/aliases b/test/integration/targets/ansible-test-cloud-foreman/aliases deleted file mode 100644 index a4bdcea..0000000 --- a/test/integration/targets/ansible-test-cloud-foreman/aliases +++ /dev/null @@ -1,3 +0,0 @@ -cloud/foreman -shippable/generic/group1 -context/controller diff --git a/test/integration/targets/ansible-test-cloud-foreman/tasks/main.yml b/test/integration/targets/ansible-test-cloud-foreman/tasks/main.yml deleted file mode 100644 index 4170d83..0000000 --- a/test/integration/targets/ansible-test-cloud-foreman/tasks/main.yml +++ /dev/null @@ -1,6 +0,0 @@ -- name: Verify endpoints respond - uri: - url: "{{ item }}" - validate_certs: no - with_items: - - http://{{ ansible_env.FOREMAN_HOST }}:{{ ansible_env.FOREMAN_PORT }}/ping diff --git a/test/integration/targets/ansible-test-cloud-openshift/aliases b/test/integration/targets/ansible-test-cloud-openshift/aliases index 6e32db7..b714e82 100644 --- a/test/integration/targets/ansible-test-cloud-openshift/aliases +++ b/test/integration/targets/ansible-test-cloud-openshift/aliases @@ -1,4 +1,4 @@ cloud/openshift shippable/generic/group1 -disabled # disabled due to requirements conflict: botocore 1.20.6 has requirement urllib3<1.27,>=1.25.4, but you have urllib3 1.24.3. +disabled # the container crashes when using a non-default network on some docker hosts (such as Ubuntu 20.04) context/controller diff --git a/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml b/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml index c3b5190..6acb67d 100644 --- a/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml +++ b/test/integration/targets/ansible-test-cloud-openshift/tasks/main.yml @@ -1,6 +1,13 @@ +- name: Load kubeconfig + include_vars: "{{ lookup('env', 'K8S_AUTH_KUBECONFIG') }}" + +- name: Verify endpoints exist + assert: + that: clusters + - name: Verify endpoints respond uri: - url: "{{ item }}" + url: "{{ item.cluster.server }}" validate_certs: no with_items: - - https://openshift-origin:8443/ + - "{{ clusters }}" diff --git a/test/integration/targets/ansible-test-cloud-vcenter/aliases b/test/integration/targets/ansible-test-cloud-vcenter/aliases deleted file mode 100644 index 0cd8ad2..0000000 --- a/test/integration/targets/ansible-test-cloud-vcenter/aliases +++ /dev/null @@ -1,3 +0,0 @@ -cloud/vcenter -shippable/generic/group1 -context/controller diff --git a/test/integration/targets/ansible-test-cloud-vcenter/tasks/main.yml b/test/integration/targets/ansible-test-cloud-vcenter/tasks/main.yml deleted file mode 100644 index 49e5c16..0000000 --- a/test/integration/targets/ansible-test-cloud-vcenter/tasks/main.yml +++ /dev/null @@ -1,6 +0,0 @@ -- name: Verify endpoints respond - uri: - url: "{{ item }}" - validate_certs: no - with_items: - - http://{{ vcenter_hostname }}:5000/ # control endpoint for the simulator diff --git a/test/integration/targets/ansible-test-container/runme.py b/test/integration/targets/ansible-test-container/runme.py index 8ff48e0..3c86b6d 100755 --- a/test/integration/targets/ansible-test-container/runme.py +++ b/test/integration/targets/ansible-test-container/runme.py @@ -996,7 +996,7 @@ class AptBootstrapper(Bootstrapper): @classmethod def install_podman(cls) -> bool: """Return True if podman will be installed.""" - return not (os_release.id == 'ubuntu' and os_release.version_id == '20.04') + return not (os_release.id == 'ubuntu' and os_release.version_id in {'20.04', '22.04'}) @classmethod def install_docker(cls) -> bool: @@ -1053,13 +1053,14 @@ class ApkBootstrapper(Bootstrapper): # crun added as podman won't install it as dep if runc is present # but we don't want runc as it fails # The edge `crun` package installed below requires ip6tables, and in - # edge, the `iptables` package includes `ip6tables`, but in 3.16 they - # are separate. + # edge, the `iptables` package includes `ip6tables`, but in 3.18 they + # are separate. Remove `ip6tables` once we update to 3.19. packages = ['docker', 'podman', 'openssl', 'crun', 'ip6tables'] run_command('apk', 'add', *packages) - # 3.16 only contains crun 1.4.5, to get 1.9.2 to resolve the run/shm issue, install crun from edge - run_command('apk', 'upgrade', '-U', '--repository=http://dl-cdn.alpinelinux.org/alpine/edge/community', 'crun') + # 3.18 only contains crun 1.8.4, to get 1.9.2 to resolve the run/shm issue, install crun from 3.19 + # Remove once we update to 3.19 + run_command('apk', 'upgrade', '-U', '--repository=http://dl-cdn.alpinelinux.org/alpine/v3.19/community', 'crun') run_command('service', 'docker', 'start') run_command('modprobe', 'tun') diff --git a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py index f59b909..f662b97 100644 --- a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py +++ b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor1.py @@ -16,10 +16,10 @@ RETURN = '''#''' from ansible.plugins.lookup import LookupBase # noinspection PyUnresolvedReferences -from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded +from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded # pylint: disable=unused-import try: - import demo + import demo # pylint: disable=unused-import except ImportError: pass else: diff --git a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py index 22b4236..38860b0 100644 --- a/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py +++ b/test/integration/targets/ansible-test-sanity-import/ansible_collections/ns/col/plugins/lookup/vendor2.py @@ -16,10 +16,10 @@ RETURN = '''#''' from ansible.plugins.lookup import LookupBase # noinspection PyUnresolvedReferences -from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded +from ansible.plugins import loader # import the loader to verify it works when the collection loader has already been loaded # pylint: disable=unused-import try: - import demo + import demo # pylint: disable=unused-import except ImportError: pass else: diff --git a/test/integration/targets/ansible-test-sanity-import/expected.txt b/test/integration/targets/ansible-test-sanity-import/expected.txt new file mode 100644 index 0000000..ab41fd7 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-import/expected.txt @@ -0,0 +1,2 @@ +plugins/lookup/stderr.py:0:0: stderr: unwanted stderr +plugins/lookup/stdout.py:0:0: stdout: unwanted stdout diff --git a/test/integration/targets/ansible-test-sanity-import/runme.sh b/test/integration/targets/ansible-test-sanity-import/runme.sh index a12e3e3..a49a71a 100755 --- a/test/integration/targets/ansible-test-sanity-import/runme.sh +++ b/test/integration/targets/ansible-test-sanity-import/runme.sh @@ -1,7 +1,29 @@ #!/usr/bin/env bash +set -eu + +# Create test scenarios at runtime that do not pass sanity tests. +# This avoids the need to create ignore entries for the tests. + +mkdir -p ansible_collections/ns/col/plugins/lookup + +( + cd ansible_collections/ns/col/plugins/lookup + + echo "import sys; sys.stdout.write('unwanted stdout')" > stdout.py # stdout: unwanted stdout + echo "import sys; sys.stderr.write('unwanted stderr')" > stderr.py # stderr: unwanted stderr +) + source ../collection/setup.sh +# Run regular import sanity tests. + +ansible-test sanity --test import --color --failure-ok --lint --python "${ANSIBLE_TEST_PYTHON_VERSION}" "${@}" 1> actual-stdout.txt 2> actual-stderr.txt +diff -u "${TEST_DIR}/expected.txt" actual-stdout.txt +grep -f "${TEST_DIR}/expected.txt" actual-stderr.txt + +# Run import sanity tests which require modifications to the source directory. + vendor_dir="$(python -c 'import pathlib, ansible._vendor; print(pathlib.Path(ansible._vendor.__file__).parent)')" cleanup() { diff --git a/test/integration/targets/ansible-test-sanity-no-get-exception/aliases b/test/integration/targets/ansible-test-sanity-no-get-exception/aliases new file mode 100644 index 0000000..7741d44 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-no-get-exception/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/do-not-check-me.py b/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/do-not-check-me.py new file mode 100644 index 0000000..ca25269 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/do-not-check-me.py @@ -0,0 +1,5 @@ +from ansible.module_utils.pycompat24 import get_exception + + +def do_stuff(): + get_exception() diff --git a/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/plugins/modules/check-me.py b/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/plugins/modules/check-me.py new file mode 100644 index 0000000..ca25269 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-no-get-exception/ansible_collections/ns/col/plugins/modules/check-me.py @@ -0,0 +1,5 @@ +from ansible.module_utils.pycompat24 import get_exception + + +def do_stuff(): + get_exception() diff --git a/test/integration/targets/ansible-test-sanity-no-get-exception/expected.txt b/test/integration/targets/ansible-test-sanity-no-get-exception/expected.txt new file mode 100644 index 0000000..4c432cb --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-no-get-exception/expected.txt @@ -0,0 +1,2 @@ +plugins/modules/check-me.py:1:44: do not use `get_exception` +plugins/modules/check-me.py:5:4: do not use `get_exception` diff --git a/test/integration/targets/ansible-test-sanity-no-get-exception/runme.sh b/test/integration/targets/ansible-test-sanity-no-get-exception/runme.sh new file mode 100755 index 0000000..b8ec2d0 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-no-get-exception/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eu + +source ../collection/setup.sh + +set -x + +ansible-test sanity --test no-get-exception --color --lint --failure-ok "${@}" > actual.txt + +diff -u "${TEST_DIR}/expected.txt" actual.txt +diff -u do-not-check-me.py plugins/modules/check-me.py diff --git a/test/integration/targets/ansible-test-sanity-pylint/aliases b/test/integration/targets/ansible-test-sanity-pylint/aliases new file mode 100644 index 0000000..7741d44 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-pylint/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/galaxy.yml b/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/galaxy.yml new file mode 100644 index 0000000..53a7727 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/galaxy.yml @@ -0,0 +1,6 @@ +namespace: ns +name: col +version: +readme: README.rst +authors: + - Ansible diff --git a/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/plugins/lookup/deprecated.py b/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/plugins/lookup/deprecated.py new file mode 100644 index 0000000..b7908b6 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-pylint/ansible_collections/ns/col/plugins/lookup/deprecated.py @@ -0,0 +1,22 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +name: deprecated +short_description: lookup +description: Lookup. +author: + - Ansible Core Team +''' + +EXAMPLES = '''#''' +RETURN = '''#''' + +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + def run(self, **kwargs): + return [] diff --git a/test/integration/targets/ansible-test-sanity-pylint/expected.txt b/test/integration/targets/ansible-test-sanity-pylint/expected.txt new file mode 100644 index 0000000..df7bbc2 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-pylint/expected.txt @@ -0,0 +1 @@ +plugins/lookup/deprecated.py:27:0: collection-deprecated-version: Deprecated version ('2.0.0') found in call to Display.deprecated or AnsibleModule.deprecate diff --git a/test/integration/targets/ansible-test-sanity-pylint/runme.sh b/test/integration/targets/ansible-test-sanity-pylint/runme.sh new file mode 100755 index 0000000..72190bf --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-pylint/runme.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -eu + +source ../collection/setup.sh + +# Create test scenarios at runtime that do not pass sanity tests. +# This avoids the need to create ignore entries for the tests. + +echo " +from ansible.utils.display import Display + +display = Display() +display.deprecated('', version='2.0.0', collection_name='ns.col')" >> plugins/lookup/deprecated.py + +# Verify deprecation checking works for normal releases and pre-releases. + +for version in 2.0.0 2.0.0-dev0; do + echo "Checking version: ${version}" + sed "s/^version:.*\$/version: ${version}/" < galaxy.yml > galaxy.yml.tmp + mv galaxy.yml.tmp galaxy.yml + ansible-test sanity --test pylint --color --failure-ok --lint "${@}" 1> actual-stdout.txt 2> actual-stderr.txt + diff -u "${TEST_DIR}/expected.txt" actual-stdout.txt + grep -f "${TEST_DIR}/expected.txt" actual-stderr.txt +done diff --git a/test/integration/targets/ansible-test-sanity-replace-urlopen/aliases b/test/integration/targets/ansible-test-sanity-replace-urlopen/aliases new file mode 100644 index 0000000..7741d44 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-replace-urlopen/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/do-not-check-me.py b/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/do-not-check-me.py new file mode 100644 index 0000000..9b9c7e6 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/do-not-check-me.py @@ -0,0 +1,5 @@ +import urllib.request + + +def do_stuff(): + urllib.request.urlopen('https://www.ansible.com/') diff --git a/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/plugins/modules/check-me.py b/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/plugins/modules/check-me.py new file mode 100644 index 0000000..9b9c7e6 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/plugins/modules/check-me.py @@ -0,0 +1,5 @@ +import urllib.request + + +def do_stuff(): + urllib.request.urlopen('https://www.ansible.com/') diff --git a/test/integration/targets/ansible-test-sanity-replace-urlopen/expected.txt b/test/integration/targets/ansible-test-sanity-replace-urlopen/expected.txt new file mode 100644 index 0000000..4dd1bfb --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-replace-urlopen/expected.txt @@ -0,0 +1 @@ +plugins/modules/check-me.py:5:20: use `ansible.module_utils.urls.open_url` instead of `urlopen` diff --git a/test/integration/targets/ansible-test-sanity-replace-urlopen/runme.sh b/test/integration/targets/ansible-test-sanity-replace-urlopen/runme.sh new file mode 100755 index 0000000..e6637c5 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-replace-urlopen/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eu + +source ../collection/setup.sh + +set -x + +ansible-test sanity --test replace-urlopen --color --lint --failure-ok "${@}" > actual.txt + +diff -u "${TEST_DIR}/expected.txt" actual.txt +diff -u do-not-check-me.py plugins/modules/check-me.py diff --git a/test/integration/targets/ansible-test-sanity-use-compat-six/aliases b/test/integration/targets/ansible-test-sanity-use-compat-six/aliases new file mode 100644 index 0000000..7741d44 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-use-compat-six/aliases @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff --git a/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/do-not-check-me.py b/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/do-not-check-me.py new file mode 100644 index 0000000..7f7f9f5 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/do-not-check-me.py @@ -0,0 +1,5 @@ +import six + + +def do_stuff(): + assert six.text_type diff --git a/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/plugins/modules/check-me.py b/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/plugins/modules/check-me.py new file mode 100644 index 0000000..7f7f9f5 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-use-compat-six/ansible_collections/ns/col/plugins/modules/check-me.py @@ -0,0 +1,5 @@ +import six + + +def do_stuff(): + assert six.text_type diff --git a/test/integration/targets/ansible-test-sanity-use-compat-six/expected.txt b/test/integration/targets/ansible-test-sanity-use-compat-six/expected.txt new file mode 100644 index 0000000..42ba83b --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-use-compat-six/expected.txt @@ -0,0 +1 @@ +plugins/modules/check-me.py:1:1: use `ansible.module_utils.six` instead of `six` diff --git a/test/integration/targets/ansible-test-sanity-use-compat-six/runme.sh b/test/integration/targets/ansible-test-sanity-use-compat-six/runme.sh new file mode 100755 index 0000000..dbd38f9 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-use-compat-six/runme.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -eu + +source ../collection/setup.sh + +set -x + +ansible-test sanity --test use-compat-six --color --lint --failure-ok "${@}" > actual.txt + +diff -u "${TEST_DIR}/expected.txt" actual.txt +diff -u do-not-check-me.py plugins/modules/check-me.py diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/meta/runtime.yml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/meta/runtime.yml new file mode 100644 index 0000000..7c4b25d --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/meta/runtime.yml @@ -0,0 +1,4 @@ +plugin_routing: + modules: + module: + action_plugin: ns.col.action diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/lookup/import_order_lookup.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/lookup/import_order_lookup.py new file mode 100644 index 0000000..5a1f0ec --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/lookup/import_order_lookup.py @@ -0,0 +1,16 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +from ansible.plugins.lookup import LookupBase + +DOCUMENTATION = """ +name: import_order_lookup +short_description: Import order lookup +description: Import order lookup. +""" + + +class LookupModule(LookupBase): + def run(self, terms, variables=None, **kwargs): + return [] diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_1.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_1.py new file mode 100644 index 0000000..1b23b49 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_1.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_1 +short_description: Test for check mode attribute 1 +description: Test for check mode attribute 1. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # doc says full support, code says none + support: full + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=False) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_2.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_2.py new file mode 100644 index 0000000..0687e9f --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_2.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_2 +short_description: Test for check mode attribute 2 +description: Test for check mode attribute 2. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # doc says partial support, code says none + support: partial + details: Whatever this means. + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=False) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_3.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_3.py new file mode 100644 index 0000000..61226e6 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_3.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_3 +short_description: Test for check mode attribute 3 +description: Test for check mode attribute 3. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # doc says no support, code says some + support: none + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=True) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_4.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_4.py new file mode 100644 index 0000000..1cb7813 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_4.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_4 +short_description: Test for check mode attribute 4 +description: Test for check mode attribute 4. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # documentation says some support, but no details + support: partial + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=True) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_5.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_5.py new file mode 100644 index 0000000..a8d8556 --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_5.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_5 +short_description: Test for check mode attribute 5 +description: Test for check mode attribute 5. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # Everything is correct: both docs and code claim no support + support: none + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=False) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_6.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_6.py new file mode 100644 index 0000000..cd5a4fb --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_6.py @@ -0,0 +1,34 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_6 +short_description: Test for check mode attribute 6 +description: Test for check mode attribute 6. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # Everything is correct: docs says partial support *with details*, code claims (at least some) support + support: partial + details: Some details. + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=True) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_7.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_7.py new file mode 100644 index 0000000..73d976c --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/check_mode_attribute_7.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: check_mode_attribute_7 +short_description: Test for check mode attribute 7 +description: Test for check mode attribute 7. +author: + - Ansible Core Team +extends_documentation_fragment: + - ansible.builtin.action_common_attributes +attributes: + check_mode: + # Everything is correct: docs says full support, code claims (at least some) support + support: full + diff_mode: + support: none + platform: + platforms: all +''' + +EXAMPLES = '''#''' +RETURN = '''''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict(), supports_check_mode=True) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/import_order.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/import_order.py new file mode 100644 index 0000000..f4f3c9b --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/import_order.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible.module_utils.basic import AnsibleModule + +DOCUMENTATION = ''' +module: import_order +short_description: Import order test module +description: Import order test module. +author: + - Ansible Core Team +''' + +EXAMPLES = '''#''' +RETURN = '''''' + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict()) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/semantic_markup.py b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/semantic_markup.py new file mode 100644 index 0000000..587731d --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/semantic_markup.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r''' +module: semantic_markup +short_description: Test semantic markup +description: + - Test semantic markup. + - RV(does.not.exist=true). + +author: + - Ansible Core Team + +options: + foo: + description: + - Test. + type: str + + a1: + description: + - O(foo) + - O(foo=bar) + - O(foo[1]=bar) + - O(ignore:bar=baz) + - O(ansible.builtin.copy#module:path=/) + - V(foo) + - V(bar(1\\2\)3) + - V(C(foo\)). + - E(env(var\)) + - RV(ansible.builtin.copy#module:backup) + - RV(bar=baz) + - RV(ignore:bam) + - RV(ignore:bam.bar=baz) + - RV(bar). + - P(ansible.builtin.file#lookup) + type: str + + a2: + description: V(C\(foo\)). + type: str + + a3: + description: RV(bam). + type: str + + a4: + description: P(foo.bar#baz). + type: str + + a5: + description: P(foo.bar.baz). + type: str + + a6: + description: P(foo.bar.baz#woof). + type: str + + a7: + description: E(foo\(bar). + type: str + + a8: + description: O(bar). + type: str + + a9: + description: O(bar=bam). + type: str + + a10: + description: O(foo.bar=1). + type: str + + a11: + description: Something with suboptions. + type: dict + suboptions: + b1: + description: + - V(C\(foo\)). + - RV(bam). + - P(foo.bar#baz). + - P(foo.bar.baz). + - P(foo.bar.baz#woof). + - E(foo\(bar). + - O(bar). + - O(bar=bam). + - O(foo.bar=1). + type: str +''' + +EXAMPLES = '''#''' + +RETURN = r''' +bar: + description: Bar. + type: int + returned: success + sample: 5 +''' + +from ansible.module_utils.basic import AnsibleModule + + +if __name__ == '__main__': + module = AnsibleModule(argument_spec=dict( + foo=dict(), + a1=dict(), + a2=dict(), + a3=dict(), + a4=dict(), + a5=dict(), + a6=dict(), + a7=dict(), + a8=dict(), + a9=dict(), + a10=dict(), + a11=dict(type='dict', options=dict( + b1=dict(), + )) + )) + module.exit_json() diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml index c257542..4ca20ef 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/col/plugins/modules/sidecar.yaml @@ -17,6 +17,9 @@ DOCUMENTATION: default: foo author: - Ansible Core Team + seealso: + - plugin: ns.col.import_order_lookup + plugin_type: lookup EXAMPLES: | - name: example for sidecar diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md index bf1003f..d158a98 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/README.md @@ -1,3 +1,4 @@ README ------ + This is a simple collection used to test failures with ``ansible-test sanity --test validate-modules``. diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/meta/runtime.yml b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/meta/runtime.yml new file mode 100644 index 0000000..7c163fe --- /dev/null +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/failure/meta/runtime.yml @@ -0,0 +1,4 @@ +plugin_routing: + lookup: + lookup: + action_plugin: invalid diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md index bbdd513..9c1c1c3 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md +++ b/test/integration/targets/ansible-test-sanity-validate-modules/ansible_collections/ns/ps_only/README.md @@ -1,3 +1,4 @@ README ------ + This is a simple PowerShell-only collection used to verify that ``ansible-test`` works on a collection. diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt index 95f12f3..ca6e52a 100644 --- a/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt +++ b/test/integration/targets/ansible-test-sanity-validate-modules/expected.txt @@ -1,5 +1,26 @@ +plugins/lookup/import_order_lookup.py:5:0: import-before-documentation: Import found before documentation variables. All imports must appear below DOCUMENTATION/EXAMPLES/RETURN. +plugins/modules/check_mode_attribute_1.py:0:0: attributes-check-mode: The module does not declare support for check mode, but the check_mode attribute's support value is 'full' and not 'none' +plugins/modules/check_mode_attribute_2.py:0:0: attributes-check-mode: The module does not declare support for check mode, but the check_mode attribute's support value is 'partial' and not 'none' +plugins/modules/check_mode_attribute_3.py:0:0: attributes-check-mode: The module does declare support for check mode, but the check_mode attribute's support value is 'none' +plugins/modules/check_mode_attribute_4.py:0:0: attributes-check-mode-details: The module declares it does not fully support check mode, but has no details on what exactly that means +plugins/modules/import_order.py:8:0: import-before-documentation: Import found before documentation variables. All imports must appear below DOCUMENTATION/EXAMPLES/RETURN. plugins/modules/invalid_yaml_syntax.py:0:0: deprecation-mismatch: "meta/runtime.yml" and DOCUMENTATION.deprecation do not agree. plugins/modules/invalid_yaml_syntax.py:0:0: missing-documentation: No DOCUMENTATION provided plugins/modules/invalid_yaml_syntax.py:8:15: documentation-syntax-error: DOCUMENTATION is not valid YAML plugins/modules/invalid_yaml_syntax.py:12:15: invalid-examples: EXAMPLES is not valid YAML plugins/modules/invalid_yaml_syntax.py:16:15: return-syntax-error: RETURN is not valid YAML +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.0: While parsing "V(C\(" at index 1: Unnecessarily escaped "(" @ data['options']['a11']['suboptions']['b1']['description'][0]. Got 'V(C\\(foo\\)).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.2: While parsing "P(foo.bar#baz)" at index 1: Plugin name "foo.bar" is not a FQCN @ data['options']['a11']['suboptions']['b1']['description'][2]. Got 'P(foo.bar#baz).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.3: While parsing "P(foo.bar.baz)" at index 1: Parameter "foo.bar.baz" is not of the form FQCN#type @ data['options']['a11']['suboptions']['b1']['description'][3]. Got 'P(foo.bar.baz).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.4: Directive "P(foo.bar.baz#woof)" must contain a valid plugin type; found "woof" @ data['options']['a11']['suboptions']['b1']['description'][4]. Got 'P(foo.bar.baz#woof).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a11.suboptions.b1.description.5: While parsing "E(foo\(" at index 1: Unnecessarily escaped "(" @ data['options']['a11']['suboptions']['b1']['description'][5]. Got 'E(foo\\(bar).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a2.description: While parsing "V(C\(" at index 1: Unnecessarily escaped "(" for dictionary value @ data['options']['a2']['description']. Got 'V(C\\(foo\\)).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a4.description: While parsing "P(foo.bar#baz)" at index 1: Plugin name "foo.bar" is not a FQCN for dictionary value @ data['options']['a4']['description']. Got 'P(foo.bar#baz).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a5.description: While parsing "P(foo.bar.baz)" at index 1: Parameter "foo.bar.baz" is not of the form FQCN#type for dictionary value @ data['options']['a5']['description']. Got 'P(foo.bar.baz).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a6.description: Directive "P(foo.bar.baz#woof)" must contain a valid plugin type; found "woof" for dictionary value @ data['options']['a6']['description']. Got 'P(foo.bar.baz#woof).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: DOCUMENTATION.options.a7.description: While parsing "E(foo\(" at index 1: Unnecessarily escaped "(" for dictionary value @ data['options']['a7']['description']. Got 'E(foo\\(bar).' +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(bar)" contains a non-existing option "bar" +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(bar=bam)" contains a non-existing option "bar" +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "O(foo.bar=1)" contains a non-existing option "foo.bar" +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "RV(bam)" contains a non-existing return value "bam" +plugins/modules/semantic_markup.py:0:0: invalid-documentation-markup: Directive "RV(does.not.exist=true)" contains a non-existing return value "does.not.exist" diff --git a/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh b/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh index e029996..5e2365a 100755 --- a/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh +++ b/test/integration/targets/ansible-test-sanity-validate-modules/runme.sh @@ -6,7 +6,17 @@ set -eux ansible-test sanity --test validate-modules --color --truncate 0 --failure-ok --lint "${@}" 1> actual-stdout.txt 2> actual-stderr.txt diff -u "${TEST_DIR}/expected.txt" actual-stdout.txt -grep -f "${TEST_DIR}/expected.txt" actual-stderr.txt +grep -F -f "${TEST_DIR}/expected.txt" actual-stderr.txt + +cd ../col +ansible-test sanity --test runtime-metadata + +cd ../failure +if ansible-test sanity --test runtime-metadata 2>&1 | tee out.txt; then + echo "runtime-metadata in failure should be invalid" + exit 1 +fi +grep out.txt -e 'extra keys not allowed' cd ../ps_only diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md index d8138d3..67b8a83 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/README.md @@ -1,3 +1,4 @@ README ------ + This is a simple collection used to verify that ``ansible-test`` works on a collection. diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml index fee22ad..76ead13 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/meta/runtime.yml @@ -2,4 +2,11 @@ requires_ansible: '>=2.11' # force ansible-doc to check the Ansible version (re plugin_routing: modules: hi: - redirect: hello + redirect: ns.col2.hello + hiya: + redirect: ns.col2.package.subdir.hiya + module_utils: + hi: + redirect: ansible_collections.ns.col2.plugins.module_utils + hello: + redirect: ns.col2.hiya diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py index 580f9d8..16e0bc8 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/bad.py @@ -19,9 +19,9 @@ EXAMPLES = ''' RETURN = ''' # ''' from ansible.plugins.lookup import LookupBase -from ansible import constants +from ansible import constants # pylint: disable=unused-import -import lxml +import lxml # pylint: disable=unused-import class LookupModule(LookupBase): diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py index dbb479a..5cdd096 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/lookup/world.py @@ -19,7 +19,7 @@ EXAMPLES = ''' RETURN = ''' # ''' from ansible.plugins.lookup import LookupBase -from ansible import constants +from ansible import constants # pylint: disable=unused-import class LookupModule(LookupBase): diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py index e79613b..8780e35 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/modules/bad.py @@ -19,7 +19,7 @@ EXAMPLES = ''' RETURN = '''''' from ansible.module_utils.basic import AnsibleModule -from ansible import constants # intentionally trigger pylint ansible-bad-module-import error +from ansible import constants # intentionally trigger pylint ansible-bad-module-import error # pylint: disable=unused-import def main(): diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/filter/check_pylint.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py index f1be4f3..1fe4dfa 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/filter/check_pylint.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/plugin_utils/check_pylint.py @@ -9,15 +9,10 @@ __metaclass__ = type # syntax-error: Cannot import 'string' due to syntax error 'invalid syntax (<unknown>, line 109)' # Python 3.9 fails with astroid 2.2.5 but works on 2.3.3 # syntax-error: Cannot import 'string' due to syntax error 'invalid syntax (<unknown>, line 104)' -import string +import string # pylint: disable=unused-import # Python 3.9 fails with pylint 2.3.1 or 2.4.4 with astroid 2.3.3 but works with pylint 2.5.0 and astroid 2.4.0 # 'Call' object has no attribute 'value' result = {None: None}[{}.get('something')] -# pylint 2.3.1 and 2.4.4 report the following error but 2.5.0 and 2.6.0 do not -# blacklisted-name: Black listed name "foo" -# see: https://github.com/PyCQA/pylint/issues/3701 -# regression: documented as a known issue and removed from ignore.txt so pylint can be upgraded to 2.6.0 -# if future versions of pylint fix this issue then the ignore should be restored foo = {}.keys() diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py index 2e35cf8..e34d1c3 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/plugins/random_directory/bad.py @@ -5,4 +5,4 @@ __metaclass__ = type # This is not an allowed import, but since this file is in a plugins/ subdirectory that is not checked, # the import sanity test will not complain. -import lxml +import lxml # pylint: disable=unused-import diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py index 8221543..a5d896f 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/integration/targets/hello/files/bad.py @@ -4,12 +4,12 @@ __metaclass__ = type import tempfile try: - import urllib2 # intentionally trigger pylint ansible-bad-import error + import urllib2 # intentionally trigger pylint ansible-bad-import error # pylint: disable=unused-import except ImportError: urllib2 = None try: - from urllib2 import Request # intentionally trigger pylint ansible-bad-import-from error + from urllib2 import Request # intentionally trigger pylint ansible-bad-import-from error # pylint: disable=unused-import except ImportError: Request = None diff --git a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt index e1b3f4c..dcbe827 100644 --- a/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt +++ b/test/integration/targets/ansible-test-sanity/ansible_collections/ns/col/tests/sanity/ignore.txt @@ -1,6 +1,7 @@ plugins/modules/bad.py import plugins/modules/bad.py pylint:ansible-bad-module-import plugins/lookup/bad.py import +plugins/plugin_utils/check_pylint.py pylint:disallowed-name tests/integration/targets/hello/files/bad.py pylint:ansible-bad-function tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import tests/integration/targets/hello/files/bad.py pylint:ansible-bad-import-from diff --git a/test/integration/targets/ansible-test-sanity/runme.sh b/test/integration/targets/ansible-test-sanity/runme.sh index 233db74..9258495 100755 --- a/test/integration/targets/ansible-test-sanity/runme.sh +++ b/test/integration/targets/ansible-test-sanity/runme.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +set -eux + +ansible-test sanity --color --allow-disabled -e "${@}" + +set +x + source ../collection/setup.sh set -x diff --git a/test/integration/targets/ansible-test-units-assertions/aliases b/test/integration/targets/ansible-test-units-assertions/aliases new file mode 100644 index 0000000..f25bc67 --- /dev/null +++ b/test/integration/targets/ansible-test-units-assertions/aliases @@ -0,0 +1,4 @@ +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection +needs/target/ansible-test diff --git a/test/integration/targets/ansible-test-units-assertions/ansible_collections/ns/col/tests/unit/plugins/modules/test_assertion.py b/test/integration/targets/ansible-test-units-assertions/ansible_collections/ns/col/tests/unit/plugins/modules/test_assertion.py new file mode 100644 index 0000000..e172200 --- /dev/null +++ b/test/integration/targets/ansible-test-units-assertions/ansible_collections/ns/col/tests/unit/plugins/modules/test_assertion.py @@ -0,0 +1,6 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +def test_assertion(): + assert dict(yes=True) == dict(no=False) diff --git a/test/integration/targets/ansible-test-units-assertions/runme.sh b/test/integration/targets/ansible-test-units-assertions/runme.sh new file mode 100755 index 0000000..86fe5c8 --- /dev/null +++ b/test/integration/targets/ansible-test-units-assertions/runme.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +source ../collection/setup.sh + +set -x + +options=$("${TEST_DIR}"/../ansible-test/venv-pythons.py --only-versions) +IFS=', ' read -r -a pythons <<< "${options}" + +for python in "${pythons[@]}"; do + if ansible-test units --truncate 0 --python "${python}" --requirements "${@}" 2>&1 | tee pytest.log; then + echo "Test did not fail as expected." + exit 1 + fi + + if [ "${python}" = "2.7" ]; then + grep "^E *AssertionError$" pytest.log + else + + grep "^E *AssertionError: assert {'yes': True} == {'no': False}$" pytest.log + fi +done diff --git a/test/integration/targets/ansible-test-units-forked/aliases b/test/integration/targets/ansible-test-units-forked/aliases new file mode 100644 index 0000000..79d7dbd --- /dev/null +++ b/test/integration/targets/ansible-test-units-forked/aliases @@ -0,0 +1,5 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection +needs/target/ansible-test diff --git a/test/integration/targets/ansible-test-units-forked/ansible_collections/ns/col/tests/unit/plugins/modules/test_ansible_forked.py b/test/integration/targets/ansible-test-units-forked/ansible_collections/ns/col/tests/unit/plugins/modules/test_ansible_forked.py new file mode 100644 index 0000000..828099c --- /dev/null +++ b/test/integration/targets/ansible-test-units-forked/ansible_collections/ns/col/tests/unit/plugins/modules/test_ansible_forked.py @@ -0,0 +1,43 @@ +"""Unit tests to verify the functionality of the ansible-forked pytest plugin.""" +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import os +import pytest +import signal +import sys +import warnings + + +warnings.warn("This verifies that warnings generated during test collection are reported.") + + +@pytest.mark.xfail +def test_kill_xfail(): + os.kill(os.getpid(), signal.SIGKILL) # causes pytest to report stdout and stderr + + +def test_kill(): + os.kill(os.getpid(), signal.SIGKILL) # causes pytest to report stdout and stderr + + +@pytest.mark.xfail +def test_exception_xfail(): + sys.stdout.write("This stdout message should be hidden due to xfail.") + sys.stderr.write("This stderr message should be hidden due to xfail.") + raise Exception("This error is expected, but should be hidden due to xfail.") + + +def test_exception(): + sys.stdout.write("This stdout message should be reported since we're throwing an exception.") + sys.stderr.write("This stderr message should be reported since we're throwing an exception.") + raise Exception("This error is expected and should be visible.") + + +def test_warning(): + warnings.warn("This verifies that warnings generated at test time are reported.") + + +def test_passed(): + pass diff --git a/test/integration/targets/ansible-test-units-forked/runme.sh b/test/integration/targets/ansible-test-units-forked/runme.sh new file mode 100755 index 0000000..c39f3c4 --- /dev/null +++ b/test/integration/targets/ansible-test-units-forked/runme.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +source ../collection/setup.sh + +set -x + +options=$("${TEST_DIR}"/../ansible-test/venv-pythons.py --only-versions) +IFS=', ' read -r -a pythons <<< "${options}" + +for python in "${pythons[@]}"; do + echo "*** Checking Python ${python} ***" + + if ansible-test units --truncate 0 --target-python "venv/${python}" "${@}" > output.log 2>&1 ; then + cat output.log + echo "Unit tests on Python ${python} did not fail as expected. See output above." + exit 1 + fi + + cat output.log + echo "Unit tests on Python ${python} failed as expected. See output above. Checking for expected output ..." + + # Verify that the appropriate tests pased, failed or xfailed. + grep 'PASSED tests/unit/plugins/modules/test_ansible_forked.py::test_passed' output.log + grep 'PASSED tests/unit/plugins/modules/test_ansible_forked.py::test_warning' output.log + grep 'XFAIL tests/unit/plugins/modules/test_ansible_forked.py::test_kill_xfail' output.log + grep 'FAILED tests/unit/plugins/modules/test_ansible_forked.py::test_kill' output.log + grep 'FAILED tests/unit/plugins/modules/test_ansible_forked.py::test_exception' output.log + grep 'XFAIL tests/unit/plugins/modules/test_ansible_forked.py::test_exception_xfail' output.log + + # Verify that warnings are properly surfaced. + grep 'UserWarning: This verifies that warnings generated at test time are reported.' output.log + grep 'UserWarning: This verifies that warnings generated during test collection are reported.' output.log + + # Verify there are no unexpected warnings. + grep 'Warning' output.log | grep -v 'UserWarning: This verifies that warnings generated ' && exit 1 + + # Verify that details from failed tests are properly surfaced. + grep "^Test CRASHED with exit code -9.$" output.log + grep "^This stdout message should be reported since we're throwing an exception.$" output.log + grep "^This stderr message should be reported since we're throwing an exception.$" output.log + grep '^> *raise Exception("This error is expected and should be visible.")$' output.log + grep "^E *Exception: This error is expected and should be visible.$" output.log + + echo "*** Done Checking Python ${python} ***" +done diff --git a/test/integration/targets/ansible-test/venv-pythons.py b/test/integration/targets/ansible-test/venv-pythons.py index b380f14..97998bc 100755 --- a/test/integration/targets/ansible-test/venv-pythons.py +++ b/test/integration/targets/ansible-test/venv-pythons.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """Return target Python options for use with ansible-test.""" +import argparse import os import shutil import subprocess @@ -10,6 +11,11 @@ from ansible import release def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--only-versions', action='store_true') + + options = parser.parse_args() + ansible_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(release.__file__)))) source_root = os.path.join(ansible_root, 'test', 'lib') @@ -33,6 +39,10 @@ def main(): print(f'{executable} - {"fail" if process.returncode else "pass"}', file=sys.stderr) if not process.returncode: + if options.only_versions: + args.append(python_version) + continue + args.extend(['--target-python', f'venv/{python_version}']) print(' '.join(args)) diff --git a/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml b/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml index 71dbacc..2365d47 100644 --- a/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml +++ b/test/integration/targets/ansible-vault/invalid_format/broken-group-vars-tasks.yml @@ -20,4 +20,4 @@ # 3366323866663763660a323766383531396433663861656532373663373134376263383263316261 # 3137 -# $ ansible-playbook -i inventory --vault-password-file=vault-secret tasks.yml +# $ ansible-playbook -i inventory --vault-password-file=vault-secret tasks.yml diff --git a/test/integration/targets/ansible-vault/runme.sh b/test/integration/targets/ansible-vault/runme.sh index 50720ea..98399ec 100755 --- a/test/integration/targets/ansible-vault/runme.sh +++ b/test/integration/targets/ansible-vault/runme.sh @@ -47,6 +47,18 @@ echo $? # view the vault encrypted password file ansible-vault view "$@" --vault-id vault-password encrypted-vault-password +# check if ansible-vault fails when destination is not writable +NOT_WRITABLE_DIR="${MYTMPDIR}/not_writable" +TEST_FILE_EDIT4="${NOT_WRITABLE_DIR}/testfile" +mkdir "${NOT_WRITABLE_DIR}" +touch "${TEST_FILE_EDIT4}" +chmod ugo-w "${NOT_WRITABLE_DIR}" +ansible-vault encrypt "$@" --vault-password-file vault-password "${TEST_FILE_EDIT4}" < /dev/null > log 2>&1 && : +grep "not writable" log && : +WRONG_RC=$? +echo "rc was $WRONG_RC (1 is expected)" +[ $WRONG_RC -eq 1 ] + # encrypt with a password from a vault encrypted password file and multiple vault-ids # should fail because we dont know which vault id to use to encrypt with ansible-vault encrypt "$@" --vault-id vault-password --vault-id encrypted-vault-password "${TEST_FILE_ENC_PASSWORD}" && : @@ -574,3 +586,23 @@ ansible-playbook realpath.yml "$@" --vault-password-file symlink/get-password-sy # using symlink ansible-playbook symlink.yml "$@" --vault-password-file script/vault-secret.sh 2>&1 |grep "${ER}" + +### SALT TESTING ### +# prep files for encryption +for salted in test1 test2 test3 +do + echo 'this is salty' > "salted_${salted}" +done + +# encrypt files +ANSIBLE_VAULT_ENCRYPT_SALT=salty ansible-vault encrypt salted_test1 --vault-password-file example1_password "$@" +ANSIBLE_VAULT_ENCRYPT_SALT=salty ansible-vault encrypt salted_test2 --vault-password-file example1_password "$@" +ansible-vault encrypt salted_test3 --vault-password-file example1_password "$@" + +# should be the same +out=$(diff salted_test1 salted_test2) +[ "${out}" == "" ] + +# shoudl be diff +out=$(diff salted_test1 salted_test3 || true) +[ "${out}" != "" ] diff --git a/test/integration/targets/ansible-vault/test_vault.yml b/test/integration/targets/ansible-vault/test_vault.yml index 7f8ed11..c21d49a 100644 --- a/test/integration/targets/ansible-vault/test_vault.yml +++ b/test/integration/targets/ansible-vault/test_vault.yml @@ -1,6 +1,6 @@ - hosts: testhost gather_facts: False vars: - - output_dir: . + output_dir: . roles: - { role: test_vault, tags: test_vault} diff --git a/test/integration/targets/ansible-vault/test_vaulted_template.yml b/test/integration/targets/ansible-vault/test_vaulted_template.yml index b495211..6a16ec8 100644 --- a/test/integration/targets/ansible-vault/test_vaulted_template.yml +++ b/test/integration/targets/ansible-vault/test_vaulted_template.yml @@ -1,6 +1,6 @@ - hosts: testhost gather_facts: False vars: - - output_dir: . + output_dir: . roles: - { role: test_vaulted_template, tags: test_vaulted_template} diff --git a/test/integration/targets/ansible/aliases b/test/integration/targets/ansible/aliases index 8278ec8..c7f2050 100644 --- a/test/integration/targets/ansible/aliases +++ b/test/integration/targets/ansible/aliases @@ -1,2 +1,3 @@ shippable/posix/group3 context/controller +needs/target/support-callback_plugins diff --git a/test/integration/targets/ansible/ansible-testé.cfg b/test/integration/targets/ansible/ansible-testé.cfg index 09af947..a0e4e8d 100644 --- a/test/integration/targets/ansible/ansible-testé.cfg +++ b/test/integration/targets/ansible/ansible-testé.cfg @@ -1,3 +1,3 @@ [defaults] remote_user = admin -collections_paths = /tmp/collections +collections_path = /tmp/collections diff --git a/test/integration/targets/ansible/runme.sh b/test/integration/targets/ansible/runme.sh index e9e72a9..d678021 100755 --- a/test/integration/targets/ansible/runme.sh +++ b/test/integration/targets/ansible/runme.sh @@ -14,9 +14,9 @@ ANSIBLE_REMOTE_USER=administrator ansible-config dump| grep 'DEFAULT_REMOTE_USER ansible-config list | grep 'DEFAULT_REMOTE_USER' # Collection -ansible-config view -c ./ansible-testé.cfg | grep 'collections_paths = /tmp/collections' +ansible-config view -c ./ansible-testé.cfg | grep 'collections_path = /tmp/collections' ansible-config dump -c ./ansible-testé.cfg | grep 'COLLECTIONS_PATHS([^)]*) =' -ANSIBLE_COLLECTIONS_PATHS=/tmp/collections ansible-config dump| grep 'COLLECTIONS_PATHS([^)]*) =' +ANSIBLE_COLLECTIONS_PATH=/tmp/collections ansible-config dump| grep 'COLLECTIONS_PATHS([^)]*) =' ansible-config list | grep 'COLLECTIONS_PATHS' # 'view' command must fail when config file is missing or has an invalid file extension @@ -34,7 +34,7 @@ ansible localhost -m debug -a var=playbook_dir --playbook-dir=/doesnotexist/tmp env -u ANSIBLE_PLAYBOOK_DIR ANSIBLE_CONFIG=./playbookdir_cfg.ini ansible localhost -m debug -a var=playbook_dir | grep '"playbook_dir": "/doesnotexist/tmp"' # test adhoc callback triggers -ANSIBLE_STDOUT_CALLBACK=callback_debug ANSIBLE_LOAD_CALLBACK_PLUGINS=1 ansible --playbook-dir . testhost -i ../../inventory -m ping | grep -E '^v2_' | diff -u adhoc-callback.stdout - +ANSIBLE_CALLBACK_PLUGINS=../support-callback_plugins/callback_plugins ANSIBLE_STDOUT_CALLBACK=callback_debug ANSIBLE_LOAD_CALLBACK_PLUGINS=1 ansible --playbook-dir . testhost -i ../../inventory -m ping | grep -E '^v2_' | diff -u adhoc-callback.stdout - # CB_WANTS_IMPLICIT isn't anything in Ansible itself. # Our test cb plugin just accepts it. It lets us avoid copypasting the whole diff --git a/test/integration/targets/apt/aliases b/test/integration/targets/apt/aliases index 5f892f9..20c8709 100644 --- a/test/integration/targets/apt/aliases +++ b/test/integration/targets/apt/aliases @@ -1,6 +1,5 @@ shippable/posix/group2 destructive skip/freebsd -skip/osx skip/macos skip/rhel diff --git a/test/integration/targets/apt/tasks/apt.yml b/test/integration/targets/apt/tasks/apt.yml index d273eda..a0bc199 100644 --- a/test/integration/targets/apt/tasks/apt.yml +++ b/test/integration/targets/apt/tasks/apt.yml @@ -372,7 +372,7 @@ - libcaca-dev - libslang2-dev -# https://github.com/ansible/ansible/issues/38995 +# # https://github.com/ansible/ansible/issues/38995 - name: build-dep for a package apt: name: tree @@ -524,6 +524,55 @@ - "allow_change_held_packages_no_update is not changed" - "allow_change_held_packages_hello_version.stdout == allow_change_held_packages_hello_version_again.stdout" +# Remove pkg on hold +- name: Put hello on hold + shell: apt-mark hold hello + +- name: Get hold list + shell: apt-mark showhold + register: hello_hold + +- name: Check that the package hello is on the hold list + assert: + that: + - "'hello' in hello_hold.stdout" + +- name: Try removing package hello + apt: + name: hello + state: absent + register: package_removed + ignore_errors: true + +- name: verify the package is not removed with dpkg + shell: dpkg -l hello + register: dpkg_result + +- name: Verify that package was not removed + assert: + that: + - package_removed is failed + - dpkg_result is success + +- name: Try removing package (allow_change_held_packages=yes) + apt: + name: hello + state: absent + allow_change_held_packages: yes + register: package_removed + +- name: verify the package is removed with dpkg + shell: dpkg -l hello + register: dpkg_result + ignore_errors: true + +- name: Verify that package removal was succesfull + assert: + that: + - package_removed is success + - dpkg_result is failed + - package_removed.changed + # Virtual package - name: Install a virtual package apt: diff --git a/test/integration/targets/apt/tasks/repo.yml b/test/integration/targets/apt/tasks/repo.yml index d4cce78..b1d08af 100644 --- a/test/integration/targets/apt/tasks/repo.yml +++ b/test/integration/targets/apt/tasks/repo.yml @@ -400,6 +400,8 @@ - { upgrade_type: safe, force_apt_get: True } - { upgrade_type: full, force_apt_get: True } + - include_tasks: "upgrade_scenarios.yml" + - name: Remove aptitude if not originally present apt: pkg: aptitude diff --git a/test/integration/targets/apt/tasks/upgrade_scenarios.yml b/test/integration/targets/apt/tasks/upgrade_scenarios.yml new file mode 100644 index 0000000..a8bf76b --- /dev/null +++ b/test/integration/targets/apt/tasks/upgrade_scenarios.yml @@ -0,0 +1,25 @@ +# See https://github.com/ansible/ansible/issues/77868 +# fail_on_autoremove is not valid parameter for aptitude +- name: Use fail_on_autoremove using aptitude + apt: + upgrade: yes + fail_on_autoremove: yes + register: fail_on_autoremove_result + +- name: Check if fail_on_autoremove does not fail with aptitude + assert: + that: + - not fail_on_autoremove_result.failed + +# See https://github.com/ansible/ansible/issues/77868 +# allow_downgrade is not valid parameter for aptitude +- name: Use allow_downgrade using aptitude + apt: + upgrade: yes + allow_downgrade: yes + register: allow_downgrade_result + +- name: Check if allow_downgrade does not fail with aptitude + assert: + that: + - not allow_downgrade_result.failed diff --git a/test/integration/targets/apt_key/aliases b/test/integration/targets/apt_key/aliases index a820ec9..97f534a 100644 --- a/test/integration/targets/apt_key/aliases +++ b/test/integration/targets/apt_key/aliases @@ -1,5 +1,4 @@ shippable/posix/group1 skip/freebsd -skip/osx skip/macos skip/rhel diff --git a/test/integration/targets/apt_key/tasks/main.yml b/test/integration/targets/apt_key/tasks/main.yml index ffb89b2..7aee56a 100644 --- a/test/integration/targets/apt_key/tasks/main.yml +++ b/test/integration/targets/apt_key/tasks/main.yml @@ -21,7 +21,7 @@ - import_tasks: 'apt_key_inline_data.yml' when: ansible_distribution in ('Ubuntu', 'Debian') - + - import_tasks: 'file.yml' when: ansible_distribution in ('Ubuntu', 'Debian') diff --git a/test/integration/targets/apt_repository/aliases b/test/integration/targets/apt_repository/aliases index 34e2b54..b4fe8db 100644 --- a/test/integration/targets/apt_repository/aliases +++ b/test/integration/targets/apt_repository/aliases @@ -1,6 +1,5 @@ destructive shippable/posix/group1 skip/freebsd -skip/osx skip/macos skip/rhel diff --git a/test/integration/targets/apt_repository/tasks/apt.yml b/test/integration/targets/apt_repository/tasks/apt.yml index 9c15e64..2ddf414 100644 --- a/test/integration/targets/apt_repository/tasks/apt.yml +++ b/test/integration/targets/apt_repository/tasks/apt.yml @@ -152,6 +152,11 @@ - 'result.changed' - 'result.state == "present"' - 'result.repo == test_ppa_spec' + - '"sources_added" in result' + - 'result.sources_added | length == 1' + - '"git" in result.sources_added[0]' + - '"sources_removed" in result' + - 'result.sources_removed | length == 0' - result_cache is not changed - name: 'examine apt cache mtime' @@ -167,6 +172,17 @@ apt_repository: repo='{{test_ppa_spec}}' state=absent register: result +- assert: + that: + - 'result.changed' + - 'result.state == "absent"' + - 'result.repo == test_ppa_spec' + - '"sources_added" in result' + - 'result.sources_added | length == 0' + - '"sources_removed" in result' + - 'result.sources_removed | length == 1' + - '"git" in result.sources_removed[0]' + # When installing a repo with the spec, the key is *NOT* added - name: 'ensure ppa key is absent (expect: pass)' apt_key: id='{{test_ppa_key}}' state=absent @@ -224,7 +240,7 @@ - assert: that: - result is failed - - result.msg == 'Please set argument \'repo\' to a non-empty value' + - result.msg.startswith("argument 'repo' is of type <class 'NoneType'> and we were unable to convert to str") - name: Test apt_repository with an empty value for repo apt_repository: diff --git a/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml b/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml index 726de11..62960cc 100644 --- a/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml +++ b/test/integration/targets/apt_repository/tasks/mode_cleanup.yaml @@ -4,4 +4,4 @@ - name: Delete existing repo file: path: "{{ test_repo_path }}" - state: absent
\ No newline at end of file + state: absent diff --git a/test/integration/targets/argspec/library/argspec.py b/test/integration/targets/argspec/library/argspec.py index b6d6d11..2d86d77 100644 --- a/test/integration/targets/argspec/library/argspec.py +++ b/test/integration/targets/argspec/library/argspec.py @@ -23,6 +23,10 @@ def main(): 'type': 'str', 'choices': ['absent', 'present'], }, + 'default_value': { + 'type': 'bool', + 'default': True, + }, 'path': {}, 'content': {}, 'mapping': { @@ -246,7 +250,7 @@ def main(): ('state', 'present', ('path', 'content'), True), ), mutually_exclusive=( - ('path', 'content'), + ('path', 'content', 'default_value',), ), required_one_of=( ('required_one_of_one', 'required_one_of_two'), diff --git a/test/integration/targets/become/tasks/main.yml b/test/integration/targets/become/tasks/main.yml index 4a2ce64..c05824d 100644 --- a/test/integration/targets/become/tasks/main.yml +++ b/test/integration/targets/become/tasks/main.yml @@ -11,8 +11,8 @@ ansible_become_user: "{{ become_test_config.user }}" ansible_become_method: "{{ become_test_config.method }}" ansible_become_password: "{{ become_test_config.password | default(None) }}" - loop: "{{ - (become_methods | selectattr('skip', 'undefined') | list)+ + loop: "{{ + (become_methods | selectattr('skip', 'undefined') | list)+ (become_methods | selectattr('skip', 'defined') | rejectattr('skip') | list) }}" loop_control: diff --git a/test/integration/targets/blockinfile/tasks/append_newline.yml b/test/integration/targets/blockinfile/tasks/append_newline.yml new file mode 100644 index 0000000..ae3aef8 --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/append_newline.yml @@ -0,0 +1,119 @@ +- name: Create append_newline test file + copy: + dest: "{{ remote_tmp_dir_test }}/append_newline.txt" + content: | + line1 + line2 + line3 + +- name: add content to file appending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + insertafter: "line1" + block: | + line1.5 + register: insert_appending_a_new_line + +- name: add content to file appending a new line (again) + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + insertafter: "line1" + block: | + line1.5 + register: insert_appending_a_new_line_again + +- name: get file content after adding content appending a new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: appended_a_new_line + +- name: check content is the expected one after inserting content appending a new line + assert: + that: + - insert_appending_a_new_line is changed + - insert_appending_a_new_line_again is not changed + - appended_a_new_line.stat.checksum == "525ffd613a0b0eb6675e506226dc2adedf621f34" + +- name: add content to file without appending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line2" + block: | + line2.5 + register: insert_without_appending_new_line + +- name: get file content after adding content without appending a new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: without_appending_new_line + +- name: check content is the expected one after inserting without appending a new line + assert: + that: + - insert_without_appending_new_line is changed + - without_appending_new_line.stat.checksum == "d5f5ed1428af50b5484a5184dc7e1afda1736646" + +- name: append a new line to existing block + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line2" + block: | + line2.5 + register: append_new_line_to_existing_block + +- name: get file content after appending a line to existing block + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: new_line_appended + +- name: check content is the expected one after appending a new line to an existing block + assert: + that: + - append_new_line_to_existing_block is changed + - new_line_appended.stat.checksum == "b09dd16be73a0077027d5a324294db8a75a7b0f9" + +- name: add a block appending a new line at the end of the file + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} END OF FILE TEXT" + insertafter: "line3" + block: | + line3.5 + register: insert_appending_new_line_at_the_end_of_file + +- name: get file content after appending new line at the end of the file + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: inserted_block_appending_new_line_at_the_end_of_the_file + +- name: check content is the expected one after adding a block appending a new line at the end of the file + assert: + that: + - insert_appending_new_line_at_the_end_of_file is changed + - inserted_block_appending_new_line_at_the_end_of_the_file.stat.checksum == "9b90722b84d9bdda1be781cc4bd44d8979887691" + + +- name: Removing a block with append_newline set to true does not append another line + blockinfile: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + append_newline: true + marker: "#{mark} UNWRAPPED TEXT" + state: absent + register: remove_block_appending_new_line + +- name: get file content after removing existing block appending new line + stat: + path: "{{ remote_tmp_dir_test }}/append_newline.txt" + register: removed_block_appending_new_line + +- name: check content is the expected one after removing a block appending a new line + assert: + that: + - remove_block_appending_new_line is changed + - removed_block_appending_new_line.stat.checksum == "9a40d4c0969255cd6147537b38309d69a9b10049" diff --git a/test/integration/targets/blockinfile/tasks/create_dir.yml b/test/integration/targets/blockinfile/tasks/create_dir.yml new file mode 100644 index 0000000..a16ada5 --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/create_dir.yml @@ -0,0 +1,29 @@ +- name: Set up a directory to test module error handling + file: + path: "{{ remote_tmp_dir_test }}/unreadable" + state: directory + mode: "000" + +- name: Create a directory and file with blockinfile + blockinfile: + path: "{{ remote_tmp_dir_test }}/unreadable/createme/file.txt" + block: | + line 1 + line 2 + state: present + create: yes + register: permissions_error + ignore_errors: yes + +- name: assert the error looks right + assert: + that: + - permissions_error.msg.startswith('Error creating') + when: "ansible_user_id != 'root'" + +- name: otherwise (root) assert the directory and file exists + stat: + path: "{{ remote_tmp_dir_test }}/unreadable/createme/file.txt" + register: path_created + failed_when: path_created.exists is false + when: "ansible_user_id == 'root'" diff --git a/test/integration/targets/blockinfile/tasks/main.yml b/test/integration/targets/blockinfile/tasks/main.yml index 054e554..f26cb16 100644 --- a/test/integration/targets/blockinfile/tasks/main.yml +++ b/test/integration/targets/blockinfile/tasks/main.yml @@ -31,6 +31,7 @@ - import_tasks: add_block_to_existing_file.yml - import_tasks: create_file.yml +- import_tasks: create_dir.yml - import_tasks: preserve_line_endings.yml - import_tasks: block_without_trailing_newline.yml - import_tasks: file_without_trailing_newline.yml @@ -39,3 +40,5 @@ - import_tasks: insertafter.yml - import_tasks: insertbefore.yml - import_tasks: multiline_search.yml +- import_tasks: append_newline.yml +- import_tasks: prepend_newline.yml diff --git a/test/integration/targets/blockinfile/tasks/prepend_newline.yml b/test/integration/targets/blockinfile/tasks/prepend_newline.yml new file mode 100644 index 0000000..535db01 --- /dev/null +++ b/test/integration/targets/blockinfile/tasks/prepend_newline.yml @@ -0,0 +1,119 @@ +- name: Create prepend_newline test file + copy: + dest: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + content: | + line1 + line2 + line3 + +- name: add content to file prepending a new line at the beginning of the file + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + insertbefore: "line1" + block: | + line0.5 + register: insert_prepending_a_new_line_at_the_beginning_of_the_file + +- name: get file content after adding content prepending a new line at the beginning of the file + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: prepended_a_new_line_at_the_beginning_of_the_file + +- name: check content is the expected one after prepending a new line at the beginning of the file + assert: + that: + - insert_prepending_a_new_line_at_the_beginning_of_the_file is changed + - prepended_a_new_line_at_the_beginning_of_the_file.stat.checksum == "bfd32c880bbfadd1983c67836c46bf8ed9d50343" + +- name: add content to file prepending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} WRAPPED TEXT" + insertafter: "line1" + block: | + line1.5 + register: insert_prepending_a_new_line + +- name: add content to file prepending a new line (again) + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} WRAPPED TEXT" + insertafter: "line1" + block: | + line1.5 + register: insert_prepending_a_new_line_again + +- name: get file content after adding content prepending a new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: prepended_a_new_line + +- name: check content is the expected one after inserting content prepending a new line + assert: + that: + - insert_prepending_a_new_line is changed + - insert_prepending_a_new_line_again is not changed + - prepended_a_new_line.stat.checksum == "d5b8b42690f4a38b9a040adc3240a6f81ad5f8ee" + +- name: add content to file without prepending a new line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line3" + block: | + line3.5 + register: insert_without_prepending_new_line + +- name: get file content after adding content without prepending a new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: without_prepending_new_line + +- name: check content is the expected one after inserting without prepending a new line + assert: + that: + - insert_without_prepending_new_line is changed + - without_prepending_new_line.stat.checksum == "ad06200e7ee5b22b7eff4c57075b42d038eaffb6" + +- name: prepend a new line to existing block + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} UNWRAPPED TEXT" + insertafter: "line3" + block: | + line3.5 + register: prepend_new_line_to_existing_block + +- name: get file content after prepending a new line to an existing block + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: new_line_prepended + +- name: check content is the expected one after prepending a new line to an existing block + assert: + that: + - prepend_new_line_to_existing_block is changed + - new_line_prepended.stat.checksum == "f2dd48160fb3c7c8e02d292666a1a3f08503f6bf" + +- name: Removing a block with prepend_newline set to true does not prepend another line + blockinfile: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + prepend_newline: true + marker: "#{mark} UNWRAPPED TEXT" + state: absent + register: remove_block_prepending_new_line + +- name: get file content after removing existing block prepending new line + stat: + path: "{{ remote_tmp_dir_test }}/prepend_newline.txt" + register: removed_block_prepending_new_line + +- name: check content is the expected one after removing a block prepending a new line + assert: + that: + - remove_block_prepending_new_line is changed + - removed_block_prepending_new_line.stat.checksum == "c97c3da7d607acfd5d786fbb81f3d93d867c914a"
\ No newline at end of file diff --git a/test/integration/targets/blocks/unsafe_failed_task.yml b/test/integration/targets/blocks/unsafe_failed_task.yml index adfa492..e74327b 100644 --- a/test/integration/targets/blocks/unsafe_failed_task.yml +++ b/test/integration/targets/blocks/unsafe_failed_task.yml @@ -1,7 +1,7 @@ - hosts: localhost gather_facts: false vars: - - data: {} + data: {} tasks: - block: - name: template error diff --git a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout index 71a4ef9..ed45575 100644 --- a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout +++ b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_lossy_verbose.stdout @@ -43,6 +43,7 @@ fatal: [testhost]: FAILED! => TASK [Skipped task] ************************************************************ skipping: [testhost] => changed: false + false_condition: false skip_reason: Conditional result was False TASK [Task with var in name (foo bar)] ***************************************** @@ -120,6 +121,7 @@ ok: [testhost] => (item=debug-3) => msg: debug-3 skipping: [testhost] => (item=debug-4) => ansible_loop_var: item + false_condition: item != 4 item: 4 fatal: [testhost]: FAILED! => msg: One or more items failed @@ -199,9 +201,11 @@ skipping: [testhost] => TASK [debug] ******************************************************************* skipping: [testhost] => (item=1) => ansible_loop_var: item + false_condition: false item: 1 skipping: [testhost] => (item=2) => ansible_loop_var: item + false_condition: false item: 2 skipping: [testhost] => msg: All items skipped diff --git a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout index 7a99cc7..3a121a5 100644 --- a/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout +++ b/test/integration/targets/callback_default/callback_default.out.result_format_yaml_verbose.stdout @@ -45,6 +45,7 @@ fatal: [testhost]: FAILED! => TASK [Skipped task] ************************************************************ skipping: [testhost] => changed: false + false_condition: false skip_reason: Conditional result was False TASK [Task with var in name (foo bar)] ***************************************** @@ -126,6 +127,7 @@ ok: [testhost] => (item=debug-3) => msg: debug-3 skipping: [testhost] => (item=debug-4) => ansible_loop_var: item + false_condition: item != 4 item: 4 fatal: [testhost]: FAILED! => msg: One or more items failed @@ -206,9 +208,11 @@ skipping: [testhost] => TASK [debug] ******************************************************************* skipping: [testhost] => (item=1) => ansible_loop_var: item + false_condition: false item: 1 skipping: [testhost] => (item=2) => ansible_loop_var: item + false_condition: false item: 2 skipping: [testhost] => msg: All items skipped diff --git a/test/integration/targets/check_mode/check_mode.yml b/test/integration/targets/check_mode/check_mode.yml index a577750..ebf1c5b 100644 --- a/test/integration/targets/check_mode/check_mode.yml +++ b/test/integration/targets/check_mode/check_mode.yml @@ -1,7 +1,7 @@ - name: Test that check works with check_mode specified in roles hosts: testhost vars: - - output_dir: . + output_dir: . roles: - { role: test_always_run, tags: test_always_run } - { role: test_check_mode, tags: test_check_mode } diff --git a/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml b/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml index f926d14..ce9ecbf 100644 --- a/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml +++ b/test/integration/targets/check_mode/roles/test_check_mode/tasks/main.yml @@ -25,8 +25,8 @@ register: foo - name: verify that the file was marked as changed in check mode - assert: - that: + assert: + that: - "template_result is changed" - "not foo.stat.exists" @@ -44,7 +44,7 @@ check_mode: no - name: verify that the file was not changed - assert: - that: + assert: + that: - "checkmode_disabled is changed" - "template_result2 is not changed" diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py index fc19a99..77f8050 100644 --- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/connection/localconn.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native from ansible.plugins.connection import ConnectionBase DOCUMENTATION = """ diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py index b945eb6..6f3a19d 100644 --- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing.py @@ -2,10 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json -import sys - -from ..module_utils import bogusmu # pylint: disable=relative-beyond-top-level +from ..module_utils import bogusmu # pylint: disable=relative-beyond-top-level,unused-import def main(): diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py index 59cb3c5..6f2320d 100644 --- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_collection.py @@ -2,10 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json -import sys - -from ..module_utils import missing_redirect_target_collection # pylint: disable=relative-beyond-top-level +from ..module_utils import missing_redirect_target_collection # pylint: disable=relative-beyond-top-level,unused-import def main(): diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py index 31ffd17..de5c2e5 100644 --- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/modules/uses_mu_missing_redirect_module.py @@ -2,10 +2,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json -import sys - -from ..module_utils import missing_redirect_target_module # pylint: disable=relative-beyond-top-level +from ..module_utils import missing_redirect_target_module # pylint: disable=relative-beyond-top-level,unused-import def main(): diff --git a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py index ae6941f..9269648 100644 --- a/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py +++ b/test/integration/targets/collections/collections/ansible_collections/testns/content_adj/plugins/inventory/statichost.py @@ -19,7 +19,6 @@ DOCUMENTATION = ''' required: True ''' -from ansible.errors import AnsibleParserError from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable diff --git a/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py index 23cce10..f1242e1 100644 --- a/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py +++ b/test/integration/targets/collections/test_task_resolved_plugin/callback_plugins/display_resolved_action.py @@ -14,7 +14,6 @@ DOCUMENTATION = ''' - Enable in configuration. ''' -from ansible import constants as C from ansible.plugins.callback import CallbackBase diff --git a/test/integration/targets/command_nonexisting/tasks/main.yml b/test/integration/targets/command_nonexisting/tasks/main.yml index d21856e..e54ecb3 100644 --- a/test/integration/targets/command_nonexisting/tasks/main.yml +++ b/test/integration/targets/command_nonexisting/tasks/main.yml @@ -1,4 +1,4 @@ - command: commandthatdoesnotexist --would-be-awkward register: res changed_when: "'changed' in res.stdout" - failed_when: "res.stdout != '' or res.stderr != ''"
\ No newline at end of file + failed_when: "res.stdout != '' or res.stderr != ''" diff --git a/test/integration/targets/command_shell/scripts/yoink.sh b/test/integration/targets/command_shell/scripts/yoink.sh new file mode 100755 index 0000000..ca955da --- /dev/null +++ b/test/integration/targets/command_shell/scripts/yoink.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +sleep 10 diff --git a/test/integration/targets/command_shell/tasks/main.yml b/test/integration/targets/command_shell/tasks/main.yml index 1f4aa5d..2cc365d 100644 --- a/test/integration/targets/command_shell/tasks/main.yml +++ b/test/integration/targets/command_shell/tasks/main.yml @@ -284,6 +284,30 @@ that: - "command_result6.stdout == '9cd0697c6a9ff6689f0afb9136fa62e0b3fee903'" +- name: check default var expansion + command: /bin/sh -c 'echo "\$TEST"' + environment: + TEST: z + register: command_result7 + +- name: assert vars were expanded + assert: + that: + - command_result7.stdout == '\\z' + +- name: check disabled var expansion + command: /bin/sh -c 'echo "\$TEST"' + args: + expand_argument_vars: false + environment: + TEST: z + register: command_result8 + +- name: assert vars were not expanded + assert: + that: + - command_result8.stdout == '$TEST' + ## ## shell ## @@ -546,3 +570,21 @@ - command_strip.stderr == 'hello \n ' - command_no_strip.stdout== 'hello \n \r\n' - command_no_strip.stderr == 'hello \n \r\n' + +- name: run shell with expand_argument_vars + shell: echo 'hi' + args: + expand_argument_vars: false + register: shell_expand_failure + ignore_errors: true + +- name: assert shell with expand_arguments_vars failed + assert: + that: + - shell_expand_failure is failed + - "shell_expand_failure.msg == 'Unsupported parameters for (shell) module: expand_argument_vars'" + +- name: Run command that backgrounds, to ensure no hang + shell: '{{ role_path }}/scripts/yoink.sh &' + delegate_to: localhost + timeout: 5 diff --git a/test/integration/targets/conditionals/play.yml b/test/integration/targets/conditionals/play.yml index 455818c..56ec843 100644 --- a/test/integration/targets/conditionals/play.yml +++ b/test/integration/targets/conditionals/play.yml @@ -665,3 +665,29 @@ - item loop: - 1 == 1 + + - set_fact: + sentinel_file: '{{ lookup("env", "OUTPUT_DIR")}}/LOOKUP_SIDE_EFFECT.txt' + + - name: ensure sentinel file is absent + file: + path: '{{ sentinel_file }}' + state: absent + - name: get an untrusted var that's a valid Jinja expression with a side-effect + shell: | + echo "lookup('pipe', 'echo bang > \"$SENTINEL_FILE\" && cat \"$SENTINEL_FILE\"')" + environment: + SENTINEL_FILE: '{{ sentinel_file }}' + register: untrusted_expr + - name: use a conditional with an inline template that refers to the untrusted expression + debug: + msg: look at some seemingly innocuous stuff + when: '"foo" in {{ untrusted_expr.stdout }}' + ignore_errors: true + - name: ensure the untrusted expression side-effect has not executed + stat: + path: '{{ sentinel_file }}' + register: sentinel_stat + - assert: + that: + - not sentinel_stat.stat.exists diff --git a/test/integration/targets/connection_delegation/aliases b/test/integration/targets/connection_delegation/aliases index 6c96566..0ce7601 100644 --- a/test/integration/targets/connection_delegation/aliases +++ b/test/integration/targets/connection_delegation/aliases @@ -1,6 +1,5 @@ shippable/posix/group3 context/controller skip/freebsd # No sshpass -skip/osx # No sshpass skip/macos # No sshpass skip/rhel # No sshpass diff --git a/test/integration/targets/connection_paramiko_ssh/test_connection.inventory b/test/integration/targets/connection_paramiko_ssh/test_connection.inventory index a3f34ab..cd17c09 100644 --- a/test/integration/targets/connection_paramiko_ssh/test_connection.inventory +++ b/test/integration/targets/connection_paramiko_ssh/test_connection.inventory @@ -2,6 +2,6 @@ paramiko_ssh-pipelining ansible_ssh_pipelining=true paramiko_ssh-no-pipelining ansible_ssh_pipelining=false [paramiko_ssh:vars] -ansible_host=localhost +ansible_host={{ 'localhost'|string }} ansible_connection=paramiko_ssh ansible_python_interpreter="{{ ansible_playbook_python }}" diff --git a/test/integration/targets/connection_psrp/tests.yml b/test/integration/targets/connection_psrp/tests.yml index dabbf40..08832b1 100644 --- a/test/integration/targets/connection_psrp/tests.yml +++ b/test/integration/targets/connection_psrp/tests.yml @@ -6,6 +6,9 @@ gather_facts: no tasks: + - name: reboot the host + ansible.windows.win_reboot: + - name: test complex objects in raw output # until PyYAML is upgraded to 4.x we need to use the \U escape for a unicode codepoint # and enclose in a quote to it translates the \U @@ -29,15 +32,8 @@ - raw_out.stdout_lines[4] == "winrm" - raw_out.stdout_lines[5] == "string - \U0001F4A9" - # Become only works on Server 2008 when running with basic auth, skip this host for now as it is too complicated to - # override the auth protocol in the tests. - - name: check if we running on Server 2008 - win_shell: '[System.Environment]::OSVersion.Version -ge [Version]"6.1"' - register: os_version - - name: test out become with psrp win_whoami: - when: os_version|bool register: whoami_out become: yes become_method: runas @@ -47,7 +43,6 @@ assert: that: - whoami_out.account.sid == "S-1-5-18" - when: os_version|bool - name: test out async with psrp win_shell: Start-Sleep -Seconds 2; Write-Output abc diff --git a/test/integration/targets/connection_winrm/tests.yml b/test/integration/targets/connection_winrm/tests.yml index 78f92a4..b086a3a 100644 --- a/test/integration/targets/connection_winrm/tests.yml +++ b/test/integration/targets/connection_winrm/tests.yml @@ -6,6 +6,9 @@ gather_facts: no tasks: + - name: reboot the host + ansible.windows.win_reboot: + - name: setup remote tmp dir import_role: name: ../../setup_remote_tmp_dir diff --git a/test/integration/targets/copy/tasks/main.yml b/test/integration/targets/copy/tasks/main.yml index b86c56a..601312f 100644 --- a/test/integration/targets/copy/tasks/main.yml +++ b/test/integration/targets/copy/tasks/main.yml @@ -84,6 +84,7 @@ - import_tasks: check_mode.yml # https://github.com/ansible/ansible/issues/57618 + # https://github.com/ansible/ansible/issues/79749 - name: Test diff contents copy: content: 'Ansible managed\n' @@ -95,6 +96,7 @@ that: - 'diff_output.diff[0].before == ""' - '"Ansible managed" in diff_output.diff[0].after' + - '"file.txt" in diff_output.diff[0].after_header' - name: tests with remote_src and non files import_tasks: src_remote_file_is_not_file.yml diff --git a/test/integration/targets/copy/tasks/tests.yml b/test/integration/targets/copy/tasks/tests.yml index d6c8e63..40ea9de 100644 --- a/test/integration/targets/copy/tasks/tests.yml +++ b/test/integration/targets/copy/tasks/tests.yml @@ -420,6 +420,80 @@ - "stat_results2.stat.mode == '0547'" # +# test copying an empty dir to a dest dir with remote_src=True +# + +- name: create empty test dir + file: + path: '{{ remote_dir }}/testcase_empty_dir' + state: directory + +- name: test copying an empty dir to a dir that does not exist (dest ends with slash) + copy: + src: '{{ remote_dir }}/testcase_empty_dir/' + remote_src: yes + dest: '{{ remote_dir }}/testcase_empty_dir_dest/' + register: copy_result + +- name: get stat of newly created dir + stat: + path: '{{ remote_dir }}/testcase_empty_dir_dest' + register: stat_result + +- assert: + that: + - copy_result.changed + - stat_result.stat.exists + - stat_result.stat.isdir + +- name: test no change is made running the task twice + copy: + src: '{{ remote_dir }}/testcase_empty_dir/' + remote_src: yes + dest: '{{ remote_dir }}/testcase_empty_dir_dest/' + register: copy_result + failed_when: copy_result is changed + +- name: remove to test dest with no trailing slash + file: + path: '{{ remote_dir }}/testcase_empty_dir_dest/' + state: absent + +- name: test copying an empty dir to a dir that does not exist (both src/dest have no trailing slash) + copy: + src: '{{ remote_dir }}/testcase_empty_dir' + remote_src: yes + dest: '{{ remote_dir }}/testcase_empty_dir_dest' + register: copy_result + +- name: get stat of newly created dir + stat: + path: '{{ remote_dir }}/testcase_empty_dir_dest' + register: stat_result + +- assert: + that: + - copy_result.changed + - stat_result.stat.exists + - stat_result.stat.isdir + +- name: test no change is made running the task twice + copy: + src: '{{ remote_dir }}/testcase_empty_dir/' + remote_src: yes + dest: '{{ remote_dir }}/testcase_empty_dir_dest/' + register: copy_result + failed_when: copy_result is changed + +- name: clean up src and dest + file: + path: "{{ item }}" + state: absent + loop: + - '{{ remote_dir }}/testcase_empty_dir' + - '{{ remote_dir }}/testcase_empty_dir_dest' + +# # test recursive copy local_follow=False, no trailing slash # @@ -2284,3 +2358,81 @@ that: - fail_copy_directory_with_enc_file is failed - fail_copy_directory_with_enc_file.msg == 'A vault password or secret must be specified to decrypt {{role_path}}/files-different/vault/vault-file' + +# +# Test for issue 74536: recursively copy all nested directories with remote_src=yes and src='dir/' when dest exists +# +- vars: + src: '{{ remote_dir }}/testcase_74536' + block: + - name: create source dir with 3 nested subdirs + file: + path: '{{ src }}/a/b1/c1' + state: directory + + - name: copy the source dir with a trailing slash + copy: + src: '{{ src }}/' + remote_src: yes + dest: '{{ src }}_dest/' + register: copy_result + failed_when: copy_result is not changed + + - name: remove the source dir to recreate with different subdirs + file: + path: '{{ src }}' + state: absent + + - name: recreate source dir + file: + path: "{{ item }}" + state: directory + loop: + - '{{ src }}/a/b1/c2' + - '{{ src }}/a/b2/c3' + + - name: copy the source dir containing new subdirs into the existing dest dir + copy: + src: '{{ src }}/' + remote_src: yes + dest: '{{ src }}_dest/' + register: copy_result + + - name: stat each directory that should exist + stat: + path: '{{ item }}' + register: stat_result + loop: + - '{{ src }}_dest' + - '{{ src }}_dest/a' + - '{{ src }}_dest/a/b1' + - '{{ src }}_dest/a/b2' + - '{{ src }}_dest/a/b1/c1' + - '{{ src }}_dest/a/b1/c2' + - '{{ src }}_dest/a/b2/c3' + + - debug: msg="{{ stat_result }}" + + - assert: + that: + - copy_result is changed + # all paths exist + - stat_result.results | map(attribute='stat') | map(attribute='exists') | unique == [true] + # all paths are dirs + - stat_result.results | map(attribute='stat') | map(attribute='isdir') | unique == [true] + + - name: copy the src again to verify no changes will be made + copy: + src: '{{ src }}/' + remote_src: yes + dest: '{{ src }}_dest/' + register: copy_result + failed_when: copy_result is changed + + - name: clean up src and dest + file: + path: '{{ item }}' + state: absent + loop: + - '{{ src }}' + - '{{ src }}_dest' diff --git a/test/integration/targets/cron/aliases b/test/integration/targets/cron/aliases index f2f9ac9..f3703f8 100644 --- a/test/integration/targets/cron/aliases +++ b/test/integration/targets/cron/aliases @@ -1,4 +1,3 @@ destructive shippable/posix/group1 -skip/osx skip/macos diff --git a/test/integration/targets/deb822_repository/aliases b/test/integration/targets/deb822_repository/aliases new file mode 100644 index 0000000..34e2b54 --- /dev/null +++ b/test/integration/targets/deb822_repository/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/freebsd +skip/osx +skip/macos +skip/rhel diff --git a/test/integration/targets/deb822_repository/meta/main.yml b/test/integration/targets/deb822_repository/meta/main.yml new file mode 100644 index 0000000..83e789e --- /dev/null +++ b/test/integration/targets/deb822_repository/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - prepare_tests + - role: setup_deb_repo + install_repo: false diff --git a/test/integration/targets/deb822_repository/tasks/install.yml b/test/integration/targets/deb822_repository/tasks/install.yml new file mode 100644 index 0000000..a5dce43 --- /dev/null +++ b/test/integration/targets/deb822_repository/tasks/install.yml @@ -0,0 +1,40 @@ +- name: Create repo to install from + deb822_repository: + name: ansible-test local + uris: file:{{ repodir }} + suites: + - stable + - testing + components: + - main + architectures: + - all + trusted: yes + register: deb822_install_repo + +- name: Update apt cache + apt: + update_cache: yes + when: deb822_install_repo is changed + +- block: + - name: Install package from local repo + apt: + name: foo=1.0.0 + register: deb822_install_pkg + always: + - name: Uninstall foo + apt: + name: foo + state: absent + when: deb822_install_pkg is changed + + - name: remove repo + deb822_repository: + name: ansible-test local + state: absent + +- assert: + that: + - deb822_install_repo is changed + - deb822_install_pkg is changed diff --git a/test/integration/targets/deb822_repository/tasks/main.yml b/test/integration/targets/deb822_repository/tasks/main.yml new file mode 100644 index 0000000..561ef2a --- /dev/null +++ b/test/integration/targets/deb822_repository/tasks/main.yml @@ -0,0 +1,19 @@ +- meta: end_play + when: ansible_os_family != 'Debian' + +- block: + - name: install python3-debian + apt: + name: python3-debian + state: present + register: py3_deb_install + + - import_tasks: test.yml + + - import_tasks: install.yml + always: + - name: uninstall python3-debian + apt: + name: python3-debian + state: absent + when: py3_deb_install is changed diff --git a/test/integration/targets/deb822_repository/tasks/test.yml b/test/integration/targets/deb822_repository/tasks/test.yml new file mode 100644 index 0000000..4911bb9 --- /dev/null +++ b/test/integration/targets/deb822_repository/tasks/test.yml @@ -0,0 +1,229 @@ +- name: Create deb822 repo - check_mode + deb822_repository: + name: ansible-test focal archive + uris: http://us.archive.ubuntu.com/ubuntu + suites: + - focal + - focal-updates + components: + - main + - restricted + register: deb822_check_mode_1 + check_mode: true + +- name: Create deb822 repo + deb822_repository: + name: ansible-test focal archive + uris: http://us.archive.ubuntu.com/ubuntu + suites: + - focal + - focal-updates + components: + - main + - restricted + date_max_future: 10 + register: deb822_create_1 + +- name: Check file mode + stat: + path: /etc/apt/sources.list.d/ansible-test-focal-archive.sources + register: deb822_create_1_stat_1 + +- name: Create another deb822 repo + deb822_repository: + name: ansible-test focal security + uris: http://security.ubuntu.com/ubuntu + suites: + - focal-security + components: + - main + - restricted + register: deb822_create_2 + +- name: Create deb822 repo idempotency + deb822_repository: + name: ansible-test focal archive + uris: http://us.archive.ubuntu.com/ubuntu + suites: + - focal + - focal-updates + components: + - main + - restricted + date_max_future: 10 + register: deb822_create_1_idem + +- name: Create deb822 repo - check_mode + deb822_repository: + name: ansible-test focal archive + uris: http://us.archive.ubuntu.com/ubuntu + suites: + - focal + - focal-updates + components: + - main + - restricted + date_max_future: 10 + register: deb822_check_mode_2 + check_mode: yes + +- name: Change deb822 repo mode + deb822_repository: + name: ansible-test focal archive + uris: http://us.archive.ubuntu.com/ubuntu + suites: + - focal + - focal-updates + components: + - main + - restricted + date_max_future: 10 + mode: '0600' + register: deb822_create_1_mode + +- name: Check file mode + stat: + path: /etc/apt/sources.list.d/ansible-test-focal-archive.sources + register: deb822_create_1_stat_2 + +- assert: + that: + - deb822_check_mode_1 is changed + + - deb822_check_mode_2 is not changed + + - deb822_create_1 is changed + - deb822_create_1.dest == '/etc/apt/sources.list.d/ansible-test-focal-archive.sources' + - deb822_create_1.repo|trim == focal_archive_expected + + - deb822_create_1_idem is not changed + + - deb822_create_1_mode is changed + - deb822_create_1_stat_1.stat.mode == '0644' + - deb822_create_1_stat_2.stat.mode == '0600' + vars: + focal_archive_expected: |- + X-Repolib-Name: ansible-test focal archive + URIs: http://us.archive.ubuntu.com/ubuntu + Suites: focal focal-updates + Components: main restricted + Date-Max-Future: 10 + Types: deb + +- name: Remove repos + deb822_repository: + name: '{{ item }}' + state: absent + register: remove_repos_1 + loop: + - ansible-test focal archive + - ansible-test focal security + +- name: Check for repo files + stat: + path: /etc/apt/sources.list.d/ansible-test-{{ item }}.sources + register: remove_stats + loop: + - focal-archive + - focal-security + +- assert: + that: + - remove_repos_1 is changed + - remove_stats.results|map(attribute='stat')|selectattr('exists') == [] + +- name: Add repo with signed_by + deb822_repository: + name: ansible-test + types: deb + uris: https://deb.debian.org + suites: stable + components: + - main + - contrib + - non-free + signed_by: |- + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mDMEYCQjIxYJKwYBBAHaRw8BAQdAD/P5Nvvnvk66SxBBHDbhRml9ORg1WV5CvzKY + CuMfoIS0BmFiY2RlZoiQBBMWCgA4FiEErCIG1VhKWMWo2yfAREZd5NfO31cFAmAk + IyMCGyMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQREZd5NfO31fbOwD6ArzS + dM0Dkd5h2Ujy1b6KcAaVW9FOa5UNfJ9FFBtjLQEBAJ7UyWD3dZzhvlaAwunsk7DG + 3bHcln8DMpIJVXht78sL + =IE0r + -----END PGP PUBLIC KEY BLOCK----- + register: signed_by_inline + +- name: Change signed_by to URL + deb822_repository: + name: ansible-test + types: deb + uris: https://deb.debian.org + suites: stable + components: + - main + - contrib + - non-free + signed_by: https://ci-files.testing.ansible.com/test/integration/targets/apt_key/apt-key-example-binary.gpg + register: signed_by_url + +- assert: + that: + - signed_by_inline.key_filename is none + - signed_by_inline.repo|trim == signed_by_inline_expected + - signed_by_url is changed + - signed_by_url.key_filename == '/etc/apt/keyrings/ansible-test.gpg' + - > + 'BEGIN' not in signed_by_url.repo + vars: + signed_by_inline_expected: |- + X-Repolib-Name: ansible-test + Types: deb + URIs: https://deb.debian.org + Suites: stable + Components: main contrib non-free + Signed-By: + -----BEGIN PGP PUBLIC KEY BLOCK----- + . + mDMEYCQjIxYJKwYBBAHaRw8BAQdAD/P5Nvvnvk66SxBBHDbhRml9ORg1WV5CvzKY + CuMfoIS0BmFiY2RlZoiQBBMWCgA4FiEErCIG1VhKWMWo2yfAREZd5NfO31cFAmAk + IyMCGyMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQREZd5NfO31fbOwD6ArzS + dM0Dkd5h2Ujy1b6KcAaVW9FOa5UNfJ9FFBtjLQEBAJ7UyWD3dZzhvlaAwunsk7DG + 3bHcln8DMpIJVXht78sL + =IE0r + -----END PGP PUBLIC KEY BLOCK----- + +- name: remove ansible-test repo + deb822_repository: + name: ansible-test + state: absent + register: ansible_test_repo_remove + +- name: check for ansible-test repo and key + stat: + path: '{{ item }}' + register: ansible_test_repo_stats + loop: + - /etc/apt/sources.list.d/ansible-test.sources + - /etc/apt/keyrings/ansible-test.gpg + +- assert: + that: + - ansible_test_repo_remove is changed + - ansible_test_repo_stats.results|map(attribute='stat')|selectattr('exists') == [] + +- name: Check if http-agent works when using cloudflare repo - check_mode + deb822_repository: + name: cloudflared + types: deb + uris: https://pkg.cloudflare.com/cloudflared + suites: "bullseye" + components: main + signed_by: https://pkg.cloudflare.com/cloudflare-main.gpg + state: present + check_mode: true + register: ansible_test_http_agent + +- assert: + that: + - ansible_test_http_agent is changed diff --git a/test/integration/targets/debconf/tasks/main.yml b/test/integration/targets/debconf/tasks/main.yml index d3d63cd..f923626 100644 --- a/test/integration/targets/debconf/tasks/main.yml +++ b/test/integration/targets/debconf/tasks/main.yml @@ -33,4 +33,44 @@ - 'debconf_test0.current is defined' - '"tzdata/Zones/Etc" in debconf_test0.current' - 'debconf_test0.current["tzdata/Zones/Etc"] == "UTC"' - when: ansible_distribution in ('Ubuntu', 'Debian') + + - name: install debconf-utils + apt: + name: debconf-utils + state: present + register: debconf_utils_deb_install + + - name: Check if password is set + debconf: + name: ddclient + question: ddclient/password + value: "MySecretValue" + vtype: password + register: debconf_test1 + + - name: validate results for test 1 + assert: + that: + - debconf_test1.changed + + - name: Change password again + debconf: + name: ddclient + question: ddclient/password + value: "MySecretValue" + vtype: password + no_log: yes + register: debconf_test2 + + - name: validate results for test 1 + assert: + that: + - not debconf_test2.changed + always: + - name: uninstall debconf-utils + apt: + name: debconf-utils + state: absent + when: debconf_utils_deb_install is changed + + when: ansible_distribution in ('Ubuntu', 'Debian')
\ No newline at end of file diff --git a/test/integration/targets/delegate_to/delegate_local_from_root.yml b/test/integration/targets/delegate_to/delegate_local_from_root.yml index c9be4ff..b44f83b 100644 --- a/test/integration/targets/delegate_to/delegate_local_from_root.yml +++ b/test/integration/targets/delegate_to/delegate_local_from_root.yml @@ -3,7 +3,7 @@ gather_facts: false remote_user: root tasks: - - name: ensure we copy w/o errors due to remote user not being overriden + - name: ensure we copy w/o errors due to remote user not being overridden copy: src: testfile dest: "{{ playbook_dir }}" diff --git a/test/integration/targets/delegate_to/runme.sh b/test/integration/targets/delegate_to/runme.sh index 1bdf27c..e0dcc74 100755 --- a/test/integration/targets/delegate_to/runme.sh +++ b/test/integration/targets/delegate_to/runme.sh @@ -76,3 +76,7 @@ ansible-playbook test_delegate_to_lookup_context.yml -i inventory -v "$@" ansible-playbook delegate_local_from_root.yml -i inventory -v "$@" -e 'ansible_user=root' ansible-playbook delegate_with_fact_from_delegate_host.yml "$@" ansible-playbook delegate_facts_loop.yml -i inventory -v "$@" +ansible-playbook test_random_delegate_to_with_loop.yml -i inventory -v "$@" + +# Run playbook multiple times to ensure there are no false-negatives +for i in $(seq 0 10); do ansible-playbook test_random_delegate_to_without_loop.yml -i inventory -v "$@"; done; diff --git a/test/integration/targets/delegate_to/test_delegate_to.yml b/test/integration/targets/delegate_to/test_delegate_to.yml index dcfa9d0..eb601e0 100644 --- a/test/integration/targets/delegate_to/test_delegate_to.yml +++ b/test/integration/targets/delegate_to/test_delegate_to.yml @@ -1,9 +1,9 @@ - hosts: testhost3 vars: - - template_role: ./roles/test_template - - output_dir: "{{ playbook_dir }}" - - templated_var: foo - - templated_dict: { 'hello': 'world' } + template_role: ./roles/test_template + output_dir: "{{ playbook_dir }}" + templated_var: foo + templated_dict: { 'hello': 'world' } tasks: - name: Test no delegate_to setup: @@ -57,6 +57,25 @@ - name: remove test file file: path={{ output_dir }}/tmp.txt state=absent + - name: Use omit to thwart delegation + ping: + delegate_to: "{{ jenkins_install_key_on|default(omit) }}" + register: d_omitted + + - name: Use empty to thwart delegation should fail + ping: + delegate_to: "{{ jenkins_install_key_on }}" + when: jenkins_install_key_on != "" + vars: + jenkins_install_key_on: '' + ignore_errors: true + register: d_empty + + - name: Ensure previous 2 tests actually did what was expected + assert: + that: + - d_omitted is success + - d_empty is failed - name: verify delegation with per host vars hosts: testhost6 diff --git a/test/integration/targets/delegate_to/test_random_delegate_to_with_loop.yml b/test/integration/targets/delegate_to/test_random_delegate_to_with_loop.yml new file mode 100644 index 0000000..cd7b888 --- /dev/null +++ b/test/integration/targets/delegate_to/test_random_delegate_to_with_loop.yml @@ -0,0 +1,26 @@ +- hosts: localhost + gather_facts: false + tasks: + - add_host: + name: 'host{{ item }}' + groups: + - test + loop: '{{ range(10) }}' + + # This task may fail, if it does, it means the same thing as if the assert below fails + - set_fact: + dv: '{{ ansible_delegated_vars[ansible_host]["ansible_host"] }}' + delegate_to: '{{ groups.test|random }}' + delegate_facts: true + # Purposefully smaller loop than group count + loop: '{{ range(5) }}' + +- hosts: test + gather_facts: false + tasks: + - assert: + that: + - dv == inventory_hostname + # The small loop above means we won't set this var for every host + # and a smaller loop is faster, and may catch the error in the above task + when: dv is defined diff --git a/test/integration/targets/delegate_to/test_random_delegate_to_without_loop.yml b/test/integration/targets/delegate_to/test_random_delegate_to_without_loop.yml new file mode 100644 index 0000000..9527862 --- /dev/null +++ b/test/integration/targets/delegate_to/test_random_delegate_to_without_loop.yml @@ -0,0 +1,13 @@ +- hosts: localhost + gather_facts: false + tasks: + - add_host: + name: 'host{{ item }}' + groups: + - test + loop: '{{ range(10) }}' + + - set_fact: + dv: '{{ ansible_delegated_vars[ansible_host]["ansible_host"] }}' + delegate_to: '{{ groups.test|random }}' + delegate_facts: true diff --git a/test/integration/targets/dnf/aliases b/test/integration/targets/dnf/aliases index d6f27b8..b12f354 100644 --- a/test/integration/targets/dnf/aliases +++ b/test/integration/targets/dnf/aliases @@ -1,6 +1,4 @@ destructive shippable/posix/group1 -skip/power/centos skip/freebsd -skip/osx skip/macos diff --git a/test/integration/targets/dnf/tasks/dnf.yml b/test/integration/targets/dnf/tasks/dnf.yml index ec1c36f..9845f3d 100644 --- a/test/integration/targets/dnf/tasks/dnf.yml +++ b/test/integration/targets/dnf/tasks/dnf.yml @@ -224,7 +224,7 @@ - assert: that: - dnf_result is success - - dnf_result.results|length == 2 + - dnf_result.results|length >= 2 - "dnf_result.results[0].startswith('Removed: ')" - "dnf_result.results[1].startswith('Removed: ')" @@ -427,6 +427,10 @@ - shell: 'dnf -y group install "Custom Group" && dnf -y group remove "Custom Group"' register: shell_dnf_result +- dnf: + name: "@Custom Group" + state: absent + # GROUP UPGRADE - this will go to the same method as group install # but through group_update - it is its invocation we're testing here # see commit 119c9e5d6eb572c4a4800fbe8136095f9063c37b @@ -446,6 +450,10 @@ # cleanup until https://github.com/ansible/ansible/issues/27377 is resolved - shell: dnf -y group install "Custom Group" && dnf -y group remove "Custom Group" +- dnf: + name: "@Custom Group" + state: absent + - name: try to install non existing group dnf: name: "@non-existing-group" @@ -551,30 +559,35 @@ - "'No package non-existent-rpm available' in dnf_result['failures'][0]" - "'Failed to install some of the specified packages' in dnf_result['msg']" -- name: use latest to install httpd +- name: ensure sos isn't installed dnf: - name: httpd + name: sos + state: absent + +- name: use latest to install sos + dnf: + name: sos state: latest register: dnf_result -- name: verify httpd was installed +- name: verify sos was installed assert: that: - - "'changed' in dnf_result" + - dnf_result is changed -- name: uninstall httpd +- name: uninstall sos dnf: - name: httpd + name: sos state: removed -- name: update httpd only if it exists +- name: update sos only if it exists dnf: - name: httpd + name: sos state: latest update_only: yes register: dnf_result -- name: verify httpd not installed +- name: verify sos not installed assert: that: - "not dnf_result is changed" @@ -655,6 +668,28 @@ - "'changed' in dnf_result" - "'results' in dnf_result" +# Install RPM from url with update_only +- name: install from url with update_only + dnf: + name: "file://{{ pkg_path }}" + state: latest + update_only: true + disable_gpg_check: true + register: dnf_result + +- name: verify installation + assert: + that: + - "dnf_result is success" + - "not dnf_result is changed" + - "dnf_result is not failed" + +- name: verify dnf module outputs + assert: + that: + - "'changed' in dnf_result" + - "'results' in dnf_result" + - name: Create a temp RPM file which does not contain nevra information file: name: "/tmp/non_existent_pkg.rpm" diff --git a/test/integration/targets/dnf/tasks/main.yml b/test/integration/targets/dnf/tasks/main.yml index 65b77ce..4941e2c 100644 --- a/test/integration/targets/dnf/tasks/main.yml +++ b/test/integration/targets/dnf/tasks/main.yml @@ -61,6 +61,7 @@ when: - (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('29', '>=')) or (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>=')) + - not dnf5|default(false) tags: - dnf_modularity @@ -69,5 +70,6 @@ (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>=')) - include_tasks: cacheonly.yml - when: (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or - (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>=')) + when: + - (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('23', '>=')) or + (ansible_distribution in ['RedHat', 'CentOS'] and ansible_distribution_major_version is version('8', '>=')) diff --git a/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml b/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml index 503cb4c..f54c0a8 100644 --- a/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml +++ b/test/integration/targets/dnf/tasks/skip_broken_and_nobest.yml @@ -240,7 +240,8 @@ - name: Do an "upgrade" to an older version of broken-a, allow_downgrade=false dnf: name: - - broken-a-1.2.3-1* + #- broken-a-1.2.3-1* + - broken-a-1.2.3-1.el7.x86_64 state: latest allow_downgrade: false check_mode: true diff --git a/test/integration/targets/dnf/tasks/test_sos_removal.yml b/test/integration/targets/dnf/tasks/test_sos_removal.yml index 0d70cf7..5e161db 100644 --- a/test/integration/targets/dnf/tasks/test_sos_removal.yml +++ b/test/integration/targets/dnf/tasks/test_sos_removal.yml @@ -15,5 +15,5 @@ that: - sos_rm is successful - sos_rm is changed - - "'Removed: sos-' ~ sos_version ~ '-' ~ sos_release in sos_rm.results[0]" - - sos_rm.results|length == 1 + - sos_rm.results|select("contains", "Removed: sos-{{ sos_version }}-{{ sos_release }}")|length > 0 + - sos_rm.results|length > 0 diff --git a/test/integration/targets/dnf5/aliases b/test/integration/targets/dnf5/aliases new file mode 100644 index 0000000..4baf6e6 --- /dev/null +++ b/test/integration/targets/dnf5/aliases @@ -0,0 +1,6 @@ +destructive +shippable/posix/group1 +skip/freebsd +skip/macos +context/target +needs/target/dnf diff --git a/test/integration/targets/dnf5/playbook.yml b/test/integration/targets/dnf5/playbook.yml new file mode 100644 index 0000000..16dfd22 --- /dev/null +++ b/test/integration/targets/dnf5/playbook.yml @@ -0,0 +1,19 @@ +- hosts: localhost + tasks: + - block: + - command: "dnf install -y 'dnf-command(copr)'" + - command: dnf copr enable -y rpmsoftwaremanagement/dnf5-unstable + - command: dnf install -y python3-libdnf5 + + - include_role: + name: dnf + vars: + dnf5: true + dnf_log_files: + - /var/log/dnf5.log + when: + - ansible_distribution == 'Fedora' + - ansible_distribution_major_version is version('37', '>=') + module_defaults: + dnf: + use_backend: dnf5 diff --git a/test/integration/targets/dnf5/runme.sh b/test/integration/targets/dnf5/runme.sh new file mode 100755 index 0000000..51a6bf4 --- /dev/null +++ b/test/integration/targets/dnf5/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -ux +export ANSIBLE_ROLES_PATH=../ +ansible-playbook playbook.yml "$@" diff --git a/test/integration/targets/dpkg_selections/aliases b/test/integration/targets/dpkg_selections/aliases index c0d5684..9c44d75 100644 --- a/test/integration/targets/dpkg_selections/aliases +++ b/test/integration/targets/dpkg_selections/aliases @@ -1,6 +1,5 @@ shippable/posix/group1 destructive skip/freebsd -skip/osx skip/macos skip/rhel diff --git a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml index 080db26..016d771 100644 --- a/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml +++ b/test/integration/targets/dpkg_selections/tasks/dpkg_selections.yaml @@ -87,3 +87,15 @@ apt: name: hello state: absent + +- name: Try to select non-existent package + dpkg_selections: + name: kernel + selection: hold + ignore_errors: yes + register: result + +- name: Check that module fails for non-existent package + assert: + that: + - "'Failed to find package' in result.msg" diff --git a/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py b/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py index c0c5ccd..28227fc 100644 --- a/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py +++ b/test/integration/targets/egg-info/lookup_plugins/import_pkg_resources.py @@ -1,7 +1,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import pkg_resources +import pkg_resources # pylint: disable=unused-import from ansible.plugins.lookup import LookupBase diff --git a/test/integration/targets/environment/test_environment.yml b/test/integration/targets/environment/test_environment.yml index 43f9c74..f295cf3 100644 --- a/test/integration/targets/environment/test_environment.yml +++ b/test/integration/targets/environment/test_environment.yml @@ -7,8 +7,8 @@ - hosts: testhost vars: - - test1: - key1: val1 + test1: + key1: val1 environment: PATH: '{{ansible_env.PATH + ":/lola"}}' lola: 'ido' @@ -41,9 +41,9 @@ - hosts: testhost vars: - - test1: - key1: val1 - - test2: + test1: + key1: val1 + test2: key1: not1 other1: val2 environment: "{{test1}}" diff --git a/test/integration/targets/error_from_connection/connection_plugins/dummy.py b/test/integration/targets/error_from_connection/connection_plugins/dummy.py index 59a81a1..d322fe0 100644 --- a/test/integration/targets/error_from_connection/connection_plugins/dummy.py +++ b/test/integration/targets/error_from_connection/connection_plugins/dummy.py @@ -11,7 +11,6 @@ DOCUMENTATION = """ version_added: "2.0" options: {} """ -import ansible.constants as C from ansible.errors import AnsibleError from ansible.plugins.connection import ConnectionBase diff --git a/test/integration/targets/expect/tasks/main.yml b/test/integration/targets/expect/tasks/main.yml index 7bf18c5..2aef595 100644 --- a/test/integration/targets/expect/tasks/main.yml +++ b/test/integration/targets/expect/tasks/main.yml @@ -148,6 +148,15 @@ - "echo_result.stdout_lines[-2] == 'foobar'" - "echo_result.stdout_lines[-1] == 'bar'" +- name: test timeout is valid as null + expect: + command: "{{ansible_python_interpreter}} {{test_command_file}}" + responses: + foo: bar + echo: true + timeout: null # wait indefinitely + timeout: 2 # but shouldn't be waiting long + - name: test response list expect: command: "{{ansible_python_interpreter}} {{test_command_file}} foo foo" diff --git a/test/integration/targets/facts_linux_network/aliases b/test/integration/targets/facts_linux_network/aliases index 100ce23..c9e1dc5 100644 --- a/test/integration/targets/facts_linux_network/aliases +++ b/test/integration/targets/facts_linux_network/aliases @@ -1,7 +1,6 @@ needs/privileged shippable/posix/group1 skip/freebsd -skip/osx skip/macos context/target destructive diff --git a/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml b/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml index 8a6b5b7..d0bf9bd 100644 --- a/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml +++ b/test/integration/targets/fetch/roles/fetch_tests/tasks/failures.yml @@ -28,6 +28,15 @@ register: failed_fetch_dest_dir ignore_errors: true +- name: Test unreachable + fetch: + src: "{{ remote_tmp_dir }}/orig" + dest: "{{ output_dir }}" + register: unreachable_fetch + ignore_unreachable: true + vars: + ansible_user: wrong + - name: Ensure fetch failed assert: that: @@ -39,3 +48,4 @@ - failed_fetch_no_access.msg is search('file is not readable') - failed_fetch_dest_dir is failed - failed_fetch_dest_dir.msg is search('dest is an existing directory') + - unreachable_fetch is unreachable diff --git a/test/integration/targets/file/tasks/link_rewrite.yml b/test/integration/targets/file/tasks/link_rewrite.yml index b0e1af3..2416c2c 100644 --- a/test/integration/targets/file/tasks/link_rewrite.yml +++ b/test/integration/targets/file/tasks/link_rewrite.yml @@ -16,11 +16,11 @@ dest: "{{ tempdir.path }}/somelink" state: link -- stat: +- stat: path: "{{ tempdir.path }}/somelink" register: link -- stat: +- stat: path: "{{ tempdir.path }}/somefile" register: file @@ -32,12 +32,12 @@ - file: path: "{{ tempdir.path }}/somelink" mode: 0644 - -- stat: + +- stat: path: "{{ tempdir.path }}/somelink" register: link -- stat: +- stat: path: "{{ tempdir.path }}/somefile" register: file diff --git a/test/integration/targets/file/tasks/main.yml b/test/integration/targets/file/tasks/main.yml index a5bd68d..c1b4c79 100644 --- a/test/integration/targets/file/tasks/main.yml +++ b/test/integration/targets/file/tasks/main.yml @@ -779,7 +779,7 @@ register: touch_result_in_check_mode_fails_not_existing_group - assert: - that: + that: - touch_result_in_check_mode_not_existing.changed - touch_result_in_check_mode_preserve_access_time.changed - touch_result_in_check_mode_change_only_mode.changed diff --git a/test/integration/targets/filter_core/tasks/main.yml b/test/integration/targets/filter_core/tasks/main.yml index 2d08419..9d287a1 100644 --- a/test/integration/targets/filter_core/tasks/main.yml +++ b/test/integration/targets/filter_core/tasks/main.yml @@ -454,6 +454,38 @@ - password_hash_2 is failed - "'not support' in password_hash_2.msg" +- name: install passlib if needed + pip: + name: passlib + state: present + register: installed_passlib + +- name: test using passlib with an unsupported hash type + set_fact: + foo: '{{"hey"|password_hash("msdcc")}}' + ignore_errors: yes + register: unsupported_hash_type + +- name: remove passlib if it was installed + pip: + name: passlib + state: absent + when: installed_passlib.changed + +- assert: + that: + - unsupported_hash_type.msg == msg + vars: + msg: "msdcc is not in the list of supported passlib algorithms: md5, blowfish, sha256, sha512" + +- name: test password_hash can work with bcrypt without passlib installed + debug: + msg: "{{ 'somestring'|password_hash('bcrypt') }}" + register: crypt_bcrypt + # Some implementations of crypt do not fail outright and return some short value. + failed_when: crypt_bcrypt is failed or (crypt_bcrypt.msg|length|int) != 60 + when: ansible_facts.os_family in ['RedHat', 'Debian'] + - name: Verify to_uuid throws on weird namespace set_fact: foo: '{{"hey"|to_uuid(namespace=22)}}' diff --git a/test/integration/targets/filter_encryption/base.yml b/test/integration/targets/filter_encryption/base.yml index 8bf25f7..1479f73 100644 --- a/test/integration/targets/filter_encryption/base.yml +++ b/test/integration/targets/filter_encryption/base.yml @@ -2,6 +2,7 @@ gather_facts: true vars: data: secret + data2: 'foo: bar\n' dvault: '{{ "secret"|vault("test")}}' password: test s_32: '{{(2**31-1)}}' @@ -21,6 +22,15 @@ is_64: '{{ "64" in ansible_facts["architecture"] }}' salt: '{{ is_64|bool|ternary(s_64, s_32)|random(seed=inventory_hostname)}}' vaultedstring: '{{ is_64|bool|ternary(vaultedstring_64, vaultedstring_32) }}' + # command line vaulted data2 + vaulted_id: !vault | + $ANSIBLE_VAULT;1.2;AES256;test1 + 36383733336533656264393332663131613335333332346439356164383935656234663631356430 + 3533353537343834333538356366376233326364613362640a623832636339363966336238393039 + 35316562626335306534356162623030613566306235623863373036626531346364626166656134 + 3063376436656635330a363636376131663362633731313964353061663661376638326461393736 + 3863 + vaulted_to_id: "{{data2|vault('test1@secret', vault_id='test1')}}" tasks: - name: check vaulting @@ -35,3 +45,5 @@ that: - vaultedstring|unvault(password) == data - vault|unvault(password) == data + - vaulted_id|unvault('test1@secret', vault_id='test1') + - vaulted_to_id|unvault('test1@secret', vault_id='test1') diff --git a/test/integration/targets/filter_mathstuff/tasks/main.yml b/test/integration/targets/filter_mathstuff/tasks/main.yml index 019f00e..33fcae8 100644 --- a/test/integration/targets/filter_mathstuff/tasks/main.yml +++ b/test/integration/targets/filter_mathstuff/tasks/main.yml @@ -64,44 +64,44 @@ that: - '[1,2,3]|intersect([4,5,6]) == []' - '[1,2,3]|intersect([3,4,5,6]) == [3]' - - '[1,2,3]|intersect([3,2,1]) == [1,2,3]' - - '(1,2,3)|intersect((4,5,6))|list == []' - - '(1,2,3)|intersect((3,4,5,6))|list == [3]' + - '[1,2,3]|intersect([3,2,1]) | sort == [1,2,3]' + - '(1,2,3)|intersect((4,5,6)) == []' + - '(1,2,3)|intersect((3,4,5,6)) == [3]' - '["a","A","b"]|intersect(["B","c","C"]) == []' - '["a","A","b"]|intersect(["b","B","c","C"]) == ["b"]' - - '["a","A","b"]|intersect(["b","A","a"]) == ["a","A","b"]' - - '("a","A","b")|intersect(("B","c","C"))|list == []' - - '("a","A","b")|intersect(("b","B","c","C"))|list == ["b"]' + - '["a","A","b"]|intersect(["b","A","a"]) | sort(case_sensitive=True) == ["A","a","b"]' + - '("a","A","b")|intersect(("B","c","C")) == []' + - '("a","A","b")|intersect(("b","B","c","C")) == ["b"]' - name: Verify difference tags: difference assert: that: - - '[1,2,3]|difference([4,5,6]) == [1,2,3]' - - '[1,2,3]|difference([3,4,5,6]) == [1,2]' + - '[1,2,3]|difference([4,5,6]) | sort == [1,2,3]' + - '[1,2,3]|difference([3,4,5,6]) | sort == [1,2]' - '[1,2,3]|difference([3,2,1]) == []' - - '(1,2,3)|difference((4,5,6))|list == [1,2,3]' - - '(1,2,3)|difference((3,4,5,6))|list == [1,2]' - - '["a","A","b"]|difference(["B","c","C"]) == ["a","A","b"]' - - '["a","A","b"]|difference(["b","B","c","C"]) == ["a","A"]' + - '(1,2,3)|difference((4,5,6)) | sort == [1,2,3]' + - '(1,2,3)|difference((3,4,5,6)) | sort == [1,2]' + - '["a","A","b"]|difference(["B","c","C"]) | sort(case_sensitive=True) == ["A","a","b"]' + - '["a","A","b"]|difference(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","a"]' - '["a","A","b"]|difference(["b","A","a"]) == []' - - '("a","A","b")|difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","a","b"]' - - '("a","A","b")|difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","a"]' + - '("a","A","b")|difference(("B","c","C")) | sort(case_sensitive=True) == ["A","a","b"]' + - '("a","A","b")|difference(("b","B","c","C")) | sort(case_sensitive=True) == ["A","a"]' - name: Verify symmetric_difference tags: symmetric_difference assert: that: - - '[1,2,3]|symmetric_difference([4,5,6]) == [1,2,3,4,5,6]' - - '[1,2,3]|symmetric_difference([3,4,5,6]) == [1,2,4,5,6]' + - '[1,2,3]|symmetric_difference([4,5,6]) | sort == [1,2,3,4,5,6]' + - '[1,2,3]|symmetric_difference([3,4,5,6]) | sort == [1,2,4,5,6]' - '[1,2,3]|symmetric_difference([3,2,1]) == []' - - '(1,2,3)|symmetric_difference((4,5,6))|list == [1,2,3,4,5,6]' - - '(1,2,3)|symmetric_difference((3,4,5,6))|list == [1,2,4,5,6]' - - '["a","A","b"]|symmetric_difference(["B","c","C"]) == ["a","A","b","B","c","C"]' - - '["a","A","b"]|symmetric_difference(["b","B","c","C"]) == ["a","A","B","c","C"]' + - '(1,2,3)|symmetric_difference((4,5,6)) | sort == [1,2,3,4,5,6]' + - '(1,2,3)|symmetric_difference((3,4,5,6)) | sort == [1,2,4,5,6]' + - '["a","A","b"]|symmetric_difference(["B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '["a","A","b"]|symmetric_difference(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","c"]' - '["a","A","b"]|symmetric_difference(["b","A","a"]) == []' - - '("a","A","b")|symmetric_difference(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]' - - '("a","A","b")|symmetric_difference(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","c"]' + - '("a","A","b")|symmetric_difference(("B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '("a","A","b")|symmetric_difference(("b","B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","c"]' - name: Verify union tags: union @@ -112,11 +112,11 @@ - '[1,2,3]|union([3,2,1]) == [1,2,3]' - '(1,2,3)|union((4,5,6))|list == [1,2,3,4,5,6]' - '(1,2,3)|union((3,4,5,6))|list == [1,2,3,4,5,6]' - - '["a","A","b"]|union(["B","c","C"]) == ["a","A","b","B","c","C"]' - - '["a","A","b"]|union(["b","B","c","C"]) == ["a","A","b","B","c","C"]' - - '["a","A","b"]|union(["b","A","a"]) == ["a","A","b"]' - - '("a","A","b")|union(("B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]' - - '("a","A","b")|union(("b","B","c","C"))|list|sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '["a","A","b"]|union(["B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '["a","A","b"]|union(["b","B","c","C"]) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '["a","A","b"]|union(["b","A","a"]) | sort(case_sensitive=True) == ["A","a","b"]' + - '("a","A","b")|union(("B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' + - '("a","A","b")|union(("b","B","c","C")) | sort(case_sensitive=True) == ["A","B","C","a","b","c"]' - name: Verify min tags: min diff --git a/test/integration/targets/find/tasks/main.yml b/test/integration/targets/find/tasks/main.yml index 89c62b9..9c4a960 100644 --- a/test/integration/targets/find/tasks/main.yml +++ b/test/integration/targets/find/tasks/main.yml @@ -374,3 +374,6 @@ - 'remote_tmp_dir_test ~ "/astest/old.txt" in astest_list' - 'remote_tmp_dir_test ~ "/astest/.hidden.txt" in astest_list' - '"checksum" in result.files[0]' + +- name: Run mode tests + import_tasks: mode.yml diff --git a/test/integration/targets/find/tasks/mode.yml b/test/integration/targets/find/tasks/mode.yml new file mode 100644 index 0000000..1c900ea --- /dev/null +++ b/test/integration/targets/find/tasks/mode.yml @@ -0,0 +1,68 @@ +- name: create test files for mode matching + file: + path: '{{ remote_tmp_dir_test }}/mode_{{ item }}' + state: touch + mode: '{{ item }}' + loop: + - '0644' + - '0444' + - '0400' + - '0700' + - '0666' + +- name: exact mode octal + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: '0644' + exact_mode: true + register: exact_mode_0644 + +- name: exact mode symbolic + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: 'u=rw,g=r,o=r' + exact_mode: true + register: exact_mode_0644_symbolic + +- name: find all user readable files octal + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: '0400' + exact_mode: false + register: user_readable_octal + +- name: find all user readable files symbolic + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: 'u=r' + exact_mode: false + register: user_readable_symbolic + +- name: all other readable files octal + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: '0004' + exact_mode: false + register: other_readable_octal + +- name: all other readable files symbolic + find: + path: '{{ remote_tmp_dir_test }}' + pattern: 'mode_*' + mode: 'o=r' + exact_mode: false + register: other_readable_symbolic + +- assert: + that: + - exact_mode_0644.files == exact_mode_0644_symbolic.files + - exact_mode_0644.files[0].path == remote_tmp_dir_test ~ '/mode_0644' + - user_readable_octal.files == user_readable_symbolic.files + - user_readable_octal.files|map(attribute='path')|map('basename')|sort == ['mode_0400', 'mode_0444', 'mode_0644', 'mode_0666', 'mode_0700'] + - other_readable_octal.files == other_readable_symbolic.files + - other_readable_octal.files|map(attribute='path')|map('basename')|sort == ['mode_0444', 'mode_0644', 'mode_0666'] diff --git a/test/integration/targets/fork_safe_stdio/aliases b/test/integration/targets/fork_safe_stdio/aliases index e968db7..7761837 100644 --- a/test/integration/targets/fork_safe_stdio/aliases +++ b/test/integration/targets/fork_safe_stdio/aliases @@ -1,3 +1,3 @@ shippable/posix/group3 context/controller -skip/macos +needs/target/test_utils diff --git a/test/integration/targets/fork_safe_stdio/runme.sh b/test/integration/targets/fork_safe_stdio/runme.sh index 4438c3f..863582f 100755 --- a/test/integration/targets/fork_safe_stdio/runme.sh +++ b/test/integration/targets/fork_safe_stdio/runme.sh @@ -7,7 +7,7 @@ echo "testing for stdio deadlock on forked workers (10s timeout)..." # Enable a callback that trips deadlocks on forked-child stdout, time out after 10s; forces running # in a pty, since that tends to be much slower than raw file I/O and thus more likely to trigger the deadlock. # Redirect stdout to /dev/null since it's full of non-printable garbage we don't want to display unless it failed -ANSIBLE_CALLBACKS_ENABLED=spewstdio SPEWSTDIO_ENABLED=1 python run-with-pty.py timeout 10s ansible-playbook -i hosts -f 5 test.yml > stdout.txt && RC=$? || RC=$? +ANSIBLE_CALLBACKS_ENABLED=spewstdio SPEWSTDIO_ENABLED=1 python run-with-pty.py ../test_utils/scripts/timeout.py -- 10 ansible-playbook -i hosts -f 5 test.yml > stdout.txt && RC=$? || RC=$? if [ $RC != 0 ]; then echo "failed; likely stdout deadlock. dumping raw output (may be very large)" diff --git a/test/integration/targets/gathering_facts/library/dummy1 b/test/integration/targets/gathering_facts/library/dummy1 new file mode 100755 index 0000000..5a10e2d --- /dev/null +++ b/test/integration/targets/gathering_facts/library/dummy1 @@ -0,0 +1,19 @@ +#!/bin/sh + +CANARY="${OUTPUT_DIR}/canary.txt" + +echo "$0" >> "${CANARY}" +LINES=0 + +until test "${LINES}" -gt 2 +do + LINES=`wc -l "${CANARY}" |awk '{print $1}'` + sleep 1 +done + +echo '{ + "changed": false, + "ansible_facts": { + "dummy": "$0" + } +}' diff --git a/test/integration/targets/gathering_facts/library/dummy2 b/test/integration/targets/gathering_facts/library/dummy2 new file mode 100755 index 0000000..5a10e2d --- /dev/null +++ b/test/integration/targets/gathering_facts/library/dummy2 @@ -0,0 +1,19 @@ +#!/bin/sh + +CANARY="${OUTPUT_DIR}/canary.txt" + +echo "$0" >> "${CANARY}" +LINES=0 + +until test "${LINES}" -gt 2 +do + LINES=`wc -l "${CANARY}" |awk '{print $1}'` + sleep 1 +done + +echo '{ + "changed": false, + "ansible_facts": { + "dummy": "$0" + } +}' diff --git a/test/integration/targets/gathering_facts/library/dummy3 b/test/integration/targets/gathering_facts/library/dummy3 new file mode 100755 index 0000000..5a10e2d --- /dev/null +++ b/test/integration/targets/gathering_facts/library/dummy3 @@ -0,0 +1,19 @@ +#!/bin/sh + +CANARY="${OUTPUT_DIR}/canary.txt" + +echo "$0" >> "${CANARY}" +LINES=0 + +until test "${LINES}" -gt 2 +do + LINES=`wc -l "${CANARY}" |awk '{print $1}'` + sleep 1 +done + +echo '{ + "changed": false, + "ansible_facts": { + "dummy": "$0" + } +}' diff --git a/test/integration/targets/gathering_facts/library/file_utils.py b/test/integration/targets/gathering_facts/library/file_utils.py index 5853802..38fa926 100644 --- a/test/integration/targets/gathering_facts/library/file_utils.py +++ b/test/integration/targets/gathering_facts/library/file_utils.py @@ -1,9 +1,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import json -import sys - from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.facts.utils import ( get_file_content, diff --git a/test/integration/targets/gathering_facts/library/slow b/test/integration/targets/gathering_facts/library/slow new file mode 100644 index 0000000..3984662 --- /dev/null +++ b/test/integration/targets/gathering_facts/library/slow @@ -0,0 +1,26 @@ +#!/bin/sh + +sleep 10 + +echo '{ + "changed": false, + "ansible_facts": { + "factsone": "from slow module", + "common_fact": "also from slow module", + "common_dict_fact": { + "key_one": "from slow ", + "key_two": "from slow " + }, + "common_list_fact": [ + "never", + "does", + "see" + ], + "common_list_fact2": [ + "see", + "does", + "never", + "theee" + ] + } +}' diff --git a/test/integration/targets/gathering_facts/runme.sh b/test/integration/targets/gathering_facts/runme.sh index c1df560..a90de0f 100755 --- a/test/integration/targets/gathering_facts/runme.sh +++ b/test/integration/targets/gathering_facts/runme.sh @@ -25,3 +25,17 @@ ansible-playbook test_module_defaults.yml "$@" --tags default_fact_module ANSIBLE_FACTS_MODULES='ansible.legacy.setup' ansible-playbook test_module_defaults.yml "$@" --tags custom_fact_module ansible-playbook test_module_defaults.yml "$@" --tags networking + +# test it works by default +ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ "$@" + +# test that gather_facts will timeout parallel modules that dont support gather_timeout when using gather_Timeout +ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=1 parallel=true' "$@" 2>&1 |grep 'Timeout exceeded' + +# test that gather_facts parallel w/o timing out +ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=30 parallel=true' "$@" 2>&1 |grep -v 'Timeout exceeded' + + +# test parallelism +ANSIBLE_FACTS_MODULES='dummy1,dummy2,dummy3' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=30 parallel=true' "$@" 2>&1 +rm "${OUTPUT_DIR}/canary.txt" diff --git a/test/integration/targets/get_url/tasks/hashlib.yml b/test/integration/targets/get_url/tasks/hashlib.yml new file mode 100644 index 0000000..cc50ad7 --- /dev/null +++ b/test/integration/targets/get_url/tasks/hashlib.yml @@ -0,0 +1,20 @@ +- name: "Set hash algorithms to test" + set_fact: + algorithms: + sha256: b1b6ce5073c8fac263a8fc5edfffdbd5dec1980c784e09c5bc69f8fb6056f006 + sha384: 298553d31087fd3f6659801d2e5cde3ff63fad609dc50ad8e194dde80bfb8a084edfa761f025928448f39d720fce55f2 + sha512: 69b589f7775fe04244e8a9db216a3c91db1680baa33ccd0c317b8d7f0334433f7362d00c8080b3365bf08d532956ba01dbebc497b51ced8f8b05a44a66b854bf + sha3_256: 64e5ea73a2f799f35abd0b1242df5e70c84248c9883f89343d4cd5f6d493a139 + sha3_384: 976edebcb496ad8be0f7fa4411cc8e2404e7e65f1088fabf7be44484458726c61d4985bdaeff8700008ed1670a9b982d + sha3_512: f8cca1d98e750e2c2ab44954dc9f1b6e8e35ace71ffcc1cd21c7770eb8eccfbd77d40b2d7d145120efbbb781599294ccc6148c6cda1aa66146363e5fdddd2336 + +- name: "Verify various checksum algorithms work" + get_url: + url: 'http://localhost:{{ http_port }}/27617.txt' # content is 'ptux' + dest: '{{ remote_tmp_dir }}/27617.{{ algorithm }}.txt' + checksum: "{{ algorithm }}:{{ algorithms[algorithm] }}" + force: yes + loop: "{{ algorithms.keys() }}" + loop_control: + loop_var: algorithm + when: ansible_python_version.startswith('3.') or not algorithm.startswith('sha3_') diff --git a/test/integration/targets/get_url/tasks/main.yml b/test/integration/targets/get_url/tasks/main.yml index 09814c7..c26cc08 100644 --- a/test/integration/targets/get_url/tasks/main.yml +++ b/test/integration/targets/get_url/tasks/main.yml @@ -398,6 +398,8 @@ port: '{{ http_port }}' state: started +- include_tasks: hashlib.yml + - name: download src with sha1 checksum url in check mode get_url: url: 'http://localhost:{{ http_port }}/27617.txt' diff --git a/test/integration/targets/get_url/tasks/use_netrc.yml b/test/integration/targets/get_url/tasks/use_netrc.yml index e1852a8..234c904 100644 --- a/test/integration/targets/get_url/tasks/use_netrc.yml +++ b/test/integration/targets/get_url/tasks/use_netrc.yml @@ -22,7 +22,7 @@ register: response_failed - name: Parse token from msg.txt - set_fact: + set_fact: token: "{{ (response_failed['content'] | b64decode | from_json).token }}" - name: assert Test Bearer authorization is failed with netrc @@ -48,7 +48,7 @@ register: response - name: Parse token from msg.txt - set_fact: + set_fact: token: "{{ (response['content'] | b64decode | from_json).token }}" - name: assert Test Bearer authorization is successfull with use_netrc=False @@ -64,4 +64,4 @@ state: absent with_items: - "{{ remote_tmp_dir }}/netrc" - - "{{ remote_tmp_dir }}/msg.txt"
\ No newline at end of file + - "{{ remote_tmp_dir }}/msg.txt" diff --git a/test/integration/targets/git/tasks/depth.yml b/test/integration/targets/git/tasks/depth.yml index e0585ca..20f1b4e 100644 --- a/test/integration/targets/git/tasks/depth.yml +++ b/test/integration/targets/git/tasks/depth.yml @@ -95,14 +95,16 @@ repo: 'file://{{ repo_dir|expanduser }}/shallow' dest: '{{ checkout_dir }}' depth: 1 - version: master + version: >- + {{ git_default_branch }} - name: DEPTH | run a second time (now fetch, not clone) git: repo: 'file://{{ repo_dir|expanduser }}/shallow' dest: '{{ checkout_dir }}' depth: 1 - version: master + version: >- + {{ git_default_branch }} register: git_fetch - name: DEPTH | ensure the fetch succeeded @@ -120,7 +122,8 @@ repo: 'file://{{ repo_dir|expanduser }}/shallow' dest: '{{ checkout_dir }}' depth: 1 - version: master + version: >- + {{ git_default_branch }} - name: DEPTH | switch to older branch with depth=1 (uses fetch) git: diff --git a/test/integration/targets/git/tasks/forcefully-fetch-tag.yml b/test/integration/targets/git/tasks/forcefully-fetch-tag.yml index 47c3747..db35e04 100644 --- a/test/integration/targets/git/tasks/forcefully-fetch-tag.yml +++ b/test/integration/targets/git/tasks/forcefully-fetch-tag.yml @@ -11,7 +11,7 @@ git add leet; git commit -m uh-oh; git tag -f herewego; - git push --tags origin master + git push --tags origin '{{ git_default_branch }}' args: chdir: "{{ repo_dir }}/tag_force_push_clone1" @@ -26,7 +26,7 @@ git add leet; git commit -m uh-oh; git tag -f herewego; - git push -f --tags origin master + git push -f --tags origin '{{ git_default_branch }}' args: chdir: "{{ repo_dir }}/tag_force_push_clone1" diff --git a/test/integration/targets/git/tasks/gpg-verification.yml b/test/integration/targets/git/tasks/gpg-verification.yml index 8c8834a..bd57ed1 100644 --- a/test/integration/targets/git/tasks/gpg-verification.yml +++ b/test/integration/targets/git/tasks/gpg-verification.yml @@ -37,8 +37,10 @@ environment: - GNUPGHOME: "{{ git_gpg_gpghome }}" shell: | - set -e + set -eEu + git init + touch an_empty_file git add an_empty_file git commit --no-gpg-sign --message "Commit, and don't sign" @@ -48,11 +50,11 @@ git tag --annotate --message "This is not a signed tag" unsigned_annotated_tag HEAD git commit --allow-empty --gpg-sign --message "Commit, and sign" git tag --sign --message "This is a signed tag" signed_annotated_tag HEAD - git checkout -b some_branch/signed_tip master + git checkout -b some_branch/signed_tip '{{ git_default_branch }}' git commit --allow-empty --gpg-sign --message "Commit, and sign" - git checkout -b another_branch/unsigned_tip master + git checkout -b another_branch/unsigned_tip '{{ git_default_branch }}' git commit --allow-empty --no-gpg-sign --message "Commit, and don't sign" - git checkout master + git checkout '{{ git_default_branch }}' args: chdir: "{{ git_gpg_source }}" diff --git a/test/integration/targets/git/tasks/localmods.yml b/test/integration/targets/git/tasks/localmods.yml index 0e0cf68..409bbae 100644 --- a/test/integration/targets/git/tasks/localmods.yml +++ b/test/integration/targets/git/tasks/localmods.yml @@ -1,6 +1,17 @@ # test for https://github.com/ansible/ansible-modules-core/pull/5505 - name: LOCALMODS | prepare old git repo - shell: rm -rf localmods; mkdir localmods; cd localmods; git init; echo "1" > a; git add a; git commit -m "1" + shell: | + set -eEu + + rm -rf localmods + mkdir localmods + cd localmods + + git init + + echo "1" > a + git add a + git commit -m "1" args: chdir: "{{repo_dir}}" @@ -55,7 +66,18 @@ # localmods and shallow clone - name: LOCALMODS | prepare old git repo - shell: rm -rf localmods; mkdir localmods; cd localmods; git init; echo "1" > a; git add a; git commit -m "1" + shell: | + set -eEu + + rm -rf localmods + mkdir localmods + cd localmods + + git init + + echo "1" > a + git add a + git commit -m "1" args: chdir: "{{repo_dir}}" diff --git a/test/integration/targets/git/tasks/main.yml b/test/integration/targets/git/tasks/main.yml index ed06eab..c990251 100644 --- a/test/integration/targets/git/tasks/main.yml +++ b/test/integration/targets/git/tasks/main.yml @@ -16,27 +16,37 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. -- import_tasks: setup.yml -- import_tasks: setup-local-repos.yml +# NOTE: Moving `$HOME` to tmp dir allows this integration test be +# NOTE: non-destructive. There is no other way to instruct Git use a custom +# NOTE: config path. There are new `$GIT_CONFIG_KEY_{COUNT,KEY,VALUE}` vars +# NOTE: for setting specific configuration values but those are only available +# NOTE: since Git v2.31 which is why we cannot rely on them yet. -- import_tasks: formats.yml -- import_tasks: missing_hostkey.yml -- import_tasks: missing_hostkey_acceptnew.yml -- import_tasks: no-destination.yml -- import_tasks: specific-revision.yml -- import_tasks: submodules.yml -- import_tasks: change-repo-url.yml -- import_tasks: depth.yml -- import_tasks: single-branch.yml -- import_tasks: checkout-new-tag.yml -- include_tasks: gpg-verification.yml - when: +- block: + - import_tasks: setup.yml + - import_tasks: setup-local-repos.yml + + - import_tasks: formats.yml + - import_tasks: missing_hostkey.yml + - import_tasks: missing_hostkey_acceptnew.yml + - import_tasks: no-destination.yml + - import_tasks: specific-revision.yml + - import_tasks: submodules.yml + - import_tasks: change-repo-url.yml + - import_tasks: depth.yml + - import_tasks: single-branch.yml + - import_tasks: checkout-new-tag.yml + - include_tasks: gpg-verification.yml + when: - not gpg_version.stderr - gpg_version.stdout - not (ansible_os_family == 'RedHat' and ansible_distribution_major_version is version('7', '<')) -- import_tasks: localmods.yml -- import_tasks: reset-origin.yml -- import_tasks: ambiguous-ref.yml -- import_tasks: archive.yml -- import_tasks: separate-git-dir.yml -- import_tasks: forcefully-fetch-tag.yml + - import_tasks: localmods.yml + - import_tasks: reset-origin.yml + - import_tasks: ambiguous-ref.yml + - import_tasks: archive.yml + - import_tasks: separate-git-dir.yml + - import_tasks: forcefully-fetch-tag.yml + environment: + HOME: >- + {{ remote_tmp_dir }} diff --git a/test/integration/targets/git/tasks/missing_hostkey.yml b/test/integration/targets/git/tasks/missing_hostkey.yml index 136c5d5..d8a2a81 100644 --- a/test/integration/targets/git/tasks/missing_hostkey.yml +++ b/test/integration/targets/git/tasks/missing_hostkey.yml @@ -35,7 +35,8 @@ git: repo: '{{ repo_format3 }}' dest: '{{ checkout_dir }}' - version: 'master' + version: >- + {{ git_default_branch }} accept_hostkey: false # should already have been accepted key_file: '{{ github_ssh_private_key }}' ssh_opts: '-o UserKnownHostsFile={{ remote_tmp_dir }}/known_hosts' diff --git a/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml b/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml index 3fd1906..338ae08 100644 --- a/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml +++ b/test/integration/targets/git/tasks/missing_hostkey_acceptnew.yml @@ -55,7 +55,8 @@ git: repo: '{{ repo_format3 }}' dest: '{{ checkout_dir }}' - version: 'master' + version: >- + {{ git_default_branch }} accept_newhostkey: false # should already have been accepted key_file: '{{ github_ssh_private_key }}' ssh_opts: '-o UserKnownHostsFile={{ remote_tmp_dir }}/known_hosts' diff --git a/test/integration/targets/git/tasks/reset-origin.yml b/test/integration/targets/git/tasks/reset-origin.yml index 8fddd4b..cb497c4 100644 --- a/test/integration/targets/git/tasks/reset-origin.yml +++ b/test/integration/targets/git/tasks/reset-origin.yml @@ -12,7 +12,14 @@ state: directory - name: RESET-ORIGIN | Initialise the repo with a file named origin,see github.com/ansible/ansible/pull/22502 - shell: git init; echo "PR 22502" > origin; git add origin; git commit -m "PR 22502" + shell: | + set -eEu + + git init + + echo "PR 22502" > origin + git add origin + git commit -m "PR 22502" args: chdir: "{{ repo_dir }}/origin" diff --git a/test/integration/targets/git/tasks/setup-local-repos.yml b/test/integration/targets/git/tasks/setup-local-repos.yml index 584a169..4626f10 100644 --- a/test/integration/targets/git/tasks/setup-local-repos.yml +++ b/test/integration/targets/git/tasks/setup-local-repos.yml @@ -9,15 +9,32 @@ - "{{ repo_dir }}/tag_force_push" - name: SETUP-LOCAL-REPOS | prepare minimal git repo - shell: git init; echo "1" > a; git add a; git commit -m "1" + shell: | + set -eEu + + git init + + echo "1" > a + git add a + git commit -m "1" args: chdir: "{{ repo_dir }}/minimal" - name: SETUP-LOCAL-REPOS | prepare git repo for shallow clone shell: | - git init; - echo "1" > a; git add a; git commit -m "1"; git tag earlytag; git branch earlybranch; - echo "2" > a; git add a; git commit -m "2"; + set -eEu + + git init + + echo "1" > a + git add a + git commit -m "1" + git tag earlytag + git branch earlybranch + + echo "2" > a + git add a + git commit -m "2" args: chdir: "{{ repo_dir }}/shallow" @@ -29,7 +46,10 @@ - name: SETUP-LOCAL-REPOS | prepare tmp git repo with two branches shell: | + set -eEu + git init + echo "1" > a; git add a; git commit -m "1" git checkout -b test_branch; echo "2" > a; git commit -m "2 on branch" a git checkout -b new_branch; echo "3" > a; git commit -m "3 on new branch" a @@ -40,6 +60,9 @@ # We make the repo here for consistency with the other repos, # but we finish setting it up in forcefully-fetch-tag.yml. - name: SETUP-LOCAL-REPOS | prepare tag_force_push git repo - shell: git init --bare + shell: | + set -eEu + + git init --bare args: chdir: "{{ repo_dir }}/tag_force_push" diff --git a/test/integration/targets/git/tasks/setup.yml b/test/integration/targets/git/tasks/setup.yml index 0651105..982c03f 100644 --- a/test/integration/targets/git/tasks/setup.yml +++ b/test/integration/targets/git/tasks/setup.yml @@ -28,10 +28,44 @@ register: gpg_version - name: SETUP | set git global user.email if not already set - shell: git config --global user.email || git config --global user.email "noreply@example.com" + shell: git config --global user.email 'noreply@example.com' - name: SETUP | set git global user.name if not already set - shell: git config --global user.name || git config --global user.name "Ansible Test Runner" + shell: git config --global user.name 'Ansible Test Runner' + +- name: SETUP | set git global init.defaultBranch + shell: >- + git config --global init.defaultBranch '{{ git_default_branch }}' + +- name: SETUP | set git global init.templateDir + # NOTE: This custom Git repository template emulates the `init.defaultBranch` + # NOTE: setting on Git versions below 2.28. + # NOTE: Ref: https://superuser.com/a/1559582. + # NOTE: Other workarounds mentioned there, like invoking + # NOTE: `git symbolic-ref HEAD refs/heads/main` after each `git init` turned + # NOTE: out to have mysterious side effects that break the tests in surprising + # NOTE: ways. + shell: | + set -eEu + + git config --global \ + init.templateDir '{{ remote_tmp_dir }}/git-templates/git.git' + + mkdir -pv '{{ remote_tmp_dir }}/git-templates' + set +e + GIT_TEMPLATES_DIR=$(\ + 2>/dev/null \ + ls -1d \ + '/Library/Developer/CommandLineTools/usr/share/git-core/templates' \ + '/usr/local/share/git-core/templates' \ + '/usr/share/git-core/templates' \ + ) + set -e + >&2 echo "Found Git's default templates directory: ${GIT_TEMPLATES_DIR}" + cp -r "${GIT_TEMPLATES_DIR}" '{{ remote_tmp_dir }}/git-templates/git.git' + + echo 'ref: refs/heads/{{ git_default_branch }}' \ + > '{{ remote_tmp_dir }}/git-templates/git.git/HEAD' - name: SETUP | create repo_dir file: diff --git a/test/integration/targets/git/tasks/single-branch.yml b/test/integration/targets/git/tasks/single-branch.yml index 5cfb4d5..ca8457a 100644 --- a/test/integration/targets/git/tasks/single-branch.yml +++ b/test/integration/targets/git/tasks/single-branch.yml @@ -52,7 +52,8 @@ repo: 'file://{{ repo_dir|expanduser }}/shallow_branches' dest: '{{ checkout_dir }}' single_branch: yes - version: master + version: >- + {{ git_default_branch }} register: single_branch_3 - name: SINGLE_BRANCH | Clone example git repo using single_branch with version again @@ -60,7 +61,8 @@ repo: 'file://{{ repo_dir|expanduser }}/shallow_branches' dest: '{{ checkout_dir }}' single_branch: yes - version: master + version: >- + {{ git_default_branch }} register: single_branch_4 - name: SINGLE_BRANCH | List revisions diff --git a/test/integration/targets/git/tasks/specific-revision.yml b/test/integration/targets/git/tasks/specific-revision.yml index 26fa7cf..f1fe41d 100644 --- a/test/integration/targets/git/tasks/specific-revision.yml +++ b/test/integration/targets/git/tasks/specific-revision.yml @@ -162,7 +162,14 @@ path: "{{ checkout_dir }}" - name: SPECIFIC-REVISION | prepare origina repo - shell: git init; echo "1" > a; git add a; git commit -m "1" + shell: | + set -eEu + + git init + + echo "1" > a + git add a + git commit -m "1" args: chdir: "{{ checkout_dir }}" @@ -191,7 +198,14 @@ force: yes - name: SPECIFIC-REVISION | create new commit in original - shell: git init; echo "2" > b; git add b; git commit -m "2" + shell: | + set -eEu + + git init + + echo "2" > b + git add b + git commit -m "2" args: chdir: "{{ checkout_dir }}" diff --git a/test/integration/targets/git/vars/main.yml b/test/integration/targets/git/vars/main.yml index b38531f..55c7c43 100644 --- a/test/integration/targets/git/vars/main.yml +++ b/test/integration/targets/git/vars/main.yml @@ -41,6 +41,7 @@ repo_update_url_2: 'https://github.com/ansible-test-robinro/git-test-new' known_host_files: - "{{ lookup('env','HOME') }}/.ssh/known_hosts" - '/etc/ssh/ssh_known_hosts' +git_default_branch: main git_version_supporting_depth: 1.9.1 git_version_supporting_ls_remote: 1.7.5 git_version_supporting_single_branch: 1.7.10 diff --git a/test/integration/targets/group/files/get_free_gid.py b/test/integration/targets/group/files/get_free_gid.py new file mode 100644 index 0000000..4c07b5e --- /dev/null +++ b/test/integration/targets/group/files/get_free_gid.py @@ -0,0 +1,23 @@ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import grp + + +def main(): + gids = [g.gr_gid for g in grp.getgrall()] + + # Start the gid numbering with 1 + # FreeBSD doesn't support the usage of gid 0, it doesn't fail (rc=0) but instead a number in the normal + # range is picked. + i = 1 + while True: + if i not in gids: + print(i) + break + i += 1 + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/group/files/get_gid_for_group.py b/test/integration/targets/group/files/get_gid_for_group.py new file mode 100644 index 0000000..5a8cc41 --- /dev/null +++ b/test/integration/targets/group/files/get_gid_for_group.py @@ -0,0 +1,18 @@ + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import grp +import sys + + +def main(): + group_name = None + if len(sys.argv) >= 2: + group_name = sys.argv[1] + + print(grp.getgrnam(group_name).gr_gid) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/group/files/gidget.py b/test/integration/targets/group/files/gidget.py deleted file mode 100644 index 4b77151..0000000 --- a/test/integration/targets/group/files/gidget.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - -import grp - -gids = [g.gr_gid for g in grp.getgrall()] - -i = 0 -while True: - if i not in gids: - print(i) - break - i += 1 diff --git a/test/integration/targets/group/tasks/main.yml b/test/integration/targets/group/tasks/main.yml index eb8126d..2123524 100644 --- a/test/integration/targets/group/tasks/main.yml +++ b/test/integration/targets/group/tasks/main.yml @@ -16,25 +16,4 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. -- name: ensure test groups are deleted before the test - group: - name: '{{ item }}' - state: absent - loop: - - ansibullgroup - - ansibullgroup2 - - ansibullgroup3 - -- block: - - name: run tests - include_tasks: tests.yml - - always: - - name: remove test groups after test - group: - name: '{{ item }}' - state: absent - loop: - - ansibullgroup - - ansibullgroup2 - - ansibullgroup3
\ No newline at end of file +- import_tasks: tests.yml diff --git a/test/integration/targets/group/tasks/tests.yml b/test/integration/targets/group/tasks/tests.yml index f9a8122..eb92cd1 100644 --- a/test/integration/targets/group/tasks/tests.yml +++ b/test/integration/targets/group/tasks/tests.yml @@ -1,343 +1,412 @@ --- -## -## group add -## - -- name: create group (check mode) - group: - name: ansibullgroup - state: present - register: create_group_check - check_mode: True - -- name: get result of create group (check mode) - script: 'grouplist.sh "{{ ansible_distribution }}"' - register: create_group_actual_check - -- name: assert create group (check mode) - assert: - that: - - create_group_check is changed - - '"ansibullgroup" not in create_group_actual_check.stdout_lines' - -- name: create group - group: - name: ansibullgroup - state: present - register: create_group - -- name: get result of create group - script: 'grouplist.sh "{{ ansible_distribution }}"' - register: create_group_actual - -- name: assert create group - assert: - that: - - create_group is changed - - create_group.gid is defined - - '"ansibullgroup" in create_group_actual.stdout_lines' - -- name: create group (idempotent) +- name: ensure test groups are deleted before the test group: - name: ansibullgroup - state: present - register: create_group_again + name: '{{ item }}' + state: absent + loop: + - ansibullgroup + - ansibullgroup2 + - ansibullgroup3 -- name: assert create group (idempotent) - assert: - that: - - not create_group_again is changed +- block: + ## + ## group add + ## -## -## group check -## + - name: create group (check mode) + group: + name: ansibullgroup + state: present + register: create_group_check + check_mode: true -- name: run existing group check tests - group: - name: "{{ create_group_actual.stdout_lines|random }}" - state: present - with_sequence: start=1 end=5 - register: group_test1 - -- name: validate results for testcase 1 - assert: - that: - - group_test1.results is defined - - group_test1.results|length == 5 - -- name: validate change results for testcase 1 - assert: - that: - - not group_test1 is changed - -## -## group add with gid -## - -- name: get the next available gid - script: gidget.py - args: - executable: '{{ ansible_python_interpreter }}' - register: gid - -- name: create a group with a gid (check mode) - group: - name: ansibullgroup2 - gid: '{{ gid.stdout_lines[0] }}' - state: present - register: create_group_gid_check - check_mode: True - -- name: get result of create a group with a gid (check mode) - script: 'grouplist.sh "{{ ansible_distribution }}"' - register: create_group_gid_actual_check - -- name: assert create group with a gid (check mode) - assert: - that: - - create_group_gid_check is changed - - '"ansibullgroup2" not in create_group_gid_actual_check.stdout_lines' - -- name: create a group with a gid - group: - name: ansibullgroup2 - gid: '{{ gid.stdout_lines[0] }}' - state: present - register: create_group_gid - -- name: get gid of created group - command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('ansibullgroup2').gr_gid)\"" - register: create_group_gid_actual - -- name: assert create group with a gid - assert: - that: - - create_group_gid is changed - - create_group_gid.gid | int == gid.stdout_lines[0] | int - - create_group_gid_actual.stdout | trim | int == gid.stdout_lines[0] | int - -- name: create a group with a gid (idempotent) - group: - name: ansibullgroup2 - gid: '{{ gid.stdout_lines[0] }}' - state: present - register: create_group_gid_again + - name: get result of create group (check mode) + script: 'grouplist.sh "{{ ansible_distribution }}"' + register: create_group_actual_check -- name: assert create group with a gid (idempotent) - assert: - that: - - not create_group_gid_again is changed - - create_group_gid_again.gid | int == gid.stdout_lines[0] | int + - name: assert create group (check mode) + assert: + that: + - create_group_check is changed + - '"ansibullgroup" not in create_group_actual_check.stdout_lines' -- block: - - name: create a group with a non-unique gid + - name: create group group: - name: ansibullgroup3 - gid: '{{ gid.stdout_lines[0] }}' - non_unique: true + name: ansibullgroup state: present - register: create_group_gid_non_unique + register: create_group - - name: validate gid required with non_unique + - name: get result of create group + script: 'grouplist.sh "{{ ansible_distribution }}"' + register: create_group_actual + + - name: assert create group + assert: + that: + - create_group is changed + - create_group.gid is defined + - '"ansibullgroup" in create_group_actual.stdout_lines' + + - name: create group (idempotent) group: - name: foo - non_unique: true - register: missing_gid - ignore_errors: true + name: ansibullgroup + state: present + register: create_group_again - - name: assert create group with a non unique gid + - name: assert create group (idempotent) assert: that: - - create_group_gid_non_unique is changed - - create_group_gid_non_unique.gid | int == gid.stdout_lines[0] | int - - missing_gid is failed - when: ansible_facts.distribution not in ['MacOSX', 'Alpine'] + - not create_group_again is changed -## -## group remove -## + ## + ## group check + ## -- name: delete group (check mode) - group: - name: ansibullgroup - state: absent - register: delete_group_check - check_mode: True + - name: run existing group check tests + group: + name: "{{ create_group_actual.stdout_lines|random }}" + state: present + with_sequence: start=1 end=5 + register: group_test1 -- name: get result of delete group (check mode) - script: grouplist.sh "{{ ansible_distribution }}" - register: delete_group_actual_check + - name: validate results for testcase 1 + assert: + that: + - group_test1.results is defined + - group_test1.results|length == 5 -- name: assert delete group (check mode) - assert: - that: - - delete_group_check is changed - - '"ansibullgroup" in delete_group_actual_check.stdout_lines' + - name: validate change results for testcase 1 + assert: + that: + - not group_test1 is changed -- name: delete group - group: - name: ansibullgroup - state: absent - register: delete_group + ## + ## group add with gid + ## -- name: get result of delete group - script: grouplist.sh "{{ ansible_distribution }}" - register: delete_group_actual + - name: get the next available gid + script: get_free_gid.py + args: + executable: '{{ ansible_python_interpreter }}' + register: gid -- name: assert delete group - assert: - that: - - delete_group is changed - - '"ansibullgroup" not in delete_group_actual.stdout_lines' + - name: create a group with a gid (check mode) + group: + name: ansibullgroup2 + gid: '{{ gid.stdout_lines[0] }}' + state: present + register: create_group_gid_check + check_mode: true -- name: delete group (idempotent) - group: - name: ansibullgroup - state: absent - register: delete_group_again - -- name: assert delete group (idempotent) - assert: - that: - - not delete_group_again is changed - -- name: Ensure lgroupadd is present - action: "{{ ansible_facts.pkg_mgr }}" - args: - name: libuser - state: present - when: ansible_facts.system in ['Linux'] and ansible_distribution != 'Alpine' and ansible_os_family != 'Suse' - tags: - - user_test_local_mode - -- name: Ensure lgroupadd is present - Alpine - command: apk add -U libuser - when: ansible_distribution == 'Alpine' - tags: - - user_test_local_mode - -# https://github.com/ansible/ansible/issues/56481 -- block: - - name: Test duplicate GID with local=yes - group: - name: "{{ item }}" - gid: 1337 - local: yes - loop: - - group1_local_test - - group2_local_test - ignore_errors: yes - register: local_duplicate_gid_result - - - assert: - that: - - local_duplicate_gid_result['results'][0] is success - - local_duplicate_gid_result['results'][1]['msg'] == "GID '1337' already exists with group 'group1_local_test'" - always: - - name: Cleanup + - name: get result of create a group with a gid (check mode) + script: 'grouplist.sh "{{ ansible_distribution }}"' + register: create_group_gid_actual_check + + - name: assert create group with a gid (check mode) + assert: + that: + - create_group_gid_check is changed + - '"ansibullgroup2" not in create_group_gid_actual_check.stdout_lines' + + - name: create a group with a gid group: - name: group1_local_test - state: absent - # only applicable to Linux, limit further to CentOS where 'luseradd' is installed - when: ansible_distribution == 'CentOS' + name: ansibullgroup2 + gid: '{{ gid.stdout_lines[0] }}' + state: present + register: create_group_gid -# https://github.com/ansible/ansible/pull/59769 -- block: - - name: create a local group with a gid - group: - name: group1_local_test - gid: 1337 - local: yes - state: present - register: create_local_group_gid - - - name: get gid of created local group - command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('group1_local_test').gr_gid)\"" - register: create_local_group_gid_actual - - - name: assert create local group with a gid - assert: + - name: get gid of created group + script: "get_gid_for_group.py ansibullgroup2" + args: + executable: '{{ ansible_python_interpreter }}' + register: create_group_gid_actual + + - name: assert create group with a gid + assert: that: - - create_local_group_gid is changed - - create_local_group_gid.gid | int == 1337 | int - - create_local_group_gid_actual.stdout | trim | int == 1337 | int - - - name: create a local group with a gid (idempotent) - group: - name: group1_local_test - gid: 1337 - state: present - register: create_local_group_gid_again - - - name: assert create local group with a gid (idempotent) - assert: + - create_group_gid is changed + - create_group_gid.gid | int == gid.stdout_lines[0] | int + - create_group_gid_actual.stdout | trim | int == gid.stdout_lines[0] | int + + - name: create a group with a gid (idempotent) + group: + name: ansibullgroup2 + gid: '{{ gid.stdout_lines[0] }}' + state: present + register: create_group_gid_again + + - name: assert create group with a gid (idempotent) + assert: that: - - not create_local_group_gid_again is changed - - create_local_group_gid_again.gid | int == 1337 | int - always: - - name: Cleanup create local group with a gid + - not create_group_gid_again is changed + - create_group_gid_again.gid | int == gid.stdout_lines[0] | int + + - block: + - name: create a group with a non-unique gid + group: + name: ansibullgroup3 + gid: '{{ gid.stdout_lines[0] }}' + non_unique: true + state: present + register: create_group_gid_non_unique + + - name: validate gid required with non_unique + group: + name: foo + non_unique: true + register: missing_gid + ignore_errors: true + + - name: assert create group with a non unique gid + assert: + that: + - create_group_gid_non_unique is changed + - create_group_gid_non_unique.gid | int == gid.stdout_lines[0] | int + - missing_gid is failed + when: ansible_facts.distribution not in ['MacOSX', 'Alpine'] + + ## + ## group remove + ## + + - name: delete group (check mode) group: - name: group1_local_test + name: ansibullgroup state: absent - # only applicable to Linux, limit further to CentOS where 'luseradd' is installed - when: ansible_distribution == 'CentOS' + register: delete_group_check + check_mode: true -# https://github.com/ansible/ansible/pull/59772 -- block: - - name: create group with a gid - group: - name: group1_test - gid: 1337 - local: no - state: present - register: create_group_gid - - - name: get gid of created group - command: "{{ ansible_python_interpreter | quote }} -c \"import grp; print(grp.getgrnam('group1_test').gr_gid)\"" - register: create_group_gid_actual - - - name: assert create group with a gid - assert: - that: - - create_group_gid is changed - - create_group_gid.gid | int == 1337 | int - - create_group_gid_actual.stdout | trim | int == 1337 | int - - - name: create local group with the same gid - group: - name: group1_test - gid: 1337 - local: yes - state: present - register: create_local_group_gid - - - name: assert create local group with a gid - assert: + - name: get result of delete group (check mode) + script: 'grouplist.sh "{{ ansible_distribution }}"' + register: delete_group_actual_check + + - name: assert delete group (check mode) + assert: that: - - create_local_group_gid.gid | int == 1337 | int - always: - - name: Cleanup create group with a gid + - delete_group_check is changed + - '"ansibullgroup" in delete_group_actual_check.stdout_lines' + + - name: delete group group: - name: group1_test - local: no + name: ansibullgroup state: absent - - name: Cleanup create local group with the same gid + register: delete_group + + - name: get result of delete group + script: 'grouplist.sh "{{ ansible_distribution }}"' + register: delete_group_actual + + - name: assert delete group + assert: + that: + - delete_group is changed + - '"ansibullgroup" not in delete_group_actual.stdout_lines' + + - name: delete group (idempotent) group: - name: group1_test - local: yes + name: ansibullgroup state: absent - # only applicable to Linux, limit further to CentOS where 'lgroupadd' is installed - when: ansible_distribution == 'CentOS' + register: delete_group_again -# create system group + - name: assert delete group (idempotent) + assert: + that: + - not delete_group_again is changed -- name: remove group - group: - name: ansibullgroup - state: absent + - name: Ensure lgroupadd is present + action: "{{ ansible_facts.pkg_mgr }}" + args: + name: libuser + state: present + when: ansible_facts.system in ['Linux'] and ansible_distribution != 'Alpine' and ansible_os_family != 'Suse' + tags: + - user_test_local_mode + + - name: Ensure lgroupadd is present - Alpine + command: apk add -U libuser + when: ansible_distribution == 'Alpine' + tags: + - user_test_local_mode + + # https://github.com/ansible/ansible/issues/56481 + - block: + - name: Test duplicate GID with local=yes + group: + name: "{{ item }}" + gid: 1337 + local: true + loop: + - group1_local_test + - group2_local_test + ignore_errors: true + register: local_duplicate_gid_result + + - assert: + that: + - local_duplicate_gid_result['results'][0] is success + - local_duplicate_gid_result['results'][1]['msg'] == "GID '1337' already exists with group 'group1_local_test'" + always: + - name: Cleanup + group: + name: group1_local_test + state: absent + # only applicable to Linux, limit further to CentOS where 'luseradd' is installed + when: ansible_distribution == 'CentOS' + + # https://github.com/ansible/ansible/pull/59769 + - block: + - name: create a local group with a gid + group: + name: group1_local_test + gid: 1337 + local: true + state: present + register: create_local_group_gid + + - name: get gid of created local group + script: "get_gid_for_group.py group1_local_test" + args: + executable: '{{ ansible_python_interpreter }}' + register: create_local_group_gid_actual + + - name: assert create local group with a gid + assert: + that: + - create_local_group_gid is changed + - create_local_group_gid.gid | int == 1337 | int + - create_local_group_gid_actual.stdout | trim | int == 1337 | int + + - name: create a local group with a gid (idempotent) + group: + name: group1_local_test + gid: 1337 + state: present + register: create_local_group_gid_again + + - name: assert create local group with a gid (idempotent) + assert: + that: + - not create_local_group_gid_again is changed + - create_local_group_gid_again.gid | int == 1337 | int + always: + - name: Cleanup create local group with a gid + group: + name: group1_local_test + state: absent + # only applicable to Linux, limit further to CentOS where 'luseradd' is installed + when: ansible_distribution == 'CentOS' + + # https://github.com/ansible/ansible/pull/59772 + - block: + - name: create group with a gid + group: + name: group1_test + gid: 1337 + local: false + state: present + register: create_group_gid + + - name: get gid of created group + script: "get_gid_for_group.py group1_test" + args: + executable: '{{ ansible_python_interpreter }}' + register: create_group_gid_actual + + - name: assert create group with a gid + assert: + that: + - create_group_gid is changed + - create_group_gid.gid | int == 1337 | int + - create_group_gid_actual.stdout | trim | int == 1337 | int + + - name: create local group with the same gid + group: + name: group1_test + gid: 1337 + local: true + state: present + register: create_local_group_gid + + - name: assert create local group with a gid + assert: + that: + - create_local_group_gid.gid | int == 1337 | int + always: + - name: Cleanup create group with a gid + group: + name: group1_test + local: false + state: absent + - name: Cleanup create local group with the same gid + group: + name: group1_test + local: true + state: absent + # only applicable to Linux, limit further to CentOS where 'lgroupadd' is installed + when: ansible_distribution == 'CentOS' + + # https://github.com/ansible/ansible/pull/78172 + - block: + - name: Create a group + group: + name: groupdeltest + state: present + + - name: Create user with primary group of groupdeltest + user: + name: groupdeluser + group: groupdeltest + state: present + + - name: Show we can't delete the group usually + group: + name: groupdeltest + state: absent + ignore_errors: true + register: failed_delete + + - name: assert we couldn't delete the group + assert: + that: + - failed_delete is failed + + - name: force delete the group + group: + name: groupdeltest + force: true + state: absent + + always: + - name: Cleanup user + user: + name: groupdeluser + state: absent + + - name: Cleanup group + group: + name: groupdeltest + state: absent + when: ansible_distribution not in ["MacOSX", "Alpine", "FreeBSD"] + + # create system group + + - name: remove group + group: + name: ansibullgroup + state: absent -- name: create system group - group: - name: ansibullgroup - state: present - system: yes + - name: create system group + group: + name: ansibullgroup + state: present + system: true + + always: + - name: remove test groups after test + group: + name: '{{ item }}' + state: absent + loop: + - ansibullgroup + - ansibullgroup2 + - ansibullgroup3 diff --git a/test/integration/targets/handlers/80880.yml b/test/integration/targets/handlers/80880.yml new file mode 100644 index 0000000..d362ea8 --- /dev/null +++ b/test/integration/targets/handlers/80880.yml @@ -0,0 +1,34 @@ +--- +- name: Test notification of handlers from other handlers + hosts: localhost + gather_facts: no + handlers: + - name: Handler 1 + debug: + msg: Handler 1 + changed_when: true + notify: Handler 2 + register: handler1_res + - name: Handler 2 + debug: + msg: Handler 2 + changed_when: true + notify: Handler 3 + register: handler2_res + - name: Handler 3 + debug: + msg: Handler 3 + register: handler3_res + tasks: + - name: Trigger handlers + ansible.builtin.debug: + msg: Task 1 + changed_when: true + notify: Handler 1 + post_tasks: + - name: Assert results + ansible.builtin.assert: + that: + - "handler1_res is defined and handler1_res is success" + - "handler2_res is defined and handler2_res is success" + - "handler3_res is defined and handler3_res is success" diff --git a/test/integration/targets/handlers/82241.yml b/test/integration/targets/handlers/82241.yml new file mode 100644 index 0000000..4a9421f --- /dev/null +++ b/test/integration/targets/handlers/82241.yml @@ -0,0 +1,6 @@ +- hosts: A + gather_facts: false + tasks: + - import_role: + name: role-82241 + tasks_from: entry_point.yml diff --git a/test/integration/targets/handlers/nested_flush_handlers_failure_force.yml b/test/integration/targets/handlers/nested_flush_handlers_failure_force.yml new file mode 100644 index 0000000..7380923 --- /dev/null +++ b/test/integration/targets/handlers/nested_flush_handlers_failure_force.yml @@ -0,0 +1,19 @@ +- hosts: A,B + gather_facts: false + force_handlers: true + tasks: + - block: + - command: echo + notify: h + + - meta: flush_handlers + rescue: + - debug: + msg: flush_handlers_rescued + always: + - debug: + msg: flush_handlers_always + handlers: + - name: h + fail: + when: inventory_hostname == "A" diff --git a/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/include_handlers.yml b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/include_handlers.yml new file mode 100644 index 0000000..f39ac4f --- /dev/null +++ b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/include_handlers.yml @@ -0,0 +1,2 @@ +- debug: + msg: handler ran diff --git a/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/main.yml b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/main.yml new file mode 100644 index 0000000..4ce8a3f --- /dev/null +++ b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/handlers/main.yml @@ -0,0 +1,2 @@ +- name: handler + include_tasks: include_handlers.yml diff --git a/test/integration/targets/handlers/roles/include_role_include_tasks_handler/tasks/main.yml b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/tasks/main.yml new file mode 100644 index 0000000..50aec1c --- /dev/null +++ b/test/integration/targets/handlers/roles/include_role_include_tasks_handler/tasks/main.yml @@ -0,0 +1,2 @@ +- command: echo + notify: handler diff --git a/test/integration/targets/handlers/roles/r1-dep_chain-vars/defaults/main.yml b/test/integration/targets/handlers/roles/r1-dep_chain-vars/defaults/main.yml new file mode 100644 index 0000000..555ff0e --- /dev/null +++ b/test/integration/targets/handlers/roles/r1-dep_chain-vars/defaults/main.yml @@ -0,0 +1 @@ +v: foo diff --git a/test/integration/targets/handlers/roles/r1-dep_chain-vars/tasks/main.yml b/test/integration/targets/handlers/roles/r1-dep_chain-vars/tasks/main.yml new file mode 100644 index 0000000..72576a0 --- /dev/null +++ b/test/integration/targets/handlers/roles/r1-dep_chain-vars/tasks/main.yml @@ -0,0 +1,2 @@ +- include_role: + name: r2-dep_chain-vars diff --git a/test/integration/targets/handlers/roles/r2-dep_chain-vars/handlers/main.yml b/test/integration/targets/handlers/roles/r2-dep_chain-vars/handlers/main.yml new file mode 100644 index 0000000..88f1248 --- /dev/null +++ b/test/integration/targets/handlers/roles/r2-dep_chain-vars/handlers/main.yml @@ -0,0 +1,4 @@ +- name: h + assert: + that: + - v is defined diff --git a/test/integration/targets/handlers/roles/r2-dep_chain-vars/tasks/main.yml b/test/integration/targets/handlers/roles/r2-dep_chain-vars/tasks/main.yml new file mode 100644 index 0000000..72eae5d --- /dev/null +++ b/test/integration/targets/handlers/roles/r2-dep_chain-vars/tasks/main.yml @@ -0,0 +1,2 @@ +- command: echo + notify: h diff --git a/test/integration/targets/handlers/roles/role-82241/handlers/main.yml b/test/integration/targets/handlers/roles/role-82241/handlers/main.yml new file mode 100644 index 0000000..ad59b96 --- /dev/null +++ b/test/integration/targets/handlers/roles/role-82241/handlers/main.yml @@ -0,0 +1,2 @@ +- name: handler + include_tasks: included_tasks.yml diff --git a/test/integration/targets/handlers/roles/role-82241/tasks/entry_point.yml b/test/integration/targets/handlers/roles/role-82241/tasks/entry_point.yml new file mode 100644 index 0000000..50aec1c --- /dev/null +++ b/test/integration/targets/handlers/roles/role-82241/tasks/entry_point.yml @@ -0,0 +1,2 @@ +- command: echo + notify: handler diff --git a/test/integration/targets/handlers/roles/role-82241/tasks/included_tasks.yml b/test/integration/targets/handlers/roles/role-82241/tasks/included_tasks.yml new file mode 100644 index 0000000..e3ffeb7 --- /dev/null +++ b/test/integration/targets/handlers/roles/role-82241/tasks/included_tasks.yml @@ -0,0 +1,2 @@ +- debug: + msg: included_task_from_tasks_dir diff --git a/test/integration/targets/handlers/roles/test_listen_role_dedup_global/handlers/main.yml b/test/integration/targets/handlers/roles/test_listen_role_dedup_global/handlers/main.yml new file mode 100644 index 0000000..6ce84e4 --- /dev/null +++ b/test/integration/targets/handlers/roles/test_listen_role_dedup_global/handlers/main.yml @@ -0,0 +1,4 @@ +- name: role_handler + debug: + msg: "a handler from a role" + listen: role_handler diff --git a/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/meta/main.yml b/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/meta/main.yml new file mode 100644 index 0000000..b6a70c2 --- /dev/null +++ b/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - test_listen_role_dedup_global diff --git a/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/tasks/main.yml b/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/tasks/main.yml new file mode 100644 index 0000000..42911e5 --- /dev/null +++ b/test/integration/targets/handlers/roles/test_listen_role_dedup_role1/tasks/main.yml @@ -0,0 +1,3 @@ +- name: a task from role1 + command: echo + notify: role_handler diff --git a/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/meta/main.yml b/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/meta/main.yml new file mode 100644 index 0000000..b6a70c2 --- /dev/null +++ b/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - test_listen_role_dedup_global diff --git a/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/tasks/main.yml b/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/tasks/main.yml new file mode 100644 index 0000000..3d5e544 --- /dev/null +++ b/test/integration/targets/handlers/roles/test_listen_role_dedup_role2/tasks/main.yml @@ -0,0 +1,3 @@ +- name: a task from role2 + command: echo + notify: role_handler diff --git a/test/integration/targets/handlers/roles/two_tasks_files_role/handlers/main.yml b/test/integration/targets/handlers/roles/two_tasks_files_role/handlers/main.yml new file mode 100644 index 0000000..3fd1318 --- /dev/null +++ b/test/integration/targets/handlers/roles/two_tasks_files_role/handlers/main.yml @@ -0,0 +1,3 @@ +- name: handler + debug: + msg: handler ran diff --git a/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/main.yml b/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/main.yml new file mode 100644 index 0000000..e6c1239 --- /dev/null +++ b/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/main.yml @@ -0,0 +1,3 @@ +- name: main.yml task + command: echo + notify: handler diff --git a/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/other.yml b/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/other.yml new file mode 100644 index 0000000..d90d46e --- /dev/null +++ b/test/integration/targets/handlers/roles/two_tasks_files_role/tasks/other.yml @@ -0,0 +1,3 @@ +- name: other.yml task + command: echo + notify: handler diff --git a/test/integration/targets/handlers/runme.sh b/test/integration/targets/handlers/runme.sh index 76fc99d..368ca44 100755 --- a/test/integration/targets/handlers/runme.sh +++ b/test/integration/targets/handlers/runme.sh @@ -50,6 +50,9 @@ for strategy in linear free; do [ "$(ansible-playbook test_force_handlers.yml -i inventory.handlers -v "$@" --tags force_false_in_play --force-handlers \ | grep -E -o CALLED_HANDLER_. | sort | uniq | xargs)" = "CALLED_HANDLER_B" ] + # https://github.com/ansible/ansible/pull/80898 + [ "$(ansible-playbook 80880.yml -i inventory.handlers -vv "$@" 2>&1)" ] + unset ANSIBLE_STRATEGY done @@ -66,6 +69,9 @@ done # Notify handler listen ansible-playbook test_handlers_listen.yml -i inventory.handlers -v "$@" +# https://github.com/ansible/ansible/issues/82363 +ansible-playbook test_multiple_handlers_with_recursive_notification.yml -i inventory.handlers -v "$@" + # Notify inexistent handlers results in error set +e result="$(ansible-playbook test_handlers_inexistent_notify.yml -i inventory.handlers "$@" 2>&1)" @@ -181,3 +187,24 @@ grep out.txt -e "ERROR! Using a block as a handler is not supported." ansible-playbook test_block_as_handler-import.yml "$@" 2>&1 | tee out.txt grep out.txt -e "ERROR! Using a block as a handler is not supported." + +ansible-playbook test_include_role_handler_once.yml -i inventory.handlers "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'handler ran')" = "1" ] + +ansible-playbook test_listen_role_dedup.yml "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'a handler from a role')" = "1" ] + +ansible localhost -m include_role -a "name=r1-dep_chain-vars" "$@" + +ansible-playbook test_include_tasks_in_include_role.yml "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'handler ran')" = "1" ] + +ansible-playbook test_run_once.yml -i inventory.handlers "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'handler ran once')" = "1" ] + +ansible-playbook 82241.yml -i inventory.handlers "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'included_task_from_tasks_dir')" = "1" ] + +ansible-playbook nested_flush_handlers_failure_force.yml -i inventory.handlers "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'flush_handlers_rescued')" = "1" ] +[ "$(grep out.txt -ce 'flush_handlers_always')" = "2" ] diff --git a/test/integration/targets/handlers/test_include_role_handler_once.yml b/test/integration/targets/handlers/test_include_role_handler_once.yml new file mode 100644 index 0000000..764aef6 --- /dev/null +++ b/test/integration/targets/handlers/test_include_role_handler_once.yml @@ -0,0 +1,20 @@ +- hosts: localhost + gather_facts: false + tasks: + - name: "Call main entry point" + include_role: + name: two_tasks_files_role + + - name: "Call main entry point again" + include_role: + name: two_tasks_files_role + + - name: "Call other entry point" + include_role: + name: two_tasks_files_role + tasks_from: other + + - name: "Call other entry point again" + include_role: + name: two_tasks_files_role + tasks_from: other diff --git a/test/integration/targets/handlers/test_include_tasks_in_include_role.yml b/test/integration/targets/handlers/test_include_tasks_in_include_role.yml new file mode 100644 index 0000000..405e4b5 --- /dev/null +++ b/test/integration/targets/handlers/test_include_tasks_in_include_role.yml @@ -0,0 +1,5 @@ +- hosts: localhost + gather_facts: false + tasks: + - include_role: + name: include_role_include_tasks_handler diff --git a/test/integration/targets/handlers/test_listen_role_dedup.yml b/test/integration/targets/handlers/test_listen_role_dedup.yml new file mode 100644 index 0000000..508eaf5 --- /dev/null +++ b/test/integration/targets/handlers/test_listen_role_dedup.yml @@ -0,0 +1,5 @@ +- hosts: localhost + gather_facts: false + roles: + - test_listen_role_dedup_role1 + - test_listen_role_dedup_role2 diff --git a/test/integration/targets/handlers/test_multiple_handlers_with_recursive_notification.yml b/test/integration/targets/handlers/test_multiple_handlers_with_recursive_notification.yml new file mode 100644 index 0000000..c4b6983 --- /dev/null +++ b/test/integration/targets/handlers/test_multiple_handlers_with_recursive_notification.yml @@ -0,0 +1,36 @@ +--- +- name: test multiple handlers with recursive notification + hosts: localhost + gather_facts: false + + tasks: + - name: notify handler 1 + command: echo + changed_when: true + notify: handler 1 + + - meta: flush_handlers + + - name: verify handlers + assert: + that: + - "ran_handler_1 is defined" + - "ran_handler_2a is defined" + - "ran_handler_2b is defined" + + handlers: + - name: handler 1 + set_fact: + ran_handler_1: True + changed_when: true + notify: handler_2 + + - name: handler 2a + set_fact: + ran_handler_2a: True + listen: handler_2 + + - name: handler 2b + set_fact: + ran_handler_2b: True + listen: handler_2 diff --git a/test/integration/targets/handlers/test_run_once.yml b/test/integration/targets/handlers/test_run_once.yml new file mode 100644 index 0000000..5418b46 --- /dev/null +++ b/test/integration/targets/handlers/test_run_once.yml @@ -0,0 +1,10 @@ +- hosts: A,B,C + gather_facts: false + tasks: + - command: echo + notify: handler + handlers: + - name: handler + run_once: true + debug: + msg: handler ran once diff --git a/test/integration/targets/include_vars/files/test_depth/sub1/sub11.yml b/test/integration/targets/include_vars/files/test_depth/sub1/sub11.yml new file mode 100644 index 0000000..9a5ecb8 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub1/sub11.yml @@ -0,0 +1 @@ +sub11: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config11.yml b/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config11.yml new file mode 100644 index 0000000..02c2897 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config11.yml @@ -0,0 +1 @@ +config11: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config112.yml b/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config112.yml new file mode 100644 index 0000000..e8bc9d9 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub1/sub11/config112.yml @@ -0,0 +1 @@ +config112: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub1/sub12.yml b/test/integration/targets/include_vars/files/test_depth/sub1/sub12.yml new file mode 100644 index 0000000..9aff287 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub1/sub12.yml @@ -0,0 +1 @@ +sub12: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub2/sub21.yml b/test/integration/targets/include_vars/files/test_depth/sub2/sub21.yml new file mode 100644 index 0000000..1f7c455 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub2/sub21.yml @@ -0,0 +1 @@ +sub21: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config211.yml b/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config211.yml new file mode 100644 index 0000000..a5126a7 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config211.yml @@ -0,0 +1 @@ +config211: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config212.yml b/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config212.yml new file mode 100644 index 0000000..633841d --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub2/sub21/config212.yml @@ -0,0 +1 @@ +config212: defined diff --git a/test/integration/targets/include_vars/files/test_depth/sub3/config3.yml b/test/integration/targets/include_vars/files/test_depth/sub3/config3.yml new file mode 100644 index 0000000..d6a8192 --- /dev/null +++ b/test/integration/targets/include_vars/files/test_depth/sub3/config3.yml @@ -0,0 +1 @@ +config3: defined diff --git a/test/integration/targets/include_vars/tasks/main.yml b/test/integration/targets/include_vars/tasks/main.yml index 6fc4e85..97636d9 100644 --- a/test/integration/targets/include_vars/tasks/main.yml +++ b/test/integration/targets/include_vars/tasks/main.yml @@ -208,6 +208,21 @@ - "config.key2.b == 22" - "config.key3 == 3" +- name: Include a vars dir with hash variables + include_vars: + dir: "{{ role_path }}/vars2/hashes/" + hash_behaviour: merge + +- name: Verify that the hash is merged after vars files are accumulated + assert: + that: + - "config | length == 3" + - "config.key0 is undefined" + - "config.key1 == 1" + - "config.key2 | length == 1" + - "config.key2.b == 22" + - "config.key3 == 3" + - include_vars: file: no_auto_unsafe.yml register: baz @@ -215,3 +230,40 @@ - assert: that: - baz.ansible_facts.foo|type_debug != "AnsibleUnsafeText" + +- name: setup test following symlinks + delegate_to: localhost + block: + - name: create directory to test following symlinks + file: + path: "{{ role_path }}/test_symlink" + state: directory + + - name: create symlink to the vars2 dir + file: + src: "{{ role_path }}/vars2" + dest: "{{ role_path }}/test_symlink/symlink" + state: link + +- name: include vars by following the symlink + include_vars: + dir: "{{ role_path }}/test_symlink" + register: follow_sym + +- assert: + that: follow_sym.ansible_included_var_files | sort == [hash1, hash2] + vars: + hash1: "{{ role_path }}/test_symlink/symlink/hashes/hash1.yml" + hash2: "{{ role_path }}/test_symlink/symlink/hashes/hash2.yml" + +- name: Test include_vars includes everything to the correct depth + ansible.builtin.include_vars: + dir: "{{ role_path }}/files/test_depth" + depth: 3 + name: test_depth_var + register: test_depth + +- assert: + that: + - "test_depth.ansible_included_var_files|length == 8" + - "test_depth_var.keys()|length == 8" diff --git a/test/integration/targets/include_vars/vars/services/service_vars.yml b/test/integration/targets/include_vars/vars/services/service_vars.yml index 96b05d6..bcac764 100644 --- a/test/integration/targets/include_vars/vars/services/service_vars.yml +++ b/test/integration/targets/include_vars/vars/services/service_vars.yml @@ -1,2 +1,2 @@ --- -service_name: 'my_custom_service'
\ No newline at end of file +service_name: 'my_custom_service' diff --git a/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml index 2c04fee..cd82eca 100644 --- a/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml +++ b/test/integration/targets/include_vars/vars/services/service_vars_fqcn.yml @@ -1,3 +1,3 @@ --- service_name_fqcn: 'my_custom_service' -service_name_tmpl_fqcn: '{{ service_name_fqcn }}'
\ No newline at end of file +service_name_tmpl_fqcn: '{{ service_name_fqcn }}' diff --git a/test/integration/targets/include_when_parent_is_dynamic/tasks.yml b/test/integration/targets/include_when_parent_is_dynamic/tasks.yml index 6831245..d500f0d 100644 --- a/test/integration/targets/include_when_parent_is_dynamic/tasks.yml +++ b/test/integration/targets/include_when_parent_is_dynamic/tasks.yml @@ -9,4 +9,4 @@ # perform an include task which should be static if all of the task's parents are static, otherwise it should be dynamic # this file was loaded using include_tasks, which is dynamic, so this include should also be dynamic -- include: syntax_error.yml +- include_tasks: syntax_error.yml diff --git a/test/integration/targets/include_when_parent_is_static/tasks.yml b/test/integration/targets/include_when_parent_is_static/tasks.yml index a234a3d..50dd234 100644 --- a/test/integration/targets/include_when_parent_is_static/tasks.yml +++ b/test/integration/targets/include_when_parent_is_static/tasks.yml @@ -9,4 +9,4 @@ # perform an include task which should be static if all of the task's parents are static, otherwise it should be dynamic # this file was loaded using import_tasks, which is static, so this include should also be static -- include: syntax_error.yml +- import_tasks: syntax_error.yml diff --git a/test/integration/targets/includes/include_on_playbook_should_fail.yml b/test/integration/targets/includes/include_on_playbook_should_fail.yml index 953459d..c9b1e81 100644 --- a/test/integration/targets/includes/include_on_playbook_should_fail.yml +++ b/test/integration/targets/includes/include_on_playbook_should_fail.yml @@ -1 +1 @@ -- include: test_includes3.yml +- include_tasks: test_includes3.yml diff --git a/test/integration/targets/includes/roles/test_includes/handlers/main.yml b/test/integration/targets/includes/roles/test_includes/handlers/main.yml index 7d3e625..453fa96 100644 --- a/test/integration/targets/includes/roles/test_includes/handlers/main.yml +++ b/test/integration/targets/includes/roles/test_includes/handlers/main.yml @@ -1 +1 @@ -- include: more_handlers.yml +- import_tasks: more_handlers.yml diff --git a/test/integration/targets/includes/roles/test_includes/tasks/main.yml b/test/integration/targets/includes/roles/test_includes/tasks/main.yml index 83ca468..2ba1ae6 100644 --- a/test/integration/targets/includes/roles/test_includes/tasks/main.yml +++ b/test/integration/targets/includes/roles/test_includes/tasks/main.yml @@ -17,47 +17,9 @@ # along with Ansible. If not, see <http://www.gnu.org/licenses/>. -- include: included_task1.yml a=1 b=2 c=3 - -- name: verify non-variable include params - assert: - that: - - "ca == '1'" - - "cb == '2'" - - "cc == '3'" - -- set_fact: - a: 101 - b: 102 - c: 103 - -- include: included_task1.yml a={{a}} b={{b}} c=103 - -- name: verify variable include params - assert: - that: - - "ca == 101" - - "cb == 102" - - "cc == 103" - -# Test that strings are not turned into numbers -- set_fact: - a: "101" - b: "102" - c: "103" - -- include: included_task1.yml a={{a}} b={{b}} c=103 - -- name: verify variable include params - assert: - that: - - "ca == '101'" - - "cb == '102'" - - "cc == '103'" - # now try long form includes -- include: included_task1.yml +- include_tasks: included_task1.yml vars: a: 201 b: 202 diff --git a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml index 5ae7882..d7bcf8e 100644 --- a/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml +++ b/test/integration/targets/includes/roles/test_includes_free/tasks/main.yml @@ -1,9 +1,9 @@ - name: this needs to be here debug: msg: "hello" -- include: inner.yml +- include_tasks: inner.yml with_items: - '1' -- ansible.builtin.include: inner_fqcn.yml +- ansible.builtin.include_tasks: inner_fqcn.yml with_items: - '1' diff --git a/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml b/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml index 7bc19fa..c06d3fe 100644 --- a/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml +++ b/test/integration/targets/includes/roles/test_includes_host_pinned/tasks/main.yml @@ -1,6 +1,6 @@ - name: this needs to be here debug: msg: "hello" -- include: inner.yml +- include_tasks: inner.yml with_items: - '1' diff --git a/test/integration/targets/includes/runme.sh b/test/integration/targets/includes/runme.sh index e619fea..8622cf6 100755 --- a/test/integration/targets/includes/runme.sh +++ b/test/integration/targets/includes/runme.sh @@ -10,7 +10,7 @@ echo "EXPECTED ERROR: Ensure we fail if using 'include' to include a playbook." set +e result="$(ansible-playbook -i ../../inventory include_on_playbook_should_fail.yml -v "$@" 2>&1)" set -e -grep -q "ERROR! 'include' is not a valid attribute for a Play" <<< "$result" +grep -q "ERROR! 'include_tasks' is not a valid attribute for a Play" <<< "$result" ansible-playbook includes_loop_rescue.yml --extra-vars strategy=linear "$@" ansible-playbook includes_loop_rescue.yml --extra-vars strategy=free "$@" diff --git a/test/integration/targets/includes/test_includes2.yml b/test/integration/targets/includes/test_includes2.yml index a32e851..da6b914 100644 --- a/test/integration/targets/includes/test_includes2.yml +++ b/test/integration/targets/includes/test_includes2.yml @@ -13,8 +13,8 @@ - role: test_includes tags: test_includes tasks: - - include: roles/test_includes/tasks/not_a_role_task.yml - - include: roles/test_includes/tasks/empty.yml + - include_tasks: roles/test_includes/tasks/not_a_role_task.yml + - include_tasks: roles/test_includes/tasks/empty.yml - assert: that: - "ca == 33000" diff --git a/test/integration/targets/includes/test_includes3.yml b/test/integration/targets/includes/test_includes3.yml index 0b4c631..f3c4964 100644 --- a/test/integration/targets/includes/test_includes3.yml +++ b/test/integration/targets/includes/test_includes3.yml @@ -1,6 +1,6 @@ - hosts: testhost tasks: - - include: test_includes4.yml + - include_tasks: test_includes4.yml with_items: ["a"] loop_control: loop_var: r diff --git a/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py b/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py index 7ca445a..43cad4f 100644 --- a/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py +++ b/test/integration/targets/inventory/inventory_plugins/contructed_with_hostvars.py @@ -14,7 +14,7 @@ DOCUMENTATION = ''' ''' from ansible.errors import AnsibleParserError -from ansible.module_utils._text import to_native +from ansible.module_utils.common.text.converters import to_native from ansible.plugins.inventory import BaseInventoryPlugin, Constructable diff --git a/test/integration/targets/inventory_ini/inventory.ini b/test/integration/targets/inventory_ini/inventory.ini index a0c99ad..a5de421 100644 --- a/test/integration/targets/inventory_ini/inventory.ini +++ b/test/integration/targets/inventory_ini/inventory.ini @@ -1,3 +1,5 @@ +gitlab-runner-01 ansible_host=gitlab-runner-01.internal.example.net ansible_user=root + [local] testhost ansible_connection=local ansible_become=no ansible_become_user=ansibletest1 diff --git a/test/integration/targets/inventory_ini/runme.sh b/test/integration/targets/inventory_ini/runme.sh index 81bf147..919e188 100755 --- a/test/integration/targets/inventory_ini/runme.sh +++ b/test/integration/targets/inventory_ini/runme.sh @@ -3,3 +3,6 @@ set -eux ansible-playbook -v -i inventory.ini test_ansible_become.yml + +ansible-inventory -v -i inventory.ini --list 2> out +test "$(grep -c 'SyntaxWarning' out)" -eq 0 diff --git a/test/integration/targets/iptables/aliases b/test/integration/targets/iptables/aliases index 7d66ecf..73df8aa 100644 --- a/test/integration/targets/iptables/aliases +++ b/test/integration/targets/iptables/aliases @@ -1,5 +1,4 @@ shippable/posix/group2 skip/freebsd -skip/osx skip/macos skip/docker diff --git a/test/integration/targets/iptables/tasks/chain_management.yml b/test/integration/targets/iptables/tasks/chain_management.yml index 0355122..dae4103 100644 --- a/test/integration/targets/iptables/tasks/chain_management.yml +++ b/test/integration/targets/iptables/tasks/chain_management.yml @@ -45,6 +45,26 @@ - result is not failed - '"FOOBAR-CHAIN" in result.stdout' +- name: add rule to foobar chain + become: true + iptables: + chain: FOOBAR-CHAIN + source: 0.0.0.0 + destination: 0.0.0.0 + jump: DROP + comment: "FOOBAR-CHAIN RULE" + +- name: get the state of the iptable rules after rule is added to foobar chain + become: true + shell: "{{ iptables_bin }} -L" + register: result + +- name: assert rule is present in foobar chain + assert: + that: + - result is not failed + - '"FOOBAR-CHAIN RULE" in result.stdout' + - name: flush the foobar chain become: true iptables: @@ -68,4 +88,3 @@ that: - result is not failed - '"FOOBAR-CHAIN" not in result.stdout' - - '"FOOBAR-RULE" not in result.stdout' diff --git a/test/integration/targets/known_hosts/defaults/main.yml b/test/integration/targets/known_hosts/defaults/main.yml index b1b56ac..cd43843 100644 --- a/test/integration/targets/known_hosts/defaults/main.yml +++ b/test/integration/targets/known_hosts/defaults/main.yml @@ -3,4 +3,4 @@ example_org_rsa_key: > example.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAglyZmHHWskQ9wkh8LYbIqzvg99/oloneH7BaZ02ripJUy/2Zynv4tgUfm9fdXvAb1XXCEuTRnts9FBer87+voU0FPRgx3CfY9Sgr0FspUjnm4lqs53FIab1psddAaS7/F7lrnjl6VqBtPwMRQZG7qlml5uogGJwYJHxX0PGtsdoTJsM= example_org_ed25519_key: > - example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIzlnSq5ESxLgW0avvPk3j7zLV59hcAPkxrMNdnZMKP2
\ No newline at end of file + example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIzlnSq5ESxLgW0avvPk3j7zLV59hcAPkxrMNdnZMKP2 diff --git a/test/integration/targets/known_hosts/tasks/main.yml b/test/integration/targets/known_hosts/tasks/main.yml index dc00ded..d5ffec4 100644 --- a/test/integration/targets/known_hosts/tasks/main.yml +++ b/test/integration/targets/known_hosts/tasks/main.yml @@ -99,7 +99,7 @@ # https://github.com/ansible/ansible/issues/78598 # test removing nonexistent host key when the other keys exist for the host - name: remove different key - known_hosts: + known_hosts: name: example.org key: "{{ example_org_ed25519_key }}" state: absent diff --git a/test/integration/targets/lookup-option-name/aliases b/test/integration/targets/lookup-option-name/aliases new file mode 100644 index 0000000..498fedd --- /dev/null +++ b/test/integration/targets/lookup-option-name/aliases @@ -0,0 +1,2 @@ +shippable/posix/group4 +context/controller diff --git a/test/integration/targets/lookup-option-name/tasks/main.yml b/test/integration/targets/lookup-option-name/tasks/main.yml new file mode 100644 index 0000000..4f248c8 --- /dev/null +++ b/test/integration/targets/lookup-option-name/tasks/main.yml @@ -0,0 +1,6 @@ +--- +- debug: + msg: "{{ lookup('vars', name='test') }}" + +- debug: + msg: "{{ query('vars', name='test') }}" diff --git a/test/integration/targets/lookup_config/tasks/main.yml b/test/integration/targets/lookup_config/tasks/main.yml index 356d2f8..e5699d3 100644 --- a/test/integration/targets/lookup_config/tasks/main.yml +++ b/test/integration/targets/lookup_config/tasks/main.yml @@ -42,6 +42,7 @@ - name: remote user and port for ssh connection set_fact: ssh_user_and_port: '{{q("config", "remote_user", "port", plugin_type="connection", plugin_name="ssh")}}' + ssh_user_and_port_and_origin: '{{q("config", "remote_user", "port", plugin_type="connection", plugin_name="ssh", show_origin=True)}}' vars: ansible_ssh_user: lola ansible_ssh_port: 2022 @@ -71,4 +72,5 @@ - lookup_config_7 is failed - '"Invalid setting" in lookup_config_7.msg' - ssh_user_and_port == ['lola', 2022] + - "ssh_user_and_port_and_origin == [['lola', 'var: ansible_ssh_user'], [2022, 'var: ansible_ssh_port']]" - yolo_remote == ["yolo"] diff --git a/test/integration/targets/lookup_fileglob/issue72873/test.yml b/test/integration/targets/lookup_fileglob/issue72873/test.yml index 218ee58..92d93d4 100644 --- a/test/integration/targets/lookup_fileglob/issue72873/test.yml +++ b/test/integration/targets/lookup_fileglob/issue72873/test.yml @@ -5,7 +5,7 @@ dir: files tasks: - file: path='{{ dir }}' state=directory - + - file: path='setvars.bat' state=touch # in current directory! - file: path='{{ dir }}/{{ item }}' state=touch @@ -20,11 +20,11 @@ - name: Get working order results and sort them set_fact: - working: '{{ query("fileglob", "setvars.bat", "{{ dir }}/*.[ch]") | sort }}' + working: '{{ query("fileglob", "setvars.bat", dir ~ "/*.[ch]") | sort }}' - name: Get broken order results and sort them set_fact: - broken: '{{ query("fileglob", "{{ dir }}/*.[ch]", "setvars.bat") | sort }}' + broken: '{{ query("fileglob", dir ~ "/*.[ch]", "setvars.bat") | sort }}' - assert: that: diff --git a/test/integration/targets/lookup_first_found/tasks/main.yml b/test/integration/targets/lookup_first_found/tasks/main.yml index 9aeaf1d..ba248bd 100644 --- a/test/integration/targets/lookup_first_found/tasks/main.yml +++ b/test/integration/targets/lookup_first_found/tasks/main.yml @@ -94,3 +94,56 @@ - assert: that: - foo is defined + +# TODO: no 'terms' test +- name: test first_found lookup with no terms + set_fact: + no_terms: "{{ query('first_found', files=['missing1', 'hosts', 'missing2'], paths=['/etc'], errors='ignore') }}" + +- assert: + that: "no_terms|first == '/etc/hosts'" + +- name: handle templatable dictionary entries + block: + + - name: Load variables specific for OS family + assert: + that: + - "item is file" + - "item|basename == 'itworks.yml'" + with_first_found: + - files: + - "{{ansible_id}}-{{ansible_lsb.major_release}}.yml" # invalid var, should be skipped + - "{{ansible_lsb.id}}-{{ansible_lsb.major_release}}.yml" # does not exist, but should try + - "{{ansible_distribution}}-{{ansible_distribution_major_version}}.yml" # does not exist, but should try + - itworks.yml + - ishouldnotbefound.yml # this exist, but should not be found + paths: + - "{{role_path}}/vars" + + - name: Load variables specific for OS family, but now as list of dicts, same options as above + assert: + that: + - "item is file" + - "item|basename == 'itworks.yml'" + with_first_found: + - files: + - "{{ansible_id}}-{{ansible_lsb.major_release}}.yml" + paths: + - "{{role_path}}/vars" + - files: + - "{{ansible_lsb.id}}-{{ansible_lsb.major_release}}.yml" + paths: + - "{{role_path}}/vars" + - files: + - "{{ansible_distribution}}-{{ansible_distribution_major_version}}.yml" + paths: + - "{{role_path}}/vars" + - files: + - itworks.yml + paths: + - "{{role_path}}/vars" + - files: + - ishouldnotbefound.yml + paths: + - "{{role_path}}/vars" diff --git a/test/integration/targets/lookup_first_found/vars/ishouldnotbefound.yml b/test/integration/targets/lookup_first_found/vars/ishouldnotbefound.yml new file mode 100644 index 0000000..e4cc6d5 --- /dev/null +++ b/test/integration/targets/lookup_first_found/vars/ishouldnotbefound.yml @@ -0,0 +1 @@ +really: i hide diff --git a/test/integration/targets/lookup_first_found/vars/itworks.yml b/test/integration/targets/lookup_first_found/vars/itworks.yml new file mode 100644 index 0000000..8f8a21a --- /dev/null +++ b/test/integration/targets/lookup_first_found/vars/itworks.yml @@ -0,0 +1 @@ +doesit: yes it does diff --git a/test/integration/targets/lookup_sequence/tasks/main.yml b/test/integration/targets/lookup_sequence/tasks/main.yml index bd0a4d8..e64801d 100644 --- a/test/integration/targets/lookup_sequence/tasks/main.yml +++ b/test/integration/targets/lookup_sequence/tasks/main.yml @@ -195,4 +195,4 @@ - ansible_failed_task.name == "EXPECTED FAILURE - test bad format string message" - ansible_failed_result.msg == expected vars: - expected: "bad formatting string: d"
\ No newline at end of file + expected: "bad formatting string: d" diff --git a/test/integration/targets/lookup_together/tasks/main.yml b/test/integration/targets/lookup_together/tasks/main.yml index 71365a1..115c9e5 100644 --- a/test/integration/targets/lookup_together/tasks/main.yml +++ b/test/integration/targets/lookup_together/tasks/main.yml @@ -26,4 +26,4 @@ - assert: that: - ansible_failed_task.name == "EXPECTED FAILURE - test empty list" - - ansible_failed_result.msg == "with_together requires at least one element in each list"
\ No newline at end of file + - ansible_failed_result.msg == "with_together requires at least one element in each list" diff --git a/test/integration/targets/lookup_url/aliases b/test/integration/targets/lookup_url/aliases index ef37fce..19b7d98 100644 --- a/test/integration/targets/lookup_url/aliases +++ b/test/integration/targets/lookup_url/aliases @@ -1,4 +1,11 @@ destructive shippable/posix/group3 needs/httptester -skip/macos/12.0 # This test crashes Python due to https://wefearchange.org/2018/11/forkmacos.rst.html +skip/macos # This test crashes Python due to https://wefearchange.org/2018/11/forkmacos.rst.html +# Example failure: +# +# TASK [lookup_url : Test that retrieving a url works] *************************** +# objc[15394]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. +# objc[15394]: +[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in t +# he fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug. +# ERROR! A worker was found in a dead state diff --git a/test/integration/targets/lookup_url/meta/main.yml b/test/integration/targets/lookup_url/meta/main.yml index 374b5fd..6853708 100644 --- a/test/integration/targets/lookup_url/meta/main.yml +++ b/test/integration/targets/lookup_url/meta/main.yml @@ -1,2 +1,2 @@ -dependencies: +dependencies: - prepare_http_tests diff --git a/test/integration/targets/lookup_url/tasks/main.yml b/test/integration/targets/lookup_url/tasks/main.yml index 2fb227a..83fd5db 100644 --- a/test/integration/targets/lookup_url/tasks/main.yml +++ b/test/integration/targets/lookup_url/tasks/main.yml @@ -1,6 +1,6 @@ - name: Test that retrieving a url works set_fact: - web_data: "{{ lookup('url', 'https://{{ httpbin_host }}/get?one') }}" + web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/get?one') }}" - name: Assert that the url was retrieved assert: @@ -9,7 +9,7 @@ - name: Test that retrieving a url with invalid cert fails set_fact: - web_data: "{{ lookup('url', 'https://{{ badssl_host }}/') }}" + web_data: "{{ lookup('url', 'https://' ~ badssl_host ~ '/') }}" ignore_errors: True register: url_invalid_cert @@ -20,12 +20,12 @@ - name: Test that retrieving a url with invalid cert with validate_certs=False works set_fact: - web_data: "{{ lookup('url', 'https://{{ badssl_host }}/', validate_certs=False) }}" + web_data: "{{ lookup('url', 'https://' ~ badssl_host ~ '/', validate_certs=False) }}" register: url_no_validate_cert - assert: that: - - "'{{ badssl_host_substring }}' in web_data" + - badssl_host_substring in web_data - vars: url: https://{{ httpbin_host }}/get @@ -52,3 +52,27 @@ - name: Test use_netrc=False import_tasks: use_netrc.yml + +- vars: + ansible_lookup_url_agent: ansible-test-lookup-url-agent + block: + - name: Test user agent + set_fact: + web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/user-agent') }}" + + - name: Assert that user agent is set + assert: + that: + - ansible_lookup_url_agent in web_data['user-agent'] + +- vars: + ansible_lookup_url_force_basic_auth: yes + block: + - name: Test force basic auth + set_fact: + web_data: "{{ lookup('url', 'https://' ~ httpbin_host ~ '/headers', username='abc') }}" + + - name: Assert that Authorization header is set + assert: + that: + - "'Authorization' in web_data.headers" diff --git a/test/integration/targets/lookup_url/tasks/use_netrc.yml b/test/integration/targets/lookup_url/tasks/use_netrc.yml index 68dc893..b90d05d 100644 --- a/test/integration/targets/lookup_url/tasks/use_netrc.yml +++ b/test/integration/targets/lookup_url/tasks/use_netrc.yml @@ -10,7 +10,7 @@ - name: test Url lookup with ~/.netrc forced Basic auth set_fact: - web_data: "{{ lookup('ansible.builtin.url', 'https://{{ httpbin_host }}/bearer', headers={'Authorization':'Bearer foobar'}) }}" + web_data: "{{ lookup('ansible.builtin.url', 'https://' ~ httpbin_host ~ '/bearer', headers={'Authorization':'Bearer foobar'}) }}" ignore_errors: yes - name: assert test Url lookup with ~/.netrc forced Basic auth @@ -18,11 +18,11 @@ that: - "web_data.token.find('v=' ~ 'Zm9vOmJhcg==') == -1" fail_msg: "Was expecting 'foo:bar' in base64, but received: {{ web_data }}" - success_msg: "Expected Basic authentication even Bearer headers were sent" + success_msg: "Expected Basic authentication even Bearer headers were sent" - name: test Url lookup with use_netrc=False set_fact: - web_data: "{{ lookup('ansible.builtin.url', 'https://{{ httpbin_host }}/bearer', headers={'Authorization':'Bearer foobar'}, use_netrc='False') }}" + web_data: "{{ lookup('ansible.builtin.url', 'https://' ~ httpbin_host ~ '/bearer', headers={'Authorization':'Bearer foobar'}, use_netrc='False') }}" - name: assert test Url lookup with netrc=False used Bearer authentication assert: @@ -34,4 +34,4 @@ - name: Clean up. Removing ~/.netrc file: path: ~/.netrc - state: absent
\ No newline at end of file + state: absent diff --git a/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml b/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml index 09322a9..bd892de 100644 --- a/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml +++ b/test/integration/targets/loop-connection/collections/ansible_collections/ns/name/meta/runtime.yml @@ -1,4 +1,4 @@ plugin_routing: connection: redirected_dummy: - redirect: ns.name.dummy
\ No newline at end of file + redirect: ns.name.dummy diff --git a/test/integration/targets/loop-connection/main.yml b/test/integration/targets/loop-connection/main.yml index fbffe30..ba60e64 100644 --- a/test/integration/targets/loop-connection/main.yml +++ b/test/integration/targets/loop-connection/main.yml @@ -30,4 +30,4 @@ - assert: that: - connected_test.results[0].stderr == "ran - 1" - - connected_test.results[1].stderr == "ran - 2"
\ No newline at end of file + - connected_test.results[1].stderr == "ran - 2" diff --git a/test/integration/targets/missing_required_lib/library/missing_required_lib.py b/test/integration/targets/missing_required_lib/library/missing_required_lib.py index 480ea00..8c7ba88 100644 --- a/test/integration/targets/missing_required_lib/library/missing_required_lib.py +++ b/test/integration/targets/missing_required_lib/library/missing_required_lib.py @@ -8,7 +8,7 @@ __metaclass__ = type from ansible.module_utils.basic import AnsibleModule, missing_required_lib try: - import ansible_missing_lib + import ansible_missing_lib # pylint: disable=unused-import HAS_LIB = True except ImportError as e: HAS_LIB = False diff --git a/test/integration/targets/module_defaults/action_plugins/debug.py b/test/integration/targets/module_defaults/action_plugins/debug.py index 2584fd3..0c43201 100644 --- a/test/integration/targets/module_defaults/action_plugins/debug.py +++ b/test/integration/targets/module_defaults/action_plugins/debug.py @@ -20,7 +20,7 @@ __metaclass__ = type from ansible.errors import AnsibleUndefinedVariable from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_text +from ansible.module_utils.common.text.converters import to_text from ansible.plugins.action import ActionBase diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py index 0d39f26..174f372 100644 --- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py +++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/eos.py @@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible.plugins.action.normal import ActionModule as ActionBase -from ansible.utils.vars import merge_hash class ActionModule(ActionBase): diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py index 20284fd..7ba2434 100644 --- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py +++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/ios.py @@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible.plugins.action.normal import ActionModule as ActionBase -from ansible.utils.vars import merge_hash class ActionModule(ActionBase): diff --git a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py index b0e1904..67050fb 100644 --- a/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py +++ b/test/integration/targets/module_defaults/collections/ansible_collections/testns/testcoll/plugins/action/vyos.py @@ -5,7 +5,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type from ansible.plugins.action.normal import ActionModule as ActionBase -from ansible.utils.vars import merge_hash class ActionModule(ActionBase): diff --git a/test/integration/targets/module_no_log/aliases b/test/integration/targets/module_no_log/aliases index 9e84f63..afa1c9c 100644 --- a/test/integration/targets/module_no_log/aliases +++ b/test/integration/targets/module_no_log/aliases @@ -1,5 +1,4 @@ shippable/posix/group3 context/controller skip/freebsd # not configured to log user.info to /var/log/syslog -skip/osx # not configured to log user.info to /var/log/syslog skip/macos # not configured to log user.info to /var/log/syslog diff --git a/test/integration/targets/module_no_log/library/module_that_has_secret.py b/test/integration/targets/module_no_log/library/module_that_has_secret.py new file mode 100644 index 0000000..035228c --- /dev/null +++ b/test/integration/targets/module_no_log/library/module_that_has_secret.py @@ -0,0 +1,19 @@ +#!/usr/bin/python +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + module = AnsibleModule(argument_spec=dict( + secret=dict(no_log=True), + notsecret=dict(no_log=False), + )) + + msg = "My secret is: (%s), but don't tell %s" % (module.params['secret'], module.params['notsecret']) + module.exit_json(msg=msg, changed=bool(module.params['secret'] == module.params['notsecret'])) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/module_no_log/tasks/main.yml b/test/integration/targets/module_no_log/tasks/main.yml index cf9e580..bf02410 100644 --- a/test/integration/targets/module_no_log/tasks/main.yml +++ b/test/integration/targets/module_no_log/tasks/main.yml @@ -59,3 +59,41 @@ # 2) the AnsibleModule.log method is not working - good_message in grep.stdout - bad_message not in grep.stdout + +- name: Ensure we do not obscure what we should not + block: + - module_that_has_secret: + secret: u + notsecret: u + register: ouch + ignore_errors: true + + - name: no log wont obscure booleans when True, but still hide in msg + assert: + that: + - ouch['changed'] is boolean + - "'*' in ouch['msg']" + + - module_that_has_secret: + secret: a + notsecret: b + register: ouch + ignore_errors: true + + - name: no log wont obscure booleans when False, but still hide in msg + assert: + that: + - ouch['changed'] is boolean + - "'*' in ouch['msg']" + + - module_that_has_secret: + secret: True + notsecret: False + register: ouch + ignore_errors: true + + - name: no log does not hide bool values + assert: + that: + - ouch['changed'] is boolean + - "'*' not in ouch['msg']" diff --git a/test/integration/targets/module_utils/library/test.py b/test/integration/targets/module_utils/library/test.py index fb6c8a8..857d3d8 100644 --- a/test/integration/targets/module_utils/library/test.py +++ b/test/integration/targets/module_utils/library/test.py @@ -11,8 +11,8 @@ import ansible.module_utils.foo0 results['foo0'] = ansible.module_utils.foo0.data # Test depthful import with no from -import ansible.module_utils.bar0.foo -results['bar0'] = ansible.module_utils.bar0.foo.data +import ansible.module_utils.bar0.foo3 +results['bar0'] = ansible.module_utils.bar0.foo3.data # Test import of module_utils/foo1.py from ansible.module_utils import foo1 @@ -72,12 +72,12 @@ from ansible.module_utils.spam8.ham import eggs results['spam8'] = (bacon.data, eggs) # Test that import of module_utils/qux1/quux.py using as works -from ansible.module_utils.qux1 import quux as one -results['qux1'] = one.data +from ansible.module_utils.qux1 import quux as two +results['qux1'] = two.data # Test that importing qux2/quux.py and qux2/quuz.py using as works -from ansible.module_utils.qux2 import quux as one, quuz as two -results['qux2'] = (one.data, two.data) +from ansible.module_utils.qux2 import quux as three, quuz as four +results['qux2'] = (three.data, four.data) # Test depth from ansible.module_utils.a.b.c.d.e.f.g.h import data diff --git a/test/integration/targets/module_utils/library/test_failure.py b/test/integration/targets/module_utils/library/test_failure.py index efb3dda..ab80cea 100644 --- a/test/integration/targets/module_utils/library/test_failure.py +++ b/test/integration/targets/module_utils/library/test_failure.py @@ -6,9 +6,9 @@ results = {} # Test that we are rooted correctly # Following files: # module_utils/yak/zebra/foo.py -from ansible.module_utils.zebra import foo +from ansible.module_utils.zebra import foo4 -results['zebra'] = foo.data +results['zebra'] = foo4.data from ansible.module_utils.basic import AnsibleModule AnsibleModule(argument_spec=dict()).exit_json(**results) diff --git a/test/integration/targets/module_utils/module_utils/bar0/foo.py b/test/integration/targets/module_utils/module_utils/bar0/foo3.py index 1072dcc..1072dcc 100644 --- a/test/integration/targets/module_utils/module_utils/bar0/foo.py +++ b/test/integration/targets/module_utils/module_utils/bar0/foo3.py diff --git a/test/integration/targets/module_utils/module_utils/foo.py b/test/integration/targets/module_utils/module_utils/foo.py deleted file mode 100644 index 20698f1..0000000 --- a/test/integration/targets/module_utils/module_utils/foo.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python - -foo = "FOO FROM foo.py" diff --git a/test/integration/targets/module_utils/module_utils/sub/bar/__init__.py b/test/integration/targets/module_utils/module_utils/sub/bar/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/test/integration/targets/module_utils/module_utils/sub/bar/__init__.py +++ /dev/null diff --git a/test/integration/targets/module_utils/module_utils/sub/bar/bam.py b/test/integration/targets/module_utils/module_utils/sub/bar/bam.py deleted file mode 100644 index 02fafd4..0000000 --- a/test/integration/targets/module_utils/module_utils/sub/bar/bam.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python - -bam = "BAM FROM sub/bar/bam.py" diff --git a/test/integration/targets/module_utils/module_utils/sub/bar/bar.py b/test/integration/targets/module_utils/module_utils/sub/bar/bar.py deleted file mode 100644 index 8566901..0000000 --- a/test/integration/targets/module_utils/module_utils/sub/bar/bar.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python - -bar = "BAR FROM sub/bar/bar.py" diff --git a/test/integration/targets/module_utils/module_utils/yak/zebra/foo.py b/test/integration/targets/module_utils/module_utils/yak/zebra/foo4.py index 89b2bfe..89b2bfe 100644 --- a/test/integration/targets/module_utils/module_utils/yak/zebra/foo.py +++ b/test/integration/targets/module_utils/module_utils/yak/zebra/foo4.py diff --git a/test/integration/targets/module_utils/module_utils_test.yml b/test/integration/targets/module_utils/module_utils_test.yml index 4e948bd..352bc58 100644 --- a/test/integration/targets/module_utils/module_utils_test.yml +++ b/test/integration/targets/module_utils/module_utils_test.yml @@ -47,7 +47,7 @@ assert: that: - result is failed - - result['msg'] == "Could not find imported module support code for ansible.modules.test_failure. Looked for (['ansible.module_utils.zebra.foo', 'ansible.module_utils.zebra'])" + - result['msg'] == "Could not find imported module support code for ansible.modules.test_failure. Looked for (['ansible.module_utils.zebra.foo4', 'ansible.module_utils.zebra'])" - name: Test that alias deprecation works test_alias_deprecation: diff --git a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 index 6170f04..9644df9 100644 --- a/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 +++ b/test/integration/targets/module_utils_Ansible.Basic/library/ansible_basic_tests.ps1 @@ -87,7 +87,7 @@ Function Assert-DictionaryEqual { } Function Exit-Module { - # Make sure Exit actually calls exit and not our overriden test behaviour + # Make sure Exit actually calls exit and not our overridden test behaviour [Ansible.Basic.AnsibleModule]::Exit = { param([Int32]$rc) exit $rc } Write-Output -InputObject (ConvertTo-Json -InputObject $module.Result -Compress -Depth 99) $module.ExitJson() diff --git a/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1 b/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1 index d18c42d..5cb1a72 100644 --- a/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1 +++ b/test/integration/targets/module_utils_Ansible.ModuleUtils.AddType/library/add_type_test.ps1 @@ -328,5 +328,73 @@ finally { } Assert-Equal -actual ([Namespace12.Class12]::GetString()) -expected "b" +$unsafe_code_fail = @' +using System; + +namespace Namespace13 +{ + public class Class13 + { + + public static int GetNumber() + { + int num = 2; + int* numPtr = # + + DoubleNumber(numPtr); + + return num; + } + + private unsafe static void DoubleNumber(int* num) + { + *num = *num * 3; + } + } +} +'@ +$failed = $false +try { + Add-CSharpType -Reference $unsafe_code_fail +} +catch { + $failed = $true + $actual = $_.Exception.Message.Contains("error CS0227: Unsafe code may only appear if compiling with /unsafe") + Assert-Equal -actual $actual -expected $true +} +Assert-Equal -actual $failed -expected $true + +$unsafe_code = @' +using System; + +//AllowUnsafe + +namespace Namespace13 +{ + public class Class13 + { + public static int GetNumber() + { + int num = 2; + unsafe + { + int* numPtr = # + + DoubleNumber(numPtr); + } + + return num; + } + + private unsafe static void DoubleNumber(int* num) + { + *num = *num * 2; + } + } +} +'@ +Add-CSharpType -Reference $unsafe_code +Assert-Equal -actual ([Namespace13.Class13]::GetNumber()) -expected 4 + $result.res = "success" Exit-Json -obj $result diff --git a/test/integration/targets/no_log/no_log_config.yml b/test/integration/targets/no_log/no_log_config.yml new file mode 100644 index 0000000..8a50880 --- /dev/null +++ b/test/integration/targets/no_log/no_log_config.yml @@ -0,0 +1,13 @@ +- hosts: testhost + gather_facts: false + tasks: + - debug: + no_log: true + + - debug: + no_log: false + + - debug: + + - debug: + loop: '{{ range(3) }}' diff --git a/test/integration/targets/no_log/runme.sh b/test/integration/targets/no_log/runme.sh index bb5c048..bf764bf 100755 --- a/test/integration/targets/no_log/runme.sh +++ b/test/integration/targets/no_log/runme.sh @@ -5,7 +5,7 @@ set -eux # This test expects 7 loggable vars and 0 non-loggable ones. # If either mismatches it fails, run the ansible-playbook command to debug. [ "$(ansible-playbook no_log_local.yml -i ../../inventory -vvvvv "$@" | awk \ -'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "26/0" ] +'BEGIN { logme = 0; nolog = 0; } /LOG_ME/ { logme += 1;} /DO_NOT_LOG/ { nolog += 1;} END { printf "%d/%d", logme, nolog; }')" = "27/0" ] # deal with corner cases with no log and loops # no log enabled, should produce 6 censored messages @@ -19,3 +19,8 @@ set -eux # test invalid data passed to a suboption [ "$(ansible-playbook no_log_suboptions_invalid.yml -i ../../inventory -vvvvv "$@" | grep -Ec '(SUPREME|IDIOM|MOCKUP|EDUCATED|FOOTREST|CRAFTY|FELINE|CRYSTAL|EXPECTANT|AGROUND|GOLIATH|FREEFALL)')" = "0" ] + +# test variations on ANSIBLE_NO_LOG +[ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ] +[ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ] +[ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "6" ] diff --git a/test/integration/targets/old_style_cache_plugins/aliases b/test/integration/targets/old_style_cache_plugins/aliases index 3777383..163129e 100644 --- a/test/integration/targets/old_style_cache_plugins/aliases +++ b/test/integration/targets/old_style_cache_plugins/aliases @@ -2,5 +2,4 @@ destructive needs/root shippable/posix/group5 context/controller -skip/osx skip/macos diff --git a/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py b/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py index 44b6cf9..23c7789 100644 --- a/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py +++ b/test/integration/targets/old_style_cache_plugins/plugins/cache/configurable_redis.py @@ -44,7 +44,6 @@ DOCUMENTATION = ''' import time import json -from ansible import constants as C from ansible.errors import AnsibleError from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder from ansible.plugins.cache import BaseCacheModule diff --git a/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml b/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml index 8aad37a..b7cd483 100644 --- a/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml +++ b/test/integration/targets/old_style_cache_plugins/setup_redis_cache.yml @@ -20,8 +20,9 @@ - name: get the latest stable redis server release get_url: - url: http://download.redis.io/redis-stable.tar.gz + url: https://download.redis.io/redis-stable.tar.gz dest: ./ + timeout: 60 - name: unzip download unarchive: diff --git a/test/integration/targets/old_style_vars_plugins/deprecation_warning/v2_vars_plugin.py b/test/integration/targets/old_style_vars_plugins/deprecation_warning/v2_vars_plugin.py new file mode 100644 index 0000000..f342b69 --- /dev/null +++ b/test/integration/targets/old_style_vars_plugins/deprecation_warning/v2_vars_plugin.py @@ -0,0 +1,6 @@ +class VarsModule: + def get_host_vars(self, entity): + return {} + + def get_group_vars(self, entity): + return {} diff --git a/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py b/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py index d5c9a42..f554be0 100644 --- a/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py +++ b/test/integration/targets/old_style_vars_plugins/deprecation_warning/vars.py @@ -2,7 +2,7 @@ from ansible.plugins.vars import BaseVarsPlugin class VarsModule(BaseVarsPlugin): - REQUIRES_WHITELIST = False + REQUIRES_WHITELIST = True def get_vars(self, loader, path, entities): return {} diff --git a/test/integration/targets/old_style_vars_plugins/roles/a/tasks/main.yml b/test/integration/targets/old_style_vars_plugins/roles/a/tasks/main.yml new file mode 100644 index 0000000..8e0742a --- /dev/null +++ b/test/integration/targets/old_style_vars_plugins/roles/a/tasks/main.yml @@ -0,0 +1,3 @@ +- assert: + that: + - auto_role_var is defined diff --git a/test/integration/targets/old_style_vars_plugins/roles/a/vars_plugins/auto_role_vars.py b/test/integration/targets/old_style_vars_plugins/roles/a/vars_plugins/auto_role_vars.py new file mode 100644 index 0000000..a1cd30d --- /dev/null +++ b/test/integration/targets/old_style_vars_plugins/roles/a/vars_plugins/auto_role_vars.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from ansible.plugins.vars import BaseVarsPlugin + + +class VarsModule(BaseVarsPlugin): + # Implicitly + # REQUIRES_ENABLED = False + + def get_vars(self, loader, path, entities): + return {'auto_role_var': True} diff --git a/test/integration/targets/old_style_vars_plugins/runme.sh b/test/integration/targets/old_style_vars_plugins/runme.sh index 4cd1916..9f41623 100755 --- a/test/integration/targets/old_style_vars_plugins/runme.sh +++ b/test/integration/targets/old_style_vars_plugins/runme.sh @@ -12,9 +12,39 @@ export ANSIBLE_VARS_PLUGINS=./vars_plugins export ANSIBLE_VARS_ENABLED=require_enabled [ "$(ansible-inventory -i localhost, --list --yaml all "$@" | grep -c 'require_enabled')" = "1" ] -# Test the deprecated class attribute +# Test deprecated features export ANSIBLE_VARS_PLUGINS=./deprecation_warning -WARNING="The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. Use 'REQUIRES_ENABLED' instead." +WARNING_1="The VarsModule class variable 'REQUIRES_WHITELIST' is deprecated. Use 'REQUIRES_ENABLED' instead." +WARNING_2="The vars plugin v2_vars_plugin .* is relying on the deprecated entrypoints 'get_host_vars' and 'get_group_vars'" ANSIBLE_DEPRECATION_WARNINGS=True ANSIBLE_NOCOLOR=True ANSIBLE_FORCE_COLOR=False \ - ansible-inventory -i localhost, --list all 2> err.txt -ansible localhost -m debug -a "msg={{ lookup('file', 'err.txt') | regex_replace('\n', '') }}" | grep "$WARNING" + ansible-inventory -i localhost, --list all "$@" 2> err.txt +for WARNING in "$WARNING_1" "$WARNING_2"; do + ansible localhost -m debug -a "msg={{ lookup('file', 'err.txt') | regex_replace('\n', '') }}" | grep "$WARNING" +done + +# Test how many times vars plugins are loaded for a simple play containing a task +# host_group_vars is stateless, so we can load it once and reuse it, every other vars plugin should be instantiated before it runs +cat << EOF > "test_task_vars.yml" +--- +- hosts: localhost + connection: local + gather_facts: no + tasks: + - debug: +EOF + +# hide the debug noise by dumping to a file +trap 'rm -rf -- "out.txt"' EXIT + +ANSIBLE_DEBUG=True ansible-playbook test_task_vars.yml > out.txt +[ "$(grep -c "Loading VarsModule 'host_group_vars'" out.txt)" -eq 1 ] +[ "$(grep -c "Loading VarsModule 'require_enabled'" out.txt)" -gt 50 ] +[ "$(grep -c "Loading VarsModule 'auto_enabled'" out.txt)" -gt 50 ] + +export ANSIBLE_VARS_ENABLED=ansible.builtin.host_group_vars +ANSIBLE_DEBUG=True ansible-playbook test_task_vars.yml > out.txt +[ "$(grep -c "Loading VarsModule 'host_group_vars'" out.txt)" -eq 1 ] +[ "$(grep -c "Loading VarsModule 'require_enabled'" out.txt)" -lt 3 ] +[ "$(grep -c "Loading VarsModule 'auto_enabled'" out.txt)" -gt 50 ] + +ansible localhost -m include_role -a 'name=a' "$@" diff --git a/test/integration/targets/omit/75692.yml b/test/integration/targets/omit/75692.yml index b4000c9..5ba8a2d 100644 --- a/test/integration/targets/omit/75692.yml +++ b/test/integration/targets/omit/75692.yml @@ -2,10 +2,10 @@ hosts: testhost gather_facts: false become: yes + # become_user needed at play level for testing this behavior become_user: nobody roles: - name: setup_test_user - become: yes become_user: root tasks: - shell: whoami diff --git a/test/integration/targets/package/tasks/main.yml b/test/integration/targets/package/tasks/main.yml index c17525d..37267aa 100644 --- a/test/integration/targets/package/tasks/main.yml +++ b/test/integration/targets/package/tasks/main.yml @@ -239,4 +239,4 @@ that: - "result is changed" - when: ansible_distribution == "Fedora"
\ No newline at end of file + when: ansible_distribution == "Fedora" diff --git a/test/integration/targets/package_facts/aliases b/test/integration/targets/package_facts/aliases index 5a5e464..f5edf4b 100644 --- a/test/integration/targets/package_facts/aliases +++ b/test/integration/targets/package_facts/aliases @@ -1,3 +1,2 @@ shippable/posix/group2 -skip/osx skip/macos diff --git a/test/integration/targets/parsing/bad_parsing.yml b/test/integration/targets/parsing/bad_parsing.yml deleted file mode 100644 index 953ec07..0000000 --- a/test/integration/targets/parsing/bad_parsing.yml +++ /dev/null @@ -1,12 +0,0 @@ -- hosts: testhost - - # the following commands should all parse fine and execute fine - # and represent quoting scenarios that should be legit - - gather_facts: False - - roles: - - # this one has a lot of things that should fail, see makefile for operation w/ tags - - - { role: test_bad_parsing } diff --git a/test/integration/targets/parsing/parsing.yml b/test/integration/targets/parsing/parsing.yml new file mode 100644 index 0000000..9d5ff41 --- /dev/null +++ b/test/integration/targets/parsing/parsing.yml @@ -0,0 +1,35 @@ +- hosts: testhost + gather_facts: no + tasks: + - name: test that a variable cannot inject raw arguments + shell: echo hi {{ chdir }} + vars: + chdir: mom chdir=/tmp + register: raw_injection + + - name: test that a variable cannot inject kvp arguments as a kvp + file: path={{ test_file }} {{ test_input }} + vars: + test_file: "{{ output_dir }}/ansible_test_file" + test_input: "owner=test" + register: kvp_kvp_injection + ignore_errors: yes + + - name: test that a variable cannot inject kvp arguments as a value + file: state=absent path='{{ kvp_in_var }}' + vars: + kvp_in_var: "{{ output_dir }}' owner='test" + register: kvp_value_injection + + - name: test that a missing filter fails + debug: + msg: "{{ output_dir | badfiltername }}" + register: filter_missing + ignore_errors: yes + + - assert: + that: + - raw_injection.stdout == 'hi mom chdir=/tmp' + - kvp_kvp_injection is failed + - kvp_value_injection.path.endswith("' owner='test") + - filter_missing is failed diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/main.yml b/test/integration/targets/parsing/roles/test_bad_parsing/tasks/main.yml deleted file mode 100644 index f1b2ec6..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/main.yml +++ /dev/null @@ -1,60 +0,0 @@ -# test code for the ping module -# (c) 2014, Michael DeHaan <michael@ansible.com> - -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see <http://www.gnu.org/licenses/>. - -# the following tests all raise errors, to use them in a Makefile, we run them with different flags, as -# otherwise ansible stops at the first one and we want to ensure STOP conditions for each - -- set_fact: - test_file: "{{ output_dir }}/ansible_test_file" # FIXME, use set tempdir - test_input: "owner=test" - bad_var: "{{ output_dir }}' owner=test" - chdir: "mom chdir=/tmp" - tags: common - -- file: name={{test_file}} state=touch - tags: common - -- name: remove touched file - file: name={{test_file}} state=absent - tags: common - -- name: include test that we cannot insert arguments - include: scenario1.yml - tags: scenario1 - -- name: include test that we cannot duplicate arguments - include: scenario2.yml - tags: scenario2 - -- name: include test that we can't do this for the shell module - include: scenario3.yml - tags: scenario3 - -- name: include test that we can't go all Little Bobby Droptables on a quoted var to add more - include: scenario4.yml - tags: scenario4 - -- name: test that a missing/malformed jinja2 filter fails - debug: msg="{{output_dir|badfiltername}}" - tags: scenario5 - register: filter_fail - ignore_errors: yes - -- assert: - that: - - filter_fail is failed diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario1.yml b/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario1.yml deleted file mode 100644 index 8a82fb9..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario1.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: test that we cannot insert arguments - file: path={{ test_file }} {{ test_input }} - failed_when: False # ignore the module, just test the parser - tags: scenario1 diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario2.yml b/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario2.yml deleted file mode 100644 index c3b4b13..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario2.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: test that we cannot duplicate arguments - file: path={{ test_file }} owner=test2 {{ test_input }} - failed_when: False # ignore the module, just test the parser - tags: scenario2 diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario3.yml b/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario3.yml deleted file mode 100644 index a228f70..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario3.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: test that we can't do this for the shell module - shell: echo hi {{ chdir }} - failed_when: False - tags: scenario3 diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario4.yml b/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario4.yml deleted file mode 100644 index 2845adc..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/tasks/scenario4.yml +++ /dev/null @@ -1,4 +0,0 @@ -- name: test that we can't go all Little Bobby Droptables on a quoted var to add more - file: "name={{ bad_var }}" - failed_when: False - tags: scenario4 diff --git a/test/integration/targets/parsing/roles/test_bad_parsing/vars/main.yml b/test/integration/targets/parsing/roles/test_bad_parsing/vars/main.yml deleted file mode 100644 index 1aaeac7..0000000 --- a/test/integration/targets/parsing/roles/test_bad_parsing/vars/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -output_dir: . diff --git a/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml b/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml index d225c0f..25e91f2 100644 --- a/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml +++ b/test/integration/targets/parsing/roles/test_good_parsing/tasks/main.yml @@ -121,7 +121,10 @@ - result.cmd == "echo foo --arg=a --arg=b" - name: test includes with params - include: test_include.yml fact_name=include_params param="{{ test_input }}" + include_tasks: test_include.yml + vars: + fact_name: include_params + param: "{{ test_input }}" - name: assert the include set the correct fact for the param assert: @@ -129,7 +132,10 @@ - include_params == test_input - name: test includes with quoted params - include: test_include.yml fact_name=double_quoted_param param="this is a param with double quotes" + include_tasks: test_include.yml + vars: + fact_name: double_quoted_param + param: "this is a param with double quotes" - name: assert the include set the correct fact for the double quoted param assert: @@ -137,7 +143,10 @@ - double_quoted_param == "this is a param with double quotes" - name: test includes with single quoted params - include: test_include.yml fact_name=single_quoted_param param='this is a param with single quotes' + include_tasks: test_include.yml + vars: + fact_name: single_quoted_param + param: 'this is a param with single quotes' - name: assert the include set the correct fact for the single quoted param assert: @@ -145,7 +154,7 @@ - single_quoted_param == "this is a param with single quotes" - name: test includes with quoted params in complex args - include: test_include.yml + include_tasks: test_include.yml vars: fact_name: complex_param param: "this is a param in a complex arg with double quotes" @@ -165,7 +174,7 @@ - result.msg == "this should be debugged" - name: test conditional includes - include: test_include_conditional.yml + include_tasks: test_include_conditional.yml when: false - name: assert the nested include from test_include_conditional was not set diff --git a/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml b/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml index 070888d..a1d8b7c 100644 --- a/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml +++ b/test/integration/targets/parsing/roles/test_good_parsing/tasks/test_include_conditional.yml @@ -1 +1 @@ -- include: test_include_nested.yml +- include_tasks: test_include_nested.yml diff --git a/test/integration/targets/parsing/runme.sh b/test/integration/targets/parsing/runme.sh index 022ce4c..2d55008 100755 --- a/test/integration/targets/parsing/runme.sh +++ b/test/integration/targets/parsing/runme.sh @@ -2,5 +2,5 @@ set -eux -ansible-playbook bad_parsing.yml -i ../../inventory -vvv "$@" --tags prepare,common,scenario5 -ansible-playbook good_parsing.yml -i ../../inventory -v "$@" +ansible-playbook parsing.yml -i ../../inventory "$@" -e "output_dir=${OUTPUT_DIR}" +ansible-playbook good_parsing.yml -i ../../inventory "$@" diff --git a/test/integration/targets/path_lookups/roles/showfile/tasks/main.yml b/test/integration/targets/path_lookups/roles/showfile/tasks/notmain.yml index 1b38057..1b38057 100644 --- a/test/integration/targets/path_lookups/roles/showfile/tasks/main.yml +++ b/test/integration/targets/path_lookups/roles/showfile/tasks/notmain.yml diff --git a/test/integration/targets/path_lookups/testplay.yml b/test/integration/targets/path_lookups/testplay.yml index 8bf4553..bc05c7e 100644 --- a/test/integration/targets/path_lookups/testplay.yml +++ b/test/integration/targets/path_lookups/testplay.yml @@ -4,9 +4,11 @@ pre_tasks: - name: remove {{ remove }} file: path={{ playbook_dir }}/{{ remove }} state=absent - roles: - - showfile - post_tasks: + tasks: + - import_role: + name: showfile + tasks_from: notmain.yml + - name: from play set_fact: play_result="{{lookup('file', 'testfile')}}" diff --git a/test/integration/targets/pause/pause-6.yml b/test/integration/targets/pause/pause-6.yml new file mode 100644 index 0000000..f7315bb --- /dev/null +++ b/test/integration/targets/pause/pause-6.yml @@ -0,0 +1,25 @@ +- name: Test pause module input isn't captured with a timeout + hosts: localhost + become: no + gather_facts: no + + tasks: + - name: pause with the default message + pause: + seconds: 3 + register: default_msg_pause + + - name: pause with a custom message + pause: + prompt: Wait for three seconds + seconds: 3 + register: custom_msg_pause + + - name: Ensure that input was not captured + assert: + that: + - default_msg_pause.user_input == '' + - custom_msg_pause.user_input == '' + + - debug: + msg: Task after pause diff --git a/test/integration/targets/pause/test-pause.py b/test/integration/targets/pause/test-pause.py index 3703470..ab771fa 100755 --- a/test/integration/targets/pause/test-pause.py +++ b/test/integration/targets/pause/test-pause.py @@ -168,7 +168,9 @@ pause_test = pexpect.spawn( pause_test.logfile = log_buffer pause_test.expect(r'Pausing for \d+ seconds') pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") +pause_test.send('\n') # test newline does not stop the prompt - waiting for a timeout or ctrl+C pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") pause_test.send('C') pause_test.expect('Task after pause') pause_test.expect(pexpect.EOF) @@ -187,6 +189,7 @@ pause_test.logfile = log_buffer pause_test.expect(r'Pausing for \d+ seconds') pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") pause_test.send('A') pause_test.expect('user requested abort!') pause_test.expect(pexpect.EOF) @@ -225,6 +228,7 @@ pause_test.expect(r'Pausing for \d+ seconds') pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") pause_test.expect(r"Waiting for two seconds:") pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") pause_test.send('C') pause_test.expect('Task after pause') pause_test.expect(pexpect.EOF) @@ -244,6 +248,7 @@ pause_test.expect(r'Pausing for \d+ seconds') pause_test.expect(r"\(ctrl\+C then 'C' = continue early, ctrl\+C then 'A' = abort\)") pause_test.expect(r"Waiting for two seconds:") pause_test.send('\x03') +pause_test.expect("Press 'C' to continue the play or 'A' to abort") pause_test.send('A') pause_test.expect('user requested abort!') pause_test.expect(pexpect.EOF) @@ -275,6 +280,24 @@ pause_test.send('\r') pause_test.expect(pexpect.EOF) pause_test.close() +# Test input is not returned if a timeout is given + +playbook = 'pause-6.yml' + +pause_test = pexpect.spawn( + 'ansible-playbook', + args=[playbook] + args, + timeout=10, + env=os.environ +) + +pause_test.logfile = log_buffer +pause_test.expect(r'Wait for three seconds:') +pause_test.send('ignored user input') +pause_test.expect('Task after pause') +pause_test.expect(pexpect.EOF) +pause_test.close() + # Test that enter presses may not continue the play when a timeout is set. diff --git a/test/integration/targets/pip/tasks/main.yml b/test/integration/targets/pip/tasks/main.yml index 66992fd..a377070 100644 --- a/test/integration/targets/pip/tasks/main.yml +++ b/test/integration/targets/pip/tasks/main.yml @@ -40,6 +40,9 @@ extra_args: "-c {{ remote_constraints }}" - include_tasks: pip.yml + + - include_tasks: no_setuptools.yml + when: ansible_python.version_info[:2] >= [3, 8] always: - name: platform specific cleanup include_tasks: "{{ cleanup_filename }}" diff --git a/test/integration/targets/pip/tasks/no_setuptools.yml b/test/integration/targets/pip/tasks/no_setuptools.yml new file mode 100644 index 0000000..695605e --- /dev/null +++ b/test/integration/targets/pip/tasks/no_setuptools.yml @@ -0,0 +1,48 @@ +- name: Get coverage version + pip: + name: coverage + check_mode: true + register: pip_coverage + +- name: create a virtualenv for use without setuptools + pip: + name: + - packaging + # coverage is needed when ansible-test is invoked with --coverage + # and using a custom ansible_python_interpreter below + - '{{ pip_coverage.stdout_lines|select("match", "coverage==")|first }}' + virtualenv: "{{ remote_tmp_dir }}/no_setuptools" + +- name: Remove setuptools + pip: + name: + - setuptools + - pkg_resources # This shouldn't be a thing, but ubuntu 20.04... + virtualenv: "{{ remote_tmp_dir }}/no_setuptools" + state: absent + +- name: Ensure pkg_resources is gone + command: "{{ remote_tmp_dir }}/no_setuptools/bin/python -c 'import pkg_resources'" + register: result + failed_when: result.rc == 0 + +- vars: + ansible_python_interpreter: "{{ remote_tmp_dir }}/no_setuptools/bin/python" + block: + - name: Checkmode install pip + pip: + name: pip + virtualenv: "{{ remote_tmp_dir }}/no_setuptools" + check_mode: true + register: pip_check_mode + + - assert: + that: + - pip_check_mode.stdout is contains "pip==" + - pip_check_mode.stdout is not contains "setuptools==" + + - name: Install fallible + pip: + name: fallible==0.0.1a2 + virtualenv: "{{ remote_tmp_dir }}/no_setuptools" + register: fallible_install diff --git a/test/integration/targets/pip/tasks/pip.yml b/test/integration/targets/pip/tasks/pip.yml index 3948061..9f1034d 100644 --- a/test/integration/targets/pip/tasks/pip.yml +++ b/test/integration/targets/pip/tasks/pip.yml @@ -568,6 +568,28 @@ that: - "version13 is success" +- name: Test virtualenv command with venv formatting + when: ansible_python.version.major > 2 + block: + - name: Clean up the virtualenv + file: + state: absent + name: "{{ remote_tmp_dir }}/pipenv" + + # ref: https://github.com/ansible/ansible/issues/76372 + - name: install using different venv formatting + pip: + name: "{{ pip_test_package }}" + virtualenv: "{{ remote_tmp_dir }}/pipenv" + virtualenv_command: "{{ ansible_python_interpreter ~ ' -mvenv' }}" + state: present + register: version14 + + - name: ensure install using virtualenv_command with venv formatting + assert: + that: + - "version14 is changed" + ### test virtualenv_command end ### # https://github.com/ansible/ansible/issues/68592 diff --git a/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py b/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py index 9f1c5c0..44412f2 100644 --- a/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py +++ b/test/integration/targets/pkg_resources/lookup_plugins/check_pkg_resources.py @@ -11,7 +11,7 @@ __metaclass__ = type # noinspection PyUnresolvedReferences try: - from pkg_resources import Requirement + from pkg_resources import Requirement # pylint: disable=unused-import except ImportError: Requirement = None diff --git a/test/integration/targets/plugin_filtering/filter_lookup.yml b/test/integration/targets/plugin_filtering/filter_lookup.yml index 694ebfc..5f183e9 100644 --- a/test/integration/targets/plugin_filtering/filter_lookup.yml +++ b/test/integration/targets/plugin_filtering/filter_lookup.yml @@ -1,6 +1,6 @@ --- filter_version: 1.0 -module_blacklist: +module_rejectlist: # Specify the name of a lookup plugin here. This should have no effect as # this is only for filtering modules - list diff --git a/test/integration/targets/plugin_filtering/filter_modules.yml b/test/integration/targets/plugin_filtering/filter_modules.yml index 6cffa67..bef7d6d 100644 --- a/test/integration/targets/plugin_filtering/filter_modules.yml +++ b/test/integration/targets/plugin_filtering/filter_modules.yml @@ -1,6 +1,6 @@ --- filter_version: 1.0 -module_blacklist: +module_rejectlist: # A pure action plugin - pause # A hybrid action plugin with module diff --git a/test/integration/targets/plugin_filtering/filter_ping.yml b/test/integration/targets/plugin_filtering/filter_ping.yml index 08e56f2..8604716 100644 --- a/test/integration/targets/plugin_filtering/filter_ping.yml +++ b/test/integration/targets/plugin_filtering/filter_ping.yml @@ -1,5 +1,5 @@ --- filter_version: 1.0 -module_blacklist: +module_rejectlist: # Ping is special - ping diff --git a/test/integration/targets/plugin_filtering/filter_stat.yml b/test/integration/targets/plugin_filtering/filter_stat.yml index c1ce42e..132bf03 100644 --- a/test/integration/targets/plugin_filtering/filter_stat.yml +++ b/test/integration/targets/plugin_filtering/filter_stat.yml @@ -1,5 +1,5 @@ --- filter_version: 1.0 -module_blacklist: +module_rejectlist: # Stat is special - stat diff --git a/test/integration/targets/plugin_filtering/no_blacklist_module.ini b/test/integration/targets/plugin_filtering/no_blacklist_module.ini deleted file mode 100644 index 65b51d6..0000000 --- a/test/integration/targets/plugin_filtering/no_blacklist_module.ini +++ /dev/null @@ -1,3 +0,0 @@ -[defaults] -retry_files_enabled = False -plugin_filters_cfg = ./no_blacklist_module.yml diff --git a/test/integration/targets/plugin_filtering/no_blacklist_module.yml b/test/integration/targets/plugin_filtering/no_rejectlist_module.yml index 52a55df..91e60a1 100644 --- a/test/integration/targets/plugin_filtering/no_blacklist_module.yml +++ b/test/integration/targets/plugin_filtering/no_rejectlist_module.yml @@ -1,3 +1,3 @@ --- filter_version: 1.0 -module_blacklist: +module_rejectlist: diff --git a/test/integration/targets/plugin_filtering/runme.sh b/test/integration/targets/plugin_filtering/runme.sh index aa0e2b0..03d78ab 100755 --- a/test/integration/targets/plugin_filtering/runme.sh +++ b/test/integration/targets/plugin_filtering/runme.sh @@ -22,11 +22,11 @@ if test $? != 0 ; then fi # -# Check that if no modules are blacklisted then Ansible should not through traceback +# Check that if no modules are rejected then Ansible should not through traceback # -ANSIBLE_CONFIG=no_blacklist_module.ini ansible-playbook tempfile.yml -i ../../inventory -vvv "$@" +ANSIBLE_CONFIG=no_rejectlist_module.ini ansible-playbook tempfile.yml -i ../../inventory -vvv "$@" if test $? != 0 ; then - echo "### Failed to run tempfile with no modules blacklisted" + echo "### Failed to run tempfile with no modules rejected" exit 1 fi @@ -87,7 +87,7 @@ fi ANSIBLE_CONFIG=filter_lookup.ini ansible-playbook lookup.yml -i ../../inventory -vvv "$@" if test $? != 0 ; then - echo "### Failed to use a lookup plugin when it is incorrectly specified in the *module* blacklist" + echo "### Failed to use a lookup plugin when it is incorrectly specified in the *module* reject list" exit 1 fi @@ -107,10 +107,10 @@ ANSIBLE_CONFIG=filter_stat.ini export ANSIBLE_CONFIG CAPTURE=$(ansible-playbook copy.yml -i ../../inventory -vvv "$@" 2>&1) if test $? = 0 ; then - echo "### Copy ran even though stat is in the module blacklist" + echo "### Copy ran even though stat is in the module reject list" exit 1 else - echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.' + echo "$CAPTURE" | grep 'The stat module was specified in the module reject list file,.*, but Ansible will not function without the stat module. Please remove stat from the reject list.' if test $? != 0 ; then echo "### Stat did not give us our custom error message" exit 1 @@ -124,10 +124,10 @@ ANSIBLE_CONFIG=filter_stat.ini export ANSIBLE_CONFIG CAPTURE=$(ansible-playbook stat.yml -i ../../inventory -vvv "$@" 2>&1) if test $? = 0 ; then - echo "### Stat ran even though it is in the module blacklist" + echo "### Stat ran even though it is in the module reject list" exit 1 else - echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.' + echo "$CAPTURE" | grep 'The stat module was specified in the module reject list file,.*, but Ansible will not function without the stat module. Please remove stat from the reject list.' if test $? != 0 ; then echo "### Stat did not give us our custom error message" exit 1 diff --git a/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py b/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py new file mode 100644 index 0000000..685b159 --- /dev/null +++ b/test/integration/targets/plugin_loader/collections/ansible_collections/n/c/plugins/action/a.py @@ -0,0 +1,6 @@ +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + return {"nca_executed": True} diff --git a/test/integration/targets/plugin_loader/file_collision/play.yml b/test/integration/targets/plugin_loader/file_collision/play.yml new file mode 100644 index 0000000..cc55800 --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/play.yml @@ -0,0 +1,7 @@ +- hosts: localhost + gather_facts: false + roles: + - r1 + - r2 + tasks: + - debug: msg={{'a'|filter1|filter2|filter3}} diff --git a/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/custom.py b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/custom.py new file mode 100644 index 0000000..7adbf7d --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/custom.py @@ -0,0 +1,15 @@ +from __future__ import annotations + + +def do_nothing(myval): + return myval + + +class FilterModule(object): + ''' Ansible core jinja2 filters ''' + + def filters(self): + return { + 'filter1': do_nothing, + 'filter3': do_nothing, + } diff --git a/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter1.yml b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter1.yml new file mode 100644 index 0000000..5bb3e34 --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter1.yml @@ -0,0 +1,18 @@ +DOCUMENTATION: + name: filter1 + version_added: "1.9" + short_description: Does nothing + description: + - Really, does nothing + notes: + - This is a test filter + positional: _input + options: + _input: + description: the input + required: true + +EXAMPLES: '' +RETURN: + _value: + description: The input diff --git a/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter3.yml b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter3.yml new file mode 100644 index 0000000..4270b32 --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/roles/r1/filter_plugins/filter3.yml @@ -0,0 +1,18 @@ +DOCUMENTATION: + name: filter3 + version_added: "1.9" + short_description: Does nothing + description: + - Really, does nothing + notes: + - This is a test filter + positional: _input + options: + _input: + description: the input + required: true + +EXAMPLES: '' +RETURN: + _value: + description: The input diff --git a/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/custom.py b/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/custom.py new file mode 100644 index 0000000..8a7a4f5 --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/custom.py @@ -0,0 +1,14 @@ +from __future__ import annotations + + +def do_nothing(myval): + return myval + + +class FilterModule(object): + ''' Ansible core jinja2 filters ''' + + def filters(self): + return { + 'filter2': do_nothing, + } diff --git a/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/filter2.yml b/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/filter2.yml new file mode 100644 index 0000000..de9195e --- /dev/null +++ b/test/integration/targets/plugin_loader/file_collision/roles/r2/filter_plugins/filter2.yml @@ -0,0 +1,18 @@ +DOCUMENTATION: + name: filter2 + version_added: "1.9" + short_description: Does nothing + description: + - Really, does nothing + notes: + - This is a test filter + positional: _input + options: + _input: + description: the input + required: true + +EXAMPLES: '' +RETURN: + _value: + description: The input diff --git a/test/integration/targets/plugin_loader/override/filters.yml b/test/integration/targets/plugin_loader/override/filters.yml index e51ab4e..569a447 100644 --- a/test/integration/targets/plugin_loader/override/filters.yml +++ b/test/integration/targets/plugin_loader/override/filters.yml @@ -1,7 +1,7 @@ - hosts: testhost gather_facts: false tasks: - - name: ensure local 'flag' filter works, 'flatten' is overriden and 'ternary' is still from core + - name: ensure local 'flag' filter works, 'flatten' is overridden and 'ternary' is still from core assert: that: - a|flag == 'flagged' diff --git a/test/integration/targets/plugin_loader/runme.sh b/test/integration/targets/plugin_loader/runme.sh index e30f624..e68f06a 100755 --- a/test/integration/targets/plugin_loader/runme.sh +++ b/test/integration/targets/plugin_loader/runme.sh @@ -34,3 +34,8 @@ done # test config loading ansible-playbook use_coll_name.yml -i ../../inventory -e 'ansible_connection=ansible.builtin.ssh' "$@" + +# test filter loading ignoring duplicate file basename +ansible-playbook file_collision/play.yml "$@" + +ANSIBLE_COLLECTIONS_PATH=$PWD/collections ansible-playbook unsafe_plugin_name.yml "$@" diff --git a/test/integration/targets/plugin_loader/unsafe_plugin_name.yml b/test/integration/targets/plugin_loader/unsafe_plugin_name.yml new file mode 100644 index 0000000..73cd439 --- /dev/null +++ b/test/integration/targets/plugin_loader/unsafe_plugin_name.yml @@ -0,0 +1,9 @@ +- hosts: localhost + gather_facts: false + tasks: + - action: !unsafe n.c.a + register: r + + - assert: + that: + - r.nca_executed diff --git a/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py b/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py index e542913..41a76d9 100644 --- a/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py +++ b/test/integration/targets/rel_plugin_loading/subdir/inventory_plugins/notyaml.py @@ -64,7 +64,7 @@ from collections.abc import MutableMapping from ansible.errors import AnsibleError, AnsibleParserError from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_native, to_text +from ansible.module_utils.common.text.converters import to_native, to_text from ansible.plugins.inventory import BaseFileInventoryPlugin NoneType = type(None) diff --git a/test/integration/targets/remote_tmp/playbook.yml b/test/integration/targets/remote_tmp/playbook.yml index 5adef62..2d0db4e 100644 --- a/test/integration/targets/remote_tmp/playbook.yml +++ b/test/integration/targets/remote_tmp/playbook.yml @@ -30,30 +30,43 @@ - name: Test tempdir is removed hosts: testhost gather_facts: false + vars: + # These tests cannot be run with pipelining as it defeats the purpose of + # ensuring remote_tmp is cleaned up. Pipelining is enabled in the test + # inventory + ansible_pipelining: false + # Ensure that the remote_tmp_dir we create allows the unpriv connection user + # to create the remote_tmp + ansible_become: false tasks: - import_role: name: ../setup_remote_tmp_dir - - file: - state: touch - path: "{{ remote_tmp_dir }}/65393" + - vars: + # Isolate the remote_tmp used by these tests + ansible_remote_tmp: "{{ remote_tmp_dir }}/remote_tmp" + block: + - file: + state: touch + path: "{{ remote_tmp_dir }}/65393" - - copy: - src: "{{ remote_tmp_dir }}/65393" - dest: "{{ remote_tmp_dir }}/65393.2" - remote_src: true + - copy: + src: "{{ remote_tmp_dir }}/65393" + dest: "{{ remote_tmp_dir }}/65393.2" + remote_src: true - - find: - path: "~/.ansible/tmp" - use_regex: yes - patterns: 'AnsiballZ_.+\.py' - recurse: true - register: result + - find: + path: "{{ ansible_remote_tmp }}" + use_regex: yes + patterns: 'AnsiballZ_.+\.py' + recurse: true + register: result - debug: var: result - assert: that: - # Should find nothing since pipelining is used - - result.files|length == 0 + # Should only be AnsiballZ_find.py because find is actively running + - result.files|length == 1 + - result.files[0].path.endswith('/AnsiballZ_find.py') diff --git a/test/integration/targets/replace/tasks/main.yml b/test/integration/targets/replace/tasks/main.yml index d267b78..ca8b4ec 100644 --- a/test/integration/targets/replace/tasks/main.yml +++ b/test/integration/targets/replace/tasks/main.yml @@ -263,3 +263,22 @@ - replace_cat8.stdout_lines[1] == "9.9.9.9" - replace_cat8.stdout_lines[7] == "0.0.0.0" - replace_cat8.stdout_lines[13] == "0.0.0.0" + +# For Python 3.6 or greater - https://github.com/ansible/ansible/issues/79364 +- name: Handle bad escape character in regular expression + replace: + path: /dev/null + after: ^ + before: $ + regexp: \. + replace: '\D' + ignore_errors: true + register: replace_test9 + when: ansible_python.version.major == 3 and ansible_python.version.minor > 6 + +- name: Validate the failure + assert: + that: + - replace_test9 is failure + - replace_test9.msg.startswith("Unable to process replace") + when: ansible_python.version.major == 3 and ansible_python.version.minor > 6 diff --git a/test/integration/targets/result_pickle_error/action_plugins/result_pickle_error.py b/test/integration/targets/result_pickle_error/action_plugins/result_pickle_error.py new file mode 100644 index 0000000..e8d712a --- /dev/null +++ b/test/integration/targets/result_pickle_error/action_plugins/result_pickle_error.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright: Contributors to the Ansible project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.plugins.action import ActionBase +from jinja2 import Undefined + + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + return {'obj': Undefined('obj')} diff --git a/test/integration/targets/result_pickle_error/aliases b/test/integration/targets/result_pickle_error/aliases new file mode 100644 index 0000000..70fbe57 --- /dev/null +++ b/test/integration/targets/result_pickle_error/aliases @@ -0,0 +1,3 @@ +shippable/posix/group5 +context/controller +needs/target/test_utils diff --git a/test/integration/targets/result_pickle_error/runme.sh b/test/integration/targets/result_pickle_error/runme.sh new file mode 100755 index 0000000..e2ec37b --- /dev/null +++ b/test/integration/targets/result_pickle_error/runme.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -ux +export ANSIBLE_ROLES_PATH=../ + +is_timeout() { + rv=$? + if [ "$rv" == "124" ]; then + echo "***hang detected, this likely means the strategy never received a result for the task***" + fi + exit $rv +} + +trap "is_timeout" EXIT + +../test_utils/scripts/timeout.py -- 10 ansible-playbook -i ../../inventory runme.yml -v "$@" diff --git a/test/integration/targets/result_pickle_error/runme.yml b/test/integration/targets/result_pickle_error/runme.yml new file mode 100644 index 0000000..6050849 --- /dev/null +++ b/test/integration/targets/result_pickle_error/runme.yml @@ -0,0 +1,7 @@ +- hosts: all + gather_facts: false + tasks: + - include_role: + name: result_pickle_error + # Just for caution loop 3 times to ensure no issues + loop: '{{ range(3) }}' diff --git a/test/integration/targets/result_pickle_error/tasks/main.yml b/test/integration/targets/result_pickle_error/tasks/main.yml new file mode 100644 index 0000000..895475d --- /dev/null +++ b/test/integration/targets/result_pickle_error/tasks/main.yml @@ -0,0 +1,14 @@ +- name: Ensure pickling error doesn't cause a hang + result_pickle_error: + ignore_errors: true + register: result + +- assert: + that: + - result.msg == expected_msg + - result is failed + vars: + expected_msg: "cannot pickle 'Undefined' object" + +- debug: + msg: Success, no hang diff --git a/test/integration/targets/roles/47023.yml b/test/integration/targets/roles/47023.yml new file mode 100644 index 0000000..6b41b52 --- /dev/null +++ b/test/integration/targets/roles/47023.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + gather_facts: no + tasks: + - include_role: name=47023_role1 diff --git a/test/integration/targets/roles/dupe_inheritance.yml b/test/integration/targets/roles/dupe_inheritance.yml new file mode 100644 index 0000000..6fda5ba --- /dev/null +++ b/test/integration/targets/roles/dupe_inheritance.yml @@ -0,0 +1,10 @@ +- name: Test + hosts: testhost + gather_facts: false + roles: + - role: top + info: First definition + testvar: abc + + - role: top + info: Second definition diff --git a/test/integration/targets/roles/privacy.yml b/test/integration/targets/roles/privacy.yml new file mode 100644 index 0000000..2f671c0 --- /dev/null +++ b/test/integration/targets/roles/privacy.yml @@ -0,0 +1,60 @@ +# use this to debug issues +#- debug: msg={{ is_private ~ ', ' ~ is_default ~ ', ' ~ privacy|default('nope')}} + +- hosts: localhost + name: test global privacy setting + gather_facts: false + roles: + - a + pre_tasks: + + - name: 'test roles: privacy' + assert: + that: + - is_private and privacy is undefined or not is_private and privacy is defined + - not is_default or is_default and privacy is defined + +- hosts: localhost + name: test import_role privacy + gather_facts: false + tasks: + - import_role: name=a + + - name: role is private, var should be undefined + assert: + that: + - is_private and privacy is undefined or not is_private and privacy is defined + - not is_default or is_default and privacy is defined + +- hosts: localhost + name: test global privacy setting on includes + gather_facts: false + tasks: + - include_role: name=a + + - name: test include_role privacy + assert: + that: + - not is_default and (is_private and privacy is undefined or not is_private and privacy is defined) or is_default and privacy is undefined + +- hosts: localhost + name: test public yes always overrides global privacy setting on includes + gather_facts: false + tasks: + - include_role: name=a public=yes + + - name: test include_role privacy + assert: + that: + - privacy is defined + +- hosts: localhost + name: test public no always overrides global privacy setting on includes + gather_facts: false + tasks: + - include_role: name=a public=no + + - name: test include_role privacy + assert: + that: + - privacy is undefined diff --git a/test/integration/targets/roles/role_complete.yml b/test/integration/targets/roles/role_complete.yml new file mode 100644 index 0000000..86cae77 --- /dev/null +++ b/test/integration/targets/roles/role_complete.yml @@ -0,0 +1,47 @@ +- name: test deduping allows for 1 successful execution of role after it is skipped + hosts: testhost + gather_facts: false + tags: [ 'conditional_skipped' ] + roles: + # Skipped the first time it executes + - role: a + when: role_set_var is defined + + - role: set_var + + # No longer skipped + - role: a + when: role_set_var is defined + # Deduplicated with the previous success + - role: a + when: role_set_var is defined + +- name: test deduping allows for successful execution of role after host is unreachable + hosts: fake,testhost + gather_facts: false + tags: [ 'unreachable' ] + ignore_unreachable: yes + roles: + # unreachable by the first host + - role: test_connectivity + + # unreachable host will try again, + # the successful host will not because it's deduplicated + - role: test_connectivity + +- name: test deduping role for failed host + hosts: testhost,localhost + gather_facts: false + tags: [ 'conditional_failed' ] + ignore_errors: yes + roles: + # Uses run_once to fail on the first host the first time it executes + - role: failed_when + + - role: set_var + - role: recover + + # Deduplicated after the failure, ONLY runs for localhost + - role: failed_when + # Deduplicated with the previous success + - role: failed_when diff --git a/test/integration/targets/roles/role_dep_chain.yml b/test/integration/targets/roles/role_dep_chain.yml new file mode 100644 index 0000000..cf99a25 --- /dev/null +++ b/test/integration/targets/roles/role_dep_chain.yml @@ -0,0 +1,6 @@ +--- +- hosts: all + tasks: + - name: static import inside dynamic include inherits defaults/vars + include_role: + name: include_import_dep_chain diff --git a/test/integration/targets/roles/roles/47023_role1/defaults/main.yml b/test/integration/targets/roles/roles/47023_role1/defaults/main.yml new file mode 100644 index 0000000..166caa3 --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role1/defaults/main.yml @@ -0,0 +1 @@ +my_default: defined diff --git a/test/integration/targets/roles/roles/47023_role1/tasks/main.yml b/test/integration/targets/roles/roles/47023_role1/tasks/main.yml new file mode 100644 index 0000000..9c408ba --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role1/tasks/main.yml @@ -0,0 +1 @@ +- include_role: name=47023_role2 diff --git a/test/integration/targets/roles/roles/47023_role1/vars/main.yml b/test/integration/targets/roles/roles/47023_role1/vars/main.yml new file mode 100644 index 0000000..bfda56b --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role1/vars/main.yml @@ -0,0 +1 @@ +my_var: defined diff --git a/test/integration/targets/roles/roles/47023_role2/tasks/main.yml b/test/integration/targets/roles/roles/47023_role2/tasks/main.yml new file mode 100644 index 0000000..4544215 --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role2/tasks/main.yml @@ -0,0 +1 @@ +- include_role: name=47023_role3 diff --git a/test/integration/targets/roles/roles/47023_role3/tasks/main.yml b/test/integration/targets/roles/roles/47023_role3/tasks/main.yml new file mode 100644 index 0000000..9479fe3 --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role3/tasks/main.yml @@ -0,0 +1 @@ +- include_role: name=47023_role4 diff --git a/test/integration/targets/roles/roles/47023_role4/tasks/main.yml b/test/integration/targets/roles/roles/47023_role4/tasks/main.yml new file mode 100644 index 0000000..64c96e9 --- /dev/null +++ b/test/integration/targets/roles/roles/47023_role4/tasks/main.yml @@ -0,0 +1,5 @@ +- debug: + msg: "Var is {{ my_var | default('undefined') }}" + +- debug: + msg: "Default is {{ my_default | default('undefined') }}" diff --git a/test/integration/targets/roles/roles/a/vars/main.yml b/test/integration/targets/roles/roles/a/vars/main.yml new file mode 100644 index 0000000..7812aa7 --- /dev/null +++ b/test/integration/targets/roles/roles/a/vars/main.yml @@ -0,0 +1 @@ +privacy: in role a diff --git a/test/integration/targets/roles/roles/bottom/tasks/main.yml b/test/integration/targets/roles/roles/bottom/tasks/main.yml new file mode 100644 index 0000000..3f37597 --- /dev/null +++ b/test/integration/targets/roles/roles/bottom/tasks/main.yml @@ -0,0 +1,3 @@ +- name: "{{ info }} - {{ role_name }}: testvar content" + debug: + msg: '{{ testvar | default("Not specified") }}' diff --git a/test/integration/targets/roles/roles/failed_when/tasks/main.yml b/test/integration/targets/roles/roles/failed_when/tasks/main.yml new file mode 100644 index 0000000..6ca4d8c --- /dev/null +++ b/test/integration/targets/roles/roles/failed_when/tasks/main.yml @@ -0,0 +1,4 @@ +- debug: + msg: "{{ role_set_var is undefined | ternary('failed_when task failed', 'failed_when task succeeded') }}" + failed_when: role_set_var is undefined + run_once: true diff --git a/test/integration/targets/roles/roles/imported_from_include/tasks/main.yml b/test/integration/targets/roles/roles/imported_from_include/tasks/main.yml new file mode 100644 index 0000000..32126f8 --- /dev/null +++ b/test/integration/targets/roles/roles/imported_from_include/tasks/main.yml @@ -0,0 +1,4 @@ +- assert: + that: + - inherit_var is defined + - inherit_default is defined diff --git a/test/integration/targets/roles/roles/include_import_dep_chain/defaults/main.yml b/test/integration/targets/roles/roles/include_import_dep_chain/defaults/main.yml new file mode 100644 index 0000000..5b8a643 --- /dev/null +++ b/test/integration/targets/roles/roles/include_import_dep_chain/defaults/main.yml @@ -0,0 +1 @@ +inherit_default: default diff --git a/test/integration/targets/roles/roles/include_import_dep_chain/tasks/main.yml b/test/integration/targets/roles/roles/include_import_dep_chain/tasks/main.yml new file mode 100644 index 0000000..84884a8 --- /dev/null +++ b/test/integration/targets/roles/roles/include_import_dep_chain/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: imported_from_include diff --git a/test/integration/targets/roles/roles/include_import_dep_chain/vars/main.yml b/test/integration/targets/roles/roles/include_import_dep_chain/vars/main.yml new file mode 100644 index 0000000..0d4aaa9 --- /dev/null +++ b/test/integration/targets/roles/roles/include_import_dep_chain/vars/main.yml @@ -0,0 +1 @@ +inherit_var: var diff --git a/test/integration/targets/roles/roles/middle/tasks/main.yml b/test/integration/targets/roles/roles/middle/tasks/main.yml new file mode 100644 index 0000000..bd2b529 --- /dev/null +++ b/test/integration/targets/roles/roles/middle/tasks/main.yml @@ -0,0 +1,6 @@ +- name: "{{ info }} - {{ role_name }}: testvar content" + debug: + msg: '{{ testvar | default("Not specified") }}' + +- include_role: + name: bottom diff --git a/test/integration/targets/roles/roles/recover/tasks/main.yml b/test/integration/targets/roles/roles/recover/tasks/main.yml new file mode 100644 index 0000000..72ea3ac --- /dev/null +++ b/test/integration/targets/roles/roles/recover/tasks/main.yml @@ -0,0 +1 @@ +- meta: clear_host_errors diff --git a/test/integration/targets/roles/roles/set_var/tasks/main.yml b/test/integration/targets/roles/roles/set_var/tasks/main.yml new file mode 100644 index 0000000..45f83eb --- /dev/null +++ b/test/integration/targets/roles/roles/set_var/tasks/main.yml @@ -0,0 +1,2 @@ +- set_fact: + role_set_var: true diff --git a/test/integration/targets/roles/roles/test_connectivity/tasks/main.yml b/test/integration/targets/roles/roles/test_connectivity/tasks/main.yml new file mode 100644 index 0000000..22fac6e --- /dev/null +++ b/test/integration/targets/roles/roles/test_connectivity/tasks/main.yml @@ -0,0 +1,2 @@ +- ping: + data: 'reachable' diff --git a/test/integration/targets/roles/roles/top/tasks/main.yml b/test/integration/targets/roles/roles/top/tasks/main.yml new file mode 100644 index 0000000..a7a5b52 --- /dev/null +++ b/test/integration/targets/roles/roles/top/tasks/main.yml @@ -0,0 +1,6 @@ +- name: "{{ info }} - {{ role_name }}: testvar content" + debug: + msg: '{{ testvar | default("Not specified") }}' + +- include_role: + name: middle diff --git a/test/integration/targets/roles/roles/vars_scope/defaults/main.yml b/test/integration/targets/roles/roles/vars_scope/defaults/main.yml new file mode 100644 index 0000000..27f3e91 --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/defaults/main.yml @@ -0,0 +1,10 @@ +default_only: default +role_vars_only: default +play_and_role_vars: default +play_and_roles_and_role_vars: default +play_and_import_and_role_vars: default +play_and_include_and_role_vars: default +play_and_role_vars_and_role_vars: default +roles_and_role_vars: default +import_and_role_vars: default +include_and_role_vars: default diff --git a/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml b/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml new file mode 100644 index 0000000..083415d --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/tasks/check_vars.yml @@ -0,0 +1,7 @@ +- debug: var={{item}} + loop: '{{possible_vars}}' + +- assert: + that: + - (item in vars and item in defined and vars[item] == defined[item]) or (item not in vars and item not in defined) + loop: '{{possible_vars}}' diff --git a/test/integration/targets/roles/roles/vars_scope/tasks/main.yml b/test/integration/targets/roles/roles/vars_scope/tasks/main.yml new file mode 100644 index 0000000..155f362 --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: check_vars.yml diff --git a/test/integration/targets/roles/roles/vars_scope/vars/main.yml b/test/integration/targets/roles/roles/vars_scope/vars/main.yml new file mode 100644 index 0000000..079353f --- /dev/null +++ b/test/integration/targets/roles/roles/vars_scope/vars/main.yml @@ -0,0 +1,9 @@ +role_vars_only: role_vars +play_and_role_vars: role_vars +play_and_roles_and_role_vars: role_vars +play_and_import_and_role_vars: role_vars +play_and_include_and_role_vars: role_vars +play_and_role_vars_and_role_vars: role_vars +roles_and_role_vars: role_vars +import_and_role_vars: role_vars +include_and_role_vars: role_vars diff --git a/test/integration/targets/roles/runme.sh b/test/integration/targets/roles/runme.sh index bb98a93..bf3aaf5 100755 --- a/test/integration/targets/roles/runme.sh +++ b/test/integration/targets/roles/runme.sh @@ -3,26 +3,47 @@ set -eux # test no dupes when dependencies in b and c point to a in roles: -[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags inroles "$@" | grep -c '"msg": "A"')" = "1" ] -[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags acrossroles "$@" | grep -c '"msg": "A"')" = "1" ] -[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags intasks "$@" | grep -c '"msg": "A"')" = "1" ] +[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags inroles | grep -c '"msg": "A"')" = "1" ] +[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags acrossroles | grep -c '"msg": "A"')" = "1" ] +[ "$(ansible-playbook no_dupes.yml -i ../../inventory --tags intasks | grep -c '"msg": "A"')" = "1" ] # but still dupe across plays -[ "$(ansible-playbook no_dupes.yml -i ../../inventory "$@" | grep -c '"msg": "A"')" = "3" ] +[ "$(ansible-playbook no_dupes.yml -i ../../inventory | grep -c '"msg": "A"')" = "3" ] + +# and don't dedupe before the role successfully completes +[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_skipped | grep -c '"msg": "A"')" = "1" ] +[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags conditional_failed | grep -c '"msg": "failed_when task succeeded"')" = "1" ] +[ "$(ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable -vvv | grep -c '"data": "reachable"')" = "1" ] +ansible-playbook role_complete.yml -i ../../inventory -i fake, --tags unreachable | grep -e 'ignored=2' # include/import can execute another instance of role -[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags importrole "$@" | grep -c '"msg": "A"')" = "2" ] -[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags includerole "$@" | grep -c '"msg": "A"')" = "2" ] +[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags importrole | grep -c '"msg": "A"')" = "2" ] +[ "$(ansible-playbook allowed_dupes.yml -i ../../inventory --tags includerole | grep -c '"msg": "A"')" = "2" ] +[ "$(ansible-playbook dupe_inheritance.yml -i ../../inventory | grep -c '"msg": "abc"')" = "3" ] # ensure role data is merged correctly ansible-playbook data_integrity.yml -i ../../inventory "$@" # ensure role fails when trying to load 'non role' in _from -ansible-playbook no_outside.yml -i ../../inventory "$@" > role_outside_output.log 2>&1 || true +ansible-playbook no_outside.yml -i ../../inventory > role_outside_output.log 2>&1 || true if grep "as it is not inside the expected role path" role_outside_output.log >/dev/null; then echo "Test passed (playbook failed with expected output, output not shown)." else echo "Test failed, expected output from playbook failure is missing, output not shown)." exit 1 fi + +# ensure vars scope is correct +ansible-playbook vars_scope.yml -i ../../inventory "$@" + +# test nested includes get parent roles greater than a depth of 3 +[ "$(ansible-playbook 47023.yml -i ../../inventory | grep '\<\(Default\|Var\)\>' | grep -c 'is defined')" = "2" ] + +# ensure import_role called from include_role has the include_role in the dep chain +ansible-playbook role_dep_chain.yml -i ../../inventory "$@" + +# global role privacy setting test, set to private, set to not private, default +ANSIBLE_PRIVATE_ROLE_VARS=1 ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@" +ANSIBLE_PRIVATE_ROLE_VARS=0 ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@" +ansible-playbook privacy.yml -e @vars/privacy_vars.yml "$@" diff --git a/test/integration/targets/roles/tasks/check_vars.yml b/test/integration/targets/roles/tasks/check_vars.yml new file mode 100644 index 0000000..083415d --- /dev/null +++ b/test/integration/targets/roles/tasks/check_vars.yml @@ -0,0 +1,7 @@ +- debug: var={{item}} + loop: '{{possible_vars}}' + +- assert: + that: + - (item in vars and item in defined and vars[item] == defined[item]) or (item not in vars and item not in defined) + loop: '{{possible_vars}}' diff --git a/test/integration/targets/roles/vars/play.yml b/test/integration/targets/roles/vars/play.yml new file mode 100644 index 0000000..dd84ae2 --- /dev/null +++ b/test/integration/targets/roles/vars/play.yml @@ -0,0 +1,26 @@ +play_only: play +play_and_roles: play +play_and_import: play +play_and_include: play +play_and_role_vars: play +play_and_roles_and_role_vars: play +play_and_import_and_role_vars: play +play_and_include_and_role_vars: play +possible_vars: + - default_only + - import_and_role_vars + - import_only + - include_and_role_vars + - include_only + - play_and_import + - play_and_import_and_role_vars + - play_and_include + - play_and_include_and_role_vars + - play_and_roles + - play_and_roles_and_role_vars + - play_and_role_vars + - play_and_role_vars_and_role_vars + - play_only + - roles_and_role_vars + - roles_only + - role_vars_only diff --git a/test/integration/targets/roles/vars/privacy_vars.yml b/test/integration/targets/roles/vars/privacy_vars.yml new file mode 100644 index 0000000..9539ed0 --- /dev/null +++ b/test/integration/targets/roles/vars/privacy_vars.yml @@ -0,0 +1,2 @@ +is_private: "{{lookup('config', 'DEFAULT_PRIVATE_ROLE_VARS')}}" +is_default: "{{lookup('env', 'ANSIBLE_PRIVATE_ROLE_VARS') == ''}}" diff --git a/test/integration/targets/roles/vars_scope.yml b/test/integration/targets/roles/vars_scope.yml new file mode 100644 index 0000000..3e6b16a --- /dev/null +++ b/test/integration/targets/roles/vars_scope.yml @@ -0,0 +1,358 @@ +- name: play and roles + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + roles: + - name: vars_scope + vars: + roles_only: roles + roles_and_role_vars: roles + play_and_roles: roles + play_and_roles_and_role_vars: roles + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: roles + play_and_roles_and_role_vars: roles + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: roles + roles_only: roles + role_vars_only: role_vars + + pre_tasks: + - include_tasks: tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars +- name: play baseline (no roles) + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + +- name: play and import + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_include: play + play_and_roles: play + play_only: play + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_and_include_and_role_vars: role_vars + play_and_roles_and_role_vars: role_vars + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - name: static import + import_role: + name: vars_scope + vars: + import_only: import + import_and_role_vars: import + play_and_import: import + play_and_import_and_role_vars: import + defined: + default_only: default + import_and_role_vars: import + import_only: import + include_and_role_vars: role_vars + play_and_import: import + play_and_import_and_role_vars: import + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + +- name: play and include + hosts: localhost + gather_facts: false + vars_files: + - vars/play.yml + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + + - name: dynamic include + include_role: + name: vars_scope + vars: + include_only: include + include_and_role_vars: include + play_and_include: include + play_and_include_and_role_vars: include + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: include + include_only: include + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: include + play_and_include_and_role_vars: include + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + play_and_import: play + play_and_import_and_role_vars: play + play_and_include: play + play_and_include_and_role_vars: play + play_and_roles: play + play_and_roles_and_role_vars: play + play_and_role_vars: play + play_only: play + +- name: play and roles and import and include + hosts: localhost + gather_facts: false + vars: + vars_files: + - vars/play.yml + roles: + - name: vars_scope + vars: + roles_only: roles + roles_and_role_vars: roles + play_and_roles: roles + play_and_roles_and_role_vars: roles + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: roles + play_and_roles_and_role_vars: roles + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: roles + roles_only: roles + role_vars_only: role_vars + + pre_tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + tasks: + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - name: static import + import_role: + name: vars_scope + vars: + import_only: import + import_and_role_vars: import + play_and_import: import + play_and_import_and_role_vars: import + defined: + default_only: default + import_and_role_vars: import + import_only: import + include_and_role_vars: role_vars + play_and_import: import + play_and_import_and_role_vars: import + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - name: dynamic include + include_role: + name: vars_scope + vars: + include_only: include + include_and_role_vars: include + play_and_include: include + play_and_include_and_role_vars: include + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: include + include_only: include + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: include + play_and_include_and_role_vars: include + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars + + - include_tasks: roles/vars_scope/tasks/check_vars.yml + vars: + defined: + default_only: default + import_and_role_vars: role_vars + include_and_role_vars: role_vars + play_and_import: play + play_and_import_and_role_vars: role_vars + play_and_include: play + play_and_include_and_role_vars: role_vars + play_and_roles: play + play_and_roles_and_role_vars: role_vars + play_and_role_vars: role_vars + play_and_role_vars_and_role_vars: role_vars + play_only: play + roles_and_role_vars: role_vars + role_vars_only: role_vars 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 index 1a1ccbe..10dce6d 100644 --- a/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml +++ b/test/integration/targets/roles_arg_spec/roles/c/meta/main.yml @@ -2,6 +2,15 @@ argument_specs: main: short_description: Main entry point for role C. options: + c_dict: + type: "dict" + required: true c_int: type: "int" required: true + c_list: + type: "list" + required: true + c_raw: + type: "raw" + required: true diff --git a/test/integration/targets/roles_arg_spec/test.yml b/test/integration/targets/roles_arg_spec/test.yml index 5eca7c7..b88d2e1 100644 --- a/test/integration/targets/roles_arg_spec/test.yml +++ b/test/integration/targets/roles_arg_spec/test.yml @@ -48,6 +48,7 @@ name: a vars: a_int: "{{ INT_VALUE }}" + a_str: "import_role" - name: "Call role entry point that is defined, but has no spec data" import_role: @@ -144,7 +145,10 @@ hosts: localhost gather_facts: false vars: + c_dict: {} c_int: 1 + c_list: [] + c_raw: ~ a_str: "some string" a_int: 42 tasks: @@ -156,6 +160,125 @@ include_role: name: c +- name: "New play to reset vars: Test nested role including/importing role fails with null required options" + hosts: localhost + gather_facts: false + vars: + a_main_spec: + a_str: + required: true + type: "str" + c_main_spec: + c_int: + required: true + type: "int" + c_list: + required: true + type: "list" + c_dict: + required: true + type: "dict" + c_raw: + required: true + type: "raw" + # role c calls a's main and alternate entrypoints + a_str: '' + c_dict: {} + c_int: 0 + c_list: [] + c_raw: ~ + tasks: + - name: test type coercion fails on None for required str + block: + - name: "Test import_role of role C (missing a_str)" + import_role: + name: c + vars: + a_str: ~ + - 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 == [error] + - ansible_failed_result.argument_spec_data == a_main_spec + vars: + error: >- + argument 'a_str' is of type <class 'NoneType'> and we were unable to convert to str: + 'None' is not a string and conversion is not allowed + + - name: test type coercion fails on None for required int + block: + - name: "Test import_role of role C (missing c_int)" + import_role: + name: c + vars: + c_int: ~ + - 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 == [error] + - ansible_failed_result.argument_spec_data == c_main_spec + vars: + error: >- + argument 'c_int' is of type <class 'NoneType'> and we were unable to convert to int: + <class 'NoneType'> cannot be converted to an int + + - name: test type coercion fails on None for required list + block: + - name: "Test import_role of role C (missing c_list)" + import_role: + name: c + vars: + c_list: ~ + - 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 == [error] + - ansible_failed_result.argument_spec_data == c_main_spec + vars: + error: >- + argument 'c_list' is of type <class 'NoneType'> and we were unable to convert to list: + <class 'NoneType'> cannot be converted to a list + + - name: test type coercion fails on None for required dict + block: + - name: "Test import_role of role C (missing c_dict)" + import_role: + name: c + vars: + c_dict: ~ + - 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 == [error] + - ansible_failed_result.argument_spec_data == c_main_spec + vars: + error: >- + argument 'c_dict' is of type <class 'NoneType'> and we were unable to convert to dict: + <class 'NoneType'> cannot be converted to a dict - name: "New play to reset vars: Test nested role including/importing role fails" hosts: localhost @@ -170,13 +293,15 @@ required: true type: "int" + c_int: 100 + c_list: [] + c_dict: {} + c_raw: ~ 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" @@ -201,7 +326,6 @@ include_role: name: c vars: - c_int: 200 a_str: "some string" - fail: diff --git a/test/integration/targets/rpm_key/tasks/rpm_key.yaml b/test/integration/targets/rpm_key/tasks/rpm_key.yaml index 89ed236..204b42a 100644 --- a/test/integration/targets/rpm_key/tasks/rpm_key.yaml +++ b/test/integration/targets/rpm_key/tasks/rpm_key.yaml @@ -123,6 +123,32 @@ assert: that: "'rsa sha1 (md5) pgp md5 OK' in sl_check.stdout or 'digests signatures OK' in sl_check.stdout" +- name: get keyid + shell: "rpm -q gpg-pubkey | head -n 1 | xargs rpm -q --qf %{version}" + register: key_id + +- name: remove GPG key using keyid + rpm_key: + state: absent + key: "{{ key_id.stdout }}" + register: remove_keyid + failed_when: remove_keyid.changed == false + +- name: remove GPG key using keyid (idempotent) + rpm_key: + state: absent + key: "{{ key_id.stdout }}" + register: key_id_idempotence + +- name: verify idempotent (key_id) + assert: + that: "not key_id_idempotence.changed" + +- name: add very first key on system again + rpm_key: + state: present + key: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/RPM-GPG-KEY-EPEL-7 + - name: Issue 20325 - Verify fingerprint of key, invalid fingerprint - EXPECTED FAILURE rpm_key: key: https://ci-files.testing.ansible.com/test/integration/targets/rpm_key/RPM-GPG-KEY.dag diff --git a/test/integration/targets/script/tasks/main.yml b/test/integration/targets/script/tasks/main.yml index 74189f8..668ec48 100644 --- a/test/integration/targets/script/tasks/main.yml +++ b/test/integration/targets/script/tasks/main.yml @@ -37,6 +37,17 @@ ## script ## +- name: Required one of free-form and cmd + script: + ignore_errors: yes + register: script_required + +- name: assert that the script fails if neither free-form nor cmd is given + assert: + that: + - script_required.failed + - "'one of the following' in script_required.msg" + - name: execute the test.sh script via command script: test.sh register: script_result0 diff --git a/test/integration/targets/service/aliases b/test/integration/targets/service/aliases index f2f9ac9..f3703f8 100644 --- a/test/integration/targets/service/aliases +++ b/test/integration/targets/service/aliases @@ -1,4 +1,3 @@ destructive shippable/posix/group1 -skip/osx skip/macos diff --git a/test/integration/targets/service/files/ansible_test_service.py b/test/integration/targets/service/files/ansible_test_service.py index 522493f..6292272 100644 --- a/test/integration/targets/service/files/ansible_test_service.py +++ b/test/integration/targets/service/files/ansible_test_service.py @@ -9,7 +9,6 @@ __metaclass__ = type import os import resource import signal -import sys import time UMASK = 0 diff --git a/test/integration/targets/service_facts/aliases b/test/integration/targets/service_facts/aliases index 17d3eb7..32e10b0 100644 --- a/test/integration/targets/service_facts/aliases +++ b/test/integration/targets/service_facts/aliases @@ -1,4 +1,3 @@ shippable/posix/group2 skip/freebsd -skip/osx skip/macos diff --git a/test/integration/targets/setup_deb_repo/tasks/main.yml b/test/integration/targets/setup_deb_repo/tasks/main.yml index 471fb2a..3e640f6 100644 --- a/test/integration/targets/setup_deb_repo/tasks/main.yml +++ b/test/integration/targets/setup_deb_repo/tasks/main.yml @@ -59,6 +59,7 @@ loop: - stable - testing + when: install_repo|default(True)|bool is true # Need to uncomment the deb-src for the universe component for build-dep state - name: Ensure deb-src for the universe component diff --git a/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml b/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml index f16d9b5..8c0b28b 100644 --- a/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml +++ b/test/integration/targets/setup_paramiko/install-Alpine-3-python-3.yml @@ -1,9 +1,2 @@ -- name: Setup remote constraints - include_tasks: setup-remote-constraints.yml - name: Install Paramiko for Python 3 on Alpine - pip: # no apk package manager in core, just use pip - name: paramiko - extra_args: "-c {{ remote_constraints }}" - environment: - # Not sure why this fixes the test, but it does. - SETUPTOOLS_USE_DISTUTILS: stdlib + command: apk add py3-paramiko diff --git a/test/integration/targets/setup_paramiko/install-CentOS-6-python-2.yml b/test/integration/targets/setup_paramiko/install-CentOS-6-python-2.yml deleted file mode 100644 index 0c7b9e8..0000000 --- a/test/integration/targets/setup_paramiko/install-CentOS-6-python-2.yml +++ /dev/null @@ -1,3 +0,0 @@ -- name: Install Paramiko for Python 2 on CentOS 6 - yum: - name: python-paramiko diff --git a/test/integration/targets/setup_paramiko/install-Fedora-35-python-3.yml b/test/integration/targets/setup_paramiko/install-Fedora-35-python-3.yml deleted file mode 100644 index bbe97a9..0000000 --- a/test/integration/targets/setup_paramiko/install-Fedora-35-python-3.yml +++ /dev/null @@ -1,9 +0,0 @@ -- name: Install Paramiko and crypto policies scripts - dnf: - name: - - crypto-policies-scripts - - python3-paramiko - install_weak_deps: no - -- name: Drop the crypto-policy to LEGACY for these tests - command: update-crypto-policies --set LEGACY diff --git a/test/integration/targets/setup_paramiko/install-Ubuntu-16-python-2.yml b/test/integration/targets/setup_paramiko/install-Ubuntu-16-python-2.yml deleted file mode 100644 index 8f76074..0000000 --- a/test/integration/targets/setup_paramiko/install-Ubuntu-16-python-2.yml +++ /dev/null @@ -1,3 +0,0 @@ -- name: Install Paramiko for Python 2 on Ubuntu 16 - apt: - name: python-paramiko diff --git a/test/integration/targets/setup_paramiko/install-python-2.yml b/test/integration/targets/setup_paramiko/install-python-2.yml deleted file mode 100644 index be337a1..0000000 --- a/test/integration/targets/setup_paramiko/install-python-2.yml +++ /dev/null @@ -1,3 +0,0 @@ -- name: Install Paramiko for Python 2 - package: - name: python2-paramiko diff --git a/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml b/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml index e9dcc62..edb504f 100644 --- a/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml +++ b/test/integration/targets/setup_paramiko/uninstall-Alpine-3-python-3.yml @@ -1,4 +1,2 @@ - name: Uninstall Paramiko for Python 3 on Alpine - pip: - name: paramiko - state: absent + command: apk del py3-paramiko diff --git a/test/integration/targets/setup_paramiko/uninstall-Fedora-35-python-3.yml b/test/integration/targets/setup_paramiko/uninstall-Fedora-35-python-3.yml deleted file mode 100644 index 6d0e9a1..0000000 --- a/test/integration/targets/setup_paramiko/uninstall-Fedora-35-python-3.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Revert the crypto-policy back to DEFAULT - command: update-crypto-policies --set DEFAULT - -- name: Uninstall Paramiko and crypto policies scripts using dnf history undo - command: dnf history undo last --assumeyes diff --git a/test/integration/targets/setup_paramiko/uninstall-apt-python-2.yml b/test/integration/targets/setup_paramiko/uninstall-apt-python-2.yml deleted file mode 100644 index 507d94c..0000000 --- a/test/integration/targets/setup_paramiko/uninstall-apt-python-2.yml +++ /dev/null @@ -1,5 +0,0 @@ -- name: Uninstall Paramiko for Python 2 using apt - apt: - name: python-paramiko - state: absent - autoremove: yes diff --git a/test/integration/targets/setup_paramiko/uninstall-zypper-python-2.yml b/test/integration/targets/setup_paramiko/uninstall-zypper-python-2.yml deleted file mode 100644 index adb26e5..0000000 --- a/test/integration/targets/setup_paramiko/uninstall-zypper-python-2.yml +++ /dev/null @@ -1,2 +0,0 @@ -- name: Uninstall Paramiko for Python 2 using zypper - command: zypper --quiet --non-interactive remove --clean-deps python2-paramiko diff --git a/test/integration/targets/setup_rpm_repo/tasks/main.yml b/test/integration/targets/setup_rpm_repo/tasks/main.yml index be20078..bf5af10 100644 --- a/test/integration/targets/setup_rpm_repo/tasks/main.yml +++ b/test/integration/targets/setup_rpm_repo/tasks/main.yml @@ -24,9 +24,18 @@ args: name: "{{ rpm_repo_packages }}" - - name: Install rpmfluff via pip - pip: - name: rpmfluff + - name: Install rpmfluff via pip, ensure it is installed with default python as python3-rpm may not exist for other versions + block: + - action: "{{ ansible_facts.pkg_mgr }}" + args: + name: + - python3-pip + - python3 + state: latest + + - pip: + name: rpmfluff + executable: pip3 when: ansible_facts.os_family == 'RedHat' and ansible_distribution_major_version is version('9', '==') - set_fact: diff --git a/test/integration/targets/strategy_linear/runme.sh b/test/integration/targets/strategy_linear/runme.sh index cbb6aea..a2734f9 100755 --- a/test/integration/targets/strategy_linear/runme.sh +++ b/test/integration/targets/strategy_linear/runme.sh @@ -5,3 +5,5 @@ set -eux ansible-playbook test_include_file_noop.yml -i inventory "$@" ansible-playbook task_action_templating.yml -i inventory "$@" + +ansible-playbook task_templated_run_once.yml -i inventory "$@" diff --git a/test/integration/targets/strategy_linear/task_templated_run_once.yml b/test/integration/targets/strategy_linear/task_templated_run_once.yml new file mode 100644 index 0000000..bacf06a --- /dev/null +++ b/test/integration/targets/strategy_linear/task_templated_run_once.yml @@ -0,0 +1,20 @@ +- hosts: testhost,testhost2 + gather_facts: no + vars: + run_once_flag: false + tasks: + - debug: + msg: "I am {{ item }}" + run_once: "{{ run_once_flag }}" + register: reg1 + loop: + - "{{ inventory_hostname }}" + + - assert: + that: + - "reg1.results[0].msg == 'I am testhost'" + when: inventory_hostname == 'testhost' + - assert: + that: + - "reg1.results[0].msg == 'I am testhost2'" + when: inventory_hostname == 'testhost2' diff --git a/test/integration/targets/subversion/aliases b/test/integration/targets/subversion/aliases index 3cc41e4..03b9643 100644 --- a/test/integration/targets/subversion/aliases +++ b/test/integration/targets/subversion/aliases @@ -1,6 +1,4 @@ shippable/posix/group2 -skip/osx skip/macos -skip/rhel/9.0b # svn checkout hangs destructive needs/root diff --git a/test/integration/targets/support-callback_plugins/aliases b/test/integration/targets/support-callback_plugins/aliases new file mode 100644 index 0000000..136c05e --- /dev/null +++ b/test/integration/targets/support-callback_plugins/aliases @@ -0,0 +1 @@ +hidden diff --git a/test/integration/targets/ansible/callback_plugins/callback_debug.py b/test/integration/targets/support-callback_plugins/callback_plugins/callback_debug.py index 2462c1f..2462c1f 100644 --- a/test/integration/targets/ansible/callback_plugins/callback_debug.py +++ b/test/integration/targets/support-callback_plugins/callback_plugins/callback_debug.py diff --git a/test/integration/targets/systemd/tasks/test_indirect_service.yml b/test/integration/targets/systemd/tasks/test_indirect_service.yml index fc11343..0df6048 100644 --- a/test/integration/targets/systemd/tasks/test_indirect_service.yml +++ b/test/integration/targets/systemd/tasks/test_indirect_service.yml @@ -34,4 +34,4 @@ - assert: that: - systemd_enable_dummy_indirect_1 is changed - - systemd_enable_dummy_indirect_2 is not changed
\ No newline at end of file + - systemd_enable_dummy_indirect_2 is not changed diff --git a/test/integration/targets/systemd/vars/Debian.yml b/test/integration/targets/systemd/vars/Debian.yml index 613410f..2dd0aff 100644 --- a/test/integration/targets/systemd/vars/Debian.yml +++ b/test/integration/targets/systemd/vars/Debian.yml @@ -1,3 +1,3 @@ ssh_service: ssh sleep_bin_path: /bin/sleep -indirect_service: dummy
\ No newline at end of file +indirect_service: dummy diff --git a/test/integration/targets/tags/runme.sh b/test/integration/targets/tags/runme.sh index 9da0b30..7dcb998 100755 --- a/test/integration/targets/tags/runme.sh +++ b/test/integration/targets/tags/runme.sh @@ -73,3 +73,12 @@ ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=list --tags t ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged --tags untagged "$@" ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=untagged_list --tags untagged,tag3 "$@" ansible-playbook -i ../../inventory ansible_run_tags.yml -e expect=tagged --tags tagged "$@" + +ansible-playbook test_template_parent_tags.yml "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt + +ansible-playbook test_template_parent_tags.yml --tags tag1 "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "1" ]; rm out.txt + +ansible-playbook test_template_parent_tags.yml --skip-tags tag1 "$@" 2>&1 | tee out.txt +[ "$(grep out.txt -ce 'Tagged_task')" = "0" ]; rm out.txt diff --git a/test/integration/targets/tags/test_template_parent_tags.yml b/test/integration/targets/tags/test_template_parent_tags.yml new file mode 100644 index 0000000..ea1c828 --- /dev/null +++ b/test/integration/targets/tags/test_template_parent_tags.yml @@ -0,0 +1,10 @@ +- hosts: localhost + gather_facts: false + vars: + tags_in_var: + - tag1 + tasks: + - block: + - name: Tagged_task + debug: + tags: "{{ tags_in_var }}" diff --git a/test/integration/targets/tasks/playbook.yml b/test/integration/targets/tasks/playbook.yml index 80d9f8b..10bd859 100644 --- a/test/integration/targets/tasks/playbook.yml +++ b/test/integration/targets/tasks/playbook.yml @@ -6,6 +6,11 @@ debug: msg: Hello + # ensure we properly test for an action name, not a task name when cheking for a meta task + - name: "meta" + debug: + msg: Hello + - name: ensure malformed raw_params on arbitrary actions are not ignored debug: garbage {{"with a template"}} diff --git a/test/integration/targets/tasks/runme.sh b/test/integration/targets/tasks/runme.sh index 594447b..57cbf28 100755 --- a/test/integration/targets/tasks/runme.sh +++ b/test/integration/targets/tasks/runme.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -ansible-playbook playbook.yml "$@" +ansible-playbook playbook.yml
\ No newline at end of file diff --git a/test/integration/targets/template/ansible_managed_79129.yml b/test/integration/targets/template/ansible_managed_79129.yml new file mode 100644 index 0000000..e00ada8 --- /dev/null +++ b/test/integration/targets/template/ansible_managed_79129.yml @@ -0,0 +1,29 @@ +--- +- hosts: testhost + gather_facts: false + tasks: + - set_fact: + output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" + + - name: check strftime + block: + - template: + src: "templates/%necho Onii-chan help Im stuck;exit 1%n.j2" + dest: "{{ output_dir }}/79129-strftime.sh" + mode: '0755' + + - shell: "exec {{ output_dir | quote }}/79129-strftime.sh" + + - name: check jinja template + block: + - template: + src: !unsafe "templates/completely{{ 1 % 0 }} safe template.j2" + dest: "{{ output_dir }}/79129-jinja.sh" + mode: '0755' + + - shell: "exec {{ output_dir | quote }}/79129-jinja.sh" + register: result + + - assert: + that: + - "'Hello' in result.stdout" diff --git a/test/integration/targets/template/arg_template_overrides.j2 b/test/integration/targets/template/arg_template_overrides.j2 new file mode 100644 index 0000000..17a79b9 --- /dev/null +++ b/test/integration/targets/template/arg_template_overrides.j2 @@ -0,0 +1,4 @@ +var_a: << var_a >> +var_b: << var_b >> +var_c: << var_c >> +var_d: << var_d >> diff --git a/test/integration/targets/template/in_template_overrides.yml b/test/integration/targets/template/in_template_overrides.yml deleted file mode 100644 index 3c2d4d9..0000000 --- a/test/integration/targets/template/in_template_overrides.yml +++ /dev/null @@ -1,28 +0,0 @@ -- hosts: localhost - gather_facts: false - vars: - var_a: "value" - var_b: "{{ var_a }}" - var_c: "<< var_a >>" - tasks: - - set_fact: - var_d: "{{ var_a }}" - - - block: - - template: - src: in_template_overrides.j2 - dest: out.txt - - - command: cat out.txt - register: out - - - assert: - that: - - "'var_a: value' in out.stdout" - - "'var_b: value' in out.stdout" - - "'var_c: << var_a >>' in out.stdout" - - "'var_d: value' in out.stdout" - always: - - file: - path: out.txt - state: absent diff --git a/test/integration/targets/template/runme.sh b/test/integration/targets/template/runme.sh index 30163af..d3913d9 100755 --- a/test/integration/targets/template/runme.sh +++ b/test/integration/targets/template/runme.sh @@ -8,7 +8,10 @@ ANSIBLE_ROLES_PATH=../ ansible-playbook template.yml -i ../../inventory -v "$@" ansible testhost -i testhost, -m debug -a 'msg={{ hostvars["localhost"] }}' -e "vars1={{ undef() }}" -e "vars2={{ vars1 }}" # Test for https://github.com/ansible/ansible/issues/27262 -ansible-playbook ansible_managed.yml -c ansible_managed.cfg -i ../../inventory -v "$@" +ANSIBLE_CONFIG=ansible_managed.cfg ansible-playbook ansible_managed.yml -i ../../inventory -v "$@" + +# Test for https://github.com/ansible/ansible/pull/79129 +ANSIBLE_CONFIG=ansible_managed.cfg ansible-playbook ansible_managed_79129.yml -i ../../inventory -v "$@" # Test for #42585 ANSIBLE_ROLES_PATH=../ ansible-playbook custom_template.yml -i ../../inventory -v "$@" @@ -39,7 +42,7 @@ ansible-playbook 72262.yml -v "$@" ansible-playbook unsafe.yml -v "$@" # ensure Jinja2 overrides from a template are used -ansible-playbook in_template_overrides.yml -v "$@" +ansible-playbook template_overrides.yml -v "$@" ansible-playbook lazy_eval.yml -i ../../inventory -v "$@" diff --git a/test/integration/targets/template/tasks/main.yml b/test/integration/targets/template/tasks/main.yml index 3c91734..34e8828 100644 --- a/test/integration/targets/template/tasks/main.yml +++ b/test/integration/targets/template/tasks/main.yml @@ -25,7 +25,7 @@ - name: show jinja2 version debug: - msg: "{{ lookup('pipe', '{{ ansible_python[\"executable\"] }} -c \"import jinja2; print(jinja2.__version__)\"') }}" + msg: "{{ lookup('pipe', ansible_python.executable ~ ' -c \"import jinja2; print(jinja2.__version__)\"') }}" - name: get default group shell: id -gn @@ -760,7 +760,7 @@ that: - test vars: - test: "{{ lookup('file', '{{ output_dir }}/empty_template.templated')|length == 0 }}" + test: "{{ lookup('file', output_dir ~ '/empty_template.templated')|length == 0 }}" - name: test jinja2 override without colon throws proper error block: diff --git a/test/integration/targets/template/template_overrides.yml b/test/integration/targets/template/template_overrides.yml new file mode 100644 index 0000000..50cfb8f --- /dev/null +++ b/test/integration/targets/template/template_overrides.yml @@ -0,0 +1,38 @@ +- hosts: localhost + gather_facts: false + vars: + output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" + var_a: "value" + var_b: "{{ var_a }}" + var_c: "<< var_a >>" + tasks: + - set_fact: + var_d: "{{ var_a }}" + + - template: + src: in_template_overrides.j2 + dest: '{{ output_dir }}/in_template_overrides.out' + + - template: + src: arg_template_overrides.j2 + dest: '{{ output_dir }}/arg_template_overrides.out' + variable_start_string: '<<' + variable_end_string: '>>' + + - command: cat '{{ output_dir }}/in_template_overrides.out' + register: in_template_overrides_out + + - command: cat '{{ output_dir }}/arg_template_overrides.out' + register: arg_template_overrides_out + + - assert: + that: + - "'var_a: value' in in_template_overrides_out.stdout" + - "'var_b: value' in in_template_overrides_out.stdout" + - "'var_c: << var_a >>' in in_template_overrides_out.stdout" + - "'var_d: value' in in_template_overrides_out.stdout" + + - "'var_a: value' in arg_template_overrides_out.stdout" + - "'var_b: value' in arg_template_overrides_out.stdout" + - "'var_c: << var_a >>' in arg_template_overrides_out.stdout" + - "'var_d: value' in arg_template_overrides_out.stdout" diff --git a/test/integration/targets/template/templates/%necho Onii-chan help Im stuck;exit 1%n.j2 b/test/integration/targets/template/templates/%necho Onii-chan help Im stuck;exit 1%n.j2 new file mode 100644 index 0000000..2d63c15 --- /dev/null +++ b/test/integration/targets/template/templates/%necho Onii-chan help Im stuck;exit 1%n.j2 @@ -0,0 +1,3 @@ +# {{ ansible_managed }} +echo 79129 test passed +exit 0 diff --git a/test/integration/targets/template/templates/completely{{ 1 % 0 }} safe template.j2 b/test/integration/targets/template/templates/completely{{ 1 % 0 }} safe template.j2 new file mode 100644 index 0000000..c9a04b4 --- /dev/null +++ b/test/integration/targets/template/templates/completely{{ 1 % 0 }} safe template.j2 @@ -0,0 +1,3 @@ +# {{ ansible_managed }} +echo Hello +exit 0 diff --git a/test/integration/targets/template/unsafe.yml b/test/integration/targets/template/unsafe.yml index bef9a4b..6f16388 100644 --- a/test/integration/targets/template/unsafe.yml +++ b/test/integration/targets/template/unsafe.yml @@ -3,6 +3,7 @@ vars: nottemplated: this should not be seen imunsafe: !unsafe '{{ nottemplated }}' + unsafe_set: !unsafe '{{ "test" }}' tasks: - set_fact: @@ -12,11 +13,15 @@ - set_fact: this_always_safe: '{{ imunsafe }}' + - set_fact: + this_unsafe_set: "{{ unsafe_set }}" + - name: ensure nothing was templated assert: that: - this_always_safe == imunsafe - imunsafe == this_was_unsafe.strip() + - unsafe_set == this_unsafe_set.strip() - hosts: localhost diff --git a/test/integration/targets/template_jinja2_non_native/macro_override.yml b/test/integration/targets/template_jinja2_non_native/macro_override.yml index 8a1cabd..c3f9ab6 100644 --- a/test/integration/targets/template_jinja2_non_native/macro_override.yml +++ b/test/integration/targets/template_jinja2_non_native/macro_override.yml @@ -12,4 +12,4 @@ - "'foobar' not in data" - "'\"foo\" \"bar\"' in data" vars: - data: "{{ lookup('file', '{{ output_dir }}/macro_override.out') }}" + data: "{{ lookup('file', output_dir ~ '/macro_override.out') }}" diff --git a/test/integration/targets/templating/tasks/main.yml b/test/integration/targets/templating/tasks/main.yml index 312e171..edbf012 100644 --- a/test/integration/targets/templating/tasks/main.yml +++ b/test/integration/targets/templating/tasks/main.yml @@ -33,3 +33,14 @@ - result is failed - >- "TemplateSyntaxError: Could not load \"asdf \": 'invalid plugin name: ansible.builtin.asdf '" in result.msg + +- name: Make sure syntax errors originating from a template being compiled into Python code object result in a failure + debug: + msg: "{{ lookup('vars', 'v1', default='', default='') }}" + ignore_errors: true + register: r + +- assert: + that: + - r is failed + - "'keyword argument repeated' in r.msg" diff --git a/test/integration/targets/test_core/tasks/main.yml b/test/integration/targets/test_core/tasks/main.yml index 8c2decb..ac06d67 100644 --- a/test/integration/targets/test_core/tasks/main.yml +++ b/test/integration/targets/test_core/tasks/main.yml @@ -126,6 +126,16 @@ hello: world register: executed_task +- name: Skip me with multiple conditions + set_fact: + hello: world + when: + - True == True + - foo == 'bar' + vars: + foo: foo + register: skipped_task_multi_condition + - name: Try skipped test on non-dictionary set_fact: hello: "{{ 'nope' is skipped }}" @@ -136,8 +146,11 @@ assert: that: - skipped_task is skipped + - skipped_task.false_condition == False - executed_task is not skipped - misuse_of_skipped is failure + - skipped_task_multi_condition is skipped + - skipped_task_multi_condition.false_condition == "foo == 'bar'" - name: Not an async task set_fact: diff --git a/test/integration/targets/test_utils/aliases b/test/integration/targets/test_utils/aliases new file mode 100644 index 0000000..136c05e --- /dev/null +++ b/test/integration/targets/test_utils/aliases @@ -0,0 +1 @@ +hidden diff --git a/test/integration/targets/test_utils/scripts/timeout.py b/test/integration/targets/test_utils/scripts/timeout.py new file mode 100755 index 0000000..f88f3e4 --- /dev/null +++ b/test/integration/targets/test_utils/scripts/timeout.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import argparse +import subprocess +import sys + +parser = argparse.ArgumentParser() +parser.add_argument('duration', type=int) +parser.add_argument('command', nargs='+') +args = parser.parse_args() + +try: + p = subprocess.run( + ' '.join(args.command), + shell=True, + timeout=args.duration, + check=False, + ) + sys.exit(p.returncode) +except subprocess.TimeoutExpired: + sys.exit(124) diff --git a/test/integration/targets/unarchive/runme.sh b/test/integration/targets/unarchive/runme.sh new file mode 100755 index 0000000..5351a0c --- /dev/null +++ b/test/integration/targets/unarchive/runme.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -eux + +ansible-playbook -i ../../inventory runme.yml -v "$@" + +# https://github.com/ansible/ansible/issues/80710 +ANSIBLE_REMOTE_TMP=./ansible ansible-playbook -i ../../inventory test_relative_tmp_dir.yml -v "$@" diff --git a/test/integration/targets/unarchive/runme.yml b/test/integration/targets/unarchive/runme.yml new file mode 100644 index 0000000..ddcd609 --- /dev/null +++ b/test/integration/targets/unarchive/runme.yml @@ -0,0 +1,4 @@ +- hosts: all + gather_facts: no + roles: + - { role: ../unarchive } diff --git a/test/integration/targets/unarchive/tasks/main.yml b/test/integration/targets/unarchive/tasks/main.yml index 148e583..b07c2fe 100644 --- a/test/integration/targets/unarchive/tasks/main.yml +++ b/test/integration/targets/unarchive/tasks/main.yml @@ -20,3 +20,4 @@ - import_tasks: test_different_language_var.yml - import_tasks: test_invalid_options.yml - import_tasks: test_ownership_top_folder.yml +- import_tasks: test_relative_dest.yml diff --git a/test/integration/targets/unarchive/tasks/test_different_language_var.yml b/test/integration/targets/unarchive/tasks/test_different_language_var.yml index 9eec658..32c84f4 100644 --- a/test/integration/targets/unarchive/tasks/test_different_language_var.yml +++ b/test/integration/targets/unarchive/tasks/test_different_language_var.yml @@ -2,10 +2,10 @@ when: ansible_os_family == 'Debian' block: - name: install fr language pack - apt: + apt: name: language-pack-fr state: present - + - name: create our unarchive destination file: path: "{{ remote_tmp_dir }}/test-unarchive-nonascii-くらとみ-tar-gz" diff --git a/test/integration/targets/unarchive/tasks/test_mode.yml b/test/integration/targets/unarchive/tasks/test_mode.yml index 06fbc7b..efd428e 100644 --- a/test/integration/targets/unarchive/tasks/test_mode.yml +++ b/test/integration/targets/unarchive/tasks/test_mode.yml @@ -3,6 +3,29 @@ path: '{{remote_tmp_dir}}/test-unarchive-tar-gz' state: directory +- name: test invalid modes + unarchive: + src: "{{ remote_tmp_dir }}/test-unarchive.tar.gz" + dest: "{{ remote_tmp_dir }}/test-unarchive-tar-gz" + remote_src: yes + mode: "{{ item }}" + list_files: True + register: unarchive_mode_errors + ignore_errors: yes + loop: + - u=foo + - foo=r + - ufoo=r + - abc=r + - ao=r + - oa=r + +- assert: + that: + - item.failed + - "'bad symbolic permission for mode: ' + item.item == item.details" + loop: "{{ unarchive_mode_errors.results }}" + - name: unarchive and set mode to 0600, directories 0700 unarchive: src: "{{ remote_tmp_dir }}/test-unarchive.tar.gz" diff --git a/test/integration/targets/unarchive/tasks/test_relative_dest.yml b/test/integration/targets/unarchive/tasks/test_relative_dest.yml new file mode 100644 index 0000000..aae31fb --- /dev/null +++ b/test/integration/targets/unarchive/tasks/test_relative_dest.yml @@ -0,0 +1,26 @@ +- name: Create relative test directory + file: + path: test-unarchive-relative + state: directory + +- name: Unarchive a file using a relative destination path + unarchive: + src: "{{ remote_tmp_dir }}/test-unarchive.tar" + dest: test-unarchive-relative + remote_src: yes + register: relative_dest_1 + +- name: Unarchive a file using a relative destination path again + unarchive: + src: "{{ remote_tmp_dir }}/test-unarchive.tar" + dest: test-unarchive-relative + remote_src: yes + register: relative_dest_2 + +- name: Ensure changes were made correctly + assert: + that: + - relative_dest_1 is changed + - relative_dest_1.warnings | length > 0 + - relative_dest_1.warnings[0] is search('absolute path') + - relative_dest_2 is not changed diff --git a/test/integration/targets/unarchive/test_relative_tmp_dir.yml b/test/integration/targets/unarchive/test_relative_tmp_dir.yml new file mode 100644 index 0000000..f368f7a --- /dev/null +++ b/test/integration/targets/unarchive/test_relative_tmp_dir.yml @@ -0,0 +1,10 @@ +- hosts: all + gather_facts: no + tasks: + - include_role: + name: ../setup_remote_tmp_dir + - include_role: + name: ../setup_gnutar + - include_tasks: tasks/prepare_tests.yml + + - include_tasks: tasks/test_tar.yml diff --git a/test/integration/targets/unsafe_writes/aliases b/test/integration/targets/unsafe_writes/aliases index da1b554..3560af2 100644 --- a/test/integration/targets/unsafe_writes/aliases +++ b/test/integration/targets/unsafe_writes/aliases @@ -1,7 +1,6 @@ context/target needs/root skip/freebsd -skip/osx skip/macos shippable/posix/group2 needs/target/setup_remote_tmp_dir diff --git a/test/integration/targets/until/tasks/main.yml b/test/integration/targets/until/tasks/main.yml index 2b2ac94..42ce9c8 100644 --- a/test/integration/targets/until/tasks/main.yml +++ b/test/integration/targets/until/tasks/main.yml @@ -82,3 +82,37 @@ register: counter delay: 0.5 until: counter.rc == 0 + +- name: test retries without explicit until, defaults to "until task succeeds" + block: + - name: EXPECTED FAILURE + fail: + retries: 3 + delay: 0.1 + register: r + ignore_errors: true + + - assert: + that: + - r.attempts == 3 + + - vars: + test_file: "{{ lookup('env', 'OUTPUT_DIR') }}/until_success_test_file" + block: + - file: + name: "{{ test_file }}" + state: absent + + - name: fail on the first invocation, succeed on the second + shell: "[ -f {{ test_file }} ] || (touch {{ test_file }} && false)" + retries: 5 + delay: 0.1 + register: r + always: + - file: + name: "{{ test_file }}" + state: absent + + - assert: + that: + - r.attempts == 2 diff --git a/test/integration/targets/unvault/main.yml b/test/integration/targets/unvault/main.yml index a0f97b4..8f0adc7 100644 --- a/test/integration/targets/unvault/main.yml +++ b/test/integration/targets/unvault/main.yml @@ -1,4 +1,5 @@ - hosts: localhost + gather_facts: false tasks: - set_fact: unvaulted: "{{ lookup('unvault', 'vault') }}" diff --git a/test/integration/targets/unvault/runme.sh b/test/integration/targets/unvault/runme.sh index df4585e..054a14d 100755 --- a/test/integration/targets/unvault/runme.sh +++ b/test/integration/targets/unvault/runme.sh @@ -2,5 +2,5 @@ set -eux - +# simple run ansible-playbook --vault-password-file password main.yml diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index 9ba09ec..ddae83a 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -132,7 +132,7 @@ - "result.changed == true" - name: "get ca certificate {{ self_signed_host }}" - get_url: + uri: url: "http://{{ httpbin_host }}/ca2cert.pem" dest: "{{ remote_tmp_dir }}/ca2cert.pem" @@ -638,9 +638,18 @@ - assert: that: - result['set_cookie'] == 'Foo=bar, Baz=qux' - # Python sorts cookies in order of most specific (ie. longest) path first + # Python 3.10 and earlier sorts cookies in order of most specific (ie. longest) path first # items with the same path are reversed from response order - result['cookies_string'] == 'Baz=qux; Foo=bar' + when: ansible_python_version is version('3.11', '<') + +- assert: + that: + - result['set_cookie'] == 'Foo=bar, Baz=qux' + # Python 3.11 no longer sorts cookies. + # See: https://github.com/python/cpython/issues/86232 + - result['cookies_string'] == 'Foo=bar; Baz=qux' + when: ansible_python_version is version('3.11', '>=') - name: Write out netrc template template: @@ -757,6 +766,30 @@ dest: "{{ remote_tmp_dir }}/output" state: absent +- name: Test download root to dir without content-disposition + uri: + url: "https://{{ httpbin_host }}/" + dest: "{{ remote_tmp_dir }}" + register: get_root_no_filename + +- name: Test downloading to dir without content-disposition + uri: + url: "https://{{ httpbin_host }}/response-headers" + dest: "{{ remote_tmp_dir }}" + register: get_dir_no_filename + +- name: Test downloading to dir with content-disposition + uri: + url: 'https://{{ httpbin_host }}/response-headers?Content-Disposition=attachment%3B%20filename%3D%22filename.json%22' + dest: "{{ remote_tmp_dir }}" + register: get_dir_filename + +- assert: + that: + - get_root_no_filename.path == remote_tmp_dir ~ "/index.html" + - get_dir_no_filename.path == remote_tmp_dir ~ "/response-headers" + - get_dir_filename.path == remote_tmp_dir ~ "/filename.json" + - name: Test follow_redirects=none import_tasks: redirect-none.yml diff --git a/test/integration/targets/uri/tasks/redirect-none.yml b/test/integration/targets/uri/tasks/redirect-none.yml index 0d1b2b3..060950d 100644 --- a/test/integration/targets/uri/tasks/redirect-none.yml +++ b/test/integration/targets/uri/tasks/redirect-none.yml @@ -240,7 +240,7 @@ url: https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything follow_redirects: none return_content: yes - method: GET + method: HEAD ignore_errors: yes register: http_308_head diff --git a/test/integration/targets/uri/tasks/redirect-urllib2.yml b/test/integration/targets/uri/tasks/redirect-urllib2.yml index 6cdafdb..73e8796 100644 --- a/test/integration/targets/uri/tasks/redirect-urllib2.yml +++ b/test/integration/targets/uri/tasks/redirect-urllib2.yml @@ -237,7 +237,7 @@ url: https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything follow_redirects: urllib2 return_content: yes - method: GET + method: HEAD ignore_errors: yes register: http_308_head @@ -250,6 +250,23 @@ - http_308_head.redirected == false - http_308_head.status == 308 - http_308_head.url == 'https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything' + # Python 3.10 and earlier do not support HTTP 308 responses. + # See: https://github.com/python/cpython/issues/84501 + when: ansible_python_version is version('3.11', '<') + +# NOTE: The HTTP HEAD turns into an HTTP GET +- assert: + that: + - http_308_head is successful + - http_308_head.json.data == '' + - http_308_head.json.method == 'GET' + - http_308_head.json.url == 'https://{{ httpbin_host }}/anything' + - http_308_head.redirected == true + - http_308_head.status == 200 + - http_308_head.url == 'https://{{ httpbin_host }}/anything' + # Python 3.11 introduced support for HTTP 308 responses. + # See: https://github.com/python/cpython/issues/84501 + when: ansible_python_version is version('3.11', '>=') # FIXME: This is fixed in https://github.com/ansible/ansible/pull/36809 - name: Test HTTP 308 using GET @@ -270,6 +287,22 @@ - http_308_get.redirected == false - http_308_get.status == 308 - http_308_get.url == 'https://{{ httpbin_host }}/redirect-to?status_code=308&url=https://{{ httpbin_host }}/anything' + # Python 3.10 and earlier do not support HTTP 308 responses. + # See: https://github.com/python/cpython/issues/84501 + when: ansible_python_version is version('3.11', '<') + +- assert: + that: + - http_308_get is successful + - http_308_get.json.data == '' + - http_308_get.json.method == 'GET' + - http_308_get.json.url == 'https://{{ httpbin_host }}/anything' + - http_308_get.redirected == true + - http_308_get.status == 200 + - http_308_get.url == 'https://{{ httpbin_host }}/anything' + # Python 3.11 introduced support for HTTP 308 responses. + # See: https://github.com/python/cpython/issues/84501 + when: ansible_python_version is version('3.11', '>=') # FIXME: This is fixed in https://github.com/ansible/ansible/pull/36809 - name: Test HTTP 308 using POST diff --git a/test/integration/targets/uri/tasks/return-content.yml b/test/integration/targets/uri/tasks/return-content.yml index 5a9b97e..cb8aeea 100644 --- a/test/integration/targets/uri/tasks/return-content.yml +++ b/test/integration/targets/uri/tasks/return-content.yml @@ -46,4 +46,4 @@ assert: that: - result is failed - - "'content' not in result"
\ No newline at end of file + - "'content' not in result" diff --git a/test/integration/targets/uri/tasks/use_netrc.yml b/test/integration/targets/uri/tasks/use_netrc.yml index da745b8..521f8eb 100644 --- a/test/integration/targets/uri/tasks/use_netrc.yml +++ b/test/integration/targets/uri/tasks/use_netrc.yml @@ -48,4 +48,4 @@ - name: Clean up file: dest: "{{ remote_tmp_dir }}/netrc" - state: absent
\ No newline at end of file + state: absent diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml index 9d36bfc..be4c4d6 100644 --- a/test/integration/targets/user/tasks/main.yml +++ b/test/integration/targets/user/tasks/main.yml @@ -31,7 +31,9 @@ - import_tasks: test_expires.yml - import_tasks: test_expires_new_account.yml - import_tasks: test_expires_new_account_epoch_negative.yml +- import_tasks: test_expires_no_shadow.yml - import_tasks: test_expires_min_max.yml +- import_tasks: test_expires_warn.yml - import_tasks: test_shadow_backup.yml - import_tasks: test_ssh_key_passphrase.yml - import_tasks: test_password_lock.yml diff --git a/test/integration/targets/user/tasks/test_create_user.yml b/test/integration/targets/user/tasks/test_create_user.yml index bced790..644dbeb 100644 --- a/test/integration/targets/user/tasks/test_create_user.yml +++ b/test/integration/targets/user/tasks/test_create_user.yml @@ -65,3 +65,15 @@ - "user_test1.results[2]['state'] == 'present'" - "user_test1.results[3]['state'] == 'present'" - "user_test1.results[4]['state'] == 'present'" + +- name: register user informations + when: ansible_facts.system == 'Darwin' + command: dscl . -read /Users/ansibulluser + register: user_test2 + +- name: validate user defaults for MacOS + when: ansible_facts.system == 'Darwin' + assert: + that: + - "'RealName: ansibulluser' in user_test2.stdout_lines " + - "'PrimaryGroupID: 20' in user_test2.stdout_lines " diff --git a/test/integration/targets/user/tasks/test_create_user_home.yml b/test/integration/targets/user/tasks/test_create_user_home.yml index 1b529f7..5561a2f 100644 --- a/test/integration/targets/user/tasks/test_create_user_home.yml +++ b/test/integration/targets/user/tasks/test_create_user_home.yml @@ -134,3 +134,21 @@ name: randomuser state: absent remove: yes + +- name: Create user home directory with /dev/null as skeleton, https://github.com/ansible/ansible/issues/75063 + # create_homedir is mostly used by linux, rest of OSs take care of it themselves via -k option (which fails this task) + when: ansible_system == 'Linux' + block: + - name: "Create user home directory with /dev/null as skeleton" + user: + name: withskeleton + state: present + skeleton: "/dev/null" + createhome: yes + register: create_user_with_skeleton_dev_null + always: + - name: "Remove test user" + user: + name: withskeleton + state: absent + remove: yes diff --git a/test/integration/targets/user/tasks/test_expires_no_shadow.yml b/test/integration/targets/user/tasks/test_expires_no_shadow.yml new file mode 100644 index 0000000..4629c6f --- /dev/null +++ b/test/integration/targets/user/tasks/test_expires_no_shadow.yml @@ -0,0 +1,47 @@ +# https://github.com/ansible/ansible/issues/71916 +- name: Test setting expiration for a user account that does not have an /etc/shadow entry + when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] + block: + - name: Remove ansibulluser + user: + name: ansibulluser + state: absent + remove: yes + + - name: Create user account entry in /etc/passwd + lineinfile: + path: /etc/passwd + line: "ansibulluser::575:575::/home/dummy:/bin/bash" + regexp: "^ansibulluser.*" + state: present + + - name: Create user with negative expiration + user: + name: ansibulluser + uid: 575 + expires: -1 + register: user_test_expires_no_shadow_1 + + - name: Create user with negative expiration again + user: + name: ansibulluser + uid: 575 + expires: -1 + register: user_test_expires_no_shadow_2 + + - name: Ensure changes were made appropriately + assert: + that: + - user_test_expires_no_shadow_1 is changed + - user_test_expires_no_shadow_2 is not changed + + - name: Get expiration date for ansibulluser + getent: + database: shadow + key: ansibulluser + + - name: LINUX | Ensure proper expiration date was set + assert: + msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}" + that: + - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0 diff --git a/test/integration/targets/user/tasks/test_expires_warn.yml b/test/integration/targets/user/tasks/test_expires_warn.yml new file mode 100644 index 0000000..afe033c --- /dev/null +++ b/test/integration/targets/user/tasks/test_expires_warn.yml @@ -0,0 +1,36 @@ +# https://github.com/ansible/ansible/issues/79882 +- name: Test setting warning days + when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] + block: + - name: create user + user: + name: ansibulluser + state: present + + - name: add warning days for password + user: + name: ansibulluser + password_expire_warn: 28 + register: pass_warn_1_0 + + - name: again add warning days for password + user: + name: ansibulluser + password_expire_warn: 28 + register: pass_warn_1_1 + + - name: validate result for warning days + assert: + that: + - pass_warn_1_0 is changed + - pass_warn_1_1 is not changed + + - name: Get shadow data for ansibulluser + getent: + database: shadow + key: ansibulluser + + - name: Ensure number of warning days was set properly + assert: + that: + - ansible_facts.getent_shadow['ansibulluser'][4] == '28' diff --git a/test/integration/targets/user/tasks/test_local.yml b/test/integration/targets/user/tasks/test_local.yml index 67c24a2..217d476 100644 --- a/test/integration/targets/user/tasks/test_local.yml +++ b/test/integration/targets/user/tasks/test_local.yml @@ -86,9 +86,11 @@ - testgroup3 - testgroup4 - testgroup5 + - testgroup6 - local_ansibulluser tags: - user_test_local_mode + register: test_groups - name: Create local_ansibulluser with groups user: @@ -113,6 +115,18 @@ tags: - user_test_local_mode +- name: Append groups for local_ansibulluser (again) + user: + name: local_ansibulluser + state: present + local: yes + groups: ['testgroup3', 'testgroup4'] + append: yes + register: local_user_test_4_again + ignore_errors: yes + tags: + - user_test_local_mode + - name: Test append without groups for local_ansibulluser user: name: local_ansibulluser @@ -133,6 +147,28 @@ tags: - user_test_local_mode +- name: Append groups for local_ansibulluser using group id + user: + name: local_ansibulluser + state: present + append: yes + groups: "{{ test_groups.results[5]['gid'] }}" + register: local_user_test_7 + ignore_errors: yes + tags: + - user_test_local_mode + +- name: Append groups for local_ansibulluser using gid (again) + user: + name: local_ansibulluser + state: present + append: yes + groups: "{{ test_groups.results[5]['gid'] }}" + register: local_user_test_7_again + ignore_errors: yes + tags: + - user_test_local_mode + # If we don't re-assign, then "Set user expiration" will # fail. - name: Re-assign named group for local_ansibulluser @@ -164,6 +200,7 @@ - testgroup3 - testgroup4 - testgroup5 + - testgroup6 - local_ansibulluser tags: - user_test_local_mode @@ -175,7 +212,10 @@ - local_user_test_2 is not changed - local_user_test_3 is changed - local_user_test_4 is changed + - local_user_test_4_again is not changed - local_user_test_6 is changed + - local_user_test_7 is changed + - local_user_test_7_again is not changed - local_user_test_remove_1 is changed - local_user_test_remove_2 is not changed tags: diff --git a/test/integration/targets/user/vars/main.yml b/test/integration/targets/user/vars/main.yml index 4b328f7..2acd1e1 100644 --- a/test/integration/targets/user/vars/main.yml +++ b/test/integration/targets/user/vars/main.yml @@ -10,4 +10,4 @@ status_command: default_user_group: openSUSE Leap: users - MacOSX: admin + MacOSX: staff diff --git a/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml b/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml index f2b2e54..ef2a06e 100644 --- a/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml +++ b/test/integration/targets/var_blending/roles/test_var_blending/tasks/main.yml @@ -1,4 +1,4 @@ -# test code +# test code # (c) 2014, Michael DeHaan <michael.dehaan@gmail.com> # This file is part of Ansible @@ -22,7 +22,7 @@ output_dir: "{{ lookup('env', 'OUTPUT_DIR') }}" - name: deploy a template that will use variables at various levels - template: src=foo.j2 dest={{output_dir}}/foo.templated + template: src=foo.j2 dest={{output_dir}}/foo.templated register: template_result - name: copy known good into place @@ -33,9 +33,9 @@ register: diff_result - name: verify templated file matches known good - assert: - that: - - 'diff_result.stdout == ""' + assert: + that: + - 'diff_result.stdout == ""' - name: check debug variable with same name as var content debug: var=same_value_as_var_name_var diff --git a/test/integration/targets/var_precedence/ansible-var-precedence-check.py b/test/integration/targets/var_precedence/ansible-var-precedence-check.py index fc31688..b03c87b 100755 --- a/test/integration/targets/var_precedence/ansible-var-precedence-check.py +++ b/test/integration/targets/var_precedence/ansible-var-precedence-check.py @@ -14,7 +14,6 @@ import stat import subprocess import tempfile import yaml -from pprint import pprint from optparse import OptionParser from jinja2 import Environment @@ -364,9 +363,9 @@ class VarTestMaker(object): block_wrapper = [debug_task, test_task] if 'include_params' in self.features: - self.tasks.append(dict(name='including tasks', include='included_tasks.yml', vars=dict(findme='include_params'))) + self.tasks.append(dict(name='including tasks', include_tasks='included_tasks.yml', vars=dict(findme='include_params'))) else: - self.tasks.append(dict(include='included_tasks.yml')) + self.tasks.append(dict(include_tasks='included_tasks.yml')) fname = os.path.join(TESTDIR, 'included_tasks.yml') with open(fname, 'w') as f: diff --git a/test/integration/targets/var_precedence/test_var_precedence.yml b/test/integration/targets/var_precedence/test_var_precedence.yml index 58584bf..bba661d 100644 --- a/test/integration/targets/var_precedence/test_var_precedence.yml +++ b/test/integration/targets/var_precedence/test_var_precedence.yml @@ -1,14 +1,18 @@ --- - hosts: testhost vars: - - ansible_hostname: "BAD!" - - vars_var: "vars_var" - - param_var: "BAD!" - - vars_files_var: "BAD!" - - extra_var_override_once_removed: "{{ extra_var_override }}" - - from_inventory_once_removed: "{{ inven_var | default('BAD!') }}" + ansible_hostname: "BAD!" + vars_var: "vars_var" + param_var: "BAD!" + vars_files_var: "BAD!" + extra_var_override_once_removed: "{{ extra_var_override }}" + from_inventory_once_removed: "{{ inven_var | default('BAD!') }}" vars_files: - vars/test_var_precedence.yml + pre_tasks: + - name: param vars should also override set_fact + set_fact: + param_var: "BAD!" roles: - { role: test_var_precedence, param_var: "param_var" } tasks: diff --git a/test/integration/targets/vars_files/aliases b/test/integration/targets/vars_files/aliases new file mode 100644 index 0000000..8278ec8 --- /dev/null +++ b/test/integration/targets/vars_files/aliases @@ -0,0 +1,2 @@ +shippable/posix/group3 +context/controller diff --git a/test/integration/targets/vars_files/inventory b/test/integration/targets/vars_files/inventory new file mode 100644 index 0000000..88dae26 --- /dev/null +++ b/test/integration/targets/vars_files/inventory @@ -0,0 +1,3 @@ +[testgroup] +testhost foo=bar +testhost2 foo=baz diff --git a/test/integration/targets/vars_files/runme.sh b/test/integration/targets/vars_files/runme.sh new file mode 100755 index 0000000..127536f --- /dev/null +++ b/test/integration/targets/vars_files/runme.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eux + +ansible-playbook runme.yml -i inventory -v "$@" diff --git a/test/integration/targets/vars_files/runme.yml b/test/integration/targets/vars_files/runme.yml new file mode 100644 index 0000000..257f929 --- /dev/null +++ b/test/integration/targets/vars_files/runme.yml @@ -0,0 +1,22 @@ +--- +- hosts: testgroup + gather_facts: no + vars_files: + - "vars/common.yml" + - + - "vars/{{ foo }}.yml" + - "vars/defaults.yml" + tasks: + - import_tasks: validate.yml + +- hosts: testgroup + gather_facts: no + vars: + _vars_files: + - 'vars/{{ foo }}.yml' + - 'vars/defaults.yml' + vars_files: + - "vars/common.yml" + - "{{ lookup('first_found', _vars_files) }}" + tasks: + - import_tasks: validate.yml diff --git a/test/integration/targets/vars_files/validate.yml b/test/integration/targets/vars_files/validate.yml new file mode 100644 index 0000000..dc889c5 --- /dev/null +++ b/test/integration/targets/vars_files/validate.yml @@ -0,0 +1,11 @@ +- assert: + that: + - common is true +- assert: + that: + - is_bar is true + when: inventory_hostname == 'testhost' +- assert: + that: + - is_bar is false + when: inventory_hostname == 'testhost2' diff --git a/test/integration/targets/vars_files/vars/bar.yml b/test/integration/targets/vars_files/vars/bar.yml new file mode 100644 index 0000000..d6f3c5b --- /dev/null +++ b/test/integration/targets/vars_files/vars/bar.yml @@ -0,0 +1 @@ +is_bar: yes diff --git a/test/integration/targets/vars_files/vars/common.yml b/test/integration/targets/vars_files/vars/common.yml new file mode 100644 index 0000000..a8cd808 --- /dev/null +++ b/test/integration/targets/vars_files/vars/common.yml @@ -0,0 +1 @@ +common: yes diff --git a/test/integration/targets/vars_files/vars/defaults.yml b/test/integration/targets/vars_files/vars/defaults.yml new file mode 100644 index 0000000..4a7bfac --- /dev/null +++ b/test/integration/targets/vars_files/vars/defaults.yml @@ -0,0 +1 @@ +is_bar: no diff --git a/test/integration/targets/wait_for/tasks/main.yml b/test/integration/targets/wait_for/tasks/main.yml index f81fd0f..74b8e9a 100644 --- a/test/integration/targets/wait_for/tasks/main.yml +++ b/test/integration/targets/wait_for/tasks/main.yml @@ -91,7 +91,7 @@ wait_for: path: "{{remote_tmp_dir}}/wait_for_keyword" search_regex: completed (?P<foo>\w+) ([0-9]+) - timeout: 5 + timeout: 25 register: waitfor - name: verify test wait for keyword in file with match groups @@ -114,6 +114,15 @@ path: "{{remote_tmp_dir}}/utf16.txt" search_regex: completed +- name: test non mmapable file + wait_for: + path: "/sys/class/net/lo/carrier" + search_regex: "1" + timeout: 30 + when: + - ansible_facts['os_family'] not in ['FreeBSD', 'Darwin'] + - not (ansible_facts['os_family'] in ['RedHat', 'CentOS'] and ansible_facts['distribution_major_version'] is version('7', '<=')) + - name: test wait for port timeout wait_for: port: 12121 diff --git a/test/integration/targets/win_exec_wrapper/action_plugins/test_rc_1.py b/test/integration/targets/win_exec_wrapper/action_plugins/test_rc_1.py new file mode 100644 index 0000000..60cffde --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/action_plugins/test_rc_1.py @@ -0,0 +1,35 @@ +# Copyright: (c) 2023, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import json + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + + def run(self, tmp=None, task_vars=None): + super().run(tmp, task_vars) + del tmp + + exec_command = self._connection.exec_command + + def patched_exec_command(*args, **kwargs): + rc, stdout, stderr = exec_command(*args, **kwargs) + + new_stdout = json.dumps({ + "rc": rc, + "stdout": stdout.decode(), + "stderr": stderr.decode(), + "failed": False, + "changed": False, + }).encode() + + return (0, new_stdout, b"") + + try: + # This is done to capture the raw rc/stdio from the module exec + self._connection.exec_command = patched_exec_command + return self._execute_module(task_vars=task_vars) + finally: + self._connection.exec_command = exec_command diff --git a/test/integration/targets/win_exec_wrapper/library/test_rc_1.ps1 b/test/integration/targets/win_exec_wrapper/library/test_rc_1.ps1 new file mode 100644 index 0000000..a987954 --- /dev/null +++ b/test/integration/targets/win_exec_wrapper/library/test_rc_1.ps1 @@ -0,0 +1,17 @@ +#!powershell + +# This scenario needs to use Legacy, the same HadErrors won't be set if using +# Ansible.Basic +#Requires -Module Ansible.ModuleUtils.Legacy + +# This will set `$ps.HadErrors` in the running pipeline but with no error +# record written. We are testing that it won't set the rc to 1 for this +# scenario. +try { + Write-Error -Message err -ErrorAction Stop +} +catch { + Exit-Json @{} +} + +Fail-Json @{} "This should not be reached" diff --git a/test/integration/targets/win_exec_wrapper/tasks/main.yml b/test/integration/targets/win_exec_wrapper/tasks/main.yml index 8fc54f7..f1342c4 100644 --- a/test/integration/targets/win_exec_wrapper/tasks/main.yml +++ b/test/integration/targets/win_exec_wrapper/tasks/main.yml @@ -272,3 +272,12 @@ assert: that: - ps_log_count.stdout | int == 0 + +- name: test module that sets HadErrors with no error records + test_rc_1: + register: module_had_errors + +- name: assert test module that sets HadErrors with no error records + assert: + that: + - module_had_errors.rc == 0 diff --git a/test/integration/targets/win_fetch/tasks/main.yml b/test/integration/targets/win_fetch/tasks/main.yml index b581835..16a2876 100644 --- a/test/integration/targets/win_fetch/tasks/main.yml +++ b/test/integration/targets/win_fetch/tasks/main.yml @@ -215,3 +215,17 @@ - fetch_special_file.checksum == '34d4150adc3347f1dd8ce19fdf65b74d971ab602' - fetch_special_file.dest == host_output_dir + "/abc$not var'quote‘" - fetch_special_file_actual.stdout == 'abc' + +- name: create file with wildcard characters + raw: Set-Content -LiteralPath '{{ remote_tmp_dir }}\abc[].txt' -Value 'abc' + +- name: fetch file with wildcard characters + fetch: + src: '{{ remote_tmp_dir }}\abc[].txt' + dest: '{{ host_output_dir }}/' + register: fetch_wildcard_file_nofail + +- name: assert fetch file with wildcard characters + assert: + that: + - "fetch_wildcard_file_nofail is not failed" diff --git a/test/integration/targets/win_script/files/test_script_with_args.ps1 b/test/integration/targets/win_script/files/test_script_with_args.ps1 index 01bb37f..669c641 100644 --- a/test/integration/targets/win_script/files/test_script_with_args.ps1 +++ b/test/integration/targets/win_script/files/test_script_with_args.ps1 @@ -2,5 +2,5 @@ # passed to the script. foreach ($i in $args) { - Write-Host $i; + Write-Host $i } diff --git a/test/integration/targets/win_script/files/test_script_with_errors.ps1 b/test/integration/targets/win_script/files/test_script_with_errors.ps1 index 56f9773..bdf7ee4 100644 --- a/test/integration/targets/win_script/files/test_script_with_errors.ps1 +++ b/test/integration/targets/win_script/files/test_script_with_errors.ps1 @@ -2,7 +2,7 @@ trap { Write-Error -ErrorRecord $_ - exit 1; + exit 1 } throw "Oh noes I has an error" diff --git a/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1 b/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1 index f170496..d23bbc7 100644 --- a/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1 +++ b/test/integration/targets/windows-minimal/library/win_ping_set_attr.ps1 @@ -16,16 +16,16 @@ # POWERSHELL_COMMON -$params = Parse-Args $args $true; +$params = Parse-Args $args $true -$data = Get-Attr $params "data" "pong"; +$data = Get-Attr $params "data" "pong" $result = @{ changed = $false ping = "pong" -}; +} # Test that Set-Attr will replace an existing attribute. Set-Attr $result "ping" $data -Exit-Json $result; +Exit-Json $result diff --git a/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1 b/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1 index 508174a..09400d0 100644 --- a/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1 +++ b/test/integration/targets/windows-minimal/library/win_ping_strict_mode_error.ps1 @@ -16,15 +16,15 @@ # POWERSHELL_COMMON -$params = Parse-Args $args $true; +$params = Parse-Args $args $true $params.thisPropertyDoesNotExist -$data = Get-Attr $params "data" "pong"; +$data = Get-Attr $params "data" "pong" $result = @{ changed = $false ping = $data -}; +} -Exit-Json $result; +Exit-Json $result diff --git a/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1 b/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1 index d4c9f07..6932d53 100644 --- a/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1 +++ b/test/integration/targets/windows-minimal/library/win_ping_syntax_error.ps1 @@ -18,13 +18,13 @@ $blah = 'I can't quote my strings correctly.' -$params = Parse-Args $args $true; +$params = Parse-Args $args $true -$data = Get-Attr $params "data" "pong"; +$data = Get-Attr $params "data" "pong" $result = @{ changed = $false ping = $data -}; +} -Exit-Json $result; +Exit-Json $result diff --git a/test/integration/targets/windows-minimal/library/win_ping_throw.ps1 b/test/integration/targets/windows-minimal/library/win_ping_throw.ps1 index 7306f4d..2fba209 100644 --- a/test/integration/targets/windows-minimal/library/win_ping_throw.ps1 +++ b/test/integration/targets/windows-minimal/library/win_ping_throw.ps1 @@ -18,13 +18,13 @@ throw -$params = Parse-Args $args $true; +$params = Parse-Args $args $true -$data = Get-Attr $params "data" "pong"; +$data = Get-Attr $params "data" "pong" $result = @{ changed = $false ping = $data -}; +} -Exit-Json $result; +Exit-Json $result diff --git a/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1 b/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1 index 09e3b7c..62de826 100644 --- a/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1 +++ b/test/integration/targets/windows-minimal/library/win_ping_throw_string.ps1 @@ -18,13 +18,13 @@ throw "no ping for you" -$params = Parse-Args $args $true; +$params = Parse-Args $args $true -$data = Get-Attr $params "data" "pong"; +$data = Get-Attr $params "data" "pong" $result = @{ changed = $false ping = $data -}; +} -Exit-Json $result; +Exit-Json $result diff --git a/test/integration/targets/yum/aliases b/test/integration/targets/yum/aliases index 1d49133..b12f354 100644 --- a/test/integration/targets/yum/aliases +++ b/test/integration/targets/yum/aliases @@ -1,5 +1,4 @@ destructive shippable/posix/group1 skip/freebsd -skip/osx skip/macos diff --git a/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py b/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py index 27f38ce..306ccd9 100644 --- a/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py +++ b/test/integration/targets/yum/filter_plugins/filter_list_of_tuples_by_first_param.py @@ -1,8 +1,6 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -from ansible.errors import AnsibleError, AnsibleFilterError - def filter_list_of_tuples_by_first_param(lst, search, startswith=False): out = [] |