Subscribe: Dean Wilson@UnixDaemon: Whatever affects one directly, affects all indirectly.
Added By: Feedage Forager Feedage Grade A rated
Language: English
aws  check  code  file  group  json  lint  puppet lint  puppet  resource  resources  run  security group  terraform  test 
Rate this Feed
Rate this feedRate this feedRate this feedRate this feedRate this feed
Rate this feed 1 starRate this feed 2 starRate this feed 3 starRate this feed 4 starRate this feed 5 star

Comments (0)

Feed Details and Statistics Feed Statistics
Preview: Dean Wilson@UnixDaemon: Whatever affects one directly, affects all indirectly.

on UnixDaemon: In search of (a) life

My name is Dean Wilson, is my personal site where I store my code, writing, rantings and anything else I feel warrants sharing with the rest of the `Net.


Removing 'magic numbers' and times from your Puppet manifests

Thu, 06 Oct 2016 11:17:00 +0000

In a large Puppet code base you’ll eventually end up with a scattering of time based ‘magic numbers‘ such as cache expiry numbers, zone file ttls and recurring job schedules. You’ll typically find these dealt with in one of a few ways. The easiest is to ignore it and leave a hopefully guessable literal value (such as 3600). The other path often taken is the dreaded heavily linked and often missed comments that start off as 86400 # seconds in a day and over time become 3600 # seconds in a day.

The time_units puppet function is a proof of concept, written for a code base that suffers heavily from this, that makes these kinds of numbers more explicit and self-documenting. Once you’ve installed the module from the puppet-forge:

puppet module install deanwilson-time_units

and restarted your puppetmaster you can use it in your code with calls like these:

time_units(15, 'minutes', 'seconds') # returns 900

You can also make calls using a slightly ‘prettier’ 4 argument version.

time_units(2, 'hours', 'in', 'minutes') # returns 120

Is the added complexity of a function worth it when peer code review can ensure your code and comments are changed together? It all depends on what your manifests look like, how you review changes and how heavily you use hiera to bring values in. Hopefully you’ll never need this, but in the cases where you’re working on legacy spaghetti it’s sometimes nice to have little wins and cleanups.

Puppet Lint Plugins - 2.0 Upgrade and new repo

Sun, 21 Aug 2016 17:47:00 +0000

After the recent puppet-lint 2.0 release and the success of our puppet-lint 2.0 upgrade at work it felt like the right moment to claw some time back and update my own (11!) puppet-lint plugins to allow them to run on either puppet-lint 1 or 2. I’ve now completed this and pushed new versions of the gems to rubygems so if you’ve been waiting for version 2 compatible gems please feel free to test away.

Now I’ve realised exactly how many plugins I’ve ended up with I’ve created a new GitHub repo, unixdaemon-puppet-lint-plugins, that will serve as a nicer discovery point to all of my plugins and a basic introduction to what they do. It’s quite bare bones at the moment but it does present a nicer approach than clicking around my github profile looking for matching repo names.

Puppet Lint 2.0 Upgrade

Thu, 04 Aug 2016 11:47:00 +0000

With the recent puppet-lint 2.0 release it seemed a good time to bump the version we use at $WORK and see what’d changed. In theory it was as simple as changing the version in our Gemfile and ideally everything should continue as normal, but in practise it was a little more work than that and in this post I’m going to explain what we found. Firstly let’s cover a lovely, free, bonus. On our test codebase puppet-lint 1.0.1 took about 25 seconds to run on average. Immediately after the upgrade to 2.0.0 our run times dropped to around 19 seconds, with no changes required to our code. While it might seem like a tiny amount of time, considering how often the tests get run, we’ve probably already recouped the time spent performing the upgrade. In terms of lint warnings the first, and easiest to fix, complaint was the change from --no-80chars-check to --no-140chars-check. While most places already disable the line length check the Puppet style guide has become a little more accepting recently and now allows lines up to 140 characters. We have some longer lines, such as embedded ssh public keys, that hit this limit so we migrated from disabling the 80 character check to disabling the 140 one. This also required us to move from using the old config file .puppet-lintrc to the newer one .puppet-lint.rc. That was a few minutes of work so shouldn’t be a blocker for anyone. The second source of lint warnings from the upgrade came as a bit of a surprise. It seems that fat arrow (=>) alignment wasn’t being correctly checked on attributes that ended with a semi-colon. We had code that looked like this: file { '/tmp/fake': content => 'Hello', mode => '0644'; } That ran fine under puppet-lint 1.0.1 but raised issues under 2.0.0. Fixing it was easy enough file { '/tmp/fake': content => 'Hello', mode => '0644'; } and then the awkward, unneeded semi-colon was replaced with a sensible comma to make future diffs bit nicer too. file { '/tmp/fake': content => 'Hello', mode => '0644', } We fixed up nearly all the code violations with no manual intervention. We always work on branches so it safe enough to run bundle exec puppet- lint --fix . over the entire code base, let it change what it wanted, and then read through the diffs. While this completed 99% of the fixes it did raise one interesting --fix edge case / bug that I have on the TODO list to investigate: - warning => "@${minimum_request_rate * 1.2}", + warning => "@${minimum_request_rate} * 1.2", The fix code is a little aggressive in its protecting of variable names and in this case changes the functionality significantly by replacing a multiplication with a variable and a literal string. There is something to be said that this’d be better as an inline_template but for now changing it back was simple enough and the checks are happy with it. In closing, technically the upgrade is worth doing for the performance improvements and stricter linting. From a community side it’s nice to see more people involved and have versioned releases coming out rather than pinning to individual git hashes. A big ‘thank you’ is deserved by all the people involved. If you want to see exactly what was done you can see the full puppet-lint 2.0 upgrade in our public puppet code. [...]

Specialising validate_re with wrapper functions in Puppet

Tue, 28 Jun 2016 20:10:00 +0000

Once your puppet code base reaches a certain size you’ll often have a number of validate_ functions testing parameters and configuration values for compliance with local rules and requirements. These invocations often look like this: validate_re($private_gpg_key_fingerprint, '^[[:alnum:]]{40}$', 'Must supply full GPG fingerprint') Once you’ve spent a minute or two reading that you’ll probably be able to understand it; but wouldn’t it be nice to not have to care about the exact details and focus on what you’re actually testing? An approach I’ve been experimenting with on one larger code base is to specialise those kind of calls using a wrapper function. Instead of the detailed call above we can now do validate_gpg_key_fingerprint($finger_print). Which of those are easier to read? Which is simpler for people new to puppet? Implementing this kind of wrapper is much easier than you’d expect. All you need is a small stub function that wraps an existing one, in our case validate_re, and supplies some sensible defaults and local validation. You can see an example below. cat modules/wrapper_functions/lib/puppet/parser/functions/validate_gpg_key_fingerprint.rb module Puppet::Parser::Functions newfunction(:validate_gpg_key_fingerprint, :doc => <<-'ENDHEREDOC') do |args| A simple wrapper function to show specialisation of 'validate_re' from the stdlib and how it can make manifests more domain specific and easier to read. ENDHEREDOC unless (args.length >= 1 && args.length <= 2) message = 'wrong arguments - [error message]' raise ArgumentError, "validate_gpg_key_fingerprint(): #{message}" end fingerprint = args[0] # here we set the local rules and sensible defaults message = args[1] || 'Must supply full GPG fingerprint' regex = '^[[:alnum:]]{40}$' # here we run the original function function_validate_re( [ fingerprint, regex, message ] ) end end This is easy to test in isolation, a nice place to encompass more complicated validations while presenting a simple usage case and requires only a small amount of shim coding. If this approach interests you it’s also quite easy to achieve a similar benefit with custom wrappers to the is_ functions in stdlib that have a greater understanding of what you’re testing than the basic, but common cases, provided in things like stdlib . For example you can wrap is_ip_address and only return true for addresses in your own valid ranges. The most obvious downside to this approach, other than the custom coding required, is that if used often enough it’s easy to convince people that puppet has a number of basic validate_ and is_ functions that don’t actually exist anywhere outside of your repos. Although these kinds of changes are not going to revolutionise your code base they are nice, gradual improvements. If you have a number of users that don’t work with puppet on a regular basis, changing the vocabulary of your functions to closer align with your local domain terms can be a nicer way to ease them into reading, and eventually contributing, to the code. [...]

CloudFormation Linting with cfn-nag

Mon, 02 May 2016 17:46:00 +0000

Over the last 3 years I’ve done a lot of CloudFormation work and while it’s an easy enough technology to get to grips with the mass of JSON can become a bit of a blur when you’re doing code reviews. It’s always nice to get a second pair of eyes, especially an unflagging, automated set, that has insight in to some of the easily overlooked security issues you can accidentally add to your templates. cfn-nag is a ruby gem that attempts to sift through your code and present guidelines on a number of frequently misused, and omitted, resource properties. gem install cfn-nag Once the gem and its dependencies finish installing you can list all the rules it currently validates against. $ cfn_nag_rules ... IAM policy should not apply directly to users. Should be on group ... I found reading through the rules to be quite a nice context refresher. While there are a few I don’t agree with there are also some I wouldn’t have thought to single out in code review so it’s well worth having a read through the possible anti-patterns. Let’s check our code with cfn-nag. cfn_nag --input-json-path . # all .json files in the directory cfn_nag --input-json-path templates/buckets.json # single file check The default output from these runs looks like: ./templates/buckets.json ------------------------------------------------------------ | WARN | | Resources: ["AssetsBucketPolicy"] | | It appears that the S3 Bucket Policy allows s3:PutObject without server-side encryption Failures count: 0 Warnings count: 1 ./templates/elb.json ------------- | WARN | | Resources: ["ELB"] | | Elastic Load Balancer should have access logging configured Failures count: 0 Warnings count: 1 If you’d like to reprocess the issues in another part of your tooling / pipelining then the json output formatter might be more helpful. cfn_nag --input-json-path . --output-format json { "type": "WARN", "message": "Elastic Load Balancer should have access logging configured", "logical_resource_ids": [ "ELB" ], "violating_code": null } While the provided rules are useful it’s always a good idea to have an understanding of how easy a linting tool makes adding your own checks. In the case of cfn-nag there are two typed of rules. Some use JSON and jq and the others are pure ruby code. Let’s add a simple pure ruby rule to ensure all our security groups have descriptions. At the moment this requires you to drop code directly in to the gems contents but I imagine this will be fixed in the future. First we’ll create our own rule: # first we find where the gem installs its custom rules $ gem contents cfn-nag | grep custom_rules ./.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/cfn-nag-0.0.19/lib/custom_rules Then we’ll add a new rule to that directory touch $full_path/lib/custom_rules/security_group_missing_description.rb Our custom check looks like this - class SecurityGroupMissingDescription def rule_text 'Security group does not have a description' end def audit(cfn_model) logical_resource_ids = [] cfn_model.security_groups.each do |security_group| unless security_group.group_description logical_resource_ids << security_group.logical_resource_id end end if logical_resource_ids.size > 0 Violation::FAILING_VIOLATION, message: rule_text, logical_resource_ids: logical_resource_ids) else nil end end end The code above was heavily ‘borrowed’ from an existing check and a little bit of object exploration was done using pry. Once we have our new rule we need to plumb it in to the current rule loading code. This is currently a little unwieldy but it’s worth keeping an eye [...]

Terraform Modules - My Sharing Wishlist

Sun, 01 May 2016 17:15:40 +0000

I’ve been writing a few Terraform modules recently with the aim of sharing them among a few different teams and there are a couple of things missing that I think would make reusable modules much more powerful.

The first and more generic issue is using the inability to use more complex data structures. After you’ve spent a while using Terraform with AWS resources you’ll develop the urge to just create a hash of tags and use it nearly everywhere. Hopefully with the ability to override a key / value or two when actually using the hash. If your teams are using tags, and you really should be, it’s very hard to write a reusable module if the tag names in use by each team are not identical. Because you can only (currently) pass strings around, and you’re unable to use a variable as a tag name, you’re stuck with requiring everyone to use exactly the same tag names or not providing any at all. There’s no middle ground available.

tags {
    "${}" = "Baz"

# creates a Tag called literally '${}'

My second current pain point, and the one I’m more likely to have missed a solution to, is the ability to conditionally add or remove resource attributes. The most recent time this has bitten me is when trying to generalise a module that uses Elastic Load Balancers. Sometimes you’ll want an ELB with a cert and sometimes you won’t. Using the current module system there’s no way to handle this case.

If I was to do the same kind of thing in CloudFormation I’d use the AWS::NoValue pseudo parameter.

    "DBSnapshotIdentifier" : {
        "Fn::If" : [
                {"Ref" : "DBSnapshotName"},
                {"Ref" : "AWS::NoValue"}

If DBSnapshotName has a value the DBSnapshotIdentifier property is present and set to that value. If it’s not defined then the property is not set on the resource.

As an aside, after chatting with @andrewoutloud, it’s probably worth noting that you can make entire resources optional using a count and setting it to 0 when you don’t want the resource to be included. While this is handy and worth having in your Terraform toolkit it doesn’t cover my use case.

variable "include_rds" {
    default = 0
    description = "Should we include a aws_db_instance? Set to 1 to include it"

resource "aws_db_instance" "default" {
    count = "${var.include_rds}" # this serves as an if

    # ... snip ...

I’m sure these annoyances will be ironed out in time but it’s worth considering them and how they’ll impact the reusability of any modules you’d like to write or third party code you’d want to import. At the moment it’s a hard choice between rewriting everything for my own use and getting all the things I need or vendoring everything in and maintaining a branch with things like my own tagging scheme and required properties.

Testing Terraform projects

Thu, 21 Apr 2016 21:26:00 +0000

While Terraform is remarkably good at its job there are going to be some occasions when you want to test what you wanted actually happened. In the unixdaemon_terraform_experiments repository I’m handling this with awspec and a little custom rspec directory modification.

First we pull in the awspec gem.

bundle install

We also need to add the necessary scaffolding files:

echo "gem 'awspec',  '~> 0.37'" >> Gemfile

mkdir spec

echo "require 'awspec'" >> spec/spec_helper.rb

Now we’ll add a test to our simple-sg project to confirm that the security group was created.

mkdir projects/simple-sg/spec

$ cat > projects/simple-sg/spec/security_group_spec.rb <

Note that the tests live beside the terraform project resources, not in a combined spec directory. This allows us to run only the tests related to the project we’re currently working on. The code to implement this, along with another special case that allows grouping and executing by environment, can be found in the Rakefile spec task. I’ll cover the environment split more in a future post.

We then use rake spec to run tests against our chosen project.

PROJECT_NAME=simple-sg bundle exec rake spec

As we tidied up after ourselves previously this spec run will fail.

PROJECT_NAME=simple-sg bundle exec rake spec

security_group 'test-labs-sg'
  should exist (FAILED - 1)

Finished in 0.03664 seconds (files took 1.67 seconds to load)
1 example, 1 failure

We’ll now recreate the security group and then verify that it exists with the name we gave it.

$ PROJECT_NAME=simple-sg bundle exec rake apply
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

$ PROJECT_NAME=simple-sg bundle exec rake spec

security_group 'test-labs-sg'
  should exist

Finished in 0.00153 seconds (files took 1.36 seconds to load)
1 example, 0 failures

Don’t forget to destroy the security group when you’re done testing.

Something to consider is that you don’t want to duplicate all your terraform work and retest your resource declarations. Instead you should test more dynamic aspects of your configuration. Verifying a templated policy contains the expected strings or that all policies have been attached to a group are much better things to test than just the existence of a resource.

I think awspec is a wonderful little tool and I can see it being useful both when migrating from Ansible to Terraform and to later verify my newer projects.

Announcing the UnixDaemon Terraform experiments repo

Thu, 21 Apr 2016 20:25:00 +0000

Introduction While it’s possible to experiment and learn parts of Terraform in isolation sometimes it’s handy to have a larger, more complete, environment to run your tests in. For me unixdaemon_terraform_experiments this is that repo. It will contain a number of different terraform based projects that can be consistently deployed together. You can see some of my thinking behind this in the Naive first steps with Terraform post. Terraform is a very powerful, but quite young, piece of software so I’m making this repo open to encourage sharing and invite feedback on better way to do things. There is no guarantee that anything in this repo is the best or most current way to do anything. Bootstrap The bootstrap phase requires you to have AWS account credentials. For this repo it’s recommended that you store them in .aws/credentials under distinct profile names and leave [default] empty. We’ll do the initial terraform configuration out of bounds to avoid making bootstrapping difficult. First we create the S3 bucket, which must have a globally unique name, used to store the terraform state files. Then we enable bucket versioning in case of anything going hideously wrong. The AWS_REGION and DEPLOY_ENV variables will help us when we later need to have AWS resources in multiple regions or if you decide to have separate test, staging and production environments for example. export AWS_PROFILE=test-admin export AWS_REGION=eu-west-1 export DEPLOY_ENV=test export TERRAFORM_BUCKET="net.dean-wilson-terraform-state-${AWS_REGION}-${DEPLOY_ENV}" $ aws --region $AWS_REGION s3 mb "s3://${TERRAFORM_BUCKET}" make_bucket: s3://net.dean-wilson-terraform-state-eu-west-1-test/ $ aws --region $AWS_REGION \ s3api put-bucket-versioning \ --bucket ${TERRAFORM_BUCKET} \ --versioning-configuration Status=Enabled You will also need to make a change to the projects Rakefile and tell it your BUCKET_NAME andBUCKET_REGION`. These are (currently, and awkwardly) set as constants at the top of the file and should match the values you exported above. You should now install Terraform. This can be done by downloading the file from the Terraform website, or possibly installing it using your package manager. Once this is done we’ll enable our rake terraform wrapper by installing its dependencies. $ bundle install You can then see the possible rake tasks with $ bundle exec rake -T ... rake plan # Show the terraform plan ... Setting up an environment Before we add our first Terraform project we’ll configure an environment. I’ve decided to structure this repo and code to have three environments, test, staging and production. Each of those will be implemented as a distinct Amazon AWS Account and will have their own S3 distinct bucket for state. If you want to have your own environment names then you’ll need to change ALLOWED_ENVIRONMENTS in the Rakefile. We then create our environment specific variable file. mkdir variables echo 'environment = "test"' > variables/test.tfvars Running an initial terraform project Now we’re past all the basic configuration we’ll create a very simple Terraform project and apply it to confirm everything is working. For our initial project we’ll create a security group and then delete it to show the entire end to end process. Our initial step is to create a directory under projects to hold our new resources. Once this is done we’ll add a single security group resource. mkdir -p projects/simple-sg/resources/ cat > projects/simple-sg/resources/ <

Contaminate AWS instances on ssh login

Sat, 02 Apr 2016 10:10:10 +0000

One of the principles of running large numbers of instances is that consistency is key. Config deviations cause oddities that’ll drain your time with investigations and nothing causes entropy on your hosts like an admin investigating an issue. In this post we’ll configure our instances to mark themselves as contaminated when someone logs in. We can then use other tooling to query, collate and probably reap, machines corrupted by the keystrokes of humans.

While the example here is step-by-step and interactive, you’d normally bake this in to your AMI or deploy it very early in your config stage, possibly using cloud-init. For our test we’ll spin up an instance and grant it an ec2 instance profile so it can alter its own tags.

In terms of moving parts we’ll install the awscli package, add a short script that’ll tag the instance when run and configure PAM to invoke the script when an ssh session opens to the machine.

# install required dependency
sudo apt-get install awscli

cat > /usr/local/bin/add-dirty-tag <

[ "$PAM_TYPE" = "open_session" ] || exit 0

INSTANCE_ID=$(ec2metadata --instance-id)

REGION=$(ec2metadata --availability-zone)
REGION=${REGION%?} # remove the last letter

aws ec2 --region $REGION create-tags --resources $INSTANCE_ID --tags Key=contaminated,Value=true

sudo chmod a+rx /usr/local/bin/add-dirty-tag

Now we have a script to add the ‘contaminated’ tag to our instance we’ll configure PAM to run it when a new ssh session starts. On a Ubuntu system the config should be placed in /etc/pam.d/sshd.

# tag the machine as contaminated if anyone sshs in.
session    optional /usr/local/bin/add-dirty-tag

It’s worth opening another ssh session and logging in to confirm this works. That will leave you with an established connection in case you misconfigure PAM in some way. Once you’ve successfully logged in and caused the new tag to be added to the instance you can run a cli filter from outside the instance to show all hosts that have been interactively connected to:

aws --region eu-west-1 ec2 describe-instances             \
    --filters "Name=tag:contaminated,Values=true"         \
    --query 'Reservations[].Instances[].{id: InstanceId}'
        "id": "i-x134x34x"

If you decide to adopt an approach like this you can expand the values stored in the tag using the values PAM exposes, such as $PAM_USER or $PAM_RUSER and a time stamp. There’s also nothing stopping you from adding something more structured. A concise JSON dict maybe. Just be careful that you don’t overwrite the details on each successive login.

2016 drive cleanup

Sun, 27 Mar 2016 11:39:46 +0000

Over the years I’ve built up a small stack of removable drives, mostly for off site backup rotation, and when one of them (a decade old Maxtor) started to sound like two angle grinders ‘passionately embracing’ I thought it was time to do some data validation and re-planning. Although I’m fully aware that most technology trends towards getting smaller and cheaper it’s been a while since I’ve been drive shopping. My god, the difference a few years makes!


