butcher 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .idea
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ tmp/*
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p125
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in butcher.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,18 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch(%r{spec/support/.+\.rb}) { "spec" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+ end
10
+
11
+
12
+ guard 'cucumber' do
13
+ watch(%r{^bin/(.+)$}) { |m| "features/#{m[1]}.feature" }
14
+ watch(%r{^lib/butcher/(.+)$}) { |m| "features/#{m[1]}.feature" }
15
+ watch(%r{^features/.+\.feature$})
16
+ watch(%r{^features/support/.+$}) { 'features' }
17
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
18
+ end
data/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # Butcher
2
+
3
+ Butcher is a set of command line tools intended to ease the use of Chef with a managed
4
+ Opscode account.
5
+
6
+ ## Commands
7
+
8
+ The following commands are currently available. All commands should be run from the top
9
+ level of a chef directory.
10
+
11
+ ### Stab
12
+
13
+ SSH into a node based on a grep of the node name.
14
+
15
+ Usage: stab [options] node_name
16
+ -c, --cache-dir DIR # saves node list here (default: ~/.butcher/cache)
17
+ -f, --force # download new node list even if a cache file exists
18
+ -h, --help # prints usage
19
+ -l, --login LOGIN # ssh with specified username
20
+ -v, --verbose # be expressive
21
+
22
+ Node name is loosely matched against name attributes given to nodes in chef. If multiple
23
+ nodes match a given string, a Butcher::AmbiguousNode error is thrown and the program exits.
24
+
25
+ > knife status
26
+ 1 hours ago, app.node, app.domain.com, 1.1.1.1, solaris2 5.11.
27
+ 5 minutes ago, other.node, other.domain.com, 1.1.1.2, solaris2 5.11.
28
+
29
+ > stab something
30
+ Unable to find node "something"
31
+
32
+ > stab node
33
+ Multiple nodes match "node"
34
+ ["app.node", "app.domain.com"] => 1.1.1.1
35
+ ["other.node", "other.domain.com"] => 1.1.1.2
36
+
37
+ Nodes are cached in a file named after your organization in Opscode. Stab discovers this
38
+ by looking at the chef_server_url in your knife.rb. For this reason, stab can only be run
39
+ from the top level of a chef repo.
40
+
41
+
42
+ ## License
43
+
44
+ Copyright 2012 ModCloth
45
+
46
+ Licensed under the Apache License, Version 2.0 (the "License");
47
+ you may not use this file except in compliance with the License.
48
+ You may obtain a copy of the License at
49
+
50
+ http://www.apache.org/licenses/LICENSE-2.0
51
+
52
+ Unless required by applicable law or agreed to in writing, software
53
+ distributed under the License is distributed on an "AS IS" BASIS,
54
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55
+ See the License for the specific language governing permissions and
56
+ limitations under the License.
57
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/stab ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'butcher'
4
+ require 'optparse'
5
+
6
+ EX_USAGE = 64
7
+ EX_UNMATCHED = 65
8
+ EX_AMBIGUOUS = 66
9
+ EX_KNIFE = 67
10
+
11
+ options = {}
12
+ parser = OptionParser.new do |opts|
13
+ opts.banner = "Usage: stab [options] node_name"
14
+
15
+ opts.on('-c', '--cache-dir DIR', 'Location to save cache files (default: ~/.butcher/cache)') do |dir|
16
+ ENV["CACHE_DIR"] = dir
17
+ end
18
+
19
+ opts.on('-f', '--force', 'Check for new nodes even if a cache file exists') do
20
+ options[:force] = true
21
+ end
22
+
23
+ opts.on('-h', '--help', 'Display this screen') do
24
+ puts opts
25
+ exit
26
+ end
27
+
28
+ opts.on('-l', '--login LOGIN', 'Use LOGIN for ssh session') do |login|
29
+ options[:login] = login
30
+ end
31
+
32
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
33
+ options[:verbose] = v
34
+ end
35
+ end
36
+
37
+ begin parser.parse!
38
+ rescue OptionParser::InvalidOption => e
39
+ puts e
40
+ puts parser
41
+ exit EX_USAGE
42
+ end
43
+
44
+ if ARGV == []
45
+ puts parser
46
+ exit EX_USAGE
47
+ end
48
+
49
+ begin
50
+ Butcher::Stab::CLI.new.run(ARGV, options)
51
+ rescue Butcher::UnmatchedNode
52
+ STDERR.puts %Q{Unable to find node "#{ARGV.first}"}
53
+ exit EX_UNMATCHED
54
+ rescue Butcher::AmbiguousNode => e
55
+ STDERR.puts %Q{Multiple nodes match "#{ARGV.first}"}
56
+ STDERR.puts e.message
57
+ exit EX_AMBIGUOUS
58
+ rescue Butcher::NoKnifeRB
59
+ STDERR.puts "Unable to find knife.rb in ./.chef"
60
+ STDERR.puts "Are you stabbing from a valid working chef directory?"
61
+ exit EX_KNIFE
62
+ rescue Butcher::NoKnifeOrganization
63
+ STDERR.puts "Unable to read organization from knife.rb"
64
+ STDERR.puts "Expected .chef/knife.rb to contain a chef_server_url"
65
+ exit EX_KNIFE
66
+ end
data/butcher.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "butcher/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "butcher"
7
+ s.version = Butcher::VERSION
8
+ s.authors = ["ModCloth", "Eric Saxby"]
9
+ s.email = %W(git@modcloth.com)
10
+ s.homepage = "https://github.com/modcloth/butcher"
11
+ s.summary = %q{All the things to make a chef}
12
+ s.description = %q{Chef is a tool for managing server automation. A good butcher makes for a good chef.}
13
+
14
+ s.rubyforge_project = "butcher"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = %W(lib)
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec", "> 2"
23
+ s.add_development_dependency "aruba"
24
+ s.add_development_dependency "aruba-doubles"
25
+ s.add_development_dependency "guard-rspec"
26
+ s.add_development_dependency "guard-cucumber"
27
+ s.add_development_dependency "mocha"
28
+ # s.add_runtime_dependency "x"
29
+ end
@@ -0,0 +1,121 @@
1
+ Feature: Stab
2
+
3
+ Background:
4
+ # stubbing knife currently doesn't work, but tests pass anyways
5
+ #Given I could run `knife status` with stdout:
6
+ #"""
7
+ #1 minute ago, app.node, app.domain, 1.1.1.1, os
8
+ #"""
9
+ Given I have a knife configuration file
10
+ And I could run `ssh 1.1.1.1` with stdout:
11
+ """
12
+ ssh yay!
13
+ """
14
+
15
+ Scenario: Usage information with --help option
16
+ When I run `stab --help`
17
+ Then the exit status should be 0
18
+ And the output should contain:
19
+ """
20
+ Usage: stab [options] node_name
21
+ """
22
+
23
+ Scenario: Usage information when no node name is given
24
+ When I run `stab`
25
+ Then the exit status should be 64
26
+ And the output should contain:
27
+ """
28
+ Usage: stab [options] node_name
29
+ """
30
+
31
+ Scenario: Invalid option
32
+ When I run `stab --invalid-option`
33
+ Then the exit status should be 64
34
+ And the output should contain:
35
+ """
36
+ invalid option: --invalid-option
37
+ Usage: stab [options] node_name
38
+ """
39
+
40
+ Scenario: Set cache directory
41
+ Given a directory named "tmp/test_dir" should not exist
42
+ When I run `stab app.node -c tmp/test_dir`
43
+ Then a directory named "tmp/test_dir" should exist
44
+
45
+ Scenario: Tell user what IP address ssh uses
46
+ Given I have the following chef nodes:
47
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
48
+ When I run `stab app.node -c tmp/test -v`
49
+ Then the output should contain "Connecting to app.node at 1.1.1.1"
50
+ Then the output should contain "ssh yay!"
51
+
52
+ Scenario: Don't download node list if already cached
53
+ Given I have the following chef nodes:
54
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
55
+ Then a file named "tmp/test/my_organization.cache" should exist
56
+ When I run `stab app.node -c tmp/test -v`
57
+ Then the output should not contain "Creating cache file of nodes"
58
+
59
+ Scenario: Force download of cache file
60
+ Given I have the following chef nodes:
61
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
62
+ When I run `stab app.node -c tmp/test -f -v`
63
+ Then the output should contain "Creating cache file of nodes"
64
+ #And the exit status should be 0 ## Butcher::Cache does not use stubbed knife
65
+
66
+ Scenario: User sees error message if no node matches given name
67
+ Given I have the following chef nodes:
68
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
69
+ When I run `stab some.node -c tmp/test`
70
+ Then the stderr should contain:
71
+ """
72
+ Unable to find node "some.node"
73
+ """
74
+ And the exit status should be 65
75
+
76
+ Scenario: User sees error message if multiple nodes match given name
77
+ Given I have the following chef nodes:
78
+ | 1 minute ago | other.node | other.domain | 1.1.1.2 | os |
79
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
80
+ When I run `stab node -c tmp/test`
81
+ Then the stderr should contain:
82
+ """
83
+ Multiple nodes match "node"
84
+ ["app.node", "app.domain"] => 1.1.1.1
85
+ ["other.node", "other.domain"] => 1.1.1.2
86
+ """
87
+ And the exit status should be 66
88
+
89
+ Scenario: User can connect to server with given user name
90
+ Given I have the following chef nodes:
91
+ | 1 minute ago | app.node | app.domain | 1.1.1.1 | os |
92
+ Given I could run `ssh 1.1.1.1 -l user` with stdout:
93
+ """
94
+ user: I'm a computer!
95
+ """
96
+ When I run `stab app.node -c tmp/test -l user`
97
+ Then the stdout should contain:
98
+ """
99
+ user: I'm a computer!
100
+ """
101
+
102
+ Scenario: User sees error message if knife.rb cannot be found
103
+ Given I don't have a knife configuration file
104
+ When I run `stab app.node -c tmp/test`
105
+ Then the stderr should contain:
106
+ """
107
+ Unable to find knife.rb in ./.chef
108
+ Are you stabbing from a valid working chef directory?
109
+ """
110
+ And the exit status should be 67
111
+
112
+ Scenario: User sees error message if knife.rb is invalid
113
+ Given I have an invalid knife configuration file
114
+ When I run `stab app.node -c tmp/test`
115
+ Then the stderr should contain:
116
+ """
117
+ Unable to read organization from knife.rb
118
+ Expected .chef/knife.rb to contain a chef_server_url
119
+ """
120
+ And the exit status should be 67
121
+
@@ -0,0 +1,32 @@
1
+ Given /I (don't|) ?have a knife configuration file/ do |expectation|
2
+ if expectation == "don't"
3
+ steps %q{
4
+ When I remove the file ".chef/knife.rb"
5
+ }
6
+ else
7
+ steps %q{
8
+ Given a file named ".chef/knife.rb" with:
9
+ """
10
+ chef_server_url "https://opscode.url/organizations/my_organization"
11
+ """
12
+ }
13
+ end
14
+ end
15
+
16
+ Given /I have an invalid knife configuration file/ do
17
+ steps %q{
18
+ Given a file named ".chef/knife.rb" with:
19
+ """
20
+ some invalid content
21
+ """
22
+ }
23
+
24
+ end
25
+
26
+ Given /^I have the following chef nodes:$/ do |table|
27
+ create_cache_file("my_organization.cache") do |f|
28
+ table.raw.each do |row|
29
+ f.puts row.join(", ")
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ Before do
2
+ @dirs = ["."]
3
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'aruba-doubles/cucumber'
4
+ require 'aruba/cucumber'
5
+ require 'fileutils'
6
+ require 'rspec/expectations'
7
+ require 'butcher'
8
+
9
+ Dir["#{File.dirname(__FILE__)}/../../spec/support/**/*.rb"].each { |f| require f unless /_spec\.rb$/.match(f) }
@@ -0,0 +1,15 @@
1
+ require "mocha"
2
+
3
+ World(Mocha::API)
4
+
5
+ Before do
6
+ mocha_setup
7
+ end
8
+
9
+ After do
10
+ begin
11
+ mocha_verify
12
+ ensure
13
+ mocha_teardown
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ World(Butcher::TestCache::TestHelpers)
2
+
3
+ Before { Butcher::TestCache.reset }
4
+ After { Butcher::TestCache.cleanup }
data/lib/butcher.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "butcher/version"
2
+
3
+ module Butcher
4
+ class UnmatchedNode < Exception;end
5
+ class AmbiguousNode < Exception;end
6
+ class NoKnifeRB < Exception;end
7
+ class NoKnifeOrganization < Exception;end
8
+ end
9
+
10
+ require 'butcher/stab'
11
+ require 'butcher/stab/cli'
12
+ require 'butcher/cache'
@@ -0,0 +1,65 @@
1
+ require 'singleton'
2
+
3
+ class Butcher::Cache
4
+ include Singleton
5
+
6
+ CACHE_DIR = "#{ENV["HOME"]}/.butcher/cache"
7
+ KNIFE_FILE = ".chef/knife.rb"
8
+
9
+ def initialize
10
+ FileUtils.mkdir_p(cache_dir)
11
+ end
12
+
13
+ def nodes(options = {})
14
+ hash = {}
15
+ cache_file(options) do |file|
16
+ while file.gets
17
+ node = $_.split(", ")
18
+ hash[node[3]] = [node[1],node[2]]
19
+ end
20
+ end
21
+ hash
22
+ end
23
+
24
+ def cache_dir # :nodoc:
25
+ ENV["CACHE_DIR"] || CACHE_DIR
26
+ end
27
+
28
+ def self.format_nodes_for_stderr(nodes)
29
+ nodes.map do |key, value|
30
+ %Q{#{value} => #{key}}
31
+ end.sort.join("\n")
32
+ end
33
+
34
+ def nodes_file
35
+ "#{cache_dir}/#{organization}.cache"
36
+ end
37
+
38
+ private
39
+
40
+ def organization
41
+ raise Butcher::NoKnifeRB unless(File.exists?(KNIFE_FILE))
42
+ if m = File.read(KNIFE_FILE).match(/chef_server_url\s+".+organizations\/([^\/"]+)"/)
43
+ m[1]
44
+ else
45
+ raise Butcher::NoKnifeOrganization
46
+ end
47
+ end
48
+
49
+ def create_node_cachefile
50
+ File.open(nodes_file, "w") do |file|
51
+ file.puts %x[knife status]
52
+ end
53
+ end
54
+
55
+ def cache_file(options, &block)
56
+ if options[:force] || !File.exists?(nodes_file)
57
+ puts "Creating cache file of nodes" if options[:verbose]
58
+ create_node_cachefile
59
+ end
60
+
61
+ File.open(nodes_file) do |f|
62
+ block.call f
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ module Butcher::Stab
2
+ end
@@ -0,0 +1,36 @@
1
+ class Butcher::Stab::CLI
2
+ attr_accessor :node_matcher
3
+ attr_accessor :options
4
+
5
+ def run(arguments, options = {})
6
+ self.options = options
7
+ self.node_matcher = Array(arguments).first
8
+ return "" if node_matcher.nil?
9
+
10
+ ssh_to(matching_node)
11
+ end
12
+
13
+ private
14
+
15
+ def ssh_to(ip)
16
+ STDOUT.sync = true # exec takes over stdout in tests, so sync output
17
+ puts "Connecting to #{node_matcher} at #{ip}" if options[:verbose]
18
+ exec("ssh #{ip}#{ssh_options}")
19
+ end
20
+
21
+ def matching_node
22
+ nodes = Butcher::Cache.instance.nodes(options).select do |k, v|
23
+ String(v).include? self.node_matcher
24
+ end
25
+
26
+ raise(Butcher::UnmatchedNode) if nodes.size == 0
27
+ raise(Butcher::AmbiguousNode, Butcher::Cache.format_nodes_for_stderr(nodes)) if nodes.size > 1
28
+ nodes.keys.first
29
+ end
30
+
31
+ def ssh_options
32
+ if options[:login]
33
+ " -l #{options[:login]}"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module Butcher
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe Butcher::Cache, "initialization" do
4
+ ## singletons do not reset after initialization, so we run tests against clones
5
+ let(:cache_class) { Butcher::Cache.clone }
6
+
7
+ it "should accept cache_dir from env" do
8
+ ENV["CACHE_DIR"] = "tmp/cache_from_options"
9
+ test(?d, "tmp/cache_from_options").should be_false
10
+ cache_class.instance
11
+ ENV["CACHE_DIR"] = nil
12
+ test(?d, "tmp/cache_from_options").should be_true
13
+ end
14
+
15
+ it "should create cache directory" do
16
+ cache_class.any_instance.stubs(:cache_dir).returns("tmp/cache_stub")
17
+ test(?d, "tmp/cache_stub").should be_false
18
+ cache_class.instance
19
+ test(?d, "tmp/cache_stub").should be_true
20
+ end
21
+ end
22
+
23
+ describe Butcher::Cache, "#nodes_file" do
24
+ context "cannot find knife.rb" do
25
+ it "should raise an error" do
26
+ File.expects(:exists?).with(".chef/knife.rb").returns(false)
27
+ lambda {
28
+ Butcher::Cache.instance.nodes_file
29
+ }.should raise_error(Butcher::NoKnifeRB)
30
+ end
31
+ end
32
+
33
+ context "sees a knife.rb" do
34
+ before { File.expects(:exists?).with(".chef/knife.rb").returns(true) }
35
+
36
+ context "without chef_server_url" do
37
+ it "should raise an error" do
38
+ File.expects(:read).with(".chef/knife.rb").returns(
39
+ 'some random content'
40
+ )
41
+ lambda {
42
+ Butcher::Cache.instance.nodes_file
43
+ }.should raise_error(Butcher::NoKnifeOrganization)
44
+ end
45
+ end
46
+
47
+ context "with chef_server_url" do
48
+ let(:expected_file) { "#{Butcher::TestCache.cache_dir}/my_organization.cache" }
49
+
50
+ it "should set filename based on chef_server_url" do
51
+ File.expects(:read).with(".chef/knife.rb").returns(
52
+ 'chef_server_url "https://api.opscode.com/organizations/my_organization"'
53
+ )
54
+ Butcher::Cache.instance.nodes_file.should == expected_file
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ describe Butcher::Cache, "#nodes" do
61
+ before { Butcher::Cache.any_instance.stubs(:organization).returns("ops_org") }
62
+
63
+ context "cache file does not exist" do
64
+ let(:cache_file) { "#{Butcher::TestCache.cache_dir}/ops_org.cache" }
65
+
66
+ before do
67
+ File.exists?(cache_file).should be_false
68
+ Butcher::Cache.any_instance.stubs(:`).with("knife status").returns("knife return codes")
69
+ end
70
+
71
+ it "should not raise an error" do
72
+ lambda { Butcher::Cache.instance.nodes }.should_not raise_error
73
+ end
74
+
75
+ it "should create a node list from knife" do
76
+ Butcher::Cache.instance.nodes
77
+ File.exists?(cache_file).should be_true
78
+ end
79
+ end
80
+
81
+ context "cache file exists" do
82
+ create_cache_file("ops_org.cache") do |f|
83
+ f.puts "5 minutes ago, app.node, app.domain.com, 192.168.1.1, some os"
84
+ f.puts "1 minute ago, other.node, other.domain.com, 192.168.1.2, some os"
85
+ end
86
+
87
+ it "maps file to hash" do
88
+ Butcher::Cache.instance.nodes.should == {
89
+ "192.168.1.1" => %W(app.node app.domain.com),
90
+ "192.168.1.2" => %W(other.node other.domain.com)
91
+ }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Butcher::Stab::CLI do
4
+
5
+ context "arguments are nil" do
6
+ it "should return empty string" do
7
+ Butcher::Stab::CLI.new.run(nil).should == ""
8
+ end
9
+ end
10
+
11
+ context "node cache file exists" do
12
+ mock_cache(:nodes) do
13
+ {"10.1.1.1" => %W(app.node app.node.com), "10.1.1.2" => %W(other.node other.node.com)}
14
+ end
15
+
16
+ context "stabbing an existing node" do
17
+ it "should open an SSH session to named node" do
18
+ Butcher::Stab::CLI.any_instance.expects(:exec).with("ssh 10.1.1.1").returns(true).once
19
+ Butcher::Stab::CLI.new.run("app")
20
+ end
21
+
22
+ it "should ssh to IP based on matched node" do
23
+ Butcher::Stab::CLI.any_instance.expects(:exec).with("ssh 10.1.1.1").never
24
+ Butcher::Stab::CLI.any_instance.expects(:exec).with("ssh 10.1.1.2").returns(true).once
25
+ Butcher::Stab::CLI.new.run("other")
26
+ end
27
+ end
28
+
29
+ context "stabbing a non-existing node" do
30
+ it "should raise an UnmatchedNode error" do
31
+ Butcher::Stab::CLI.any_instance.expects(:exec).never
32
+ lambda {
33
+ Butcher::Stab::CLI.new.run("nil.node")
34
+ }.should raise_error(Butcher::UnmatchedNode)
35
+ end
36
+ end
37
+
38
+ context "ambiguous stabbing" do
39
+ it "should raise an AmbiguousNode error" do
40
+ Butcher::Stab::CLI.any_instance.expects(:exec).never
41
+ lambda {
42
+ Butcher::Stab::CLI.new.run("node")
43
+ }.should raise_error(Butcher::AmbiguousNode)
44
+ end
45
+ end
46
+ end
47
+
48
+ context "options" do
49
+ mock_cache(:nodes) do
50
+ {"10.1.1.1" => %W(app.node app.node.com)}
51
+ end
52
+
53
+ describe ":login" do
54
+ it "should include login in ssh params" do
55
+ Butcher::Stab::CLI.any_instance.expects(:exec).with("ssh 10.1.1.1 -l username").returns(true).once
56
+ Butcher::Stab::CLI.new.run("app", {:login => "username"})
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ require "butcher"
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f unless /_spec\.rb$/.match(f) }
9
+
10
+ RSpec.configure do |config|
11
+ config.treat_symbols_as_metadata_keys_with_true_values = true
12
+ config.run_all_when_everything_filtered = true
13
+ config.filter_run :focus
14
+
15
+ config.mock_with :mocha
16
+
17
+ Butcher::TestCache.setup_rspec(config)
18
+ end
@@ -0,0 +1,112 @@
1
+ # = Butcher::TestCache
2
+ #
3
+ # Providing helpers for testing Butcher using RSpec and Cucumber.
4
+ #
5
+ # Author:: Eric Saxby (mailto:e.saxby@modcloth.com)
6
+ # Copyright:: Copyright (c) 2012 ModCloth
7
+ # License:: Distributes under the same terms as Ruby
8
+ #
9
+ # == RSpec Usage:
10
+ #
11
+ # RSpec.configure do |config|
12
+ # Butcher::TestCache.setup_rspec(config)
13
+ # end
14
+ #
15
+ # == Cucumber Usage:
16
+ #
17
+ # require 'butcher'
18
+ # Before { Butcher::TestCache.reset }
19
+ # After { Butcher::TestCache.cleanup }
20
+ # World(Butcher::TestCache::TestHelpers)
21
+ #
22
+ module Butcher::TestCache
23
+ def self.setup_rspec(config)
24
+ config.before(:each) do
25
+ Butcher::TestCache.reset
26
+ end
27
+
28
+ config.after(:suite) do
29
+ Butcher::TestCache.cleanup
30
+ end
31
+
32
+ config.extend(RSpecExampleHelpers)
33
+ config.include(TestHelpers)
34
+ end
35
+
36
+ def self.reset # :nodoc:
37
+ cleanup
38
+ setup
39
+ end
40
+
41
+ def self.cleanup # :nodoc:
42
+ FileUtils.rm_rf("tmp")
43
+ FileUtils.rm_rf(".chef")
44
+ ENV.delete("CACHE_DIR")
45
+ end
46
+
47
+ def self.cache_dir # :nodoc:
48
+ File.expand_path("tmp/test")
49
+ end
50
+
51
+ private
52
+
53
+ def self.setup
54
+ stub_cache
55
+ FileUtils.mkdir_p("tmp/test")
56
+ end
57
+
58
+ def self.stub_cache
59
+ ENV["CACHE_DIR"] = cache_dir
60
+ end
61
+
62
+ public
63
+
64
+ # == RSpecExampleHelpers
65
+ #
66
+ # RSpec helpers that can be used in the scope of an example group or a describe block.
67
+ # These cannot be called from within tests themselves.
68
+ module RSpecExampleHelpers
69
+
70
+ # Creates a file that Butcher::Cache can parse before every test in the current context.
71
+ # Used for testing the Cache class itself.
72
+ #
73
+ # example:
74
+ # create_cache_file("nodes.cache") do |f|
75
+ # f.puts "1 hour ago, node.name, node.domain, 192.168.1.1, os name"
76
+ # end
77
+ #
78
+ def create_cache_file(filename, &block)
79
+ before do
80
+ create_cache_file(filename, &block)
81
+ end
82
+ end
83
+
84
+ # Mock out responses on Butcher::Cache for every test in the current context.
85
+ # Used in classes where you want to test against expected output from Cache.
86
+ #
87
+ # example:
88
+ # mock_cache(:nodes) do
89
+ # {"192.168.1.1" => ["node.name","node.domain"]}
90
+ # end
91
+ #
92
+ def mock_cache(type, &block)
93
+ before do
94
+ Butcher::Cache.any_instance.stubs(type).returns(block.call)
95
+ end
96
+ end
97
+ end
98
+
99
+ # == TestHelpers
100
+ #
101
+ # RSpec helpers that can be used within tests
102
+ module TestHelpers
103
+
104
+ # Creates a file that Butcher::Cache can parse
105
+ def create_cache_file(filename)
106
+ File.open("#{Butcher::TestCache.cache_dir}/#{filename}", "w") do |file|
107
+ yield file
108
+ end
109
+ end
110
+ end
111
+ end
112
+
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: butcher
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ModCloth
9
+ - Eric Saxby
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-08 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &70346417175740 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>'
21
+ - !ruby/object:Gem::Version
22
+ version: '2'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *70346417175740
26
+ - !ruby/object:Gem::Dependency
27
+ name: aruba
28
+ requirement: &70346417175140 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *70346417175140
37
+ - !ruby/object:Gem::Dependency
38
+ name: aruba-doubles
39
+ requirement: &70346417174680 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *70346417174680
48
+ - !ruby/object:Gem::Dependency
49
+ name: guard-rspec
50
+ requirement: &70346417174260 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *70346417174260
59
+ - !ruby/object:Gem::Dependency
60
+ name: guard-cucumber
61
+ requirement: &70346417173820 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *70346417173820
70
+ - !ruby/object:Gem::Dependency
71
+ name: mocha
72
+ requirement: &70346417173380 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *70346417173380
81
+ description: Chef is a tool for managing server automation. A good butcher makes for
82
+ a good chef.
83
+ email:
84
+ - git@modcloth.com
85
+ executables:
86
+ - stab
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .rbenv-version
92
+ - .rspec
93
+ - Gemfile
94
+ - Guardfile
95
+ - README.md
96
+ - Rakefile
97
+ - bin/stab
98
+ - butcher.gemspec
99
+ - features/stab.feature
100
+ - features/step_definitions/chef_steps.rb
101
+ - features/support/aruba.rb
102
+ - features/support/env.rb
103
+ - features/support/mocha.rb
104
+ - features/support/test_cache.rb
105
+ - lib/butcher.rb
106
+ - lib/butcher/cache.rb
107
+ - lib/butcher/stab.rb
108
+ - lib/butcher/stab/cli.rb
109
+ - lib/butcher/version.rb
110
+ - spec/lib/cache_spec.rb
111
+ - spec/lib/stab/cli_spec.rb
112
+ - spec/spec_helper.rb
113
+ - spec/support/test_cache.rb
114
+ homepage: https://github.com/modcloth/butcher
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project: butcher
134
+ rubygems_version: 1.8.17
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: All the things to make a chef
138
+ test_files:
139
+ - features/stab.feature
140
+ - features/step_definitions/chef_steps.rb
141
+ - features/support/aruba.rb
142
+ - features/support/env.rb
143
+ - features/support/mocha.rb
144
+ - features/support/test_cache.rb
145
+ - spec/lib/cache_spec.rb
146
+ - spec/lib/stab/cli_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/test_cache.rb