Sunday, July 6, 2014

A simple puppet test framework with Docker



I've been looking at the puppet testing setups (rspec etc) and while I may be missing the point, they don't appear to meet my immediate requirements.

I have a *lot* of puppet manifests that have been coded to allow for complex server sets, for example a server A may have 10+ configuration sets applied, server B 20+ etc.   A complicating factor is that these configuration sets (manifests) will be sourced from different parts of the business, so I've been looking for a testing solution I can run on the CI server (bamboo) to test these assorted manifesto's before they hit and potentially break in production.

Enter Docker.

I've included this simple example at: https://github.com/agronaught/puppettest

$puppetroot/modules  -- puppet modules.$puppetroot/scripts   -- scripts for automating the use of puppet.

$puppetroot/docker   -- docker test scripts.

The modules directory contains, oddly enough, puppet modules.  For this example I've included one of the sudo modules from the puppet forge and my test manifest:

$puppetroot/modules/sudo -- sudo manifests from puppet forge
$puppetroot/modules/testbase -- the test manifests

In this example the test manifest itself is very simple:
class testbase {
  class { '::sudo':
    purge => true,
    config_file_replace => false,
  }
  sudo::conf { "root-notty":
    content => "Defaults:root !requiretty"
  }
}
it essentially installs sudo and then adds a config to allow the root account to run sudo in a batch script.

The docker script is also very simple:
# DOCKER-VERSION 1.0.1
FROM centos:6.4
MAINTAINER Jason Ball <jason@ball.net>
# Install yum repo for puppet and install it
RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm
RUN yum install -y puppet
# Mount the puppet modules...
RUN mkdir -p /tmp/puppet
ADD . /tmp/puppet
RUN /usr/bin/puppet apply -e 'class { "::testbase": }' --verbose --modulepath=/tmp/puppet/modules
This container is based on the publicly available centos image and:

  • installs puppet from the puppet repository
  • mounts the manifests to be tested under /tmp/puppet
  • runs the 'testbase' manifests on the container

There is a high level script to run the docker build and any test scripts if finds against the resultant image. This should allow for test scripts to be run against the resultant image to ensure configurations load, check for errors/failures, test new features, etc.

#!/bin/bash
##
# housekeeping
##
function cleanup {
    rm Dockerfile
    echo "Stopping autotest docker"
    docker ps -a | grep autotest | awk '{print $1}' | xargs docker rm
    docker images | grep autotest | awk '{print $3}' | xargs docker rmi
}
#trap cleanup EXIT
#
# Run the scripts
#
echo "Running Autotest"
echo "Building test image"
cp ./docker/autotest/Dockerfile .
docker build --no-cache=true --tag="autotest" .
echo "Running Test Scripts"
for script in `find docker -name "test*.sh" | xargs`; do
    echo "Running $script..."
    $script
    status=$?
    if [ $status -ne 0 ]; then
        echo "ERROR: Script returned non zero status: $status -- error in $script"
        break
fi
done
exit $status
The rest should be fairly self explanatory, the only prerequisite is that docker is setup on the system thats going to run the scripts.

The run for the docker build:

$ ./scripts/run_autotest.sh Running AutotestBuilding test imageSending build context to Docker daemon 878.1 kBSending build context to Docker daemon Step 0 : FROM centos:6.4 ---> 539c0211cd76Step 1 : MAINTAINER Jason Ball <jason@ball.net> ---> Running in a48a2627ec29 ---> cbda1f07a5beRemoving intermediate container a48a2627ec29Step 2 : ---> Running in 1b3869f27a98 ---> 9c047488909aRemoving intermediate container 1b3869f27a98Step 3 :  ---> Running in 9e04aab7d891 ---> 9b83b773effeRemoving intermediate container 9e04aab7d891Step 4 : RUN rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpm ---> Running in c3c1f0fe0f6ewarning: /var/tmp/rpm-tmp.tSV15D: Header V4 RSA/SHA1 Signature, key ID 4bd6ec30: NOKEYRetrieving http://yum.puppetlabs.com/puppetlabs-release-el-6.noarch.rpmPreparing...                ##################################################puppetlabs-release          ################################################## ---> d325d0e86a97Removing intermediate container c3c1f0fe0f6eStep 5 : RUN yum install -y puppet ---> Running in 67e098975729Loaded plugins: fastestmirrorSetting up Install ProcessResolving Dependencies--> Running transaction check---> Package puppet.noarch 0:3.6.2-1.el6 will be installed--> Processing Dependency: facter >= 1:1.7.0 for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby >= 1.8.7 for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: hiera >= 1.0.0 for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby >= 1.8 for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby-rgen >= 0.6.5 for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby(selinux) for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: /usr/bin/ruby for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: rubygem-json for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby-shadow for package: puppet-3.6.2-1.el6.noarch--> Processing Dependency: ruby-augeas for package: puppet-3.6.2-1.el6.noarch--> Running transaction check---> Package facter.x86_64 1:2.1.0-1.el6 will be installed--> Processing Dependency: dmidecode for package: 1:facter-2.1.0-1.el6.x86_64--> Processing Dependency: pciutils for package: 1:facter-2.1.0-1.el6.x86_64--> Processing Dependency: virt-what for package: 1:facter-2.1.0-1.el6.x86_64--> Processing Dependency: which for package: 1:facter-2.1.0-1.el6.x86_64---> Package hiera.noarch 0:1.3.4-1.el6 will be installed---> Package libselinux-ruby.x86_64 0:2.0.94-5.3.el6_4.1 will be installed--> Processing Dependency: libselinux = 2.0.94-5.3.el6_4.1 for package: libselinux-ruby-2.0.94-5.3.el6_4.1.x86_64---> Package ruby.x86_64 0:1.8.7.352-13.el6 will be installed--> Processing Dependency: ruby-libs = 1.8.7.352-13.el6 for package: ruby-1.8.7.352-13.el6.x86_64--> Processing Dependency: libruby.so.1.8()(64bit) for package: ruby-1.8.7.352-13.el6.x86_64---> Package ruby-augeas.x86_64 0:0.4.1-3.el6 will be installed--> Processing Dependency: augeas-libs >= 0.8.0 for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0(AUGEAS_0.10.0)(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0(AUGEAS_0.1.0)(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0(AUGEAS_0.11.0)(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0(AUGEAS_0.8.0)(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0(AUGEAS_0.12.0)(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64--> Processing Dependency: libaugeas.so.0()(64bit) for package: ruby-augeas-0.4.1-3.el6.x86_64---> Package ruby-rgen.noarch 0:0.6.5-2.el6 will be installed---> Package ruby-shadow.x86_64 1:2.2.0-2.el6 will be installed---> Package rubygem-json.x86_64 0:1.5.5-1.el6 will be installed--> Processing Dependency: rubygems for package: rubygem-json-1.5.5-1.el6.x86_64--> Running transaction check---> Package augeas-libs.x86_64 0:1.0.0-5.el6_5.1 will be installed---> Package dmidecode.x86_64 1:2.12-5.el6_5 will be installed---> Package libselinux.x86_64 0:2.0.94-5.3.el6 will be updated--> Processing Dependency: libselinux = 2.0.94-5.3.el6 for package: libselinux-utils-2.0.94-5.3.el6.x86_64---> Package libselinux.x86_64 0:2.0.94-5.3.el6_4.1 will be an update---> Package pciutils.x86_64 0:3.1.10-2.el6 will be installed--> Processing Dependency: pciutils-libs = 3.1.10-2.el6 for package: pciutils-3.1.10-2.el6.x86_64--> Processing Dependency: libpci.so.3(LIBPCI_3.1)(64bit) for package: pciutils-3.1.10-2.el6.x86_64--> Processing Dependency: libpci.so.3(LIBPCI_3.0)(64bit) for package: pciutils-3.1.10-2.el6.x86_64--> Processing Dependency: libpci.so.3()(64bit) for package: pciutils-3.1.10-2.el6.x86_64---> Package ruby-libs.x86_64 0:1.8.7.352-13.el6 will be installed--> Processing Dependency: libssl.so.10(libssl.so.10)(64bit) for package: ruby-libs-1.8.7.352-13.el6.x86_64--> Processing Dependency: libcrypto.so.10(libcrypto.so.10)(64bit) for package: ruby-libs-1.8.7.352-13.el6.x86_64--> Processing Dependency: libreadline.so.5()(64bit) for package: ruby-libs-1.8.7.352-13.el6.x86_64---> Package rubygems.noarch 0:1.3.7-5.el6 will be installed--> Processing Dependency: ruby-rdoc for package: rubygems-1.3.7-5.el6.noarch---> Package virt-what.x86_64 0:1.11-1.2.el6 will be installed---> Package which.x86_64 0:2.19-6.el6 will be installed--> Running transaction check---> Package compat-readline5.x86_64 0:5.2-17.1.el6 will be installed---> Package libselinux-utils.x86_64 0:2.0.94-5.3.el6 will be updated---> Package libselinux-utils.x86_64 0:2.0.94-5.3.el6_4.1 will be an update---> Package openssl.x86_64 0:1.0.0-27.el6_4.2 will be updated---> Package openssl.x86_64 0:1.0.1e-16.el6_5.14 will be an update--> Processing Dependency: make for package: openssl-1.0.1e-16.el6_5.14.x86_64---> Package pciutils-libs.x86_64 0:3.1.10-2.el6 will be installed---> Package ruby-rdoc.x86_64 0:1.8.7.352-13.el6 will be installed--> Processing Dependency: ruby-irb = 1.8.7.352-13.el6 for package: ruby-rdoc-1.8.7.352-13.el6.x86_64--> Running transaction check---> Package make.x86_64 1:3.81-20.el6 will be installed---> Package ruby-irb.x86_64 0:1.8.7.352-13.el6 will be installed--> Finished Dependency Resolution
Dependencies Resolved
================================================================================ Package            Arch     Version                Repository             Size================================================================================Installing: puppet             noarch   3.6.2-1.el6            puppetlabs-products   1.3 MInstalling for dependencies: augeas-libs        x86_64   1.0.0-5.el6_5.1        updates               309 k compat-readline5   x86_64   5.2-17.1.el6           base                  130 k dmidecode          x86_64   1:2.12-5.el6_5         updates                73 k facter             x86_64   1:2.1.0-1.el6          puppetlabs-products    89 k hiera              noarch   1.3.4-1.el6            puppetlabs-products    23 k libselinux-ruby    x86_64   2.0.94-5.3.el6_4.1     base                   99 k make               x86_64   1:3.81-20.el6          base                  389 k pciutils           x86_64   3.1.10-2.el6           base                   85 k pciutils-libs      x86_64   3.1.10-2.el6           base                   34 k ruby               x86_64   1.8.7.352-13.el6       updates               534 k ruby-augeas        x86_64   0.4.1-3.el6            puppetlabs-deps        21 k ruby-irb           x86_64   1.8.7.352-13.el6       updates               314 k ruby-libs          x86_64   1.8.7.352-13.el6       updates               1.6 M ruby-rdoc          x86_64   1.8.7.352-13.el6       updates               377 k ruby-rgen          noarch   0.6.5-2.el6            puppetlabs-deps       237 k ruby-shadow        x86_64   1:2.2.0-2.el6          puppetlabs-deps        13 k rubygem-json       x86_64   1.5.5-1.el6            puppetlabs-deps       763 k rubygems           noarch   1.3.7-5.el6            base                  207 k virt-what          x86_64   1.11-1.2.el6           base                   24 k which              x86_64   2.19-6.el6             base                   38 kUpdating for dependencies: libselinux         x86_64   2.0.94-5.3.el6_4.1     base                  108 k libselinux-utils   x86_64   2.0.94-5.3.el6_4.1     base                   81 k openssl            x86_64   1.0.1e-16.el6_5.14     updates               1.5 M
Transaction Summary================================================================================Install      21 Package(s)Upgrade       3 Package(s)
Total download size: 8.3 MDownloading Packages:--------------------------------------------------------------------------------Total                                           1.4 MB/s | 8.3 MB     00:06     warning: rpmts_HdrFromFdno: Header V3 RSA/SHA1 Signature, key ID c105b9de: NOKEYRetrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6Importing GPG key 0xC105B9DE: Userid : CentOS-6 Key (CentOS 6 Official Signing Key) <centos-6-key@centos.org> Package: centos-release-6-4.el6.centos.10.x86_64 (@febootstrap/$releasever) From   : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6warning: rpmts_HdrFromFdno: Header V4 RSA/SHA512 Signature, key ID 4bd6ec30: NOKEYRetrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabsImporting GPG key 0x4BD6EC30: Userid : Puppet Labs Release Key (Puppet Labs Release Key) <info@puppetlabs.com> Package: puppetlabs-release-6-10.noarch (installed) From   : /etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabsRunning rpm_check_debugRunning Transaction TestTransaction Test SucceededRunning TransactionWarning: RPMDB altered outside of yum.  Updating   : libselinux-2.0.94-5.3.el6_4.1.x86_64                        1/27   Installing : 1:dmidecode-2.12-5.el6_5.x86_64                             2/27   Installing : virt-what-1.11-1.2.el6.x86_64                               3/27   Updating   : libselinux-utils-2.0.94-5.3.el6_4.1.x86_64                  4/27   Installing : augeas-libs-1.0.0-5.el6_5.1.x86_64                          5/27   Installing : libselinux-ruby-2.0.94-5.3.el6_4.1.x86_64                   6/27   Installing : 1:make-3.81-20.el6.x86_64                                   7/27   Updating   : openssl-1.0.1e-16.el6_5.14.x86_64                           8/27   Installing : compat-readline5-5.2-17.1.el6.x86_64                        9/27   Installing : ruby-libs-1.8.7.352-13.el6.x86_64                          10/27   Installing : ruby-1.8.7.352-13.el6.x86_64                               11/27   Installing : 1:ruby-shadow-2.2.0-2.el6.x86_64                           12/27   Installing : ruby-rgen-0.6.5-2.el6.noarch                               13/27   Installing : ruby-irb-1.8.7.352-13.el6.x86_64                           14/27   Installing : ruby-rdoc-1.8.7.352-13.el6.x86_64                          15/27   Installing : rubygems-1.3.7-5.el6.noarch                                16/27   Installing : rubygem-json-1.5.5-1.el6.x86_64                            17/27   Installing : hiera-1.3.4-1.el6.noarch                                   18/27   Installing : ruby-augeas-0.4.1-3.el6.x86_64                             19/27   Installing : pciutils-libs-3.1.10-2.el6.x86_64                          20/27   Installing : pciutils-3.1.10-2.el6.x86_64                               21/27   Installing : which-2.19-6.el6.x86_64                                    22/27   Installing : 1:facter-2.1.0-1.el6.x86_64                                23/27   Installing : puppet-3.6.2-1.el6.noarch                                  24/27   Cleanup    : libselinux-utils-2.0.94-5.3.el6.x86_64                     25/27   Cleanup    : libselinux-2.0.94-5.3.el6.x86_64                           26/27   Cleanup    : openssl-1.0.0-27.el6_4.2.x86_64                            27/27   Verifying  : ruby-libs-1.8.7.352-13.el6.x86_64                           1/27   Verifying  : which-2.19-6.el6.x86_64                                     2/27   Verifying  : libselinux-2.0.94-5.3.el6_4.1.x86_64                        3/27   Verifying  : 1:ruby-shadow-2.2.0-2.el6.x86_64                            4/27   Verifying  : libselinux-utils-2.0.94-5.3.el6_4.1.x86_64                  5/27   Verifying  : puppet-3.6.2-1.el6.noarch                                   6/27   Verifying  : pciutils-libs-3.1.10-2.el6.x86_64                           7/27   Verifying  : ruby-augeas-0.4.1-3.el6.x86_64                              8/27   Verifying  : virt-what-1.11-1.2.el6.x86_64                               9/27   Verifying  : compat-readline5-5.2-17.1.el6.x86_64                       10/27   Verifying  : ruby-rdoc-1.8.7.352-13.el6.x86_64                          11/27   Verifying  : 1:facter-2.1.0-1.el6.x86_64                                12/27   Verifying  : pciutils-3.1.10-2.el6.x86_64                               13/27   Verifying  : 1:make-3.81-20.el6.x86_64                                  14/27   Verifying  : rubygems-1.3.7-5.el6.noarch                                15/27   Verifying  : 1:dmidecode-2.12-5.el6_5.x86_64                            16/27   Verifying  : ruby-rgen-0.6.5-2.el6.noarch                               17/27   Verifying  : ruby-irb-1.8.7.352-13.el6.x86_64                           18/27   Verifying  : rubygem-json-1.5.5-1.el6.x86_64                            19/27   Verifying  : openssl-1.0.1e-16.el6_5.14.x86_64                          20/27   Verifying  : augeas-libs-1.0.0-5.el6_5.1.x86_64                         21/27   Verifying  : libselinux-ruby-2.0.94-5.3.el6_4.1.x86_64                  22/27   Verifying  : ruby-1.8.7.352-13.el6.x86_64                               23/27   Verifying  : hiera-1.3.4-1.el6.noarch                                   24/27   Verifying  : libselinux-2.0.94-5.3.el6.x86_64                           25/27   Verifying  : libselinux-utils-2.0.94-5.3.el6.x86_64                     26/27   Verifying  : openssl-1.0.0-27.el6_4.2.x86_64                            27/27 
Installed:  puppet.noarch 0:3.6.2-1.el6                                                   
Dependency Installed:  augeas-libs.x86_64 0:1.0.0-5.el6_5.1                                            compat-readline5.x86_64 0:5.2-17.1.el6                                          dmidecode.x86_64 1:2.12-5.el6_5                                                 facter.x86_64 1:2.1.0-1.el6                                                     hiera.noarch 0:1.3.4-1.el6                                                      libselinux-ruby.x86_64 0:2.0.94-5.3.el6_4.1                                     make.x86_64 1:3.81-20.el6                                                       pciutils.x86_64 0:3.1.10-2.el6                                                  pciutils-libs.x86_64 0:3.1.10-2.el6                                             ruby.x86_64 0:1.8.7.352-13.el6                                                  ruby-augeas.x86_64 0:0.4.1-3.el6                                                ruby-irb.x86_64 0:1.8.7.352-13.el6                                              ruby-libs.x86_64 0:1.8.7.352-13.el6                                             ruby-rdoc.x86_64 0:1.8.7.352-13.el6                                             ruby-rgen.noarch 0:0.6.5-2.el6                                                  ruby-shadow.x86_64 1:2.2.0-2.el6                                                rubygem-json.x86_64 0:1.5.5-1.el6                                               rubygems.noarch 0:1.3.7-5.el6                                                   virt-what.x86_64 0:1.11-1.2.el6                                                 which.x86_64 0:2.19-6.el6                                                     
Dependency Updated:  libselinux.x86_64 0:2.0.94-5.3.el6_4.1                                          libselinux-utils.x86_64 0:2.0.94-5.3.el6_4.1                                    openssl.x86_64 0:1.0.1e-16.el6_5.14                                           
Complete! ---> 3929c2fd7335Removing intermediate container 67e098975729Step 6 : RUN mkdir -p /tmp/puppet ---> Running in 1862953608fa ---> 4eaf5c96f9a3Removing intermediate container 1862953608faStep 7 : ADD . /tmp/puppet ---> c47c7a0a6743Removing intermediate container 0e2be2ebd336Step 8 : RUN /usr/bin/puppet apply -e 'class { "::testbase": }'  --verbose --modulepath=/tmp/puppet/modules ---> Running in e728af515dffInfo: Loading facts in /tmp/puppet/modules/stdlib/lib/facter/puppet_vardir.rbInfo: Loading facts in /tmp/puppet/modules/stdlib/lib/facter/root_home.rbInfo: Loading facts in /tmp/puppet/modules/stdlib/lib/facter/pe_version.rbInfo: Loading facts in /tmp/puppet/modules/stdlib/lib/facter/facter_dot_d.rbWarning: Config file /etc/puppet/hiera.yaml not found, using Hiera defaultsNotice:secondsWarning: The package type's allow_virtual parameter will be changing its default value from false to true in a future release. If you do not want to allow virtual packages, please explicitly set allow_virtual to false.   (at /usr/lib/ruby/site_ruby/1.8/puppet/type.rb:816:in `set_default')Info: Applying configuration version '1404712257'Notice: /Stage[main]/Sudo::Package/Package[sudo]/ensure: createdNotice: /Stage[main]/Sudo/File[/etc/sudoers.d/]/mode: mode changed '0750' to '0550'Notice: /Stage[main]/Testbase/Sudo::Conf[root-notty]/File[10_root-notty]/ensure: createdInfo: /Stage[main]/Testbase/Sudo::Conf[root-notty]/File[10_root-notty]: Scheduling refresh of Exec[sudo-syntax-check for file /etc/sudoers.d/10_root-notty]Notice: /Stage[main]/Testbase/Sudo::Conf[root-notty]/Exec[sudo-syntax-check for file /etc/sudoers.d/10_root-notty]: Triggered 'refresh' from 1 eventsInfo: Creating state file /var/lib/puppet/state/state.yamlNotice: Finished catalog run in 2.17 seconds ---> 0abda7c7aed6Removing intermediate container e728af515dffSuccessfully built 0abda7c7aed6
And the test scripts:
Successfully built 0abda7c7aed6
Running Test Scripts
Running docker/autotest/test_sudo.sh...
Running docker/autotest/test_sudo_bad.sh...
sudo: unknown user: baduser
sudo: unable to initialize policy plugin
ERROR: Script returned non zero status: 1 -- error in docker/autotest/test_sudo_bad.sh

Nice and simple and easily automated in Bamboo.   In my case I have an auto test dock for each logical group of manifests representing a production environment and this approach effectively allows me to perform a sociability test on scripts prior to hitting production.

The test scripts can be coded using whatever tool set your happiest with.







No comments:

Post a Comment