Skip to content

Instantly share code, notes, and snippets.

@lusis
Last active September 14, 2020 17:47
Show Gist options
  • Save lusis/9c0fd50e0de51c3d80b2 to your computer and use it in GitHub Desktop.
Save lusis/9c0fd50e0de51c3d80b2 to your computer and use it in GitHub Desktop.
terraform template to generate serverspec properties

This uses terraform's template_file resource to generate a yaml properties file for serverspec to use.

  • create the Rakefile in your terraform project root
  • create a spec directory and put spec_helper.rb in it
  • create the templates/properties.tmpl.yml file
  • create the serverspec.tf
  • terraform apply

tests

Tests will be matched based on roles defined for a given node. If you have a directory called spec/consul_server with a file called something_spec.rb, then nodes in the properties.yml with that role will run the tests defined in that file. For instance, my test file looks like so:

require 'spec_helper'

describe process("consul") do
  it { should be_running }
  its(:user) { should eq "consul" }
end

describe port(8300) do
  it { should be_listening }
end

template variables

The variables in this example are one's used locally/elsewhere in the terraform run. Obviously yours will be different.

sample output after terraform run

> rake -T
rake serverspec:consul_node_a  # Run serverspec to consul_node_a
rake serverspec:consul_node_b  # Run serverspec to consul_node_b
rake serverspec:consul_node_c  # Run serverspec to consul_node_c
rake spec

> rake spec
Running specs on consul_node_a
/opt/chefdk/embedded/bin/ruby -I/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-support-3.3.0/lib:/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/lib /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/exe/rspec --pattern spec/\{consul_server,nat_node\}/\*_spec.rb

Process "consul"
  should be running
  user
    should eq "consul"

Port "8300"
  should be listening

Finished in 5.88 seconds (files took 0.56516 seconds to load)
3 examples, 0 failures

Running specs on consul_node_b
/opt/chefdk/embedded/bin/ruby -I/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-support-3.3.0/lib:/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/lib /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/exe/rspec --pattern spec/\{consul_server,nat_node\}/\*_spec.rb

Process "consul"
  should be running
  user
    should eq "consul"

Port "8300"
  should be listening

Finished in 3.51 seconds (files took 0.55459 seconds to load)
3 examples, 0 failures

Running specs on consul_node_c
/opt/chefdk/embedded/bin/ruby -I/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-support-3.3.0/lib:/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/lib /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/rspec-core-3.3.2/exe/rspec --pattern spec/\{consul_server,nat_node\}/\*_spec.rb

Process "consul"
  should be running
  user
    should eq "consul"

Port "8300"
  should be listening

Finished in 2.46 seconds (files took 0.56116 seconds to load)
3 examples, 0 failures

Things to note

If you make a change to the template file or the null resource, terraform doesn't always do the right thing on reapply. Just taint the resources first:

> terraform taint template_file.serverspec_properties
> terraform taint null_resource.serverspec_properties
> terraform plan

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

-/+ null_resource.serverspec_properties

-/+ template_file.serverspec_properties
    filename:           "" => "templates/properties.tmpl.yml"
    rendered:           "" => "<computed>"
    vars.#:             "" => "4"
    vars.consul_node_a: "" => "1.1.1.1"
    vars.consul_node_b: "" => "2.2.2.2"
    vars.consul_node_c: "" => "3.3.3.3"
    vars.keyfile:       "" => "sample-tf-org-deployer-key"


Plan: 2 to add, 0 to change, 0 to destroy.

> terraform apply
${consul_node_a}:
name: consul_node_a
user: "admin"
keys: ["${keyfile}"]
:roles:
- consul_server
- nat_node
${consul_node_b}:
name: consul_node_b
user: "admin"
keys: ["${keyfile}"]
:roles:
- consul_server
- nat_node
${consul_node_c}:
name: consul_node_c
user: "admin"
keys: ["${keyfile}"]
:roles:
- consul_server
- nat_node
require 'rake'
require 'rspec/core/rake_task'
require 'yaml'
properties = YAML.load_file('properties.yml')
desc "Run serverspec to all hosts"
task :spec => 'serverspec:all'
namespace :serverspec do
task :all => properties.keys.map {|key| "serverspec:#{properties[key]['name']}"}
properties.keys.each do |key|
desc "Run serverspec to #{properties[key]['name']}"
RSpec::Core::RakeTask.new(properties[key]['name'].to_sym) do |t|
puts "Running specs on #{properties[key]['name']}"
ENV['TARGET_HOST'] = key
t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb'
end
end
end
resource "template_file" "serverspec_properties" {
filename = "templates/properties.tmpl.yml"
vars {
keyfile = "${aws_key_pair.deployer.key_name}"
consul_node_a = "${module.vpc.nat_a.public_ip}"
consul_node_b = "${module.vpc.nat_b.public_ip}"
consul_node_c = "${module.vpc.nat_c.public_ip}"
}
}
resource "null_resource" "serverspec_properties" {
provisioner "local-exec" {
command = "printf '${template_file.serverspec_properties.rendered}' > ${path.cwd}/properties.yml"
}
}
require 'serverspec'
require 'net/ssh'
require 'net/ssh/proxy/command'
require 'yaml'
properties = YAML.load_file('properties.yml')
set :backend, :ssh
host = ENV['TARGET_HOST']
set :host, host
set :ssh_options, {
:user => properties[host]['user'],
:keys => properties[host]['keys'],
:auth_methods => ["publickey"]
}
set :request_pty, true
set :env, :LANG => 'C', :LC_MESSAGES => 'C'
set :path, '/sbin:/usr/local/sbin:$PATH'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment