diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9da32b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +roles diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bec7f9 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# ansible-role-refstack-client + +## About +It's an ansible playbook for running [refstack-client](https://opendev.org/osf/refstack-client). +It can be useful in automation where this role can be included in other playbooks. +The role is importable to [Infrared](https://github.com/redhat-openstack/infrared.git) as an infrared +plugin, so right after Infrared deploys an environment, this role can be executed to ensure the +deployment is working by passing refstack tests. + +## Required Role Variables +| Variable name | Required | Default | Type | Description | +|------------------------------|----------|---------------------------------------------------------------------|--------|---------------------------------------------------------------------------------------------------------| +| private_key_path | True if upload_results is True | None | String | Results are uploaded to the corresponding account. | +| source_admin_credentials | only if accounts_path not defined | None | String | File or command to be sourced for admin credentials. | +| source_credentials | True | None | String | File or command to be sourced: keystonerc_admin/openrc admin admin. | + +## Optional Role Variables + +| Variable name | Required | Default | Type | Description | +|------------------------------|----------|---------------------------------------------------------------------|--------|---------------------------------------------------------------------------------------------------------| +| accounts_path | False | None | String | Path to a tempest accounts file. | +| additional_tempestconf_params| False | None | String | Additional arguments to passed to discover-tempest-config tool. | +| deployer_input | False | None | String | Pat to a deployer input file. | +| dest_dir * | False | pwd | String | Local directory where the files will be stored. | +| download_artifacts | False | True | Bool | Whether artifacts should be downloaded to the host or not. | +| guideline | False | 2020.06 | String | Specific guideline | +| private_key_path_src * | False | None | String | If defined, the key defined by the param is copied to the targeted machine to private_key_path location.| +| refstack_client_source | False | ~/.refstack-client | String | Destination where refstack-client will be cloned. | +| server | False | https://refstack.openstack.org/api | String | Server url where results will be uploaded. | +| tempest_config_path | False | None | String | Destination of tempest configuration file to be used for running refstack tests. | +| tempest_tag | False | refstack-client's default | String | Tempest will be cloned and checkouted to this specific tag. | +| test_list | False | None | String | A path or an URL to a test list text file containing specific test cases. | +| upload_results | False | True | Bool | Whether results should be uploaded to a server or not. | +| url_cirros_image | False | http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img | String | Path or link to cirros image. | + +**\* it's a local path, the path on a machine, the playbook is executed from** + +## Example +To run the role from the repository: +``` +$ git clone https://github.com/kopecmartin/ansible-role-refstack-client.git +$ cd ansible-role-refstack-client +$ mkdir roles && ln -s $(pwd) roles/ansible-role-refstack-client +``` +Then create a `playbook.yaml`: +``` +--- +- hosts: localhost + vars: + source_credentials: ~/overcloudrc + source_admin_credentials: ~/overcloudrc_admin + private_key_path: ~/.ssh/id_rsa + roles: + - ansible-role-refstack-client +``` +And run it: +``` +$ ansible-playbook playbook.yaml +``` + + +## Usage with Infrared + +Run the following steps to run the plugin: +1. Install infrared and add ansible-role-refstack-client plugin by providing the url to this repo: + ``` + (infrared)$ ir plugin add https://github.com/kopecmartin/ansible-role-refstack-client.git --src-path infrared_plugin + ``` +2. You can verify that the plugin is imported by: + ``` + (infrared)$ ir plugin list + ``` +4. Run the plugin: + ``` + (infrared)$ ir ansible-role-refstack-client + ``` + +### Example +``` +(infrared)$ ir ansible-role-refstack-client \ + --source_credentials /home/stack/overcloudrc \ + --source_admin_credentials /home/stack/overcloudrc \ + --deployer_input /home/stack/ir-tempest-deployer-input.conf \ + --private_key_path /home/stack/refstack_key \ + --private_key_path_src $(pwd)/refstack_key +``` + +In the example above `tempest_config_path` is not defined, so `source_admin_credentials` are a required parameter +because the role will try to generate an `accounts.yaml` before generating a `tempest.conf`. diff --git a/defaults/main.yaml b/defaults/main.yaml new file mode 100644 index 0000000..d9f8409 --- /dev/null +++ b/defaults/main.yaml @@ -0,0 +1,10 @@ +server: "https://refstack.openstack.org/api" +refstack_client_source: "~/.refstack-client" +upload_results: True +download_artifacts: True +url_cirros_image: "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img" + +# Local directory where the files will be stored +dest_dir: "{{ lookup('env', 'PWD') }}" +# the latest guideline by default +guideline: "2020.06" diff --git a/infrared_plugin/main.yml b/infrared_plugin/main.yml new file mode 100644 index 0000000..766a9f7 --- /dev/null +++ b/infrared_plugin/main.yml @@ -0,0 +1,15 @@ +--- +# This file and plugin.spec are required by Infrared project + +- hosts: tester + tasks: + + - name: Remap infrared parameters to role variables + set_fact: + "{{ item.key }}": "{{ item.value }}" + with_dict: "{{ test }}" + + - name: ansible-role-refstack-client + include_role: + name: ansible-role-refstack-client + diff --git a/infrared_plugin/plugin.spec b/infrared_plugin/plugin.spec new file mode 100644 index 0000000..e1b79f5 --- /dev/null +++ b/infrared_plugin/plugin.spec @@ -0,0 +1,81 @@ +--- +# This file and main.yml are required by Infrared project +config: + plugin_type: test + entry_point: main.yml + roles_path: ../ +subparsers: + ansible-role-refstack-client: + description: OpenStack interoperability tests + include_groups: ["Ansible options", "Inventory", "Common options", "Answers file"] + groups: + - title: OpenStack Interoperability Tests + options: + accounts_path: + type: Value + help: | + Path to a tempest accounts file. + additional_tempestconf_params: + type: Value + help: | + Additional parameters to be passed to discover-tempest-config tool. + deployer_input: + type: Value + help: | + Path to a deployer-input file. + dest_dir: + type: Value + help: | + Local directory where the files will be stored. + download_artifacts: + type: Bool + help: | + Whether artifacts should be downloaded to the host machine or not. + guideline: + type: Value + help: | + Specific guideline. + private_key_path: + type: Value + help: | + If defined, results will be uploaded to the corresponding account. + private_key_path_src: + type: Value + help: | + If defined, the key is copied to the targeted machine to private_key_path location. + refstack_client_source: + type: Value + help: | + Destination where refstack-client will be cloned. + server: + type: Value + help: | + Server url where results will be uploaded. + source_credentials: + type: Value + help: | + File or command to be sourced: keystonerc_admin/openrc admin admin. + source_admin_credentials: + type: Value + help: | + File or command to be sourced: keystonerc_admin/openrc admin admin. + tempest_config_path: + type: Value + help: | + Destination of tempest configuration file to be used for running refstack tests. + tempest_tag: + type: Value + help: | + Tempest will be cloned and checkouted to this specific tag. + test_list: + type: Value + help: | + A path or an URL to a test list text file containing specific test cases. + upload_results: + type: Bool + help: | + Whether results should be uploaded to a server or not. + url_cirros_image: + type: Value + help: | + Path or link to cirros image. diff --git a/tasks/generate-accounts.yaml b/tasks/generate-accounts.yaml new file mode 100644 index 0000000..1dd3759 --- /dev/null +++ b/tasks/generate-accounts.yaml @@ -0,0 +1,54 @@ +--- +- name: Generate tempest.conf as admin + shell: | + set -ex + source .venv/bin/activate + source {{ source_admin_credentials }} + discover-tempest-config \ + --debug \ + -v \ + --create \ + --out ./etc/tempest.conf + args: + executable: /bin/bash + chdir: "{{ refstack_client_source }}" + when: additional_tempestconf_params is not defined + +- name: Generate tempest.conf as admin additional params + shell: | + set -ex + source .venv/bin/activate + source {{ source_admin_credentials }} + discover-tempest-config \ + --debug \ + -v \ + --create \ + --out ./etc/tempest.conf \ + {{ additional_tempestconf_params }} \ + image.http_image {{ url_cirros_image }} + args: + executable: /bin/bash + chdir: "{{ refstack_client_source }}" + when: additional_tempestconf_params is defined + +- name: Generate tempest accounts.yaml file + shell: | + set -ex + export PATH=$PATH:/usr/local/sbin:/usr/sbin + source .venv/bin/activate + source {{ source_admin_credentials }} + printenv + tempest account-generator \ + --config-file ./etc/tempest.conf \ + --concurrency 3 \ + ./etc/accounts.yaml + args: + executable: /bin/bash + chdir: "{{ refstack_client_source }}" + +- name: Cat generated accounts.yaml file + shell: | + cat ./etc/accounts.yaml + args: + executable: /bin/bash + chdir: "{{ refstack_client_source }}" diff --git a/tasks/generate-tempestconf.sh.j2 b/tasks/generate-tempestconf.sh.j2 new file mode 100644 index 0000000..49375da --- /dev/null +++ b/tasks/generate-tempestconf.sh.j2 @@ -0,0 +1,21 @@ +# Script to generate tempest.conf +set -ex +export PATH=$PATH:/usr/local/sbin:/usr/sbin +source {{ refstack_client_source }}/.venv/bin/activate +source {{ source_credentials }} +printenv +discover-tempest-config \ +--debug \ +-v \ +--non-admin \ +--test-accounts {{ path_to_accounts_file }} \ +{% if deployer_input is defined %} +--deployer-input {{ deployer_input }} \ +{% endif %} +validation.run_validation true \ +compute.allow_tenant_isolation true \ +compute-feature-enabled.resize true \ +{% if additional_tempestconf_params is defined %} +{{ additional_tempestconf_params }} \ +{% endif %} +image.http_image {{ url_cirros_image }} diff --git a/tasks/install-packages.yaml b/tasks/install-packages.yaml new file mode 100644 index 0000000..2bb31ab --- /dev/null +++ b/tasks/install-packages.yaml @@ -0,0 +1,49 @@ +--- +- name: Is python available + command: "python --version" + ignore_errors: true + register: python_is_available + changed_when: false + +- name: Install git + become: yes + package: + name: git + +- name: Install virtualenv + become: yes + package: + name: python-virtualenv + when: python_is_available.rc == 0 + +- name: Install virtualenv + become: yes + package: + name: python3-virtualenv + when: python_is_available.rc != 0 + +- name: Check if pip is already installed + command: "pip --version" + ignore_errors: true + register: pip_is_installed + changed_when: false + +- name: Check if pip3 is already installed + command: "pip3 --version" + ignore_errors: true + register: pip3_is_installed + changed_when: false + +- when: + - pip_is_installed.rc != 0 + - pip3_is_installed.rc != 0 + block: + - name: download get-pip.py + get_url: url=https://bootstrap.pypa.io/get-pip.py dest=/tmp + + - name: install pip + become: yes + command: "python /tmp/get-pip.py" + + - name: delete get-pip.py + file: state=absent path=/tmp/get-pip.py diff --git a/tasks/main.yaml b/tasks/main.yaml new file mode 100644 index 0000000..40928d0 --- /dev/null +++ b/tasks/main.yaml @@ -0,0 +1,193 @@ +--- +- name: Install required packages + include: install-packages.yaml + +- name: Clone refstack-client + git: + repo: 'https://github.com/openstack/refstack-client.git' + dest: "{{ refstack_client_source }}" + +- name: Look for python3 + command: "python3 --version" + ignore_errors: yes + register: python3_is_available + changed_when: false + +- name: Set python3 params for setup_env + set_fact: + python3_param: "-p 3" + when: python3_is_available.rc == 0 + +- name: Install refstack-client + command: ./setup_env {{ python3_param | default('') }} + args: + chdir: "{{ refstack_client_source }}" + when: tempest_tag is not defined + +- name: Install refstack-client and clone Tempest from specific tag + command: ./setup_env -t {{ tempest_tag }} {{ python3_param | default('') }} + args: + chdir: "{{ refstack_client_source }}" + when: tempest_tag is defined + +- name: Generate accounts.yaml file + include: generate-accounts.yaml + when: accounts_path is not defined + +- name: Set path to newly generated accounts.yaml + set_fact: + path_to_accounts_file: "{{ refstack_client_source }}/etc/accounts.yaml" + when: accounts_path is not defined + +- name: Set path to provided accounts.yaml + set_fact: + path_to_accounts_file: "{{ accounts_path }}" + when: accounts_path is defined + +- name: Generate tempest configuration script + template: + src: generate-tempestconf.sh.j2 + dest: "{{ refstack_client_source }}/generate-tempestconf.sh" + mode: 0744 + when: tempest_config_path is not defined + +- name: Generate tempest configuration file + shell: | + ./generate-tempestconf.sh + args: + chdir: "{{ refstack_client_source }}" + executable: /bin/bash + when: tempest_config_path is not defined + +- name: Set path to newly generated tempest.conf + set_fact: + path_to_tempest_config: "{{ refstack_client_source }}/etc/tempest.conf" + when: tempest_config_path is not defined + +- name: Set path to provided tempest.conf + set_fact: + path_to_tempest_config: "{{ tempest_config_path }}" + when: tempest_config_path is defined + +- name: Validate setup + shell: | + set -ex + source .venv/bin/activate + refstack-client test -c {{ path_to_tempest_config }} \ + -v \ + -- \ + --regex tempest.api.identity.v3.test_tokens.TokensV3Test.test_create_token + args: + chdir: "{{ refstack_client_source }}" + executable: /bin/bash + +- name: Run tests with the defined test list + shell: | + set -ex + export PATH=$PATH:/usr/local/sbin:/usr/sbin + source .venv/bin/activate + printenv + refstack-client test \ + -c {{ path_to_tempest_config }} \ + -v \ + --test-list {{ test_list }} + args: + chdir: "{{ refstack_client_source }}" + executable: /bin/bash + register: refstack_result + ignore_errors: yes + when: test_list is defined + +- name: Run tests with the default test list + shell: | + set -ex + export PATH=$PATH:/usr/local/sbin:/usr/sbin + source .venv/bin/activate + printenv + refstack-client test \ + -c {{ path_to_tempest_config }} \ + -v \ + --test-list "https://refstack.openstack.org/api/v1/guidelines/{{ guideline }}/tests?target=platform&type=required&alias=true&flag=false" + args: + chdir: "{{ refstack_client_source }}" + executable: /bin/bash + register: refstack_result + ignore_errors: yes + when: test_list is not defined + +- name: Find the test result json file + shell: | + set -ex + ls | grep "\.json" | tail -1 + register: ls_out + args: + chdir: "{{ refstack_client_source }}/.tempest/.stestr" + executable: /bin/bash + +- name: Copy private key + copy: + src: "{{ private_key_path_src }}" + dest: "{{ private_key_path }}" + when: + - upload_results | bool + - private_key_path is defined + - private_key_path_src is defined + +- name: Upload results with signature + shell: | + set -ex + source .venv/bin/activate + yes | refstack-client upload ".tempest/.stestr/{{ ls_out.stdout }}" \ + --url {{ server }} \ + -i {{ private_key_path }} + register: upload_out + args: + chdir: "{{ refstack_client_source }}" + executable: /bin/bash + when: + - upload_results | bool + - private_key_path is defined + +- block: + - name: Download results file in .json + fetch: + src: "{{ refstack_client_source }}/.tempest/.stestr/{{ ls_out.stdout }}" + dest: "{{ dest_dir }}/test_results.json" + flat: yes + + - name: Download results file in subunit + fetch: + src: "{{ refstack_client_source }}/.tempest/.stestr/{{ ls_out.stdout | splitext | first }}" + dest: "{{ dest_dir }}/test_results_subunit" + flat: yes + + - debug: + msg: "{{ upload_out.stdout }}" + + - name: Dump output of upload command + copy: + content: "{{ upload_out.stdout }}" + dest: "{{ dest_dir }}/upload_output.txt" + delegate_to: localhost + + - name: Download tempest.conf file + fetch: + src: "{{ path_to_tempest_config }}" + dest: "{{ dest_dir }}/tempest.conf" + flat: yes + args: + chdir: "{{ refstack_client_source }}" + + - name: Download accounts.yaml file + fetch: + src: "{{ path_to_accounts_file }}" + dest: "{{ dest_dir }}/accounts.yaml" + flat: yes + args: + chdir: "{{ refstack_client_source }}" + when: download_artifacts | bool + +- name: Check if we passed refstack tests + fail: + msg: "Refstack tests failed." + when: refstack_result.rc > 0