Example 1: Run Ansible playbooks in cron

Run ssh-agent

ssh-agent is needed to provide the ssh connection plugin with the password to the private key, when used. The script below is executed by the command interpreter for login shells

 1shell> cat /home/admin/.profile
 2if [ -n "$BASH_VERSION" ]; then
 3    if [ -f "$HOME/.bashrc" ]; then
 4       . "$HOME/.bashrc"
 5    fi
 6    if [ -f "$HOME/.bashrc_ssh" ]; then
 7       . "$HOME/.bashrc_ssh"
 8    fi
 9fi
10if [ -d "$HOME/bin" ] ; then
11    PATH="$HOME/bin:$PATH"
12fi

and will start ssh-agent on login and prepare SSH_ENV (5)

 1shell> cat /home/admin/.bashrc_ssh
 2SSH_ENV="$HOME/.ssh/environment"
 3function start_agent {
 4    echo "Initializing new SSH agent..."
 5    /usr/bin/ssh-agent | sed 's/^echo/#echo/' > "${SSH_ENV}"
 6    echo succeeded
 7    chmod 600 "${SSH_ENV}"
 8    . "${SSH_ENV}" > /dev/null
 9    /usr/bin/ssh-add;
10}
11if [ -f "${SSH_ENV}" ]; then
12    . "${SSH_ENV}" > /dev/null
13    #ps ${SSH_AGENT_PID} doesn't work under cywgin
14    ps -ef | grep ${SSH_AGENT_PID} | grep ssh-agent$ > /dev/null || {
15        start_agent;
16    }
17else
18    start_agent;
19fi

Example of .ssh/environment created by ssh-agent

shell> cat /home/admin/.ssh/environment
SSH_AUTH_SOCK=/tmp/ssh-8fUkZ7qOzVPs/agent.5214; export SSH_AUTH_SOCK;
SSH_AGENT_PID=5216; export SSH_AGENT_PID;
#echo Agent pid 5216;

Run gpg-agent

Start gpg-agent manually by running gpg. gpg-agent is needed to provide gpg with the password to the private gpg key, when used. For example, to sign or encrypt emails, or to configure user’s passwords with help of the passwordstore. The configuration below enables gpg-agent also within a ssh session. In particular, no-grab (2) allows cut&paste, no-allow-external-cache (3) disables any keyrings and pinentry-curses (4) asks for the password in the terminal instead of default pinentry asking in the remote (in the case of ssh) desktop window. The time to live ttl (5,6) is set to 24 hours. This way, it’s not necessary to re-enter the password when the cron, which invokes the play with gpg-agent, is run daily.

1shell> cat ~/.gnupg/gpg-agent.conf
2no-grab
3no-allow-external-cache
4pinentry-program /usr/bin/pinentry-curses
5default-cache-ttl 86400
6max-cache-ttl 86400

Wrapper ansible-runner

Wrapper of ansible-runner will source .ssh/environment (42) and run the playbook from the project (44)

[arwrapper.bash]

 1#!/bin/bash
 2
 3# All rights reserved (c) 2020, Vladimir Botka <vbotka@gmail.com>
 4# Simplified BSD License, https://opensource.org/licenses/BSD-2-Clause
 5
 6version="1.0.0"
 7runner=$HOME/bin/ansible-runner
 8project=$PWD/$2
 9param=${3:-all.yml}
10usage="$(basename "$0") ver $version
11
12Usage:
13  $(basename "$0") <cmd> project [param]
14
15Where:
16  cmd ....... One of the commands: run, runid, stdout, custom, clean, test
17  project ... Private data directory. See ansible-runner.
18  param ..... Command specific parameter. See commands.
19
20Commands:
21  run project playbook.yml ......... Run playbook.yml in project
22  runid project playbook.yml ....... The same as run plus display artifact id
23  stdout project id ................ Display project/id/stdout
24  custom project id ................ Display custom stat from project/id/stdout
25  clean project .................... Delete project/artifacts
26  test project playbook.yml ........ Test playbook.yml in project
27
28Examples:
29  arwrapper run priv9 t9.yml ....... Run playbook t9.yml in priv9
30  arwrapper test priv9 t9.yml ...... Test playbook t9.yml in priv9
31  arwrapper clean priv9 ............ Delete pri9/artifacts
32  arwrapper stdout priv9 id1 ....... Display priv9/artifacts/id1/stdout
33  arwrapper custom priv9 id1........ Display custom stat from stdout\n"
34
35case "$1" in
36    test)
37	echo $(date '+%Y-%m-%d %H:%M:%S') $runner run $project -p $param
38	;;
39    run|runid)
40	echo $(date '+%Y-%m-%d %H:%M:%S') $0
41	if [ -f "$HOME/.ssh/environment" ]; then
42	    source $HOME/.ssh/environment
43	fi
44	$runner run $project -p $param
45	if [ "$1" = "runid" ]; then
46	    ls -t $project/artifacts | head -1
47	fi
48	;;
49    stdout)
50	cat $project/artifacts/$param/stdout
51	;;
52    custom)
53	cat $project/artifacts/$param/stdout | \
54        sed -n '/PLAY RECAP/,$p' | \
55        sed -n '/CUSTOM STATS/,$p' | \
56        tail -n +2 | \
57	cut -d ":" -f2-
58	;;
59    clean)
60	rm -rf $project/artifacts
61	;;
62    *)
63	printf "$usage\n"
64	exit 1
65	;;
66esac
67exit

Command for cron

The script below will use arwrapper.bash (5) to run the playbook pb-01.yml in the projects test_01, test_02, and test_03 (11-13). If the command (18) succeeds the script will print [OK] report (23). If you don’t want to receive email on success remove this line. Optionally enable/disable the cleaning of the artifacts (24).

 1shell> cat /home/admin/bin/ansible-cron-test.bash
 2#!/bin/bash
 3
 4cmd=$HOME/bin/arwrapper.bash
 5subcmd=${1:-run}
 6rc=0
 7
 8typeset -A projects
 9projects=(
10    [test_01]="pb-01.yml"
11    [test_02]="pb-01.yml"
12    [test_03]="pb-01.yml"
13)
14
15for project in "${!projects[@]}"; do
16    for playbook in ${projects[$project]}; do
17        out=$("$cmd" "$subcmd" "$project" "$playbook" 2>&1)
18        if [ "$?" -eq "0" ]; then
19            if [ "$subcmd" = "test" ]; then
20                printf '%s\n' "[DRY] $out"
21            fi
22            printf '%s\n' "[OK] "$project" "$playbook" PASSED"
23            $cmd clean $project
24        else
25            printf '%s\n' "[ERR] $out"
26            rc=1
27        fi
28    done
29 done
30 exit $rc

Crontab

Schedule the script in cron

shell> whoami
admin
cntrlr> crontab -l
MAILTO=admin
#Ansible: Ansible runner daily test
50 20 * * * $HOME/bin/ansible-cron-test.bash

See also

Email sent by cron

In our case the /etc/aliases redirect the emails for root to the user admin. Cron will report the result of the scpript ansible-cron-test.bash. If you want to receive email on a failure only remove the [OK] report from the script and optionally clean the artifacts. The artifacts will be available for a review if the script fails

Date: Tue,  7 Jul 2020 20:50:06 +0200 (CEST)
From: Cron Daemon <root@cntrlr.example.com>
To: admin@cntrlr.example.com
Subject: Cron <admin@cntrlr> $HOME/bin/ansible-cron-test.bash

[OK]  test_01 pb-01.yml PASSED
[OK]  test_02 pb-01.yml PASSED
[OK]  test_03 pb-01.yml PASSED

Project

Example of the project’s directory without the artifacts. The artifacts will be created by ansible-runner

shell> tree /home/admin/.ansible/runner/test_01
/home/admin/.ansible/runner/test_01
├── env
├── inventory
│   └── hosts
└── project
    ├── ansible.cfg
    ├── group_vars
    ├── host_vars
    └── pb-01.yml

Note

It’s necessary to provide ansible-playbook with the vault password if any data were encrypted. Use env/cmdline. For example,

shell> cat /home/admin/.ansible/runner/test_01/env/cmdline
--vault-password-file $HOME/.vault-psswd

See also

Playbook

Example of a playbook used in the test

shell> cat /home/admin/.ansible/runner/test_01/project/pb-01.yml
- hosts: test_01
  remote_user: admin
  gather_facts: false
  tasks:
    - debug:
        msg: TEST

Artifacts

Example of the project’s artifacts

shell> tree /home/admin/.ansible/runner/test_01/artifacts/
/home/admin/.ansible/runner/test_01/artifacts
└── aaa5d36e-e8d4-432a-ab52-b69062c85311
    ├── command
    ├── fact_cache
    ├── job_events
       ├── 1-2b5c9412-f0c4-45dc-a425-5c8c29e37ec0.json
       ├── 2-5ce0c5a2-1f02-cdab-8869-00000000001f.json
       ├── 3-5ce0c5a2-1f02-cdab-8869-000000000021.json
       ├── 4-28749e27-409a-46c4-9551-7ce80c02be83.json
       ├── 5-997d90c1-6357-45c6-8df9-437c2940c74e.json
       └── 6-6e41cf27-8c1e-4266-9ffb-8a54375bd4cc.json
    ├── rc
    ├── status
    └── stdout