diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:04:21 +0000 |
commit | 8a754e0858d922e955e71b253c139e071ecec432 (patch) | |
tree | 527d16e74bfd1840c85efd675fdecad056c54107 /test/integration/targets/ansible-galaxy-collection/tasks | |
parent | Initial commit. (diff) | |
download | ansible-core-8a754e0858d922e955e71b253c139e071ecec432.tar.xz ansible-core-8a754e0858d922e955e71b253c139e071ecec432.zip |
Adding upstream version 2.14.3.upstream/2.14.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'test/integration/targets/ansible-galaxy-collection/tasks')
16 files changed, 2905 insertions, 0 deletions
diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/build.yml b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml new file mode 100644 index 0000000..8140d46 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/build.yml @@ -0,0 +1,74 @@ +--- +- name: build basic collection based on current directory + command: ansible-galaxy collection build {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/scratch/ansible_test/my_collection' + register: build_current_dir + +- name: get result of build basic collection on current directory + stat: + path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/ansible_test-my_collection-1.0.0.tar.gz' + register: build_current_dir_actual + +- name: assert build basic collection based on current directory + assert: + that: + - '"Created collection for ansible_test.my_collection" in build_current_dir.stdout' + - build_current_dir_actual.stat.exists + +- name: build basic collection based on relative dir + command: ansible-galaxy collection build scratch/ansible_test/my_collection {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + register: build_relative_dir + +- name: get result of build basic collection based on relative dir + stat: + path: '{{ galaxy_dir }}/ansible_test-my_collection-1.0.0.tar.gz' + register: build_relative_dir_actual + +- name: assert build basic collection based on relative dir + assert: + that: + - '"Created collection for ansible_test.my_collection" in build_relative_dir.stdout' + - build_relative_dir_actual.stat.exists + +- name: fail to build existing collection without force + command: ansible-galaxy collection build scratch/ansible_test/my_collection {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + ignore_errors: yes + register: build_existing_no_force + +- name: build existing collection with force + command: ansible-galaxy collection build scratch/ansible_test/my_collection --force {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + register: build_existing_force + +- name: assert build existing collection + assert: + that: + - '"use --force to re-create the collection artifact" in build_existing_no_force.stderr' + - '"Created collection for ansible_test.my_collection" in build_existing_force.stdout' + +- name: build collection containing ignored files + command: ansible-galaxy collection build + args: + chdir: '{{ galaxy_dir }}/scratch/ansible_test/ignore' + +- name: list the created tar contents + command: tar -tf {{ galaxy_dir }}/scratch/ansible_test/ignore/ansible_test-ignore-1.0.0.tar.gz + register: tar_output + +- name: assert ignored files are not present in the archive + assert: + that: + - item not in tar_output.stdout_lines + loop: '{{ collection_ignored_files }}' + +- name: assert ignored directories are not present in the archive + assert: + that: + - item not in tar_output.stdout_lines + loop: '{{ collection_ignored_directories }}' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/download.yml b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml new file mode 100644 index 0000000..b651a73 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/download.yml @@ -0,0 +1,176 @@ +--- +- name: create test download dir + file: + path: '{{ galaxy_dir }}/download' + 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 }} + register: download_collection + args: + chdir: '{{ galaxy_dir }}/download' + +- name: get result of download collection with multiple dependencies + find: + path: '{{ galaxy_dir }}/download/collections' + file_type: file + register: download_collection_actual + +- name: assert download collection with multiple dependencies --no-deps + assert: + that: + - >- + "Downloading collection 'parent_dep.parent_collection:1.0.0' to '/tmp/" + in download_collection.stdout + - >- + "Downloading collection 'child_dep.child_collection" + not in download_collection.stdout + - >- + "Downloading collection 'child_dep.child_dep2" + not in download_collection.stdout + - download_collection_actual.examined == 2 + - download_collection_actual.matched == 2 + - (download_collection_actual.files[0].path | basename) in ['requirements.yml', 'parent_dep-parent_collection-1.0.0.tar.gz'] + - (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 }} + register: download_collection + args: + chdir: '{{ galaxy_dir }}/download' + +- name: get result of download collection with multiple dependencies + find: + path: '{{ galaxy_dir }}/download/collections' + file_type: file + register: download_collection_actual + +- name: assert download collection with multiple dependencies + assert: + that: + - '"Downloading collection ''parent_dep.parent_collection:1.0.0'' to" in download_collection.stdout' + - '"Downloading collection ''child_dep.child_collection:0.9.9'' to" in download_collection.stdout' + - '"Downloading collection ''child_dep.child_dep2:1.2.2'' to" in download_collection.stdout' + - download_collection_actual.examined == 4 + - download_collection_actual.matched == 4 + - (download_collection_actual.files[0].path | basename) in ['requirements.yml', 'child_dep-child_dep2-1.2.2.tar.gz', 'child_dep-child_collection-0.9.9.tar.gz', 'parent_dep-parent_collection-1.0.0.tar.gz'] + - (download_collection_actual.files[1].path | basename) in ['requirements.yml', 'child_dep-child_dep2-1.2.2.tar.gz', 'child_dep-child_collection-0.9.9.tar.gz', 'parent_dep-parent_collection-1.0.0.tar.gz'] + - (download_collection_actual.files[2].path | basename) in ['requirements.yml', 'child_dep-child_dep2-1.2.2.tar.gz', 'child_dep-child_collection-0.9.9.tar.gz', 'parent_dep-parent_collection-1.0.0.tar.gz'] + - (download_collection_actual.files[3].path | basename) in ['requirements.yml', 'child_dep-child_dep2-1.2.2.tar.gz', 'child_dep-child_collection-0.9.9.tar.gz', 'parent_dep-parent_collection-1.0.0.tar.gz'] + +- name: test install of download requirements file + command: ansible-galaxy collection install -r requirements.yml -p '{{ galaxy_dir }}/download' {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/download/collections' + register: install_download + +- name: get result of test install of download requirements file + slurp: + path: '{{ galaxy_dir }}/download/ansible_collections/{{ collection.namespace }}/{{ collection.name }}/MANIFEST.json' + register: install_download_actual + loop_control: + loop_var: collection + loop: + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- name: assert test install of download requirements file + assert: + that: + - '"Installing ''parent_dep.parent_collection:1.0.0'' to" in install_download.stdout' + - '"Installing ''child_dep.child_collection:0.9.9'' to" in install_download.stdout' + - '"Installing ''child_dep.child_dep2:1.2.2'' to" in install_download.stdout' + - (install_download_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_download_actual.results[1].content | b64decode | from_json).collection_info.version == '0.9.9' + - (install_download_actual.results[2].content | b64decode | from_json).collection_info.version == '1.2.2' + +- name: create test requirements file for download + copy: + content: | + collections: + - name: namespace1.name1 + version: 1.1.0-beta.1 + + dest: '{{ galaxy_dir }}/download/download.yml' + +- name: download collection with req to custom dir + command: ansible-galaxy collection download -r '{{ galaxy_dir }}/download/download.yml' -s galaxy_ng -p '{{ galaxy_dir }}/download/collections-custom' {{ galaxy_verbosity }} + register: download_req_custom_path + +- name: get result of download collection with req to custom dir + find: + path: '{{ galaxy_dir }}/download/collections-custom' + file_type: file + register: download_req_custom_path_actual + +- name: assert download collection with multiple dependencies + assert: + that: + - '"Downloading collection ''namespace1.name1:1.1.0-beta.1'' to" in download_req_custom_path.stdout' + - download_req_custom_path_actual.examined == 2 + - download_req_custom_path_actual.matched == 2 + - (download_req_custom_path_actual.files[0].path | basename) in ['requirements.yml', 'namespace1-name1-1.1.0-beta.1.tar.gz'] + - (download_req_custom_path_actual.files[1].path | basename) in ['requirements.yml', 'namespace1-name1-1.1.0-beta.1.tar.gz'] + +# https://github.com/ansible/ansible/issues/68186 +- name: create test requirements file without roles and collections + copy: + content: | + collections: + roles: + + dest: '{{ galaxy_dir }}/download/no_roles_no_collections.yml' + +- name: install collection with requirements + command: ansible-galaxy collection install -r '{{ galaxy_dir }}/download/no_roles_no_collections.yml' {{ galaxy_verbosity }} + register: install_no_requirements + +- name: assert install collection with no roles and no collections in requirements + assert: + that: + - '"Skipping install, no requirements found" in install_no_requirements.stdout' + +- name: Test downloading a tar.gz collection artifact + block: + + - name: get result of build basic collection on current directory + stat: + path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection/ansible_test-my_collection-1.0.0.tar.gz' + register: result + + - name: create default skeleton + command: ansible-galaxy collection init ansible_test.my_collection {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/scratch' + when: not result.stat.exists + + - name: build the tar.gz + command: ansible-galaxy collection build {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/scratch/ansible_test/my_collection' + when: not result.stat.exists + + - name: download a tar.gz file + command: ansible-galaxy collection download '{{ galaxy_dir }}/scratch/ansible_test/my_collection/ansible_test-my_collection-1.0.0.tar.gz' + args: + chdir: '{{ galaxy_dir }}/download' + register: download_collection + + - name: get result of downloaded tar.gz + stat: + path: '{{ galaxy_dir }}/download/collections/ansible_test-my_collection-1.0.0.tar.gz' + register: download_collection_actual + + - assert: + that: + - '"Downloading collection ''ansible_test.my_collection:1.0.0'' to" in download_collection.stdout' + - download_collection_actual.stat.exists + +- name: remove test download dir + file: + path: '{{ galaxy_dir }}/download' + state: absent 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 new file mode 100644 index 0000000..eb471f8 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/fail_fast_resolvelib.yml @@ -0,0 +1,45 @@ +# 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 +- name: test resolvelib removes incompatibilites in find_matches and errors quickly (prevent infinite recursion) + block: + - name: create collection dir + file: + dest: "{{ galaxy_dir }}/resolvelib/ns/coll" + state: directory + + - name: create galaxy.yml with a dependecy on a galaxy-sourced collection + copy: + dest: "{{ galaxy_dir }}/resolvelib/ns/coll/galaxy.yml" + content: | + namespace: ns + name: coll + authors: + - ansible-core + readme: README.md + version: "1.0.0" + dependencies: + namespace1.name1: "0.0.5" + + - name: build the collection + command: ansible-galaxy collection build ns/coll + args: + chdir: "{{ galaxy_dir }}/resolvelib" + + - name: install a conflicting version of the dep with the tarfile (expected failure) + command: ansible-galaxy collection install namespace1.name1:1.0.9 ns-coll-1.0.0.tar.gz -vvvvv -s {{ test_name }} -p collections/ + args: + chdir: "{{ galaxy_dir }}/resolvelib" + timeout: 30 + ignore_errors: yes + register: incompatible + + - assert: + that: + - incompatible.failed + - not incompatible.msg.startswith("The command action failed to execute in the expected time frame") + + always: + - name: cleanup resolvelib test + file: + dest: "{{ galaxy_dir }}/resolvelib" + state: absent diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/init.yml b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml new file mode 100644 index 0000000..17a000d --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/init.yml @@ -0,0 +1,124 @@ +--- +- name: create default skeleton + command: ansible-galaxy collection init ansible_test.my_collection {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/scratch' + register: init_relative + +- name: get result of create default skeleton + find: + path: '{{ galaxy_dir }}/scratch/ansible_test/my_collection' + recurse: yes + file_type: directory + register: init_relative_actual + +- debug: + var: init_relative_actual.files | map(attribute='path') | list + +- name: assert create default skeleton + assert: + that: + - '"Collection ansible_test.my_collection was created successfully" in init_relative.stdout' + - init_relative_actual.files | length == 4 + - (init_relative_actual.files | map(attribute='path') | list)[0] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_relative_actual.files | map(attribute='path') | list)[1] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_relative_actual.files | map(attribute='path') | list)[2] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_relative_actual.files | map(attribute='path') | list)[3] | basename in ['docs', 'plugins', 'roles', 'meta'] + +- name: create collection with custom init path + command: ansible-galaxy collection init ansible_test2.my_collection --init-path "{{ galaxy_dir }}/scratch/custom-init-dir" {{ galaxy_verbosity }} + register: init_custom_path + +- name: get result of create default skeleton + find: + path: '{{ galaxy_dir }}/scratch/custom-init-dir/ansible_test2/my_collection' + file_type: directory + register: init_custom_path_actual + +- name: assert create collection with custom init path + assert: + that: + - '"Collection ansible_test2.my_collection was created successfully" in init_custom_path.stdout' + - init_custom_path_actual.files | length == 4 + - (init_custom_path_actual.files | map(attribute='path') | list)[0] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_custom_path_actual.files | map(attribute='path') | list)[1] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (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: add a directory to the init collection path to test that --force removes it + file: + state: directory + path: "{{ galaxy_dir }}/scratch/custom-init-dir/ansible_test2/my_collection/remove_me" + +- name: create collection with custom init path + command: ansible-galaxy collection init ansible_test2.my_collection --init-path "{{ galaxy_dir }}/scratch/custom-init-dir" --force {{ galaxy_verbosity }} + register: init_custom_path + +- name: get result of create default skeleton + find: + path: '{{ galaxy_dir }}/scratch/custom-init-dir/ansible_test2/my_collection' + file_type: directory + register: init_custom_path_actual + +- name: assert create collection with custom init path + assert: + that: + - '"Collection ansible_test2.my_collection was created successfully" in init_custom_path.stdout' + - init_custom_path_actual.files | length == 4 + - (init_custom_path_actual.files | map(attribute='path') | list)[0] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_custom_path_actual.files | map(attribute='path') | list)[1] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (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: create collection in cwd with custom init path + command: ansible-galaxy collection init ansible_test2.my_collection --init-path ../../ --force {{ galaxy_verbosity }} + args: + chdir: "{{ galaxy_dir }}/scratch/custom-init-dir/ansible_test2/my_collection" + register: init_custom_path + +- name: get result of create default skeleton + find: + path: '{{ galaxy_dir }}/scratch/custom-init-dir/ansible_test2/my_collection' + file_type: directory + register: init_custom_path_actual + +- name: assert create collection with custom init path + assert: + that: + - '"Collection ansible_test2.my_collection was created successfully" in init_custom_path.stdout' + - init_custom_path_actual.files | length == 4 + - (init_custom_path_actual.files | map(attribute='path') | list)[0] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (init_custom_path_actual.files | map(attribute='path') | list)[1] | basename in ['docs', 'plugins', 'roles', 'meta'] + - (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: create collection for ignored files and folders + command: ansible-galaxy collection init ansible_test.ignore + args: + chdir: '{{ galaxy_dir }}/scratch' + +- name: create list of ignored files + set_fact: + collection_ignored_files: + - plugins/compiled.pyc + - something.retry + - .git + +- name: plant ignored files into the ansible_test.ignore collection + copy: + dest: '{{ galaxy_dir }}/scratch/ansible_test/ignore/{{ item }}' + content: '{{ item }}' + loop: '{{ collection_ignored_files }}' + +- name: create list of ignored directories + set_fact: + collection_ignored_directories: + - docs/.git + - plugins/doc_fragments/__pycache__ + - .svn + +- name: plant ignored folders into the ansible_test.ignore collection + file: + path: '{{ galaxy_dir }}/scratch/ansible_test/ignore/{{ item }}' + state: directory + loop: '{{ collection_ignored_directories }}' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml new file mode 100644 index 0000000..8916faf --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install.yml @@ -0,0 +1,1035 @@ +--- +- name: create test collection install directory - {{ test_id }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: directory + +- name: install simple collection from first accessible server + command: ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + register: from_first_good_server + +- name: get installed files of install simple collection from first good server + find: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1' + file_type: file + register: install_normal_files + +- name: get the manifest of install simple collection from first good server + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: install_normal_manifest + +- name: assert install simple collection from first good server + assert: + that: + - '"Installing ''namespace1.name1:1.0.9'' to" in from_first_good_server.stdout' + - install_normal_files.files | length == 3 + - install_normal_files.files[0].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[1].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[2].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - (install_normal_manifest.content | b64decode | from_json).collection_info.version == '1.0.9' + +- name: Remove the collection + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1' + state: absent + +- name: install simple collection with implicit path - {{ test_id }} + command: ansible-galaxy collection install namespace1.name1 -s '{{ test_name }}' {{ galaxy_verbosity }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + register: install_normal + +- name: get installed files of install simple collection with implicit path - {{ test_id }} + find: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1' + file_type: file + register: install_normal_files + +- name: get the manifest of install simple collection with implicit path - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: install_normal_manifest + +- name: assert install simple collection with implicit path - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.0.9'' to" in install_normal.stdout' + - install_normal_files.files | length == 3 + - install_normal_files.files[0].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[1].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[2].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - (install_normal_manifest.content | b64decode | from_json).collection_info.version == '1.0.9' + +- name: install existing without --force - {{ test_id }} + command: ansible-galaxy collection install namespace1.name1 -s '{{ test_name }}' {{ galaxy_verbosity }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + register: install_existing_no_force + +- name: assert install existing without --force - {{ test_id }} + assert: + that: + - '"Nothing to do. All requested collections are already installed" in install_existing_no_force.stdout' + +- name: install existing with --force - {{ test_id }} + command: ansible-galaxy collection install namespace1.name1 -s '{{ test_name }}' --force {{ galaxy_verbosity }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + register: install_existing_force + +- name: assert install existing with --force - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.0.9'' to" in install_existing_force.stdout' + +- name: remove test installed collection - {{ test_id }} + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1' + state: absent + +- name: install pre-release as explicit version to custom dir - {{ test_id }} + command: ansible-galaxy collection install 'namespace1.name1:1.1.0-beta.1' -s '{{ test_name }}' -p '{{ galaxy_dir }}/ansible_collections' {{ galaxy_verbosity }} + register: install_prerelease + +- name: get result of install pre-release as explicit version to custom dir - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: install_prerelease_actual + +- name: assert install pre-release as explicit version to custom dir - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.1.0-beta.1'' to" in install_prerelease.stdout' + - (install_prerelease_actual.content | b64decode | from_json).collection_info.version == '1.1.0-beta.1' + +- name: Remove beta + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1' + state: absent + +- name: install pre-release version with --pre to custom dir - {{ test_id }} + command: ansible-galaxy collection install --pre 'namespace1.name1' -s '{{ test_name }}' -p '{{ galaxy_dir }}/ansible_collections' {{ galaxy_verbosity }} + register: install_prerelease + +- name: get result of install pre-release version with --pre to custom dir - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: install_prerelease_actual + +- name: assert install pre-release version with --pre to custom dir - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.1.0-beta.1'' to" in install_prerelease.stdout' + - (install_prerelease_actual.content | b64decode | from_json).collection_info.version == '1.1.0-beta.1' + +- name: install multiple collections with dependencies - {{ test_id }} + command: ansible-galaxy collection install parent_dep.parent_collection:1.0.0 namespace2.name -s {{ test_name }} {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}/ansible_collections' + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + register: install_multiple_with_dep + +- name: get result of install multiple collections with dependencies - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection.namespace }}/{{ collection.name }}/MANIFEST.json' + register: install_multiple_with_dep_actual + loop_control: + loop_var: collection + loop: + - namespace: namespace2 + name: name + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- name: assert install multiple collections with dependencies - {{ test_id }} + assert: + that: + - (install_multiple_with_dep_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_multiple_with_dep_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_multiple_with_dep_actual.results[2].content | b64decode | from_json).collection_info.version == '0.9.9' + - (install_multiple_with_dep_actual.results[3].content | b64decode | from_json).collection_info.version == '1.2.2' + +- name: expect failure with dep resolution failure - {{ test_id }} + command: ansible-galaxy collection install fail_namespace.fail_collection -s {{ test_name }} {{ galaxy_verbosity }} + register: fail_dep_mismatch + 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' + +- name: Find artifact url for namespace3.name + uri: + url: '{{ test_server }}{{ vX }}collections/namespace3/name/versions/1.0.0/' + user: '{{ pulp_user }}' + password: '{{ pulp_password }}' + force_basic_auth: true + register: artifact_url_response + +- name: download a collection for an offline install - {{ test_id }} + get_url: + url: '{{ artifact_url_response.json.download_url }}' + dest: '{{ galaxy_dir }}/namespace3.tar.gz' + +- name: install a collection from a tarball - {{ test_id }} + command: ansible-galaxy collection install '{{ galaxy_dir }}/namespace3.tar.gz' {{ galaxy_verbosity }} + register: install_tarball + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: get result of install collection from a tarball - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace3/name/MANIFEST.json' + register: install_tarball_actual + +- name: assert install a collection from a tarball - {{ test_id }} + assert: + that: + - '"Installing ''namespace3.name:1.0.0'' to" in install_tarball.stdout' + - (install_tarball_actual.content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: write a requirements file using the artifact and a conflicting version + copy: + content: | + collections: + - name: {{ galaxy_dir }}/namespace3.tar.gz + version: 1.2.0 + dest: '{{ galaxy_dir }}/test_req.yml' + +- name: install the requirements file with mismatched versions + command: ansible-galaxy collection install -r '{{ galaxy_dir }}/test_req.yml' {{ galaxy_verbosity }} + ignore_errors: True + register: result + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: remove the requirements file + file: + path: '{{ galaxy_dir }}/test_req.yml' + state: absent + +- assert: + that: error == expected_error + vars: + error: "{{ result.stderr | regex_replace('\\n', ' ') }}" + expected_error: >- + ERROR! Failed to resolve the requested dependencies map. + Got the candidate namespace3.name:1.0.0 (direct request) + which didn't satisfy all of the following requirements: + * namespace3.name:1.2.0 + +- name: test error for mismatched dependency versions + vars: + error: "{{ result.stderr | regex_replace('\\n', ' ') }}" + expected_error: >- + ERROR! Failed to resolve the requested dependencies map. + Got the candidate namespace3.name:1.0.0 (dependency of tmp_parent.name:1.0.0) + which didn't satisfy all of the following requirements: + * namespace3.name:1.2.0 + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + block: + - name: init a new parent collection + command: ansible-galaxy collection init tmp_parent.name --init-path '{{ galaxy_dir }}/scratch' + + - name: replace the dependencies + lineinfile: + path: "{{ galaxy_dir }}/scratch/tmp_parent/name/galaxy.yml" + regexp: "^dependencies:*" + line: "dependencies: { '{{ galaxy_dir }}/namespace3.tar.gz': '1.2.0' }" + + - name: build the new artifact + command: ansible-galaxy collection build {{ galaxy_dir }}/scratch/tmp_parent/name + args: + chdir: "{{ galaxy_dir }}" + + - name: install the artifact to verify the error is handled + command: ansible-galaxy collection install '{{ galaxy_dir }}/tmp_parent-name-1.0.0.tar.gz' + ignore_errors: yes + register: result + + - debug: msg="Actual - {{ error }}" + + - debug: msg="Expected - {{ expected_error }}" + + - assert: + that: error == expected_error + always: + - name: clean up collection skeleton and artifact + file: + state: absent + path: "{{ item }}" + loop: + - "{{ galaxy_dir }}/scratch/tmp_parent/" + - "{{ galaxy_dir }}/tmp_parent-name-1.0.0.tar.gz" + +- name: setup bad tarball - {{ test_id }} + script: build_bad_tar.py {{ galaxy_dir | quote }} + +- name: fail to install a collection from a bad tarball - {{ test_id }} + command: ansible-galaxy collection install '{{ galaxy_dir }}/suspicious-test-1.0.0.tar.gz' {{ galaxy_verbosity }} + register: fail_bad_tar + failed_when: fail_bad_tar.rc != 1 and "Cannot extract tar entry '../../outside.sh' as it will be placed outside the collection directory" not in fail_bad_tar.stderr + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: get result of failed collection install - {{ test_id }} + stat: + path: '{{ galaxy_dir }}/ansible_collections\suspicious' + register: fail_bad_tar_actual + +- name: assert result of failed collection install - {{ test_id }} + assert: + that: + - not fail_bad_tar_actual.stat.exists + +- name: Find artifact url for namespace4.name + uri: + url: '{{ test_server }}{{ vX }}collections/namespace4/name/versions/1.0.0/' + user: '{{ pulp_user }}' + password: '{{ pulp_password }}' + force_basic_auth: true + register: artifact_url_response + +- name: install a collection from a URI - {{ test_id }} + command: ansible-galaxy collection install {{ artifact_url_response.json.download_url}} {{ galaxy_verbosity }} + register: install_uri + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: get result of install collection from a URI - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace4/name/MANIFEST.json' + register: install_uri_actual + +- name: assert install a collection from a URI - {{ test_id }} + assert: + that: + - '"Installing ''namespace4.name:1.0.0'' to" in install_uri.stdout' + - (install_uri_actual.content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: fail to install a collection with an undefined URL - {{ test_id }} + command: ansible-galaxy collection install namespace5.name {{ galaxy_verbosity }} + register: fail_undefined_server + failed_when: '"No setting was provided for required configuration plugin_type: galaxy_server plugin: undefined" not in fail_undefined_server.stderr' + environment: + ANSIBLE_GALAXY_SERVER_LIST: undefined + +- when: not requires_auth + block: + - name: install a collection with an empty server list - {{ test_id }} + command: ansible-galaxy collection install namespace5.name -s '{{ test_server }}' {{ galaxy_verbosity }} + register: install_empty_server_list + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_SERVER_LIST: '' + + - name: get result of a collection with an empty server list - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace5/name/MANIFEST.json' + register: install_empty_server_list_actual + + - name: assert install a collection with an empty server list - {{ test_id }} + assert: + that: + - '"Installing ''namespace5.name:1.0.0'' to" in install_empty_server_list.stdout' + - (install_empty_server_list_actual.content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: create test requirements file with both roles and collections - {{ test_id }} + copy: + content: | + collections: + - namespace6.name + - name: namespace7.name + roles: + - skip.me + dest: '{{ galaxy_dir }}/ansible_collections/requirements-with-role.yml' + +- name: install roles from requirements file with collection-only keyring option + command: ansible-galaxy role install -r {{ req_file }} -s {{ test_name }} --keyring {{ keyring }} + vars: + req_file: '{{ galaxy_dir }}/ansible_collections/requirements-with-role.yml' + keyring: "{{ gpg_homedir }}/pubring.kbx" + ignore_errors: yes + register: invalid_opt + +- assert: + that: + - invalid_opt is failed + - "'unrecognized arguments: --keyring' in invalid_opt.stderr" + +# Need to run with -vvv to validate the roles will be skipped msg +- name: install collections only with requirements-with-role.yml - {{ test_id }} + command: ansible-galaxy collection install -r '{{ galaxy_dir }}/ansible_collections/requirements-with-role.yml' -s '{{ test_name }}' -vvv + register: install_req_collection + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: get result of install collections only with requirements-with-roles.yml - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name/MANIFEST.json' + register: install_req_collection_actual + loop_control: + loop_var: collection + loop: + - namespace6 + - namespace7 + +- name: assert install collections only with requirements-with-role.yml - {{ test_id }} + assert: + that: + - '"contains roles which will be ignored" in install_req_collection.stdout' + - '"Installing ''namespace6.name:1.0.0'' to" in install_req_collection.stdout' + - '"Installing ''namespace7.name:1.0.0'' to" in install_req_collection.stdout' + - (install_req_collection_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_collection_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: create test requirements file with just collections - {{ test_id }} + copy: + content: | + collections: + - namespace8.name + - name: namespace9.name + dest: '{{ galaxy_dir }}/ansible_collections/requirements.yaml' + +- name: install collections with ansible-galaxy install - {{ test_id }} + command: ansible-galaxy install -r '{{ galaxy_dir }}/ansible_collections/requirements.yaml' -s '{{ test_name }}' + register: install_req + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + +- name: get result of install collections with ansible-galaxy install - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name/MANIFEST.json' + register: install_req_actual + loop_control: + loop_var: collection + loop: + - namespace8 + - namespace9 + +- name: assert install collections with ansible-galaxy install - {{ test_id }} + assert: + that: + - '"Installing ''namespace8.name:1.0.0'' to" in install_req.stdout' + - '"Installing ''namespace9.name:1.0.0'' to" in install_req.stdout' + - (install_req_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: Test deviations on -r and --role-file without collection or role sub command + command: '{{ cmd }}' + loop: + - ansible-galaxy install -vr '{{ galaxy_dir }}/ansible_collections/requirements.yaml' -s '{{ test_name }}' -vv + - ansible-galaxy install --role-file '{{ galaxy_dir }}/ansible_collections/requirements.yaml' -s '{{ test_name }}' -vvv + - ansible-galaxy install --role-file='{{ galaxy_dir }}/ansible_collections/requirements.yaml' -s '{{ test_name }}' -vvv + loop_control: + loop_var: cmd + +- name: uninstall collections for next requirements file test + file: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name' + state: absent + loop_control: + loop_var: collection + loop: + - namespace7 + - namespace8 + - namespace9 + +- name: rewrite requirements file with collections and signatures + copy: + content: | + collections: + - name: namespace7.name + version: "1.0.0" + signatures: + - "{{ not_mine }}" + - "{{ also_not_mine }}" + - "file://{{ gpg_homedir }}/namespace7-name-1.0.0-MANIFEST.json.asc" + - namespace8.name + - name: namespace9.name + signatures: + - "file://{{ gpg_homedir }}/namespace9-name-1.0.0-MANIFEST.json.asc" + dest: '{{ galaxy_dir }}/ansible_collections/requirements.yaml' + vars: + not_mine: "file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + also_not_mine: "file://{{ gpg_homedir }}/namespace1-name1-1.0.9-MANIFEST.json.asc" + +- name: installing only roles does not fail if keyring for collections is not provided + command: ansible-galaxy role install -r {{ galaxy_dir }}/ansible_collections/requirements.yaml + register: roles_only + +- assert: + that: + - roles_only is success + +- name: installing only roles implicitly does not fail if keyring for collections is not provided + # if -p/--roles-path are specified, only roles are installed + command: ansible-galaxy install -r {{ galaxy_dir }}/ansible_collections/requirements.yaml }} -p {{ galaxy_dir }} + register: roles_only + +- assert: + that: + - roles_only is success + +- name: installing roles and collections requires keyring if collections have signatures + command: ansible-galaxy install -r {{ galaxy_dir }}/ansible_collections/requirements.yaml }} + ignore_errors: yes + register: collections_and_roles + +- assert: + that: + - collections_and_roles is failed + - "'no keyring was configured' in collections_and_roles.stderr" + +- name: install collection with mutually exclusive options + command: ansible-galaxy collection install -r {{ req_file }} -s {{ test_name }} {{ cli_signature }} + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + # --signature is an ansible-galaxy collection install subcommand, but mutually exclusive with -r + cli_signature: "--signature file://{{ gpg_homedir }}/namespace7-name-1.0.0-MANIFEST.json.asc" + ignore_errors: yes + register: mutually_exclusive_opts + +- assert: + that: + - mutually_exclusive_opts is failed + - expected_error in actual_error + vars: + expected_error: >- + The --signatures option and --requirements-file are mutually exclusive. + Use the --signatures with positional collection_name args or provide a + 'signatures' key for requirements in the --requirements-file. + actual_error: "{{ mutually_exclusive_opts.stderr }}" + +- name: install a collection with user-supplied signatures for verification but no keyring + command: ansible-galaxy collection install namespace1.name1:1.0.0 {{ cli_signature }} + vars: + cli_signature: "--signature file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + ignore_errors: yes + register: required_together + +- assert: + that: + - required_together is failed + - '"ERROR! Signatures were provided to verify namespace1.name1 but no keyring was configured." in required_together.stderr' + +- name: install collections with ansible-galaxy install -r with invalid signatures - {{ test_id }} + # Note that --keyring is a valid option for 'ansible-galaxy install -r ...', not just 'ansible-galaxy collection ...' + command: ansible-galaxy install -r {{ req_file }} -s {{ test_name }} --keyring {{ keyring }} {{ galaxy_verbosity }} + register: install_req + ignore_errors: yes + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + keyring: "{{ gpg_homedir }}/pubring.kbx" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + +- name: assert invalid signature is fatal with ansible-galaxy install - {{ test_id }} + assert: + that: + - install_req is failed + - '"Installing ''namespace7.name:1.0.0'' to" in install_req.stdout' + - '"Not installing namespace7.name because GnuPG signature verification failed" in install_req.stderr' + # The other collections shouldn't be installed because they're listed + # after the failing collection and --ignore-errors was not provided + - '"Installing ''namespace8.name:1.0.0'' to" not in install_req.stdout' + - '"Installing ''namespace9.name:1.0.0'' to" not in install_req.stdout' + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: install collections with ansible-galaxy install and --ignore-errors - {{ test_id }} + command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} -vvvv + register: install_req + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + cli_opts: "-s {{ test_name }} --keyring {{ keyring }} --ignore-errors" + keyring: "{{ gpg_homedir }}/pubring.kbx" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: get result of install collections with ansible-galaxy install - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name/MANIFEST.json' + register: install_req_actual + loop_control: + loop_var: collection + loop: + - namespace8 + - namespace9 + +# SIVEL +- name: assert invalid signature is not fatal with ansible-galaxy install --ignore-errors - {{ test_id }} + 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)" in install_req.stdout' + - '"Not installing namespace7.name because GnuPG signature verification failed." in install_stderr' + - '"Failed to install collection namespace7.name:1.0.0 but skipping due to --ignore-errors being set." 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' + - (install_req_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + vars: + install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" + +- name: clean up collections from last test + file: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name' + state: absent + loop_control: + loop_var: collection + loop: + - namespace8 + - namespace9 + +- name: install collections with only one valid signature using ansible-galaxy install - {{ test_id }} + command: ansible-galaxy install -r {{ req_file }} {{ cli_opts }} {{ galaxy_verbosity }} + register: install_req + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + cli_opts: "-s {{ test_name }} --keyring {{ keyring }}" + keyring: "{{ gpg_homedir }}/pubring.kbx" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: get result of install collections with ansible-galaxy install - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name/MANIFEST.json' + register: install_req_actual + loop_control: + loop_var: collection + loop: + - namespace7 + - namespace8 + - namespace9 + +- name: assert just one valid signature is not fatal with ansible-galaxy install - {{ test_id }} + 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' + - (install_req_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[2].content | b64decode | from_json).collection_info.version == '1.0.0' + vars: + install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" + +- name: clean up collections from last test + file: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name' + state: absent + loop_control: + loop_var: collection + loop: + - namespace7 + - namespace8 + - namespace9 + +- 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 + vars: + req_file: "{{ galaxy_dir }}/ansible_collections/requirements.yaml" + cli_opts: "-s {{ test_name }} --keyring {{ keyring }}" + keyring: "{{ gpg_homedir }}/pubring.kbx" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + ANSIBLE_GALAXY_IGNORE_SIGNATURE_STATUS_CODES: BADSIG # cli option is appended and both status codes are ignored + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- name: get result of install collections with ansible-galaxy install - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name/MANIFEST.json' + register: install_req_actual + loop_control: + loop_var: collection + loop: + - namespace7 + - namespace8 + - namespace9 + +- name: assert invalid signature is not fatal with ansible-galaxy install - {{ test_id }} + 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' + - (install_req_actual.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + - (install_req_actual.results[2].content | b64decode | from_json).collection_info.version == '1.0.0' + vars: + install_stderr: "{{ install_req.stderr | regex_replace('\\n', ' ') }}" + +- name: clean up collections from last test + file: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/name' + state: absent + loop_control: + loop_var: collection + loop: + - namespace7 + - 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 + +- 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' + register: install_symlink + +- find: + paths: '{{ galaxy_dir }}/ansible_collections/symlink/symlink' + recurse: yes + file_type: any + +- name: get result of install collection with symlink - {{ test_id }} + stat: + path: '{{ galaxy_dir }}/ansible_collections/symlink/symlink/{{ path }}' + register: install_symlink_actual + loop_control: + loop_var: path + loop: + - REÅDMÈ.md-link + - docs/REÅDMÈ.md + - plugins/REÅDMÈ.md + - REÅDMÈ.md-outside-link + - docs-link + - docs-link/REÅDMÈ.md + +- name: assert install collection with symlink - {{ test_id }} + assert: + that: + - '"Installing ''symlink.symlink:1.0.0'' to" in install_symlink.stdout' + - install_symlink_actual.results[0].stat.islnk + - install_symlink_actual.results[0].stat.lnk_target == 'REÅDMÈ.md' + - install_symlink_actual.results[1].stat.islnk + - install_symlink_actual.results[1].stat.lnk_target == '../REÅDMÈ.md' + - install_symlink_actual.results[2].stat.islnk + - install_symlink_actual.results[2].stat.lnk_target == '../REÅDMÈ.md' + - install_symlink_actual.results[3].stat.isreg + - install_symlink_actual.results[4].stat.islnk + - install_symlink_actual.results[4].stat.lnk_target == 'docs' + - install_symlink_actual.results[5].stat.islnk + - install_symlink_actual.results[5].stat.lnk_target == '../REÅDMÈ.md' + +- name: remove install directory for the next test because parent_dep.parent_collection was installed - {{ test_id }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent + +- 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' + register: install_req + +- name: assert install collections with ansible-galaxy install - {{ test_id }} + assert: + that: + - '"Installing ''parent_dep.parent_collection:1.0.0'' to" in install_req.stdout' + - '"Installing ''parent_dep2.parent_collection:1.0.0'' to" in install_req.stdout' + - '"Installing ''child_dep.child_collection:0.5.0'' to" in install_req.stdout' + +- name: install a collection to a directory that contains another collection with no metadata + block: + + # Collections are usable in ansible without a galaxy.yml or MANIFEST.json + - name: create a collection directory + file: + state: directory + path: '{{ galaxy_dir }}/ansible_collections/unrelated_namespace/collection_without_metadata/plugins' + + - 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' + register: install_req + + - name: assert installed collections with ansible-galaxy install - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.0.9'' to" in install_req.stdout' + +- name: remove test collection install directory - {{ test_id }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: install collection with signature with invalid keyring + command: ansible-galaxy collection install namespace1.name1 -vvvv {{ signature_option }} {{ keyring_option }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + vars: + signature_option: "--signature file://{{ gpg_homedir }}/namespace1-name1-1.0.9-MANIFEST.json.asc" + keyring_option: '--keyring {{ gpg_homedir }}/i_do_not_exist.kbx' + ignore_errors: yes + register: keyring_error + +- assert: + that: + - keyring_error is failed + - expected_errors[0] in actual_error + - expected_errors[1] in actual_error + - expected_errors[2] in actual_error + - unexpected_warning not in actual_warning + vars: + keyring: "{{ gpg_homedir }}/i_do_not_exist.kbx" + expected_errors: + - "Signature verification failed for 'namespace1.name1' (return code 2):" + - "* The public key is not available." + - >- + * It was not possible to check the signature. This may be caused + by a missing public key or an unsupported algorithm. A RC of 4 + indicates unknown algorithm, a 9 indicates a missing public key. + unexpected_warning: >- + The GnuPG keyring used for collection signature + verification was not configured but signatures were + provided by the Galaxy server to verify authenticity. + Configure a keyring for ansible-galaxy to use + or disable signature verification. + Skipping signature verification. + actual_warning: "{{ keyring_error.stderr | regex_replace('\\n', ' ') }}" + # Remove formatting from the reason so it's one line + actual_error: "{{ keyring_error.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +# TODO: Uncomment once signatures are provided by pulp-galaxy-ng +#- name: install collection with signature provided by Galaxy server (no keyring) +# command: ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }} +# environment: +# ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' +# ANSIBLE_NOCOLOR: True +# ANSIBLE_FORCE_COLOR: False +# ignore_errors: yes +# register: keyring_warning +# +#- name: assert a warning was given but signature verification did not occur without configuring the keyring +# assert: +# that: +# - keyring_warning is not failed +# - - '"Installing ''namespace1.name1:1.0.9'' to" in keyring_warning.stdout' +# # TODO: Don't just check the stdout, make sure the collection was installed. +# - expected_warning in actual_warning +# vars: +# expected_warning: >- +# The GnuPG keyring used for collection signature +# verification was not configured but signatures were +# provided by the Galaxy server to verify authenticity. +# Configure a keyring for ansible-galaxy to use +# or disable signature verification. +# Skipping signature verification. +# actual_warning: "{{ keyring_warning.stderr | regex_replace('\\n', ' ') }}" + +- name: install simple collection from first accessible server with valid detached signature + command: ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }} {{ signature_options }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.9-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: from_first_good_server + +- name: get installed files of install simple collection from first good server + find: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1' + file_type: file + register: install_normal_files + +- name: get the manifest of install simple collection from first good server + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: install_normal_manifest + +- name: assert install simple collection from first good server + assert: + that: + - '"Installing ''namespace1.name1:1.0.9'' to" in from_first_good_server.stdout' + - install_normal_files.files | length == 3 + - install_normal_files.files[0].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[1].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - install_normal_files.files[2].path | basename in ['MANIFEST.json', 'FILES.json', 'README.md'] + - (install_normal_manifest.content | b64decode | from_json).collection_info.version == '1.0.9' + +- name: Remove the collection + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1' + state: absent + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: install simple collection with invalid detached signature + command: ansible-galaxy collection install namespace1.name1 -vvvv {{ signature_options }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace2-name-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + ignore_errors: yes + register: invalid_signature + +- assert: + that: + - invalid_signature is failed + - "'Not installing namespace1.name1 because GnuPG signature verification failed.' in invalid_signature.stderr" + - expected_errors[0] in install_stdout + - expected_errors[1] in install_stdout + vars: + expected_errors: + - "* This is the counterpart to SUCCESS and used to indicate a program failure." + - "* The signature with the keyid has not been verified okay." + # Remove formatting from the reason so it's one line + install_stdout: "{{ invalid_signature.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +- name: validate collection directory was not created + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1' + state: absent + register: collection_dir + check_mode: yes + failed_when: collection_dir is changed + +- name: disable signature verification and install simple collection with invalid detached signature + command: ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }} {{ signature_options }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }} --disable-gpg-verify" + signature: "file://{{ gpg_homedir }}/namespace2-name-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + ignore_errors: yes + register: ignore_invalid_signature + +- assert: + that: + - ignore_invalid_signature is success + - '"Installing ''namespace1.name1:1.0.9'' to" in ignore_invalid_signature.stdout' + +- name: use lenient signature verification (default) without providing signatures + command: ansible-galaxy collection install namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx --force + environment: + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "all" + register: missing_signature + +- assert: + that: + - missing_signature is success + - missing_signature.rc == 0 + - '"namespace1.name1:1.0.0 was installed successfully" in missing_signature.stdout' + - '"Signature verification failed for ''namespace1.name1'': no successful signatures" not in missing_signature.stdout' + +- name: use strict signature verification without providing signatures + command: ansible-galaxy collection install namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx --force + environment: + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "+1" + ignore_errors: yes + register: missing_signature + +- assert: + that: + - missing_signature is failed + - missing_signature.rc == 1 + - '"Signature verification failed for ''namespace1.name1'': no successful signatures" in missing_signature.stdout' + - '"Not installing namespace1.name1 because GnuPG signature verification failed" in missing_signature.stderr' + +- name: Remove the collection + file: + path: '{{ galaxy_dir }}/ansible_collections/namespace1' + state: absent + +- name: download collections with pre-release dep - {{ test_id }} + command: ansible-galaxy collection download dep_with_beta.parent namespace1.name1:1.1.0-beta.1 -p '{{ galaxy_dir }}/scratch' + +- name: install collection with concrete pre-release dep - {{ test_id }} + command: ansible-galaxy collection install -r '{{ galaxy_dir }}/scratch/requirements.yml' + args: + chdir: '{{ galaxy_dir }}/scratch' + environment: + ANSIBLE_COLLECTIONS_PATHS: '{{ galaxy_dir }}/ansible_collections' + register: install_concrete_pre + +- name: get result of install collections with concrete pre-release dep - {{ test_id }} + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ collection }}/MANIFEST.json' + register: install_concrete_pre_actual + loop_control: + loop_var: collection + loop: + - namespace1/name1 + - dep_with_beta/parent + +- name: assert install collections with ansible-galaxy install - {{ test_id }} + assert: + that: + - '"Installing ''namespace1.name1:1.1.0-beta.1'' to" in install_concrete_pre.stdout' + - '"Installing ''dep_with_beta.parent:1.0.0'' to" in install_concrete_pre.stdout' + - (install_concrete_pre_actual.results[0].content | b64decode | from_json).collection_info.version == '1.1.0-beta.1' + - (install_concrete_pre_actual.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + +- name: remove collection dir after round of testing - {{ test_id }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml new file mode 100644 index 0000000..74c9983 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/install_offline.yml @@ -0,0 +1,137 @@ +- name: test tarfile dependency resolution without querying distribution servers + vars: + init_dir: "{{ galaxy_dir }}/offline/setup" + build_dir: "{{ galaxy_dir }}/offline/build" + install_dir: "{{ galaxy_dir }}/offline/collections" + block: + - name: create test directories + file: + path: "{{ item }}" + state: directory + loop: + - "{{ init_dir }}" + - "{{ build_dir }}" + - "{{ install_dir }}" + + - name: initialize two collections + command: ansible-galaxy collection init ns.{{ item }} --init-path {{ init_dir }} + loop: + - coll1 + - coll2 + + - name: add one collection as the dependency of the other + lineinfile: + path: "{{ galaxy_dir }}/offline/setup/ns/coll1/galaxy.yml" + regexp: "^dependencies:*" + line: "dependencies: {'ns.coll2': '>=1.0.0'}" + + - name: build both collections + command: ansible-galaxy collection build {{ init_dir }}/ns/{{ item }} + args: + chdir: "{{ build_dir }}" + loop: + - coll1 + - coll2 + + - name: install the dependency from the tarfile + command: ansible-galaxy collection install {{ build_dir }}/ns-coll2-1.0.0.tar.gz -p {{ install_dir }} -s offline + + - name: install the tarfile with the installed dependency + command: ansible-galaxy collection install {{ build_dir }}/ns-coll1-1.0.0.tar.gz -p {{ install_dir }} -s offline + + - name: empty the installed collections directory + file: + path: "{{ install_dir }}" + state: "{{ item }}" + loop: + - absent + - directory + + - name: edit skeleton with new versions to test upgrading + lineinfile: + path: "{{ galaxy_dir }}/offline/setup/ns/{{ item }}/galaxy.yml" + regexp: "^version:.*$" + line: "version: 2.0.0" + loop: + - coll1 + - coll2 + + - name: build the tarfiles to test upgrading + command: ansible-galaxy collection build {{ init_dir }}/ns/{{ item }} + args: + chdir: "{{ build_dir }}" + loop: + - coll1 + - coll2 + + - name: install the tarfile and its dep with an unreachable server (expected error) + command: ansible-galaxy collection install {{ build_dir }}/ns-coll1-1.0.0.tar.gz -p {{ install_dir }} -s offline + environment: + ANSIBLE_FORCE_COLOR: False + ANSIBLE_NOCOLOR: True + ignore_errors: yes + register: missing_dep + + - name: install the tarfile with a missing dependency and --offline + command: ansible-galaxy collection install --offline {{ build_dir }}/ns-coll1-1.0.0.tar.gz -p {{ install_dir }} -s offline + environment: + ANSIBLE_FORCE_COLOR: False + ANSIBLE_NOCOLOR: True + ignore_errors: yes + register: missing_dep_offline + + - assert: + that: + - missing_dep.failed + - missing_dep_offline.failed + - galaxy_err in missing_dep.stderr + - missing_err in missing_dep_offline.stderr + vars: + galaxy_err: "ERROR! Unknown error when attempting to call Galaxy at '{{ offline_server }}'" + missing_err: |- + ERROR! Failed to resolve the requested dependencies map. Could not satisfy the following requirements: + * ns.coll2:>=1.0.0 (dependency of ns.coll1:1.0.0) + + - name: install the dependency from the tarfile + command: ansible-galaxy collection install {{ build_dir }}/ns-coll2-1.0.0.tar.gz -p {{ install_dir }} + + - name: install the tarfile with --offline for dep resolution + command: ansible-galaxy collection install {{ build_dir }}/ns-coll1-1.0.0.tar.gz {{ cli_opts }} + vars: + cli_opts: "--offline -p {{ install_dir }} -s offline" + register: offline_install + + - name: upgrade using tarfile with --offline for dep resolution + command: ansible-galaxy collection install {{ build_dir }}/ns-coll1-2.0.0.tar.gz {{ cli_opts }} + vars: + cli_opts: "--offline --upgrade -p {{ install_dir }} -s offline" + register: offline_upgrade + + - name: reinstall ns-coll1-1.0.0 to test upgrading the dependency too + command: ansible-galaxy collection install {{ build_dir }}/ns-coll1-1.0.0.tar.gz {{ cli_opts }} + vars: + cli_opts: "--offline --force -p {{ install_dir }} -s offline" + + - name: upgrade both collections using tarfiles and --offline + command: ansible-galaxy collection install {{ collections }} {{ cli_opts }} + vars: + collections: "{{ build_dir }}/ns-coll1-2.0.0.tar.gz {{ build_dir }}/ns-coll2-2.0.0.tar.gz" + cli_opts: "--offline --upgrade -s offline" + register: upgrade_all + + - assert: + that: + - '"ns.coll1:1.0.0 was installed successfully" in offline_install.stdout' + - '"ns.coll1:2.0.0 was installed successfully" in offline_upgrade.stdout' + - '"ns.coll1:2.0.0 was installed successfully" in upgrade_all.stdout' + - '"ns.coll2:2.0.0 was installed successfully" in upgrade_all.stdout' + + always: + - name: clean up test directories + file: + path: "{{ item }}" + state: absent + loop: + - "{{ init_dir }}" + - "{{ build_dir }}" + - "{{ install_dir }}" diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/list.yml b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml new file mode 100644 index 0000000..b8d6349 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/list.yml @@ -0,0 +1,167 @@ +- name: initialize collection structure + command: ansible-galaxy collection init {{ item }} --init-path "{{ galaxy_dir }}/dev/ansible_collections" {{ galaxy_verbosity }} + loop: + - 'dev.collection1' + - 'dev.collection2' + - 'dev.collection3' + - 'dev.collection4' + - 'dev.collection5' + - 'dev.collection6' + +- name: replace the default version of the collections + lineinfile: + path: "{{ galaxy_dir }}/dev/ansible_collections/dev/{{ item.name }}/galaxy.yml" + line: "{{ item.version }}" + regexp: "version: .*" + loop: + - name: "collection1" + version: "version: null" + - name: "collection2" + version: "version: placeholder" + - name: "collection3" + version: "version: ''" + +- name: set the namespace, name, and version keys to None + lineinfile: + path: "{{ galaxy_dir }}/dev/ansible_collections/dev/collection4/galaxy.yml" + line: "{{ item.after }}" + regexp: "{{ item.before }}" + loop: + - before: "^namespace: dev" + after: "namespace:" + - before: "^name: collection4" + after: "name:" + - before: "^version: 1.0.0" + after: "version:" + +- name: replace galaxy.yml content with a string + copy: + content: "invalid" + dest: "{{ galaxy_dir }}/dev/ansible_collections/dev/collection5/galaxy.yml" + +- name: remove galaxy.yml key required by build + lineinfile: + path: "{{ galaxy_dir }}/dev/ansible_collections/dev/collection6/galaxy.yml" + line: "version: 1.0.0" + state: absent + +- name: list collections in development without semver versions + command: ansible-galaxy collection list {{ galaxy_verbosity }} + register: list_result + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "'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" + +- name: list collections in human format + command: ansible-galaxy collection list --format human + register: list_result_human + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "'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" + +- name: list collections in yaml format + command: ansible-galaxy collection list --format yaml + register: list_result_yaml + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "item.value | length == 6" + - "item.value['dev.collection1'].version == '*'" + - "item.value['dev.collection2'].version == 'placeholder'" + - "item.value['dev.collection3'].version == '*'" + - "item.value['dev.collection5'].version == '*'" + - "item.value['dev.collection6'].version == '*'" + with_dict: "{{ list_result_yaml.stdout | from_yaml }}" + +- name: list collections in json format + command: ansible-galaxy collection list --format json + register: list_result_json + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - "item.value | length == 6" + - "item.value['dev.collection1'].version == '*'" + - "item.value['dev.collection2'].version == 'placeholder'" + - "item.value['dev.collection3'].version == '*'" + - "item.value['dev.collection5'].version == '*'" + - "item.value['dev.collection6'].version == '*'" + with_dict: "{{ list_result_json.stdout | from_json }}" + +- name: list single collection in json format + command: "ansible-galaxy collection list {{ item.key }} --format json" + register: list_single_result_json + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + with_dict: "{{ { 'dev.collection1': '*', 'dev.collection2': 'placeholder', 'dev.collection3': '*' } }}" + +- assert: + that: + - "(item.stdout | from_json)[galaxy_dir + '/dev/ansible_collections'][item.item.key].version == item.item.value" + with_items: "{{ list_single_result_json.results }}" + +- name: list single collection in yaml format + command: "ansible-galaxy collection list {{ item.key }} --format yaml" + register: list_single_result_yaml + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + with_dict: "{{ { 'dev.collection1': '*', 'dev.collection2': 'placeholder', 'dev.collection3': '*' } }}" + +- assert: + that: + - "(item.stdout | from_yaml)[galaxy_dir + '/dev/ansible_collections'][item.item.key].version == item.item.value" + with_items: "{{ list_single_result_json.results }}" + +- name: test that no json is emitted when no collection paths are usable + command: "ansible-galaxy collection list --format json" + register: list_result_error + ignore_errors: True + environment: + ANSIBLE_COLLECTIONS_PATH: "" + +- assert: + that: + - "'{}' not in list_result_error.stdout" + +- name: install an artifact to the second collections path + command: ansible-galaxy collection install namespace1.name1 -s galaxy_ng {{ galaxy_verbosity }} -p "{{ galaxy_dir }}/prod" + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +- name: replace the artifact version + lineinfile: + path: "{{ galaxy_dir }}/prod/ansible_collections/namespace1/name1/MANIFEST.json" + line: ' "version": null,' + regexp: ' "version": .*' + +- name: test listing collections in all paths + command: ansible-galaxy collection list {{ galaxy_verbosity }} + register: list_result + ignore_errors: True + environment: + ANSIBLE_COLLECTIONS_PATH: "{{ galaxy_dir }}/dev:{{ galaxy_dir }}/prod" + +- assert: + that: + - list_result is failed + - "'is expected to have a valid SemVer version value but got None' in list_result.stderr" diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/main.yml b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml new file mode 100644 index 0000000..724c861 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/main.yml @@ -0,0 +1,220 @@ +--- +- name: set some facts for tests + set_fact: + galaxy_dir: "{{ remote_tmp_dir }}/galaxy" + +- name: create scratch dir used for testing + file: + path: '{{ galaxy_dir }}/scratch' + state: directory + +- name: run ansible-galaxy collection init tests + import_tasks: init.yml + +- name: run ansible-galaxy collection build tests + import_tasks: build.yml + +# The pulp container has a long start up time +# The first task to interact with pulp needs to wait until it responds appropriately +- name: list pulp distributions + uri: + url: '{{ pulp_api }}/pulp/api/v3/distributions/ansible/ansible/' + status_code: + - 200 + user: '{{ pulp_user }}' + password: '{{ pulp_password }}' + force_basic_auth: true + register: pulp_distributions + until: pulp_distributions is successful + delay: 1 + retries: 60 + +- name: configure pulp + include_tasks: pulp.yml + +- name: Get galaxy_ng token + uri: + url: '{{ galaxy_ng_server }}v3/auth/token/' + method: POST + body_format: json + body: {} + status_code: + - 200 + user: '{{ pulp_user }}' + password: '{{ pulp_password }}' + force_basic_auth: true + register: galaxy_ng_token + +- name: create test ansible.cfg that contains the Galaxy server list + template: + src: ansible.cfg.j2 + dest: '{{ galaxy_dir }}/ansible.cfg' + +- name: test install command using an unsupported version of resolvelib + include_tasks: unsupported_resolvelib.yml + loop: "{{ unsupported_resolvelib_versions }}" + loop_control: + loop_var: resolvelib_version + +- name: run ansible-galaxy collection offline installation tests + include_tasks: install_offline.yml + args: + apply: + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +- name: run ansible-galaxy collection publish tests for {{ test_name }} + include_tasks: publish.yml + args: + apply: + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + vars: + test_name: '{{ item.name }}' + test_server: '{{ item.server }}' + vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}' + loop: + - name: pulp_v2 + server: '{{ pulp_server }}published/api/' + - name: pulp_v3 + server: '{{ pulp_server }}published/api/' + v3: true + - name: galaxy_ng + server: '{{ galaxy_ng_server }}' + v3: true + +- include_tasks: setup_gpg.yml + +# We use a module for this so we can speed up the test time. +# For pulp interactions, we only upload to galaxy_ng which shares +# the same repo and distribution with pulp_ansible +# However, we use galaxy_ng only, since collections are unique across +# pulp repositories, and galaxy_ng maintains a 2nd list of published collections +- name: setup test collections for install and download test + setup_collections: + server: galaxy_ng + collections: '{{ collection_list }}' + signature_dir: '{{ gpg_homedir }}' + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +# Stores the cached test version number index as we run install many times +- set_fact: + cache_version_build: 0 + +- name: run ansible-galaxy collection install tests for {{ test_name }} + include_tasks: install.yml + vars: + test_id: '{{ item.name }}' + test_name: '{{ item.name }}' + test_server: '{{ item.server }}' + vX: '{{ "v3/" if item.v3|default(false) else "v2/" }}' + requires_auth: '{{ item.requires_auth|default(false) }}' + args: + apply: + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + loop: + - name: galaxy_ng + server: '{{ galaxy_ng_server }}' + v3: true + requires_auth: true + - name: pulp_v2 + server: '{{ pulp_server }}published/api/' + - name: pulp_v3 + server: '{{ pulp_server }}published/api/' + v3: true + +- name: test installing and downloading collections with the range of supported resolvelib versions + include_tasks: supported_resolvelib.yml + args: + apply: + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + loop: '{{ supported_resolvelib_versions }}' + loop_control: + loop_var: resolvelib_version + +- name: publish collection with a dep on another server + setup_collections: + server: secondary + collections: + - namespace: secondary + name: name + # parent_dep.parent_collection does not exist on the secondary server + dependencies: + parent_dep.parent_collection: '1.0.0' + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +- name: install collection with dep on another server + command: ansible-galaxy collection install secondary.name -vvv # 3 -v's will show the source in the stdout + register: install_cross_dep + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +- name: get result of install collection with dep on another server + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ item.namespace }}/{{ item.name }}/MANIFEST.json' + register: install_cross_dep_actual + loop: + - namespace: secondary + name: name + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- name: assert result of install collection with dep on another server + assert: + that: + - >- + "'secondary.name:1.0.0' obtained from server secondary" + 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" + in install_cross_dep.stdout + - >- + "'child_dep.child_collection:0.9.9' obtained from server pulp_v2" + in install_cross_dep.stdout + - >- + "'child_dep.child_dep2:1.2.2' obtained from server pulp_v2" + 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' + - (install_cross_dep_actual.results[2].content | b64decode | from_json).collection_info.version == '0.9.9' + - (install_cross_dep_actual.results[3].content | b64decode | from_json).collection_info.version == '1.2.2' + +- name: run ansible-galaxy collection download tests + include_tasks: download.yml + args: + apply: + environment: + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + +- name: run ansible-galaxy collection verify tests for {{ test_name }} + include_tasks: verify.yml + args: + apply: + environment: + 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 }}' + +- name: run ansible-galaxy collection list tests + include_tasks: list.yml + +- include_tasks: upgrade.yml + args: + apply: + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}' + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml new file mode 100644 index 0000000..241eae6 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/publish.yml @@ -0,0 +1,33 @@ +--- +- name: publish collection - {{ test_name }} + command: ansible-galaxy collection publish ansible_test-my_collection-1.0.0.tar.gz -s {{ test_name }} {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + register: publish_collection + +- name: get result of publish collection - {{ test_name }} + uri: + url: '{{ test_server }}{{ vX }}collections/ansible_test/my_collection/versions/1.0.0/' + return_content: yes + user: '{{ pulp_user }}' + password: '{{ pulp_password }}' + force_basic_auth: true + register: publish_collection_actual + +- name: assert publish collection - {{ test_name }} + assert: + that: + - '"Collection has been successfully published and imported to the Galaxy server" in publish_collection.stdout' + - publish_collection_actual.json.collection.name == 'my_collection' + - publish_collection_actual.json.namespace.name == 'ansible_test' + - publish_collection_actual.json.version == '1.0.0' + +- name: fail to publish existing collection version - {{ test_name }} + command: ansible-galaxy collection publish ansible_test-my_collection-1.0.0.tar.gz -s {{ test_name }} {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + register: fail_publish_existing + failed_when: fail_publish_existing is not failed + +- name: reset published collections - {{ test_name }} + include_tasks: pulp.yml diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/pulp.yml b/test/integration/targets/ansible-galaxy-collection/tasks/pulp.yml new file mode 100644 index 0000000..7b6c5f8 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/pulp.yml @@ -0,0 +1,11 @@ +# These tasks configure pulp/pulp_ansible so that we can use the container +# This will also reset pulp between iterations +# A module is used to make the tests run quicker as this will send lots of API requests. +- name: reset pulp content + reset_pulp: + pulp_api: '{{ pulp_api }}' + galaxy_ng_server: '{{ galaxy_ng_server }}' + url_username: '{{ pulp_user }}' + url_password: '{{ pulp_password }}' + repositories: '{{ pulp_repositories }}' + namespaces: '{{ collection_list|map(attribute="namespace")|unique + publish_namespaces }}' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/revoke_gpg_key.yml b/test/integration/targets/ansible-galaxy-collection/tasks/revoke_gpg_key.yml new file mode 100644 index 0000000..7a49eee --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/revoke_gpg_key.yml @@ -0,0 +1,14 @@ +- name: generate revocation certificate + expect: + command: "gpg --homedir {{ gpg_homedir }} --pinentry-mode loopback --output {{ gpg_homedir }}/revoke.asc --gen-revoke {{ fingerprint }}" + responses: + "Create a revocation certificate for this key": "y" + "Please select the reason for the revocation": "0" + "Enter an optional description": "" + "Is this okay": "y" + +- name: revoke key + command: "gpg --no-tty --homedir {{ gpg_homedir }} --import {{ gpg_homedir }}/revoke.asc" + +- name: list keys for debugging + command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys {{ gpg_user }}" diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/setup_gpg.yml b/test/integration/targets/ansible-galaxy-collection/tasks/setup_gpg.yml new file mode 100644 index 0000000..ddc4d8a --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/setup_gpg.yml @@ -0,0 +1,24 @@ +- name: create empty gpg homedir + file: + state: "{{ item }}" + path: "{{ gpg_homedir }}" + mode: 0700 + loop: + - absent + - directory + +- name: get username for generating key + command: whoami + register: user + +- name: generate key for user with gpg + command: "gpg --no-tty --homedir {{ gpg_homedir }} --passphrase '' --pinentry-mode loopback --quick-gen-key {{ user.stdout }} default default" + +- name: list gpg keys for user + command: "gpg --no-tty --homedir {{ gpg_homedir }} --list-keys {{ user.stdout }}" + register: gpg_list_keys + +- name: save gpg user and fingerprint of new key + set_fact: + gpg_user: "{{ user.stdout }}" + fingerprint: "{{ gpg_list_keys.stdout_lines[1] | trim }}" diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml new file mode 100644 index 0000000..763c5a1 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/supported_resolvelib.yml @@ -0,0 +1,44 @@ +- vars: + venv_cmd: "{{ ansible_python_interpreter ~ ' -m venv' }}" + venv_dest: "{{ galaxy_dir }}/test_venv_{{ resolvelib_version }}" + block: + - name: install another version of resolvelib that is supported by ansible-galaxy + pip: + name: resolvelib + version: "{{ resolvelib_version }}" + state: present + virtualenv_command: "{{ venv_cmd }}" + virtualenv: "{{ venv_dest }}" + virtualenv_site_packages: True + + - include_tasks: fail_fast_resolvelib.yml + args: + apply: + environment: + PATH: "{{ venv_dest }}/bin:{{ ansible_env.PATH }}" + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + + - include_tasks: install.yml + vars: + test_name: pulp_v3 + test_id: '{{ test_name }} (resolvelib {{ resolvelib_version }})' + test_server: '{{ pulp_server }}published/api/' + vX: "v3/" + requires_auth: false + args: + apply: + environment: + PATH: "{{ venv_dest }}/bin:{{ ansible_env.PATH }}" + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + + - include_tasks: download.yml + args: + apply: + environment: + PATH: "{{ venv_dest }}/bin:{{ ansible_env.PATH }}" + ANSIBLE_CONFIG: '{{ galaxy_dir }}/ansible.cfg' + always: + - name: remove test venv + file: + path: "{{ venv_dest }}" + state: absent diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml b/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml new file mode 100644 index 0000000..a208b29 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/unsupported_resolvelib.yml @@ -0,0 +1,44 @@ +- vars: + venv_cmd: "{{ ansible_python_interpreter ~ ' -m venv' }}" + venv_dest: "{{ galaxy_dir }}/test_resolvelib_{{ resolvelib_version }}" + block: + - name: install another version of resolvelib that is unsupported by ansible-galaxy + pip: + name: resolvelib + version: "{{ resolvelib_version }}" + state: present + virtualenv_command: "{{ venv_cmd }}" + virtualenv: "{{ venv_dest }}" + virtualenv_site_packages: True + + - name: create test collection install directory - {{ test_name }} + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: directory + + - name: install simple collection from first accessible server (expected failure) + command: "ansible-galaxy collection install namespace1.name1 {{ galaxy_verbosity }}" + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/ansible_collections' + PATH: "{{ venv_dest }}/bin:{{ ansible_env.PATH }}" + register: resolvelib_version_error + ignore_errors: yes + + - assert: + that: + - resolvelib_version_error is failed + - resolvelib_version_error.stderr | regex_search(error) + vars: + error: "({{ import_error }}|{{ compat_error }})" + import_error: "Failed to import resolvelib" + compat_error: "ansible-galaxy requires resolvelib<{{major_minor_patch}},>={{major_minor_patch}}" + major_minor_patch: "[0-9]\\d*\\.[0-9]\\d*\\.[0-9]\\d*" + + always: + - name: cleanup venv and install directory + file: + path: '{{ galaxy_dir }}/ansible_collections' + state: absent + loop: + - '{{ galaxy_dir }}/ansible_collections' + - '{{ venv_dest }}' diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml new file mode 100644 index 0000000..893ea80 --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/upgrade.yml @@ -0,0 +1,282 @@ +##### Updating collections with a new version constraint + +# No deps + +- name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +- name: install a collection + 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' + +- name: install a new version of the collection without --force + command: ansible-galaxy collection install namespace1.name1:>0.0.4,<=0.0.5 {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the version + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: metadata + +- assert: + that: + - '"namespace1.name1:0.0.5 was installed successfully" in result.stdout_lines' + - (metadata.content | b64decode | from_json).collection_info.version == '0.0.5' + +- name: don't reinstall the collection in the requirement is compatible + command: ansible-galaxy collection install namespace1.name1:>=0.0.5 {{ galaxy_verbosity }} + register: result + +- assert: + that: "\"Nothing to do. All requested collections are already installed.\" in result.stdout" + +- name: install a pre-release of the collection without --force + command: ansible-galaxy collection install namespace1.name1:1.1.0-beta.1 {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the version + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: metadata + +- assert: + that: + - '"namespace1.name1:1.1.0-beta.1 was installed successfully" in result.stdout_lines' + - (metadata.content | b64decode | from_json).collection_info.version == '1.1.0-beta.1' + +# With deps + +- name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +- name: install a dep that will need to be upgraded to be compatible with the parent + command: ansible-galaxy collection install child_dep.child_collection:0.4.0 --no-deps {{ galaxy_verbosity }} + register: result + failed_when: + - '"child_dep.child_collection:0.4.0 was installed successfully" not in result.stdout_lines' + +- name: install a dep of the dep that will need to be upgraded to be compatible + command: ansible-galaxy collection install child_dep.child_dep2:>1.2.2 {{ galaxy_verbosity }} + register: result + failed_when: + - '"child_dep.child_dep2:1.2.3 was installed successfully" not in result.stdout_lines' + +- name: install the parent without deps to test dep reconciliation during upgrade + command: ansible-galaxy collection install parent_dep.parent_collection:0.0.1 {{ galaxy_verbosity }} + register: result + failed_when: + - '"parent_dep.parent_collection:0.0.1 was installed successfully" not in result.stdout_lines' + +- name: test upgrading the parent collection and dependencies + command: ansible-galaxy collection install parent_dep.parent_collection:>=1.0.0,<1.1.0 {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the versions + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ item.namespace }}/{{ item.name }}/MANIFEST.json' + register: metadata + loop: + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- assert: + that: + - '"parent_dep.parent_collection:1.0.0 was installed successfully" in result.stdout_lines' + - (metadata.results[0].content | b64decode | from_json).collection_info.version == '1.0.0' + - '"child_dep.child_collection:0.9.9 was installed successfully" in result.stdout_lines' + - (metadata.results[1].content | b64decode | from_json).collection_info.version == '0.9.9' + - '"child_dep.child_dep2:1.2.2 was installed successfully" in result.stdout_lines' + - (metadata.results[2].content | b64decode | from_json).collection_info.version == '1.2.2' + +- name: test upgrading a collection only upgrades dependencies if necessary + command: ansible-galaxy collection install parent_dep.parent_collection:1.1.0 {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the versions + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ item.namespace }}/{{ item.name }}/MANIFEST.json' + register: metadata + loop: + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- assert: + that: + - '"parent_dep.parent_collection:1.1.0 was installed successfully" in result.stdout_lines' + - (metadata.results[0].content | b64decode | from_json).collection_info.version == '1.1.0' + - "\"'child_dep.child_collection:0.9.9' is already installed, skipping.\" in result.stdout_lines" + - (metadata.results[1].content | b64decode | from_json).collection_info.version == '0.9.9' + - "\"'child_dep.child_dep2:1.2.2' is already installed, skipping.\" in result.stdout_lines" + - (metadata.results[2].content | b64decode | from_json).collection_info.version == '1.2.2' + +##### Updating collections with --upgrade + +# No deps + +- name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +- name: install a collection + 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' + +- name: install a new version of the collection with --upgrade + command: ansible-galaxy collection install namespace1.name1:<=0.0.5 --upgrade {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the version + slurp: + path: '{{ galaxy_dir }}/ansible_collections/namespace1/name1/MANIFEST.json' + register: metadata + +- assert: + that: + - '"namespace1.name1:0.0.5 was installed successfully" in result.stdout_lines' + - (metadata.content | b64decode | from_json).collection_info.version == '0.0.5' + +- name: upgrade the collection + command: ansible-galaxy collection install namespace1.name1:<0.0.7 -U {{ galaxy_verbosity }} + register: result + +- assert: + that: '"namespace1.name1:0.0.6 was installed successfully" in result.stdout_lines' + +- name: upgrade the collection to the last version excluding prereleases + command: ansible-galaxy collection install namespace1.name1 -U {{ galaxy_verbosity }} + register: result + +- assert: + that: '"namespace1.name1:1.0.9 was installed successfully" in result.stdout_lines' + +- name: upgrade the collection to the latest available version including prereleases + command: ansible-galaxy collection install namespace1.name1 --pre -U {{ galaxy_verbosity }} + register: result + +- assert: + that: '"namespace1.name1:1.1.0-beta.1 was installed successfully" in result.stdout_lines' + +- name: run the same command again + command: ansible-galaxy collection install namespace1.name1 --pre -U {{ galaxy_verbosity }} + register: result + +- assert: + that: "\"'namespace1.name1:1.1.0-beta.1' is already installed, skipping.\" in result.stdout" + +# With deps + +- name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +- name: install a parent collection and a particular version of its dependency + command: ansible-galaxy collection install parent_dep.parent_collection:1.0.0 child_dep.child_collection:0.5.0 {{ galaxy_verbosity }} + register: result + failed_when: + - '"parent_dep.parent_collection:1.0.0 was installed successfully" not in result.stdout_lines' + - '"child_dep.child_collection:0.5.0 was installed successfully" not in result.stdout_lines' + +- name: upgrade the parent and child - the child's new version has a dependency that should be installed + command: ansible-galaxy collection install parent_dep.parent_collection -U {{ galaxy_verbosity }} + register: result + +- name: check the MANIFEST.json to verify the versions + slurp: + path: '{{ galaxy_dir }}/ansible_collections/{{ item.namespace }}/{{ item.name }}/MANIFEST.json' + register: metadata + loop: + - namespace: parent_dep + name: parent_collection + - namespace: child_dep + name: child_collection + - namespace: child_dep + name: child_dep2 + +- assert: + that: + - '"parent_dep.parent_collection:2.0.0 was installed successfully" in result.stdout_lines' + - (metadata.results[0].content | b64decode | from_json).collection_info.version == '2.0.0' + - '"child_dep.child_collection:1.0.0 was installed successfully" in result.stdout_lines' + - (metadata.results[1].content | b64decode | from_json).collection_info.version == '1.0.0' + - '"child_dep.child_dep2:1.2.2 was installed successfully" in result.stdout_lines' + - (metadata.results[2].content | b64decode | from_json).collection_info.version == '1.2.2' + +# Test using a requirements.yml file for upgrade + +- name: reset installation directory + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory + +- name: install upgradeable collections + command: ansible-galaxy collection install namespace1.name1:1.0.0 parent_dep.parent_collection:0.0.1 {{ galaxy_verbosity }} + register: result + failed_when: + - '"namespace1.name1:1.0.0 was installed successfully" not in result.stdout_lines' + - '"parent_dep.parent_collection:0.0.1 was installed successfully" not in result.stdout_lines' + - '"child_dep.child_collection:0.4.0 was installed successfully" not in result.stdout_lines' + +- name: create test requirements file with both roles and collections - {{ test_name }} + copy: + content: | + collections: + - namespace1.name1 + - name: parent_dep.parent_collection + version: <=2.0.0 + roles: + - skip.me + dest: '{{ galaxy_dir }}/ansible_collections/requirements.yml' + +- name: upgrade collections with the requirements.yml + command: ansible-galaxy collection install -r {{ requirements_path }} --upgrade {{ galaxy_verbosity }} + vars: + requirements_path: '{{ galaxy_dir }}/ansible_collections/requirements.yml' + register: result + +- assert: + that: + - '"namespace1.name1:1.0.9 was installed successfully" in result.stdout_lines' + - '"parent_dep.parent_collection:2.0.0 was installed successfully" in result.stdout_lines' + - '"child_dep.child_collection:1.0.0 was installed successfully" in result.stdout_lines' + - '"child_dep.child_dep2:1.2.2 was installed successfully" in result.stdout_lines' + +- name: cleanup + file: + state: "{{ item }}" + path: "{{ galaxy_dir }}/ansible_collections" + loop: + - absent + - directory diff --git a/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml new file mode 100644 index 0000000..dfe3d0f --- /dev/null +++ b/test/integration/targets/ansible-galaxy-collection/tasks/verify.yml @@ -0,0 +1,475 @@ +- name: create an empty collection skeleton + command: ansible-galaxy collection init ansible_test.verify + args: + chdir: '{{ galaxy_dir }}/scratch' + +- name: build the collection + command: ansible-galaxy collection build scratch/ansible_test/verify + args: + chdir: '{{ galaxy_dir }}' + +- name: publish collection - {{ test_name }} + command: ansible-galaxy collection publish ansible_test-verify-1.0.0.tar.gz -s {{ test_name }} {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + +- name: test verifying a tarfile + command: ansible-galaxy collection verify {{ galaxy_dir }}/ansible_test-verify-1.0.0.tar.gz + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - >- + "ERROR! 'file' type is not supported. The format namespace.name is expected." in verify.stderr + +- name: install the collection from the server + command: ansible-galaxy collection install ansible_test.verify:1.0.0 -s {{ test_api_fallback }} {{ galaxy_verbosity }} + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- 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 + +- assert: + that: + - verify is success + - >- + "Found API version '{{ test_api_fallback_versions }}' with Galaxy server {{ test_api_fallback }}" in verify.stdout + +- name: verify the installed collection against the server + command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + +- assert: + that: + - verify is success + - "'Collection ansible_test.verify contains modified content' not in verify.stdout" + +- name: verify the installed collection against the server, with unspecified version in CLI + command: ansible-galaxy collection verify ansible_test.verify -s {{ test_name }} {{ galaxy_verbosity }} + +- name: verify a collection that doesn't appear to be installed + command: ansible-galaxy collection verify ansible_test.verify:1.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + environment: + ANSIBLE_COLLECTIONS_PATH: '{{ galaxy_dir }}/nonexistent_dir' + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify is not installed in any of the collection paths.' in verify.stderr" + +- name: create a modules directory + file: + state: directory + path: '{{ galaxy_dir }}/scratch/ansible_test/verify/plugins/modules' + +- name: add a module to the collection + copy: + src: test_module.py + dest: '{{ galaxy_dir }}/scratch/ansible_test/verify/plugins/modules/test_module.py' + +- name: update the collection version + lineinfile: + regexp: "version: .*" + line: "version: '2.0.0'" + path: '{{ galaxy_dir }}/scratch/ansible_test/verify/galaxy.yml' + +- name: build the new version + command: ansible-galaxy collection build scratch/ansible_test/verify + args: + chdir: '{{ galaxy_dir }}' + +- name: publish the new version + command: ansible-galaxy collection publish ansible_test-verify-2.0.0.tar.gz -s {{ test_name }} {{ galaxy_verbosity }} + args: + chdir: '{{ galaxy_dir }}' + +- name: verify a version of a collection that isn't installed + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - '"ansible_test.verify has the version ''1.0.0'' but is being compared to ''2.0.0''" in verify.stdout' + +- name: install the new version from the server + command: ansible-galaxy collection install ansible_test.verify:2.0.0 --force -s {{ test_name }} {{ galaxy_verbosity }} + +- name: verify the installed collection against the server + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + +- assert: + that: + - "'Collection ansible_test.verify contains modified content' not in verify.stdout" + +# Test a modified collection + +- set_fact: + manifest_path: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/MANIFEST.json' + file_manifest_path: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/FILES.json' + module_path: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/plugins/modules/test_module.py' + +- name: load the FILES.json + set_fact: + files_manifest: "{{ lookup('file', file_manifest_path) | from_json }}" + +- name: get the real checksum of a particular module + stat: + path: "{{ module_path }}" + checksum_algorithm: sha256 + register: file + +- assert: + that: + - "file.stat.checksum == item.chksum_sha256" + loop: "{{ files_manifest.files }}" + when: "item.name == 'plugins/modules/aws_s3.py'" + +- name: append a newline to the module to modify the checksum + shell: "echo '' >> {{ module_path }}" + +- name: get the new checksum + stat: + path: "{{ module_path }}" + checksum_algorithm: sha256 + register: updated_file + +- assert: + that: + - "updated_file.stat.checksum != file.stat.checksum" + +- name: test verifying checksumes of the modified collection + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify contains modified content in the following files:\n plugins/modules/test_module.py' in verify.stdout" + +- name: modify the FILES.json to match the new checksum + lineinfile: + path: "{{ file_manifest_path }}" + regexp: ' "chksum_sha256": "{{ file.stat.checksum }}",' + line: ' "chksum_sha256": "{{ updated_file.stat.checksum }}",' + state: present + diff: true + +- name: ensure a modified FILES.json is validated + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify contains modified content in the following files:\n FILES.json' in verify.stdout" + +- name: get the checksum of the FILES.json + stat: + path: "{{ file_manifest_path }}" + checksum_algorithm: sha256 + register: manifest_info + +- name: modify the MANIFEST.json to contain a different checksum for FILES.json + lineinfile: + regexp: ' "chksum_sha256": *' + path: "{{ manifest_path }}" + line: ' "chksum_sha256": "{{ manifest_info.stat.checksum }}",' + +- name: ensure the MANIFEST.json is validated against the uncorrupted file from the server + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify contains modified content in the following files:\n MANIFEST.json' in verify.stdout" + +- name: remove the artifact metadata to test verifying a collection without it + file: + path: "{{ item }}" + state: absent + loop: + - "{{ manifest_path }}" + - "{{ file_manifest_path }}" + +- name: add some development metadata + copy: + content: | + namespace: 'ansible_test' + name: 'verify' + version: '2.0.0' + readme: 'README.md' + authors: ['Ansible'] + dest: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/galaxy.yml' + +- name: test we only verify collections containing a MANIFEST.json with the version on the server + command: ansible-galaxy collection verify ansible_test.verify:2.0.0 -s {{ test_name }} {{ galaxy_verbosity }} + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify does not have a MANIFEST.json' in verify.stderr" + +- name: update the collection version to something not present on the server + lineinfile: + regexp: "version: .*" + line: "version: '3.0.0'" + path: '{{ galaxy_dir }}/scratch/ansible_test/verify/galaxy.yml' + +- name: build the new version + command: ansible-galaxy collection build scratch/ansible_test/verify + args: + chdir: '{{ galaxy_dir }}' + +- name: force-install from local artifact + command: ansible-galaxy collection install '{{ galaxy_dir }}/ansible_test-verify-3.0.0.tar.gz' --force + +- name: verify locally only, no download or server manifest hash check + command: ansible-galaxy collection verify --offline ansible_test.verify + register: verify + +- assert: + that: + - >- + "Verifying 'ansible_test.verify:3.0.0'." in verify.stdout + - '"MANIFEST.json hash: " in verify.stdout' + - >- + "Successfully verified that checksums for 'ansible_test.verify:3.0.0' are internally consistent with its manifest." in verify.stdout + +- name: append a newline to a module to modify the checksum + shell: "echo '' >> {{ module_path }}" + +- name: create a new module file + file: + path: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/plugins/modules/test_new_file.py' + state: touch + +- name: create a new directory + file: + path: '{{ galaxy_dir }}/ansible_collections/ansible_test/verify/plugins/modules/test_new_dir' + state: directory + +- name: verify modified collection locally-only (should fail) + command: ansible-galaxy collection verify --offline ansible_test.verify + register: verify + failed_when: verify.rc == 0 + +- assert: + that: + - verify.rc != 0 + - "'Collection ansible_test.verify contains modified content in the following files:' in verify.stdout" + - "'plugins/modules/test_module.py' in verify.stdout" + - "'plugins/modules/test_new_file.py' in verify.stdout" + - "'plugins/modules/test_new_dir' in verify.stdout" + +# TODO: add a test for offline Galaxy signature metadata + +- name: install a collection that was signed by setup_collections + command: ansible-galaxy collection install namespace1.name1:1.0.0 + +- name: verify the installed collection with a detached signature + command: ansible-galaxy collection verify namespace1.name1:1.0.0 {{ galaxy_verbosity }} {{ signature_options }} + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + +- assert: + that: + - verify.rc == 0 + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: verify the installed collection with invalid detached signature + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv {{ signature_options }} + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.9-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + ignore_errors: yes + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- assert: + that: + - verify.rc != 0 + - '"Signature verification failed for ''namespace1.name1'' (return code 1)" in verify.stdout' + - expected_errors[0] in verify_stdout + - expected_errors[1] in verify_stdout + vars: + expected_errors: + - "* This is the counterpart to SUCCESS and used to indicate a program failure." + - "* The signature with the keyid has not been verified okay." + # Remove formatting from the reason so it's one line + verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: verify the installed collection with invalid detached signature offline + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv {{ signature_options }} --offline + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.9-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + ignore_errors: yes + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- assert: + that: + - verify.rc != 0 + - '"Signature verification failed for ''namespace1.name1'' (return code 1)" in verify.stdout' + - expected_errors[0] in verify_stdout + - expected_errors[1] in verify_stdout + vars: + expected_errors: + - "* This is the counterpart to SUCCESS and used to indicate a program failure." + - "* The signature with the keyid has not been verified okay." + # Remove formatting from the reason so it's one line + verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +- include_tasks: revoke_gpg_key.yml + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: verify the installed collection with a revoked detached signature + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv {{ signature_options }} + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + ignore_errors: yes + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- assert: + that: + - verify.rc != 0 + - '"Signature verification failed for ''namespace1.name1'' (return code 0)" in verify.stdout' + - expected_errors[0] in verify_stdout + - expected_errors[1] in verify_stdout + vars: + expected_errors: + - "* The used key has been revoked by its owner." + - "* The signature with the keyid is good, but the signature was made by a revoked key." + # Remove formatting from the reason so it's one line + verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +# This command is hardcoded with no verbosity purposefully to evaluate overall gpg failure +- name: verify that ignoring the signature error and no successful signatures is not successful verification + command: ansible-galaxy collection verify namespace1.name1:1.0.0 {{ signature_options }} + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + ignore_errors: yes + environment: + ANSIBLE_GALAXY_IGNORE_SIGNATURE_STATUS_CODES: REVKEYSIG,KEYREVOKED + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- assert: + that: + - verify.rc != 0 + - '"Signature verification failed for ''namespace1.name1'': fewer successful signatures than required" in verify.stdout' + - ignored_errors[0] not in verify_stdout + - ignored_errors[1] not in verify_stdout + vars: + ignored_errors: + - "* The used key has been revoked by its owner." + - "* The signature with the keyid is good, but the signature was made by a revoked key." + # Remove formatting from the reason so it's one line + verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +# This command is hardcoded with -vvvv purposefully to evaluate extra verbosity messages +- name: verify that ignoring the signature error and no successful signatures and required signature count all is successful verification + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv {{ signature_options }} + vars: + signature_options: "--signature {{ signature }} --keyring {{ keyring }}" + signature: "file://{{ gpg_homedir }}/namespace1-name1-1.0.0-MANIFEST.json.asc" + keyring: "{{ gpg_homedir }}/pubring.kbx" + register: verify + ignore_errors: yes + environment: + ANSIBLE_GALAXY_IGNORE_SIGNATURE_STATUS_CODES: REVKEYSIG,KEYREVOKED + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: all + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + +- assert: + that: + - verify is success + - verify.rc == 0 + - '"Signature verification failed for ''namespace1.name1'': fewer successful signatures than required" not in verify.stdout' + - success_messages[0] in verify_stdout + - success_messages[1] in verify_stdout + - ignored_errors[0] not in verify_stdout + - ignored_errors[1] not in verify_stdout + vars: + success_messages: + - "GnuPG signature verification succeeded, verifying contents of namespace1.name1:1.0.0" + - "Successfully verified that checksums for 'namespace1.name1:1.0.0' match the remote collection." + ignored_errors: + - "* The used key has been revoked by its owner." + - "* The signature with the keyid is good, but the signature was made by a revoked key." + # Remove formatting from the reason so it's one line + verify_stdout: "{{ verify.stdout | regex_replace('\"') | regex_replace('\\n') | regex_replace(' ', ' ') }}" + +- name: use lenient signature verification (default) without providing signatures + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx + environment: + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "1" + register: verify + ignore_errors: yes + +- assert: + that: + - verify is success + - verify.rc == 0 + - error_message not in verify.stdout + - success_messages[0] in verify.stdout + - success_messages[1] in verify.stdout + vars: + error_message: "Signature verification failed for 'namespace1.name1': fewer successful signatures than required" + success_messages: + - "GnuPG signature verification succeeded, verifying contents of namespace1.name1:1.0.0" + - "Successfully verified that checksums for 'namespace1.name1:1.0.0' match the remote collection." + +- name: use strict signature verification without providing signatures + command: ansible-galaxy collection verify namespace1.name1:1.0.0 -vvvv --keyring {{ gpg_homedir }}/pubring.kbx + environment: + ANSIBLE_GALAXY_REQUIRED_VALID_SIGNATURE_COUNT: "+1" + register: verify + ignore_errors: yes + +- assert: + that: + - verify is failed + - verify.rc == 1 + - '"Signature verification failed for ''namespace1.name1'': no successful signatures" in verify.stdout' + +- name: empty installed collections + file: + path: "{{ galaxy_dir }}/ansible_collections" + state: "{{ item }}" + loop: + - absent + - directory |