Last active
June 9, 2018 12:30
-
-
Save Turgon37/5b6e6553ef92dc65d1c7df3e1a1e290e to your computer and use it in GitHub Desktop.
A script to run basic tests on Ansible playbooks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Ansible role test shim. | |
# | |
# Usage: [OPTIONS] ./tests/test.sh | |
# - DISTRIBUTION: a supported Docker distro version (default = "centos7") | |
# - PLAYBOOK: a playbook in the tests directory (default = "test.yml") | |
# - CLEANUP: whether to remove the Docker container (default = true) | |
# - CONTAINER_ID: the --name to set for the container (default = timestamp) | |
# - DOCKER_OPTS: specific docker options | |
# - TEST_IDEMPOTENCE: whether to test playbook's idempotence (default = true) | |
# - ANSIBLE_DIFF: whether to enable diff option for Ansible | |
# | |
# Inspired by https://gist.githubusercontent.com/geerlingguy/73ef1e5ee45d8694570f334be385e181/ | |
# License: MIT | |
# Exit on any individual command failure. | |
set -e | |
# Pretty colors. | |
red='\033[0;31m' | |
green='\033[0;32m' | |
blue='\033[0;34m' | |
neutral='\033[0m' | |
timestamp=$(date +%s) | |
# Allow environment variables to override defaults. | |
DISTRIBUTION=${DISTRIBUTION:-"debian9"} | |
PLAYBOOK=${PLAYBOOK:-"test.yml"} | |
CLEANUP=${CLEANUP:-"true"} | |
CONTAINER_ID=${CONTAINER_ID:-$timestamp} | |
TEST_PLAYBOOK=${TEST_PLAYBOOK:-"true"} | |
TEST_IDEMPOTENCE=${TEST_IDEMPOTENCE:-"true"} | |
ANSIBLE_DIFF=${ANSIBLE_DIFF:-"false"} | |
ANSIBLE_VERBOSE=${ANSIBLE_VERBOSE:-"true"} | |
## Set up vars for Docker setup. | |
# CentOS 7 | |
if [ $DISTRIBUTION = 'centos7' ]; then | |
init="/usr/lib/systemd/systemd" | |
opts="--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro" | |
# Debian 9 | |
elif [ $DISTRIBUTION = 'debian9' ]; then | |
init="/lib/systemd/systemd" | |
opts="--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro" | |
# Debian 8 | |
elif [ $DISTRIBUTION = 'debian8' ]; then | |
init="/lib/systemd/systemd" | |
opts="--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro" | |
else | |
echo "Distribution $DISTRIBUTION is not supported, No any docker image is suitable to test it" | |
exit $(false) | |
fi | |
# Role local variables | |
role_name=role_under_test | |
real_role_name="$(basename $TRAVIS_REPO_SLUG |cut -d - -f 2-)" | |
# Docker local settings | |
image=turgon37/docker-$DISTRIBUTION-ansible | |
docker_volumes="--volume=$(pwd):/etc/ansible/roles/${role_name}:rw" | |
if [ "$role_name" != "$real_role_name" ]; then | |
docker_volumes="$docker_volumes --volume=$(pwd):/etc/ansible/roles/${real_role_name}:ro" | |
fi | |
# return the docker default environment variables | |
function docker_env() { | |
echo -n ' --env TERM=xterm' | |
} | |
# Ansible local settings | |
function ansible_options() { | |
if [ "x$ANSIBLE_DIFF" = 'xtrue' ]; then | |
echo -n ' --diff' | |
fi | |
if [ "x$ANSIBLE_VERBOSE" = 'xtrue' ]; then | |
echo -n ' --verbose' | |
fi | |
} | |
function ansible_env() { | |
echo -n ' --env ANSIBLE_FORCE_COLOR=1' | |
} | |
# | |
## Test requirements | |
# | |
# Run the container using the supplied OS. | |
function test_begin() { | |
printf "\n" | |
printf ${green}"Starting Docker container: $image."${neutral}"\n" | |
docker pull $image:latest | |
docker run --detach $docker_volumes --name "$CONTAINER_ID" $opts $DOCKER_OPTS $image:latest $init | |
echo $CONTAINER_ID > /tmp/container_id | |
if [ -n "$TEST_BEGIN_POST_COMMAND" ]; then | |
docker exec `docker_env` $CONTAINER_ID bash -c "$TEST_BEGIN_POST_COMMAND" | |
fi | |
} | |
# Remove the Docker container (if configured). | |
function test_end() { | |
printf "\n" | |
printf "Removing Docker container...\n" | |
docker rm -f $CONTAINER_ID | |
rm -f /tmp/container_id | |
} | |
# Show ansible version | |
function test_show_version() { | |
printf "\n" | |
printf ${green}"Print Ansible version."${neutral}"\n" | |
docker exec `docker_env` $CONTAINER_ID ansible --version | |
} | |
# Install requirements if `requirements.yml` is present. | |
function test_install_requirements() { | |
printf "\n" | |
local pip_requires=pip-requirements.txt | |
local ansible_requires=ansible-requirements.txt | |
if [[ -f tests/$pip_requires ]]; then | |
printf ${green}"PIP Requirements file detected; installing dependencies."${neutral}"\n" | |
docker exec `docker_env` $CONTAINER_ID pip install -r "/etc/ansible/roles/${role_name}/tests/${pip_requires}" | |
else | |
printf ${green}"No PIP requirements file detected."${neutral}"\n" | |
fi | |
if [[ -f tests/$ansible_requires ]]; then | |
printf ${green}"Ansible Requirements file detected; installing dependencies."${neutral}"\n" | |
docker exec `docker_env` $CONTAINER_ID ansible-galaxy install -r "/etc/ansible/roles/${role_name}/tests/${ansible_requires}" | |
else | |
printf ${green}"No ANSIBLE requirements file detected."${neutral}"\n" | |
fi | |
} | |
# | |
## Common roles tests | |
# | |
# Test Ansible syntax. | |
function test_check_syntax() { | |
printf "\n" | |
printf ${green}"Checking Ansible playbook syntax."${neutral} | |
docker exec `docker_env` $CONTAINER_ID ansible-playbook /etc/ansible/roles/role_under_test/tests/$PLAYBOOK --syntax-check | |
} | |
# Check ansible lint | |
function test_check_lint() { | |
printf "\n" | |
if command -v ansible-lint; then | |
printf ${green}"Checking Ansible coding style and lint."${neutral} | |
tips=$(/usr/bin/env ansible-lint --nocolor -p . || true) | |
echo "ansible-lint has detected '$(echo "$tips" | wc --lines)' improvable thing(s) in role" | |
fi | |
} | |
# Run Ansible playbook. | |
function test_run_playbook() { | |
local playbook="${1:-/etc/ansible/roles/role_under_test/tests/$PLAYBOOK}" | |
printf "\n" | |
printf ${green}"Running command: docker exec $docker_env $CONTAINER_ID ansible-playbook $playbook"${neutral} | |
docker exec `docker_env` `ansible_env` $CONTAINER_ID ansible-playbook $playbook `ansible_options` | |
} | |
# Run ansible playbook second time to | |
# check if playbook run without changes | |
function test_run_playbook_idempotence() { | |
# Run Ansible playbook again (idempotence test). | |
printf ${green}"Running playbook again: idempotence test"${neutral} | |
idempotence=$(mktemp) | |
docker exec `docker_env` `ansible_env` $CONTAINER_ID ansible-playbook /etc/ansible/roles/role_under_test/tests/$PLAYBOOK `ansible_options` | tee -a $idempotence | |
tail $idempotence \ | |
| grep --quiet 'changed=0.*failed=0' \ | |
&& (printf ${green}'Idempotence test: pass'${neutral}"\n") \ | |
|| (printf ${red}'Idempotence test: fail'${neutral}"\n" && exit 1) | |
} | |
# Ensure piped stdout equals a value | |
# param1: the expected value | |
function ensureEquals() { | |
local value="$(cat - )" | |
if [ "$value" = "$1" ]; then | |
printf ${green}"OK $value = $1"${neutral} | |
return $(true) | |
else | |
printf ${red}"NOK $value != $1"${neutral} | |
return $(false) | |
fi | |
} | |
# Ensure piped stdout contains the value | |
# param1: the value to check for | |
function ensureContains() { | |
local value="$(cat -)" | |
echo '### VALUE ###' | |
echo "$value" | |
echo '### VALUE ###' | |
if echo "$value" | grep --quiet "$1"; then | |
printf ${green}"OK value contains $1"${neutral} | |
return $(true) | |
else | |
printf ${red}"NOK value does not contains $1"${neutral} | |
return $(false) | |
fi | |
} | |
test_begin | |
test_show_version | |
test_install_requirements | |
test_check_syntax | |
test_check_lint | |
if [ "x$TEST_PLAYBOOK" = "xtrue" ]; then | |
test_run_playbook | |
fi | |
if [ "x$TEST_IDEMPOTENCE" = "xtrue" ]; then | |
test_run_playbook_idempotence | |
fi | |
if [ "x$CLEANUP" = "xtrue" ]; then | |
test_end | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment