You are on page 1of 206

Test-Driven Infrastructure

sethvargo@opscode.com
yz E b

BB

WORKSTATION SETUP

BB

#LEARNCHEF

BB

BB

cl.ly/PiLZ sha: 821ecd0

$ knife client list

!!

GITHUB

fd

CREATE THE COOKBOOK

$ knife cookbook create {username}-myface

$ knife cookbook create sethvargo-myface

f myface f recipes d default.rb d metadata.rb

f myface f recipes d default.rb d metadata.rb

f myface f recipes d default.rb d metadata.rb

f myface f recipes d default.rb d metadata.rb

f myface f recipes d default.rb d metadata.rb

i myface

$ (sudo) gem install bundler

gist.github.com/5737141

f myface d Gemfile

Gemfile
source 'https://rubygems.org' group :test do gem 'chefspec', '~> gem 'foodcritic', '~> gem 'strainer', '~> gem 'test-kitchen', '~> gem 'kitchen-lxc', '~> gem 'knife-spork', '~> gem 'hipchat', '~> 1.3' 2.1' 3.0' 1.0.0.alpha' 0.0.1.beta1' 1.0.17' 0.10.0'

gem 'guard', '~> 1.8' gem 'guard-foodcritic', '~> 1.0' gem 'guard-rspec', '~> 3.0' end

$ (sudo) bundle

f myface f spec d default_spec.rb d spec_helper.rb

f myface f spec d default_spec.rb d spec_helper.rb

f myface f spec d default_spec.rb d spec_helper.rb

spec/default_spec.rb
require 'spec_helper' describe 'sethvargo-myface::default' do let(:chef_run) do run = ChefSpec::ChefRunner.new(platfrom: 'ubuntu', version: '12.04') run.converge('sethvargo-myface::default') end it 'installs apache2' do expect(chef_run).to install_package('apache2') end # ... end

f myface f spec d default_spec.rb d spec_helper.rb

spec/spec_helper.rb
require 'chefspec'

f myface d Guardfile d Strainerfile

f myface d Guardfile d Strainerfile

Guardfile
guard :rspec, all_on_start: false do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^(recipes)/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { 'spec' } end ! guard :foodcritic, cookbook_paths: '.', all_on_start: false do watch(%r{attributes/.+\.rb$}) watch(%r{providers/.+\.rb$}) watch(%r{recipes/.+\.rb$}) watch(%r{resources/.+\.rb$}) end

$ bundle exec guard

!+ S

0.0134s

f myface d Guardfile d Strainerfile

Strainerfile
knife: foodcritic: rspec: bundle exec knife cookbook test $COOKBOOK bundle exec foodcritic $SANDBOX/$COOKBOOK -f any (cd $COOKBOOK && bundle exec rspec --color)

f myface f recipes d default.rb d metadata.rb

f myface f recipes d default.rb d metadata.rb

recipes/default.rb
package 'apache2' service 'apache2' do action [:enable, :start] end

!+ S

POLICY CHANGE

RUN
UNIT TESTS
BEFORE MERGING TO

MASTER

RUN
UNIT TESTS
BEFORE MERGING TO

MASTER

u HUMAN (PROS) D h a J K

HUMAN (CONS) t

7 c L r M

CREATE REPO

GITHUB INTERFACE
?

T myface

T (anything)

Create repository

$ git add . $ git commit -m "myface" $ git push

SETUP JOB

" NO ASSHOLE PLEDGE


I, _________, solemnly swear under penalty of my GPU catching fire, that I will not touch anyone's build but my own.

ci.trychef.com

J New

Job

T {username}-myface

J Build

a free-style...

J OK

T (github

URL)

J Git

T git://github.com/...

J Build

when a change...

J Color

ANSI Console...

J Add

Build Step

J Execute

Shell

bundle install --path ../../support/vendor bundle exec strainer test

J Email

Notification

T (your

email)

J Save

J Build

Now

J (the

build)

SETUP HOOK

J Settings

J Service

Hooks

J Jenkins

(GitHub Plugin)

http://ci.trychef.com/github-webhook/

http://ci.trychef.com/github-webhook/

IMPORTANT

J Active

J Update

settings

$ git checkout -b add_site

f myface f spec d default_spec.rb d spec_helper.rb

spec/default_spec.rb
describe 'sethvargo-myface::default' do # pre-existing tests # ... it 'creates the default template' do expect(chef_run).to create_file('/var/www/index.html') end it 'creates the site with the correct content' do template = chef_run.template('/var/www/index.html') expect(template.owner).to eq('root') expect(template.group).to eq('root') end end

f myface f recipes d default.rb d metadata.rb

recipes/default.rb
package 'apache2' service 'apache2' do action [:enable, :start] end template '/var/www/index.html' do owner 'root' group 'root' mode '0755' source 'index.html.erb' end

f myface f templates f default d index.html.erb

templates/default/index.html.erb
<html> <head> <title>Welcome to <%= node['fqdn'] %></title> </head> <body> <p>Here's everything you need to know about <%= node['fqdn'] %>:</p> <pre><%= JSON.pretty_generate(node.to_hash) %></pre> </body> </html>

$ git add . $ git commit -m "Write out default site"

$ git push origin add_site

J Pull

Request

J Send

pull request

J Merge

pull request

J (the

build)

J Console

Output

LET'S FIX THAT

$ git pull origin master

metadata.rb
name maintainer maintainer_email license description long_description version 'myface' 'YOUR_COMPANY_NAME' 'YOUR_EMAIL' 'All rights reserved' 'Installs/Configures myface' IO.read(File.join(File.dirname(__FILE__), 'README.md')) '0.1.0'

metadata.rb
name maintainer maintainer_email license description long_description version 'myface' 'Seth Vargo' 'sethvargo@opscode.com' 'All rights reserved' 'Installs/Configures myface' IO.read(File.join(File.dirname(__FILE__), 'README.md')) '0.1.0'

$ git add metadata.rb $ git commit -m "Fix metadata"

$ git push origin master

AWESOME

$ knife cookbook upload myface

WHY?

1 WE HAVE TESTS

1 WE HAVE TESTS 2 WE HAVE JENKINS

1 WE HAVE TESTS 2 WE HAVE JENKINS 3 WE HAVE THE TECHNOLOGY

$ knife cookbook upload myface

$ knife cookbook upload myface

$ knife cookbook upload myface

J Configure

J Add

post-build action

J Post

build task

T Strainer

marked build OK

J
T

bundle exec knife cookbook upload {username}-myface

J Save

J Build

Now

J (the

build)

J Console

Output

> knife cookbook list | grep sethvargo-myface sethvargo-myface 0.1.0

AWESOME

WE KNOW WHEN OUR BUILD FAILS


BECAUSE WE GET AN EMAIL FROM JENKINS

BUT DO WE KNOW
ARE UPLOADED TO OUR CHEF SERVER?

WHEN OUR COOKBOOKS

BUT DO WE KNOW
ARE UPLOADED TO OUR CHEF SERVER?

WHEN OUR COOKBOOKS

jonlives/knife-spork

jonlives/knife-spork

jonlives/knife-spork

J Configure

bundle exec knife spork upload {username}-myface

J Escalate

script execution...

J Save

hipchat.com/sign_in

J Launch

the web app

J Try

Chef

J Build

Now

THERE'S MORE!

THERE'S MORE!

TEST KITCHEN!

f myface d .kitchen.yml

.kitchen.yml
driver_plugin: lxc driver_config: use_sudo: true platforms: - name: ubuntu-12.04 driver_config: base_container: ubuntu_12.04 username: ubuntu password: ubuntu suites: - name: default run_list: ["recipe[myface]"] attributes: {}

Strainerfile
knife: foodcritic: rspec: kitchen: bundle exec knife cookbook test $COOKBOOK bundle exec foodcritic $SANDBOX/$COOKBOOK -f any (cd $COOKBOOK && bundle exec rspec --color) (cd $COOKBOOK && bundle exec kitchen test)

metadata.rb
name maintainer maintainer_email license description long_description version 'myface' 'Seth Vargo' 'sethvargo@opscode.com' 'All rights reserved' 'Installs/Configures myface' IO.read(File.join(File.dirname(__FILE__), 'README.md')) '0.1.0'

metadata.rb
name maintainer maintainer_email license description long_description version 'myface' 'Seth Vargo' 'sethvargo@opscode.com' 'All rights reserved' 'Installs/Configures myface' IO.read(File.join(File.dirname(__FILE__), 'README.md')) '0.1.1'

$ git add . $ git commit -m "Add Test Kitchen"

$ git push origin master

UH OH!

$ git revert HEAD

$ git revert HEAD

some_recipe.rb
template owner group source end '/etc/foo/bar' do 'root' 'root' 'bar.erb'

some_recipe.rb
template owner group source end '/etc/foo/bar' do 'root' 'root' 'bar.erb'

IF WE REMOVE THIS
WILL THE FILE BE DELETED?

some_recipe.rb
template owner group source end '/etc/foo/bar' do 'root' 'root' 'bar.erb'

NO

FAST FORWARD CHANGES


ARE ABSOLUTELY ALWAYS BETTER THAN REVERTING

some_recipe.rb
template owner group source end '/etc/foo/bar' do 'root' 'root' 'bar.erb'

some_recipe.rb
template owner group source action end '/etc/foo/bar' do 'root' 'root' 'bar.erb' :delete

$ git add . $ git commit -m "Revert abc123" $ git push origin master

chef + environments safer infrastructure

environments/production.json
{ "name" : "production", "description" : "Production cluster in EC2", "override_attributes" : { ... }, "default_attributes" : { ... } }

environments/production.json
{ "name" : "production", "description" : "Production cluster in EC2", "override_attributes" : { ... }, "default_attributes" : { ... }, "cookbook_versions" : { "myface": "0.1.0" }, }

CD

CD

PUSH

CD

PUSH

sethvargo@opscode.com
yz E b

pdiffs: youtube.com/watch?v=UMnZiTL0tUc Travis CI: travis-ci.org

You might also like