A quick purchase of two host-powered, USB 3, 4TB drives via Amazon Prime and I’ve managed to claw back more physical space in power supply storage alone than the new drives use. I’ve copied everything over to both drives, sent one off to live with relatives, and put the older ones in out of sight storage as a very last ditch option if all other restores fail. It was a slow, painful process, made worse by USB 1 transfer speeds and some bad blocks but most of the data is still safely backed up and my home technology footprint shrinks once more.

Naive first steps with Terraform

Thu, 10 Mar 2016 18:27:00 GMT

Naive First Steps with Terraform On one of the $WORK projects, we’ve recently had a chance to join seemingly the entire AWS using world and spend some time using Terraform to manage a few migration prototypes. I’ve had a few little plays with Terraform over the last few years but I’ve never tried to plan a large environment with it before and even though it’s -very- early days for me it’s been an interesting path of discovery. We initially started out with a directory of tf files that each managed groups of resources and shared a state file. This was fine for getting to grips with basic commands resource writing but once we had a few dozen resources I started to get nervous about every terraform apply. The two things that worried me were firstly that every apply could potentially change every part of the system; even if that part of the code base hadn’t been updated. While this should never really be a problem we’ve seen enough issues that it was still playing on my mind. The second concern was the terraform statefile. Although we were storing it in S3 (and who stores something that important in Consul?) it’s a risk that if any resource was ever written in a corrupted state we’d essentially lose everything in one sweep. As an aside one of my biggest wants for Terraform is a ‘discovery’ mode so we can kill the state file off. The importance of the state file was hammered home when we tried to refactor resources defined in standalone .tf files to be inside modules. This turned out to be a less than fun experience of rewriting JSON using vim and fervently hoping that the plan would eventually look like it did when we started. After we’d come out of our initial experiments with a positive attitude and new found appreciation for Terraforms remarkably comprehensive support of AWS resources it was time to take a second look and see how we’d deal with a much larger, more complicated environment. Our current prototype, built with about 8 days of experience, and I stress that this is an experiment which might have major limitations, has a simple four top level concepts layout. We’ve also, like everyone else, written a wrapper script for gluing all this together and running terraform in a consistent way. Our four basic ideas are that ‘projects’, which we’re treating as isolated groups of resources, should be self contained in both the code they consist of and the state file that represents them. This separation makes it easier to reason about possible changes, and limits the damage radius if something goes wrong. The project directory layout currently looks like this: projects/ # each project has a directory under here projects/change_management/ projects/change_management/ projects/change_management/resources # common resources should be placed here projects/change_management/resources/ projects/change_management/resources/ projects/change_management/resources/production/ # environment specific resources should be placed here projects/change_management/resources/production/ We also have resources that only need to exist in a single environment. Sometimes in addition to other resources that should exist everywhere. We’re implementing that functionality by separating those resources out to a subdirectory. Having a database in all environments but only having read replicas in production and staging, to manage costs, is an example of this. While coming up with this one thing bit us that people familiar with terraform have probably already spotted - terraform only takes a single directory of files to run. We work around this in our wrapper script by merging dir[...]

Puppet integration tests in (about) seven minutes

Mon, 08 Feb 2016 22:45:00 GMT

While puppet-lint and rspec-puppet (thanks to Tim Sharpe) will help ensure your Puppet code is both clean and produces what you’d expect in the compiled catalog there are times when you’ll want to go further than unit testing with rspec-puppet and do some basic integration tests to ensure the system ends up in the desired state. In this post, with the assumption that you have Docker installed, I’ll show a simple way to run basic integration tests against a Puppet module. Hopefully in about seven minutes. In order to collect all the shinies we’ll use Docker, in conjunction with Hashicorps Packer, as our platform and we’ll write our unit tests in json and run them under a Go based test framework. If you want to read along without copy and pasting you can find the code in the Basic Puppet integration testing with Docker and Packer repo. Packer is “a tool for creating machine and container images for multiple platforms from a single source configuration.” In our case we’ll use it to create a Docker image, install Puppet and our testing tools and then run the test case inside the container to confirm that everything did as we expected. If you’re on Linux, and installing Packer by hand, it’s as simple as: mkdir /tmp/puppet-and-packer cd /tmp/puppet-and-packer/ wget -O unzip ./packer --version 0.8.6 Software installed we’ll be good devopsy people and write our tests first. For our simple example all we’ll do with puppet is install the strace package so we’ll write a simple Goss test to verify it was installed in the container. You can read more about Testing with Goss on this very site. cat goss.json { "package": { "strace": { "installed": true } } } Now we have our test cases we can write our tiny puppet module: mkdir -p modules/strace/manifests cat modules/strace/manifests/init.pp class strace { package { 'strace': ensure => 'present', } } and the site.pp that uses it cat site.pp include strace Tiny examples created we’ll get to the meat of the configuration, our Packer file. { "builders": [{ "type": "docker", "image": "ubuntu", "commit": "true" }], "provisioners": [{ "type": "shell", "inline": [ "apt-get update", "apt-get install puppet curl -y", "curl -L > /usr/local/bin/goss && chmod +x /usr/local/bin/goss" ] }, { "type": "file", "source": "goss.json", "destination": "/tmp/goss.json" }, { "type": "puppet-masterless", "manifest_file": "site.pp", "module_paths": [ "modules" ] }, { "type": "shell", "inline": [ "/usr/local/bin/goss -g /tmp/goss.json validate --format documentation" ] }] } As an aside we see why JSON is a bad format for application config. You can’t include comments in native JSON. In our example we’ll use two of Packers three main sections, builders and provisioners. Builders create and generate images from for various platforms, in our case Docker. Provisioners install and configure software inside our images. In the above code we’re using three different types. inline shell to install our dependencies, such as Puppet and Goss fi[...]

Puppet-lint world writable files check

Wed, 03 Feb 2016 07:45:00 GMT

On a *nix system a world writable file is one that anyone can write to. This is often undesirable, especially in production, where who can write to certain files should be limited and enabled with deliberation, not by accident. Ideally you should not be deploying files with those permissions, especially not across all your machines using puppet and so I wrote this plugin to provide a small safety net.

    class locked_down_file {
      file { '/tmp/open_octal':
        ensure => 'file',
        mode   => '0666',

files should not be created with world writable permissions

The world_writable_files puppet-lint check is one possible solution to this. Once installed it will pick up any octal file modes that would grant world writable access. It doesn’t currently support symbolic file modes. I use the the no_symbolic_file_modes puppet-lint check ensure so this isn’t a problem for my repos but it might be a consideration for other peoples usages.

Yumrepo gpgcheck puppet-lint check

Tue, 02 Feb 2016 07:43:00 GMT

The most recent in my recent series of puppet-lint plugins, the yumrepo gpgcheck enabled check, will mostly be of interest to security conscious Linux users who use a yum or dnf based package manager. In this case we’re checking the gpgcheck attribute, which indicates if yum should perform a GPG signature check on packages. Having this disabled means you’ll accept any packages from your configured repo, not just those signed by the packagers. While it’s often more work to sign your own packages you should at the very least enable it for all upstream yum repositories.

The plugin issues warnings for two different forms of configuration. The first is a yumrepo without a gpgcheck attribute -

    yumrepo { 'company_app_repo':
      enabled  => 1,
      descr    => 'Local repo holding company application packages',
      baseurl  => '',

yumrepo should have the gpgcheck attribute

The second warning comes from having gpgcheck disabled:

    yumrepo { 'company_app_repo':
      enabled  => 1,
      descr    => 'Local repo holding company application packages',
      baseurl  => '',
      gpgcheck => 0,

yumrepo should enable the gpgcheck attribute

Installing the plugin is done in the usual way -

    gem 'puppet-lint-yumrepo_gpgcheck_enabled-check'

    `bundle install`

No cron resources - customisable puppet-lint check

Mon, 01 Feb 2016 18:08:00 GMT

Sometimes there are certain puppet resource types that you don’t want to include in your code base. In my case it was cron but in yours it could be the more line originated augeas or the horribly named computer. The no cron resources check puppet-lint check will display a warning each time it finds a resource of that type in your manifests.

    class cron_resource {
      cron { 'logrotate':
        command => '/usr/sbin/logrotate',
        user    => root,
        hour    => 2,
        minute  => 0,

    # and the lint check will show:
    # 'cron resources should not be used'

While installing the plugin is done in the usual way -

    # add this line to your `Gemfile`
    gem 'puppet-lint-no_cron_resources-check'

    # install the gem
    bundle install

It’s trivial to make a custom copy that detects which ever resource you don’t want to have in your code base.

    # grab this repo
    git clone

    # make a new copy of the code and cd to it
    cp -a puppet-lint-no_cron_resources-check puppet-lint-no_augeas_resources-check
    cd puppet-lint-no_augeas_resources-check/

    # rename the files and replace the resource type.
    find | grep -v .git | xargs -n 1 rename cron augeas
    find -type f | grep -v .git | xargs sed -i -e 's/cron/augeas/g'

    # and rerun the tests
    bundle exec rake spec

Hopefully the ease with which you can re-purpose this check will make it a little more useful than it would at first appear.