Base Scripts for all AnsibleCI Containers
This repository has been archived on 2026-05-06. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
  • Shell 80.4%
  • Python 10.9%
  • Dockerfile 8.7%
Find a file
2018-09-22 00:03:52 +02:00
docs/images restructured project files 2018-08-19 13:17:27 +02:00
examples/default use different test for inspec to check for envvars 2018-09-18 01:40:29 +02:00
files introduced variables TARGETUSER and TARGETPASSWORD 2018-09-15 15:39:05 +02:00
scripts always execute after-inspec hook 2018-09-22 00:03:41 +02:00
.gitlab-ci.yml added clair test 2018-09-21 18:02:45 +00:00
Dockerfile test 2018-09-15 20:40:18 +02:00
README.md always execute after-inspec hook 2018-09-22 00:03:41 +02:00

AnsibleCI 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:

  1. [optional] Run the before-playbooks.sh hook script
  2. Run the Playbook on a fresh VM to test it runs without failures
  3. [optional] Run the between-playbooks.sh hook script
  4. Repeat the Playbook run on the same VM to test idempotency
  5. [optional] Run the after-playbooks.sh hook script
  6. [optional] Run Inspec to test the results of the Playbook execution
  7. [optional] Run the after-inspec.sh hook 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