butcher 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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