Using Test Kitchen with Docker and serverspec to test Ansible roles

ansible_logo_black_square

Lets write some tests for our Ansible roles. When we are testing our roles, we can validate the quality of the role. With this blog item we are using the test kitchen framework and serverspec for the Ansible role: dj-wasabi.zabbix-agent.

With test kitchen we can start an vagrant box or an docker image and our Ansible role will be executed on this instance. There is an whole list of Test Kitchen drivers which can be found here. (If you have an more recent up2date list, please let me know and I update the link). When the Ansible role is installed, serverspec will be executed so we can verify if the installation and configuration is done correctly. Ideally you want to execute this every time when an change is done for the Role, so the best way is to do everything with Jenkins.

We will make use of the following tools:

  • Test Kitchen
  • docker
  • Serverspec

Installation of Jenkins is out of scope for this blog item, same as installation of docker. You’ll need to check these websites for installing Jenkins and  docker on your machine.

Before we even continue, we need to install test kitchen. We do this with the following command:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:02:01 - Thu Aug 20)
 (master) > gem install test-kitchen

This is easy, it only take around 10 seconds to install. We continue with the test kitchen setup by executing the next command:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:03:05 - Thu Aug 20)
 (master) > kitchen init --create-gemfile --driver=kitchen-docker
      create  .kitchen.yml
      create  chefignore
      create  test/integration/default
      create  .gitignore
      append  .gitignore
      append  .gitignore
      create  Gemfile
      append  Gemfile
      append  Gemfile
You must run `bundle install' to fetch any new gems.

The command creates some files and directories at default which we almost all will be using. We remove the chefignore file, as we don’t use chef in this case.

We update the Gemfile by adding the next line at the end of the file:

gem "kitchen-ansible"

Now we run the “bundle install” command, it will install the kitchen-docker and kitchen-ansible gems with their dependencies.

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:03:55 - Thu Aug 20)
 (master) > bundle install
Using multipart-post 2.0.0
Using faraday 0.9.1
Using highline 1.7.3
Using thor 0.19.1
Using librarian 0.1.2
Using librarian-ansible 1.0.6
Using mixlib-shellout 2.1.0
Using net-ssh 2.9.2
Using net-scp 1.2.1
Using safe_yaml 1.0.4
Using test-kitchen 1.4.2
Using kitchen-ansible 0.0.23
Using kitchen-docker 2.3.0
Using bundler 1.6.2
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:03:59 - Thu Aug 20)
 (master) > 

We can also set some version restrictions in this file, an example looks like this:

gem 'test-kitchen', '>= 1.4.0'
gem 'kitchen-docker', '>= 2.3.0'
gem 'kitchen-ansible'

With the above example, you’ll install test-kitchen with version 1.4.0 or higher and version 2.3.0 or higher for kitchen-docker. But for now, we use it without the versions.

We have an basic .kitchen.yml file, like this:

---
driver:
  name: docker

provisioner:
  name: chef_solo

platforms:
  - name: ubuntu-14.04
  - name: centos-7.1

suites:
  - name: default
    run_list:
    attributes:

We are changing the file, so it will look like this:

---
driver:
  name: docker
  provision_command: sed -i '/tsflags=nodocs/d' /etc/yum.conf

provisioner:
  name: ansible_playbook
#  ansible_yum_repo: "http://mirror.logol.ru/epel/6/x86_64/epel-release-6-8.noarch.rpm"
  hosts: localhost
#  requirements_path: requirements.yml

platforms:
  - name: centos-6.6

verifier:
  ruby_bindir: '/usr/bin'

suites:
  - name: default

What does it do:

We use the docker driver to run our playbook and tests. It will start an docker image and executes the playbook and after this, we execute serverspec tests to validate of everything should work as expected.

We use the “ansible_playbook” as provisioner. I have 2 lines commented in this .kitchen.yml file. My Ansible role doesn’t have any dependencies. With the “ansible_yum_repo” we can point it to for example the epel-release.rpm file. When the docker image is started, it will download and install this epel repository file so if the role needs some packages from Epel, it will succeed. Same as for the “requirements_path”. This is an yml file which can be used for downloading the role dependencies.

The Zabbix Agent role doesn’t have any dependencies, but for the Zabbix Server role, it has 3 dependencies. You could use it like this:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:07:22 - Thu Aug 20)
 (master) > cat requirements.yml
---
- src: geerlingguy.apache
- src: geerlingguy.mysql
- src: galaxyprojectdotorg.postgresql

There is only 1 platform in this test, centos-6.6. Same as for the “suites” there is only one. You can  specify more platforms and suits. The following example is the “suites” part of the dj-wasabi.zabbix-server role:

suites:
  - name: zabbix-server-mysql
    provisioner:
        name: ansible_playbook
        playbook: test/integration/zabbix-server-mysql.yml
  - name: zabbix-server-pgsql
    provisioner:
        name: ansible_playbook
        playbook: test/integration/zabbix-server-pgsql.yml

There are 2 suits with their own playbooks. In the above case, there is an playbook which will be executed with the “MySQL” as backend and there is an playbook with the “PostgreSQL” as backend. Both playbooks will be executed in their own docker instance. So it will start with for example the  ‘zabbix-server-mysql’ suits and when this is finished successfully, it continues with the suit ‘zabbix-server-pgsql’.

You can find on these pages some more information on the configuration of test kitchen, ansible and docker parts:

We now create our only playbook:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:07:25 - Thu Aug 20)
 (master) > vi test/integration/default.ym
--- 
- hosts: localhost
  roles:
    - role: ansible-zabbix-agent

We have configured the playbook which will be executed against the docker image on ‘localhost’.

But we are not there yet, we will need create some serverspec tests to. With these tests we validate if the execution of the Ansible role went successful.

First we have to create the following directory: test/integration/default/serverspec/localhost

We create the spec_helper.rb file:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:07:41 - Thu Aug 20)
 (master) > vi test/integration/default/serverspec/spec_helper.rb
require 'serverspec'
set :backend, :exec

We will create the serverspec file:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:08:22 - Thu Aug 20)
 (master) > vi test/integration/default/serverspec/localhost/ansible_zabbix_agent_spec.rb

The name is not that important, but it has to end with: _spec.rb. We add the following content to the file:

require 'serverspec'
require 'spec_helper'

describe 'Zabbix Agent Packages' do
    describe package('zabbix-agent') do
        it { should be_installed }
    end
end

describe 'Zabbix Agent Configuration' do
    describe file('/etc/zabbix/zabbix_agent.conf') do
        it { should be_file}
        it { should be_owned_by 'zabbix'}
        it { should be_grouped_into 'zabbix'}
    end
end

With the first “describe” we print ‘Zabbix Agent Packages’ and this is an block. When you have some rspec experience with Puppet, you would for example add the params like this: let(:params) { {:server => ‘192.168.1.1’} }
In our case, this is nothing more than printing the text.

Now we proceed with the 2nd describe: package(‘zabbix-agent’). All actions till the firsy “end” is related to this package. For the package we only have 1: it should be_installed. So, when this spec file is executed it will check if the ‘zabbix-agent’ package is installed. If not, you’ll see an error message.

We proceed with the 4th describe, file(‘/etc/zabbix/zabbix_agent.conf’). We have several checks for this file:

  • It Should be an file. (You could also choose for link, directory or even device)
  • The owner of the file needs to be user zabbix
  • The group of the file needs to be group zabbix

There are a lot of other options and checks to use in your spec file, but if we explain it here this is really gonna be an long post.

Only thing that we need to do is to run kitchen. So we execute the following command:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:09:13 - Thu Aug 20)
 (master) > kitchen test

If everything goes fine, you’ll see a lot of output. At the end of the run, serverspec will be executed:

       Zabbix Agent Packages
         Package "zabbix-agent"
           should be installed
      
       Zabbix Agent Configuration
         File "/etc/zabbix/zabbix_agent.conf"
           should be file (FAILED - 1)
           should be owned by "zabbix" (FAILED - 2)
           should be grouped into "zabbix" (FAILED - 3)
      
       Failures:
      
         1) Zabbix Agent Configuration File "/etc/zabbix/zabbix_agent.conf" should be file
            Failure/Error: it { should be_file}
              expected `File "/etc/zabbix/zabbix_agent.conf".file?` to return true, got false
              /bin/sh -c test\ -f\ /etc/zabbix/zabbix_agent.conf
             
            # /tmp/verifier/suites/serverspec/localhost/ansible_zabbix_agent_spec.rb:12:in `block (3 levels) in <top (required)>
      
         2) Zabbix Agent Configuration File "/etc/zabbix/zabbix_agent.conf" should be owned by "zabbix"
            Failure/Error: it { should be_owned_by 'zabbix'}
              expected `File "/etc/zabbix/zabbix_agent.conf".owned_by?("zabbix")` to return true, got false
              /bin/sh -c stat\ -c\ \%U\ /etc/zabbix/zabbix_agent.conf\ \|\ grep\ --\ \\\^zabbix\\\$
             
            # /tmp/verifier/suites/serverspec/localhost/ansible_zabbix_agent_spec.rb:13:in `block (3 levels) in <top (required)>'
      
         3) Zabbix Agent Configuration File "/etc/zabbix/zabbix_agent.conf" should be grouped into "zabbix"
            Failure/Error: it { should be_grouped_into 'zabbix'}
              expected `File "/etc/zabbix/zabbix_agent.conf".grouped_into?("zabbix")` to return true, got false
              /bin/sh -c stat\ -c\ \%G\ /etc/zabbix/zabbix_agent.conf\ \|\ grep\ --\ \\\^zabbix\\\$
             
            # /tmp/verifier/suites/serverspec/localhost/ansible_zabbix_agent_spec.rb:14:in `block (3 levels) in <top (required)>'
      
       Finished in 0.11823 seconds (files took 0.37248 seconds to load)
       4 examples, 3 failures

Whoops, it seems that my serverspec file was expecting something else. I made an typo, the file should be /etc/zabbix/zabbix_agentd.conf with an d! 🙂

We can see the docker image is created with the “kitchen list” command, the “Last Action” is “Set Up”:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:14:51- Thu Aug 20)
 (master) > kitchen list
Instance          Driver  Provisioner      Verifier  Transport  Last Action
default-centos-66  Docker  AnsiblePlaybook  Busser    Ssh        Set Up

There are 2 ways to proceed when we fix the typo:

  • We run ‘kitchen test’ again, but it will destroy our docker image and starts again from the start.
  • We run “kitchen verify’ and we only run the serverspec tests. (A lot quicker!)

We use the “kitchen verify” command:

wdijkerman@curiosity [ ~/git/ansible/ansible-zabbix-agent ] (13:15:12 - Thu Aug 20)
 (master) >  kitchen verify
-----> Starting Kitchen (v1.4.2)
-----> Verifying <default-centos-66>...
$$$$$$ Running legacy verify for 'Docker' Driver
       Preparing files for transfer
       Removing /tmp/verifier/suites/serverspec
       Transferring files to <default-centos-66>
-----> Running serverspec test suite
       /opt/rh/ruby193/root/usr/bin/ruby -I/tmp/verifier/suites/serverspec -I/tmp/verifier/gems/gems/rspec-support-3.3.0/lib:/tmp/verifier/gems/gems/rspec-core-3.3.2/lib /tmp/verifier/gems/bin/rspec --pattern /tmp/verifier/suites/serverspec/\*\*/\*_spec.rb --color --format documentation --default-path /tmp/verifier/suites/serverspec
      
       Zabbix Agent Packages
         Package "zabbix-agent"
           should be installed
      
       Zabbix Agent Configuration
         File "/etc/zabbix/zabbix_agentd.conf"
           should be file
           should be owned by "zabbix"
           should be grouped into "zabbix"
      
       Finished in 0.10183 seconds (files took 0.33263 seconds to load)
       4 examples, 0 failures
      
       Finished verifying <default-centos-66> (0m1.12s).
-----> Kitchen is finished. (0m1.17s)

As you see, we only run the serverspec tests and everything is ok. 4 examples with 0 failures.
We can continue with creating serverspec tests and rerun the “kitchen verify” command till we are satisfied.

In theory, you’ll create the tests first before creating the playbook. With practice ….

At the end when you are ready, you’ll create an Jenkins job which pulls for changes from your git repository. You’ll create an job which has 2 “Execute shells” steps:

  • Install bundler and test-kitchen and run bundle install
  • Execute kitchen test

Installation of the gem step:s:

gem install bundler --no-rdoc --no-ri
gem install test-kitchen --no-rdoc --no-ri
bundle install

And 2nd build step:

kitchen test

Whoot! 🙂

Advertisements

Using Librarian-Ansible to install Ansible roles from Gitlab

ansible_logo_black_square

I have some Ansible roles which I try to keep up2date and these are on Github and on my personal Gitlab instance. Sometimes this takes a little bit longer that I want to, but other projects needs some attention to.

