strainer 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@strainer --create
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2012 CustomInk
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,39 @@
1
+ # Strainer
2
+
3
+ Strainer is a gem for isolating and testing individual chef cookbooks. It allows you to keep all your cookbooks in a single repository (saving you time and money), while having all the benefits of individual repositories.
4
+
5
+ Usage
6
+ -----
7
+ Strainer is a command line tool. The first thing you should do is add the following entry into your `.gitignore` and `chefignore` files:
8
+
9
+ colander
10
+
11
+ The `colander` is where strainer puts all your temporary files for testing.
12
+
13
+ Next, create a `Colanderfile`. Cookbook-level Colanderfiles will take precedence over root-level ones. For simplicity, let's just create one in the root of our chef repository:
14
+
15
+ # Colanderfile
16
+ knife test: bundle exec knife cookbook test $COOKBOOK
17
+ foodcritic: bundle exec foodcritic -f any $COOKBOOK
18
+
19
+ `Colanderfile` exposes two variables:
20
+
21
+ - `$COOKBOOK` - the current running cookbook
22
+ - `$SANDBOX` - the sandbox path
23
+
24
+ Just like foreman, the labels don't actually matter - they are only used in formatting the output.
25
+
26
+ That `Colanderfile` will run [foodcritic](https://github.com/acrmp/foodcritic) and knife test. I recommend this as the bare minimum for a cookbook test.
27
+
28
+ To strain, simply run the `strain` command and pass in the cookbooks to strain:
29
+
30
+ # strain phantomjs and tmux
31
+ bundle exec strain phantomjs tmux
32
+
33
+ This will run `knife test` and `foodcritic` against both of the cookbooks. You can pass in as many cookbooks are you'd like.
34
+
35
+ Needs Your Help
36
+ ---------------
37
+ This is a list of features or problem *you* can help solve! Fork and submit a pull request to make Strain even better!
38
+
39
+ - **Threading** - Run each cookbook's tests (or each cookbook tests test) in a separate thread
@@ -0,0 +1,2 @@
1
+ #! /usr/bin/env rake
2
+ require 'bundler/gem_tasks'
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'strainer'
7
+ require 'strainer/cli'
8
+
9
+ args = ARGV.dup
10
+ ARGV.clear
11
+
12
+ Strainer::CLI.run(args)
@@ -0,0 +1,9 @@
1
+ require 'strainer/color'
2
+ require 'strainer/runner'
3
+ require 'strainer/sandbox'
4
+
5
+ module Strainer
6
+ def self.root
7
+ @@root ||= File.expand_path('../../', __FILE__)
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module Strainer
2
+ class CLI
3
+ def self.run(*cookbooks)
4
+ @sandbox = Strainer::Sandbox.new(cookbooks)
5
+ @runner = Strainer::Runner.new(@sandbox)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ require 'term/ansicolor'
2
+
3
+ module Strainer
4
+ class Color
5
+ extend Term::ANSIColor
6
+ end
7
+ end
@@ -0,0 +1,91 @@
1
+ module Strainer
2
+ class Runner
3
+ def initialize(sandbox, options = {})
4
+ @sandbox = sandbox
5
+ @cookbooks = @sandbox.cookbooks
6
+
7
+ @results = Hash.new(0)
8
+
9
+ @cookbooks.each do |cookbook|
10
+ puts Color.negative{ "# Straining '#{cookbook}'" }
11
+ commands_for(cookbook).collect do |command|
12
+ run(command)
13
+ end
14
+ puts "\n\n\n"
15
+ end
16
+
17
+ abort unless @results[:failed].zero?
18
+ end
19
+
20
+ private
21
+ def commands_for(cookbook)
22
+ file = File.read( colanderfile_for(cookbook) )
23
+
24
+ file = file.strip
25
+ file = file.gsub('$COOKBOOK', cookbook)
26
+ file = file.gsub('$SANDBOX', @sandbox.sandbox_path)
27
+
28
+ lines = file.split("\n").reject{|c| c.strip.empty?}.compact
29
+
30
+ # parse the line and split it into the label and command parts
31
+ #
32
+ # example line: foodcritic -f any phantomjs
33
+ lines.collect do |line|
34
+ split_line = line.split(':', 2)
35
+
36
+ {
37
+ :label => split_line[0].strip,
38
+ :command => split_line[1].strip
39
+ }
40
+ end || []
41
+ end
42
+
43
+ def colanderfile_for(cookbook)
44
+ cookbook_level = File.join(@sandbox.sandbox_path(cookbook), 'Colanderfile')
45
+ root_level = File.expand_path('Colanderfile')
46
+
47
+ if File.exists?(cookbook_level)
48
+ cookbook_level
49
+ elsif File.exists?(root_level)
50
+ root_level
51
+ else
52
+ raise "Could not find Colanderfile in #{cookbook_level} or #{root_level}"
53
+ end
54
+ end
55
+
56
+ def label_with_padding(label)
57
+ max_length = 12
58
+ colors = [ :blue, :cyan, :magenta, :yellow ]
59
+ color = colors[label.length%colors.length]
60
+
61
+ Color.send(color) do
62
+ "#{label[0...max_length].ljust(max_length)} | "
63
+ end
64
+ end
65
+
66
+ def run(command)
67
+ label = command[:label]
68
+ command = command[:command]
69
+
70
+ puts [ label_with_padding(label), Color.bold{ Color.underscore{ command } } ].join(' ')
71
+ output(label, `cd #{@sandbox.sandbox_path} && #{command}`)
72
+
73
+ if $?.success?
74
+ @results[:passed] += 1
75
+ output(label, Color.green{'Success!'})
76
+ return true
77
+ else
78
+ @results[:failed] += 1
79
+ output(label, Color.red{'Failure!'})
80
+ return false
81
+ end
82
+ end
83
+
84
+ def output(label, data)
85
+ data.to_s.strip.split("\n").each do |line|
86
+ $stdout.puts [ label_with_padding(label), line ].join(' ')
87
+ $stdout.flush
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ module Strainer
4
+ class Sandbox
5
+ attr_reader :cookbooks
6
+
7
+ def initialize(cookbooks = [], options = {})
8
+ @cookbooks = [cookbooks].flatten
9
+ @options = options
10
+
11
+ clear_sandbox
12
+ create_sandbox
13
+ end
14
+
15
+ def cookbook_path(cookbook)
16
+ path = File.join(cookbooks_path, cookbook)
17
+ raise "cookbook '#{cookbook}' was not found in #{cookbooks_path}" unless File.exists?(path)
18
+ return path
19
+ end
20
+
21
+ def sandbox_path(cookbook = nil)
22
+ File.expand_path( File.join(%W(colander cookbooks #{cookbook})) )
23
+ end
24
+
25
+ private
26
+ def cookbooks_path
27
+ @cookbooks_path ||= (@options[:cookbooks_path] || File.expand_path('cookbooks'))
28
+ end
29
+
30
+ def clear_sandbox
31
+ FileUtils.rm_rf(sandbox_path)
32
+ end
33
+
34
+ def create_sandbox
35
+ FileUtils.mkdir_p(sandbox_path)
36
+
37
+ copy_globals
38
+ copy_cookbooks
39
+ place_knife_rb
40
+ end
41
+
42
+ def copy_globals
43
+ files = %w(.rspec spec test)
44
+ FileUtils.cp_r( Dir["{#{files.join(',')}}"], sandbox_path('..') )
45
+ end
46
+
47
+ def copy_cookbooks
48
+ @cookbooks.each do |cookbook|
49
+ FileUtils.cp_r(cookbook_path(cookbook), sandbox_path)
50
+ end
51
+ end
52
+
53
+ def place_knife_rb
54
+ chef_path = File.join(sandbox_path, '..','.chef')
55
+ FileUtils.mkdir_p(chef_path)
56
+
57
+ # build the contents
58
+ contents = <<-EOH
59
+ cache_type 'BasicFile'
60
+ cache_options(:path => "\#{ENV['HOME']}/.chef/checksums")
61
+ cookbook_path '#{sandbox_path}'
62
+ EOH
63
+
64
+ # create knife.rb
65
+ File.open("#{chef_path}/knife.rb", 'w+'){ |f| f.write(contents) }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.version = '0.0.2'
3
+ gem.authors = ['Seth Vargo']
4
+ gem.email = ['sethvargo@gmail.com']
5
+ gem.description = %q{Run isolated cookbook tests against your chef repository with Strainer.}
6
+ gem.summary = %q{Strainer allows you to run cookbook tests in an isolated environment while still keeping a single Gemfile and repository for all your cookbooks.}
7
+ gem.homepage = 'https://github.com/customink/strainer'
8
+
9
+ gem.files = `git ls-files`.split($\)
10
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
11
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
12
+ gem.name = 'strainer'
13
+ gem.require_paths = ['lib']
14
+
15
+ gem.add_runtime_dependency 'term-ansicolor', '~> 1.0.7'
16
+
17
+ gem.add_development_dependency 'yard', '~> 0.8.2'
18
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strainer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Seth Vargo
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: term-ansicolor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.7
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.7
30
+ - !ruby/object:Gem::Dependency
31
+ name: yard
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.8.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.8.2
46
+ description: Run isolated cookbook tests against your chef repository with Strainer.
47
+ email:
48
+ - sethvargo@gmail.com
49
+ executables:
50
+ - strain
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - .rvmrc
56
+ - Gemfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - bin/strain
61
+ - lib/strainer.rb
62
+ - lib/strainer/cli.rb
63
+ - lib/strainer/color.rb
64
+ - lib/strainer/runner.rb
65
+ - lib/strainer/sandbox.rb
66
+ - strainer.gemspec
67
+ homepage: https://github.com/customink/strainer
68
+ licenses: []
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ! '>='
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.24
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Strainer allows you to run cookbook tests in an isolated environment while
91
+ still keeping a single Gemfile and repository for all your cookbooks.
92
+ test_files: []
93
+ has_rdoc: