- Shell 80.4%
- Python 10.9%
- Dockerfile 8.7%
| docs/images | ||
| examples/default | ||
| files | ||
| scripts | ||
| .gitlab-ci.yml | ||
| Dockerfile | ||
| README.md | ||
AnsibleCI
AnsibleCI (ACI) is testing your Ansible artifacts and could run everywhere where Docker does. It is meant to be used with Gitlab CI, such the following documentation primarily shows its usage with Gitlab CI. However you can adapt the examples to run ACI locally on your working machine or together with every CI/CD pipeline you want.
The scripts in this repository are not meant to be run alone but being used within Docker Images reflecting an Ansible target environment. Following Docker Images using this scripts:
- thomass/ansibleci-ubuntu1804
- thomass/ansibleci-archlinux
- thomass/ansibleci-centos7
However you are free to create your own Docker Images. Proceed to the chapter Custom Docker Test Images to learn more about good practices.
Enable Testing
Mainly you have to mount your Ansible artifacts into the test Docker container, tell the test script where the Ansible artifacts reside and start the test script. Following variables will tell the test script the location within the container:
PLAYBOOKDIR='/path/to/playbookdir' # default value: /playbook
PLAYBOOKFILE='my-playbook.yml' # playbook file inside the PLAYBOOKDIR; default value: site.yml
More variables to configure the test execution are:
- REQUIREMENTSDIR - directory where the Ansible Galaxy requirements file resides in; defaults to PLAYBOOKDIR
- REQUIREMENTSFILE - defaults to requirements.yml
- TARGETUSER - the SSH user for the target environment
- TARGETPASSWORD - the password for the TARGETUSER
To start testing the playbook simply run the scripts/run-tests.sh.
Testing Playbooks
With the previous knowledge in mind, testing your Ansible Playbooks with one of the Docker images told, is easy as following Gitlab CI job:
test:
stage: test
image: thomass/ansibleci-ubuntu1804
script: run-tests
Testing Roles
To enable role testing, the role must contain a folder called examples in the root of the role directory. This folder can contain an arbitrary number of subfolders containing playbooks. On the one hand those playbook folders are a documentation of the role itself and on the other hand the playbooks are instructions for ACI how to test the execution of the role. The playbook files itself must be named site.yml. The rest of the subfolders can contain any playbook content nescessary to execute the playbook.
For testing Roles you need to create test Playbooks within your Role folder. It is adviced to create an examles folder within your Roles folder where you organize your test Playbooks. Here is an example of a Role directory containing one test Playbook called default :
role
├── defaults
│ └── main.yml
├── examples
│ └── default
│ └── site.yml
└── tasks
└── main.yml
Here is an example of how the content of examples/default/site.yml could look like:
- name: example usage of my.apache
hosts: localhost
roles:
- role: my.apache
php_enabled: yes
As you can see this is a usual playbook content. You can use an arbitrary name for the hosts variable, as ACI will replace it temporarily with localhost during the test.
For testing your role with ACI you then just need to provide the path to your playbook:
test:
stage: test
image: thomass/ansibleci-ubuntu1804
variables:
PLAYBOOKDIR: examples/default
script: run-tests
The folder structure for test playbooks within your roles folder is just an advice, as you can provide any folder path to your playbook in the Gitlab CI job definition. Testing multiple playbooks within the examples folder needs multiple Gitlab CI jobs. However it seems to be a good practice to provide users of your role the test Playbooks as example how to use your role.
The Testplan
The testplan are the steps run during the test. The default testplan looks as follows:
- [optional] Run the
before-playbooks.shhook script - Run the Playbook on a fresh VM to test it runs without failures
- [optional] Run the
between-playbooks.shhook script - Repeat the Playbook run on the same VM to test idempotency
- [optional] Run the
after-playbooks.shhook script - [optional] Run Inspec to test the results of the Playbook execution
- [optional] Run the
after-inspec.shhook script
Hooks
Hooks are just bash scripts to be executed before/between/after the playbook executions. Those scripts must be stored under the relative path aci/hooks within your playbook directory. This applys to roles test playbooks as for usual playbooks. Those scripts must have following names:
playbook
├── aci
| └── hooks
| ├── after-inspec.sh
| ├── after-playbooks.sh
... ├── before-playbooks.sh
└── between-playbooks.sh
When executing tasks requiring root permissions within your hook scripts you can use sudo, as the user inside the Docker containers is not prompted for passwords.
InSpec testing
To enable InSpec testing your Playbook, the Playbook folder must contain contain a aci/inspec folder containing at leat one InSpec test file ending with _spec.rb. For example:
playbook
├── aci
| └── inspec
| └── default.rb
...
The inspec directory contains files with the InSpec instructions, e.g. as follows:
describe package('apache2') do
it { should be_installed }
end
describe service('apache2') do
it { should be_enabled }
it { should be_running }
end
describe port(80) do
it { should be_listening.with('tcp6') }
end
Further information of how to write InSpec tests see on the official website.
Create a custom Testplan
In the Chapter Configuring the testplan we have learned that ACI is executing a testplan. We are able to configure the testplan by inserting predefined hooks and adding InSpec tests. However we are also able to fully define our own testplan. All steps but the Linting can be defined inside the aci/testplan file in the playbook to test. The testplan is specific for each playbook, the linting is for the whole artifact. Thus linting is not part of the testplan and executed independently as first step.
If we would re-define the default testplan from the The Testplan chapter within the aci/testplan, it would have following appearance:
HOOK before-playbooks
ANSIBLE 'First Run' '' '--diff'
HOOK between-playbooks
ANSIBLE 'Repeated Run' 'IDEMPOTENCY' '--diff'
HOOK after-playbooks
INSPEC
HOOK after-inspec always
Every line within the aci/testplan file is a test step. Every test step can have parameters, which when having whitespaces must be enclosed in single quotes. Following step types are available:
| Step | param1 | param2 | param3 | description |
|---|---|---|---|---|
| HOOK | filename of hook inside aci/hooks directory without '.sh' |
Executes a hook script, which must be present inside the aci directory. The '.sh' postfix must not be given. |
||
| ANSIBLE | descriptive name of the step | grep pattern for success | Ansible options | Executes an ansible-playbook execution. The first parameter, the descriptive name of the run, is mandatory, the letter ones not. The second parameter can be a pattern which, if found with grep in the output of Ansible, means the step succeeds. An empty parameter means the Ansible run just should not fail. 'IDEMPOTENCY' is a special keyword, which creates a pattern for checking that no Ansible task has changed. The third parameter could be every possible Ansible option passed to the execution. |
| INSPEC | filename of specfile without | Executes InSpec. If no parameter is given, all files inside the inspec directory will be executed. If a parameter is given it would be point to the InSpec file to execute. |
Another (fictional) example for a testplan file could be following:
ANSIBLE 'First Run'
INSPEC installation.rb
HOOK create-data
ANSIBLE 'Do Backup' '' '--diff --tags backuprestore --extra-vars "dcb_do_backup=true"'
INSPEC backup.rb
HOOK destroy-data
ANSIBLE 'Do Restore' '' '--diff --tags backuprestore --extra-vars "dcb_do_restore=true"'
INSPEC restore.rb
Here we test the different behavior of the Ansible playbook depending on the --extra-vars. The first execution just deploys the playbook. The secend execution creates a backup and the third execution restores the backup. In between the state of the production is altered with hooks and tested with InSpec.
Custom Docker Test Images
Your Dockerfile has to have the following structure:
FROM thomass/ansibleci-base:latest as ansibleci-base
FROM ...
# ...
# your Dockerfile content here
# ...
COPY --from=ansibleci-base /ansibleci-base /ansibleci-base
RUN ln -s /ansibleci-base/scripts/run-tests.sh /usr/local/bin/run-tests && \
ln -s /ansibleci-base/ansible-plugins/human_log.py /usr/local/lib/python3.6/dist-packages/ansible/plugins/callback/human_log.py
CMD ["/ansibleci-base/scripts/start-docker.sh"]
The path of the Ansible callback folder differs between Linux distributions and ansible versions. For Arch Linux the path may be:
/usr/lib/python3.7/site-packages/ansible/plugins/callback/human_log.py
Your Dockerfile must not override the entrypoint or your custom entrypoint must be able to run shell commands passed as command. This is because Gitlab CI is running test commands against the container.
Within your Dockerfile you have to implement following mandatory requirements:
- Install:
- sudo
- Docker (just the client CLI is needed)
- Python 3
- Ansible (with pip; requires Python 3 setuptools)
- InSpec and docker-api (with Ruby Gems; requires Build Essentials)
- Add a user with passwordless sudo permissions
Your CI pipeline should contain following test playbook included into this project:
test-aci-base-playbook:
stage: test
variables:
PLAYBOOKDIR: '/ansibleci-base/examples/default'
image: "${CI_REGISTRY_IMAGE}:${CI_COMMIT_REF_SLUG}"
script: run-tests