For my own personal environment, I use Ansible too and this is in an seperate git repository of my Gitlab server (Repository: environment/ansible.git). There is one thing that buggers me: My Ansible roles differs from the one used in my personal Ansible setup. At moment of writing, the ‘dj-wasabi.zabbix-agent’ role is at tag 0.2.1, but I use ‘0.0.2’ in my own Ansible setup (Oh, really that old?? 🙂 ).

There should be an solution for this. But before we continue, the solution should met my goals:

  • All Ansible roles should have their own git repository in Gitlab,
  • All Ansible roles have their own Jenkins job, documentation and test cases,
  • I want to make use of tags or versions.

With this I can create specific tags/version of the role and we can run some tests via Jenkins like ‘testkitchen’. With ‘testkitchen’ we run the role on an vagrant/docker and see if everything runs fine. But for know, ‘testkitchen’ is out of scope for this.

I first looked at ‘ansible-galaxy’. It has the possibility for using an ‘requirements.yml’ file which holds all information. Like location and even an version, so we can specify the correct role. After some testing it only work when you have the repository at Github.com.
Also the repository should exists on the Galaxy itself. So for the Zabbix roles this could work, but I also have some roles created just for my own environment. These are specific and there is no need to upload them to the Galaxy or github, so the ‘ansible-galaxy’ will not work for me.

I found “librarian-ansible’. This could be something which might work for me, but didn’t found information on the web. Yes, I did found something that the Ansible creator Michael DeHaan isn’t an very big fan of this (https://groups.google.com/forum/#!msg/ansible-project/TawjChwaV08/3p6Zv24rMWgJ). So lets try it anyways, maybe it creates a fan out of me ;-).

Installation is very simple, we have to install 1 gem:

wdijkerman@curiosity [ ~/git/environment/ansible ] (14:03:56 - Sat Aug 15)
 (master) > sudo gem install librarian-ansible
Successfully installed librarian-ansible-1.0.6
1 gem installed

Now we have to create the Ansiblefile. The Ansiblefile is used for declaring the roles and where these roles can be found. We can do this with the following command:

wdijkerman@curiosity [ ~/git/environment/ansible ] (14:24:19 - Sat Aug 15)
 (master) > librarian-ansible init
      create  Ansiblefile

It creates the “Ansiblefile” in the current directory. It already has some basic roles specified, but I don’t use them.

#!/usr/bin/env ruby
#^syntax detection

site "https://galaxy.ansible.com/api/v1";

role "kunik.deploy-upstart-scripts";

role "pgolm.ansible-playbook-monit",
  github: "pgolm/ansible-playbook-monit";

With the default Ansiblefile it shows you, that you also can make use of the Ansible Galaxy. The site is configured to use Ansible Galaxy API and when you only have specified the “role” (In this case kunik.deploy-upstart-scripts”, it will be downloaded from the Galaxy.

2nd example is downloding the git repository “pgolm/ansible-playbook-monit”, which will be installed with the role name “pgolm.ansible-playbook-monit”. Nice, but I only need to make use of the “git” option. I start with the following:

#!/usr/bin/env ruby
#^syntax detection

role "zabbix-javagateway",
    git: "git@gitlab.dj-wasabi.local:ansible/zabbix-javagateway.git",
    ref: "0.1.0"

When I run “librarian-ansible install’ it will clone the git repository and checkouts the tag “0.1.0”. The role is now installed in the ‘ librarian_roles/’ directory with the name “zabbix-javagateway”. But I want it in my roles directory, so I have to run the ‘librarian-ansible’ command again, but with the config option:

librarian-ansible config path roles --global

This sets the path to my “roles” directory. This is specified in my ansible.cfg and I want them in this directory. So running again the ‘librarian-ansible install’ command and the role is installed again. But, what I didn’t know (or didn’t read in the very few sites that exists about librarian-ansible) is it will delete the content of the directory. So all my roles which were in the ‘roles/’ directory are deleted. So, ‘git checkout roles/’ and moving all roles to their own git repository and start again! 🙂

Maybe add the “roles” directory in my .Gitignore file. We don’t want to store all the roles in this repository too.

I think I’m going to be an fan for librarian-ansible. 🙂

 

Installing zabbix-agent with Ansible

ansible_logo_black_squarezabbix_logo

Not only I have an puppet module for installing Zabbix, I also have some Ansible roles for this. At the moment there are 4 roles:

In this blog item, we talk about the “zabbix-agent” role. The latest version is 0.2.0.

Defaults

Installing this role is very easy:


ansible-galaxy install dj-wasabi.zabbix-agent

It will be installed in your roles directory. Default is “/etc/ansible/roles” or whatever you have configured in the ansible.cfg file. After installation there is only 1 (or 2 when you make use of active items) parameters needed for making this role work:

agent_server: <IP_-_FQDN_OF_ZABBIX_SERVER>
agent_serveractive: <IP_-_FQDN_OF_ZABBIX_SERVER>

This will need the ip address or the FQDN of the “zabbix-server”.

OS?

This role works on several operating systems/families:

  • RedHat
  • Debian
  • Ubuntu
  • OpenSuse

If you have an operating system/family which isn’t in the list above, you can create an issue at the Github page and please fill in the request. I can’t make any guarantee that it will come, but I can try it. Or if you do have some Ansible skills, please create an Pull request and I would be happy to accept it. 🙂

Playbook

So, how does the playbook looks like? Like this:

- hosts: all
  sudo: yes
  roles:
   - role: dj-wasabi.zabbix-agent
     agent_server: <IP_-_FQDN_OF_ZABBIX_SERVER>
     agent_serveractive: <IP_-_FQDN_OF_ZABBIX_SERVER>

As you see it is very basic and does the job very good. This only installs the agent on the specific server and configures the configuration file. But we really want to automate everything right?

Cove

Few weeks ago I found this pull requests for the “ansible-modules-extra” repository. This pull requests had an few ansible modules which made sure that you can use the Zabbix API to create or update hosts configuration. In the pull requests there were something like 5 modules, but this Ansible role only use 3 of them. With this role, you can create the following:

  • host groups
  • Host itself.
  • Macros for the host

For now, when the host is created, it will only create the “zabbix interface”. Maybe with the next release I’ll make sure you can also create SNMP, JMX and IPMI interfaces.

How do we have to configure it? Something like this. You will have to change it to your environment.

- hosts: wdserver00
  roles:
     - role: zabbix-agent
       agent_server: 192.168.1.1
       agent_serveractive: 192.168.1.1
       zabbix_url: http://zabbix.example.com
       zabbix_api_use: true
       zabbix_api_user: Admin
       zabbix_api_pass: Zabbix
       zabbix_create_host: present
       zabbix_host_groups:
         - Linux servers
       zabbix_link_templates:
         - Template OS Linux
       zabbix_macros:
         - macro_key: apache_type
           macro_value: reverse_proxy

I’ll skip the first 2 parameters, as these are described earlier on this page.

zabbix_url: The url on which the Zabbix web interface is available.
zabbix_api_user: The username which will connect to the API.
zabbix_api_pass: The password for the “zabbix_api_user” user.
zabbix_create_host: present if we want to create the host, absent if we want to delete it.
zabbix_host_groups: List of hostgroup where this host belongs to.
zabbix_link_templates: List of templates which will be linked to the host.
zabbix_macros: key, value pair of macros that will be used by the host. 

When we run Ansible, we will see at the end of the run:

.. <skip> ..
TASK: [zabbix-agent | Create hostgroups] **************************************
ok: [wdserver00 -> 127.0.0.1]

TASK: [zabbix-agent | Create a new host or update an existing host's info] ****
changed: [wdserver00 -> 127.0.0.1]

TASK: [zabbix-agent | Updating host configuration with macros] ****************
changed: [wdserver00 -> 127.0.0.1] => (item={'macro_key': 'apache_type', 'macro_value': 'reverse_proxy'})

Nice! If you check the Web interface, you’ll see that the host is created with the correct host groups and templates. If not, you’ll see some error messages in the Ansible output which will say what went wrong.

This role isn’t perfect, so if you encounter an bug or found/have and enhancement, please create an Pull request at Github and I’ll accept it. We can all make this role beter. 🙂

Side note:

There are more parameters which can be overridden, please check the “defaults/main.yml” file or the README.

Ansible executing puppet agent

ansible_logo_black_squarepuppet

I manage my own environment with Ansible, which is really great! This yaml format describing what you want to do is easy to read, understand and even easy to maintain. If you can automate an specific action or just simply executing commands one by one, you can do it with Ansible.

So in my own home environment, I have to execute the puppet agent command a few times. My CI for the wdijkerman-zabbix environment consists of a few steps. One of those steps is executing the puppet agent command on a specific host. (Maybe I will describe my CI process in an blog item later.. 🙂 )

When you try to combine them, you’ll notice that every ansible run for executing the puppet agent command fails. (No worries, I was there before .. 🙂 ) When an puppet agent runs, it ends with different exit codes. Normally when an script, program or commands ends successfully, it has an exit code of 0. Ansible uses this to determine if an action is ok, changed or failed. But puppet uses it slightly different.

According to the puppet agent man page (click):

Provide transaction information via exit codes. If this is enabled, an exit code of ‘2’ means there were changes, an exit code of ‘4’ means there were failures during the transaction, and an exit code of ‘6’ means there were both changes and failures.

With this in mind, we now have the following 2 tasks in Ansible:

  - name: "Start puppet agent"
    shell: /usr/bin/puppet agent --test --verbose --detailed-exitcodes
    register: puppet_agent
    changed_when: puppet_agent.rc == 2
    failed_when: puppet_agent.rc != 2 and puppet_agent.rc != 0

  - name: "puppet output"
    debug: var=puppet_agent.stdout_lines
    when: puppet_agent|failed

The first task is the most important one. We register an variable, which will be used in this task for checking exit codes. We let Ansible know that if the exit code of the puppet agent command is an 2, the task will be “changed”. If it is something other than 0 or 2, it is failed. Thats all!

The 2nd task is actually only showing us some information when the first task is failed. I only want to see the output when the puppet agent run fails for some reason. You don’t have to use this task, as this only prints some information.

Output of the Ansible playbook when everything is ok:

[puppet-zabbix-nightly-provision] $ /bin/sh -xe /tmp/hudson5840383976762038524.sh
+ cd /opt/jenkins/environment-ansible
+ ansible-playbook -i hosts -l vserver-142 playbook/puppet-run.yml

PLAY [vserver-142] ************************************************************ 

GATHERING FACTS ***************************************************************
ok: [vserver-142]

TASK: [Start puppet agent] ****************************************************
changed: [vserver-142]

TASK: [puppet output] *********************************************************
skipping: [vserver-142]

PLAY RECAP ********************************************************************
vserver-142                : ok=2    changed=1    unreachable=0    failed=0   

[puppet-zabbix-nightly-provision] $

Everything looks good, like I suspected. Now an example when something goes wrong:

[puppet-zabbix-nightly-provision] $ /bin/sh -xe /tmp/hudson1324121987798922302.sh
+ cd /opt/jenkins/environment-ansible
+ ansible-playbook -i hosts -l vserver-142 playbook/puppet-run.yml

PLAY [vserver-142] ************************************************************ 

GATHERING FACTS ***************************************************************
ok: [vserver-142]

TASK: [Start puppet agent] ****************************************************
failed: [vserver-142] =&gt; {&quot;changed&quot;: false, &quot;cmd&quot;: &quot;/usr/bin/puppet agent --test --verbose --detailed-exitcodes&quot;, &quot;delta&quot;: &quot;0:00:04.745918&quot;, &quot;end&quot;: &quot;2015-01-31 15:08:06.708110&quot;, &quot;failed&quot;: true, &quot;failed_when_result&quot;: true, &quot;rc&quot;: 1, &quot;start&quot;: &quot;2015-01-31 15:08:01.962192&quot;, &quot;stdout_lines&quot;: [&quot;\u001b[0;32mInfo: Retrieving pluginfacts\u001b[0m&quot;, &quot;\u001b[0;32mInfo: Retrieving plugin\u001b[0m&quot;, &quot;\u001b[0;32mInfo: Loading facts\u001b[0m&quot;], &quot;warnings&quot;: []}
stderr: [1;31mError: Could not retrieve catalog from remote server: Error 400 on SERVER: unrecognized database type for server. at /etc/puppet/environments/master/modules/zabbix/manifests/web.pp:161 on node vserver-142.dj-wasabi.local[0m
[1;31mWarning: Not using cache on failed catalog[0m
[1;31mError: Could not retrieve catalog; skipping run[0m
stdout: [0;32mInfo: Retrieving pluginfacts[0m
[0;32mInfo: Retrieving plugin[0m
[0;32mInfo: Loading facts[0m

FATAL: all hosts have already failed -- aborting

PLAY RECAP ********************************************************************
           to retry, use: --limit @/var/lib/jenkins/puppet-run.retry

vserver-142                : ok=1    changed=0    unreachable=0    failed=1

Ah, I made an error in my manifest.

Nice isn’t it? 🙂