pocketknife 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ class Pocketknife
2
+ # == Version
3
+ #
4
+ # Information about the Pocketknife version.
5
+ module Version
6
+ MAJOR = 0
7
+ MINOR = 0
8
+ PATCH = 1
9
+ BUILD = nil
10
+
11
+ STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
12
+ end
13
+ end
@@ -0,0 +1,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pocketknife}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Igal Koshevoy"]
12
+ s.date = %q{2011-05-16}
13
+ s.description = %q{pocketknife is a tool for managing chef-solo nodes.}
14
+ s.email = %q{igal+pocketknife@pragmaticraft.com}
15
+ s.executables = ["pocketknife", "pocketknife"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".yardopts",
23
+ "Gemfile",
24
+ "Gemfile.lock",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "bin/pocketknife",
29
+ "lib/pocketknife.rb",
30
+ "lib/pocketknife/errors.rb",
31
+ "lib/pocketknife/node.rb",
32
+ "lib/pocketknife/node_manager.rb",
33
+ "lib/pocketknife/version.rb",
34
+ "spec/pocketknife_execution_error_spec.rb",
35
+ "spec/pocketknife_node_manager_spec.rb",
36
+ "spec/pocketknife_node_spec.rb",
37
+ "spec/pocketknife_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "spec/support/libraries.rb",
40
+ "spec/support/mkproject.rb"
41
+ ]
42
+ s.homepage = %q{http://github.com/igal/pocketknife}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.7}
46
+ s.summary = %q{pocketknife is a tool for managing chef-solo nodes.}
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<archive-tar-minitar>, ["~> 0.5.0"])
54
+ s.add_runtime_dependency(%q<rye>, ["~> 0.9.0"])
55
+ s.add_development_dependency(%q<bluecloth>, ["~> 2.1.0"])
56
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
57
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
58
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
59
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
60
+ s.add_development_dependency(%q<rcov>, [">= 0"])
61
+ else
62
+ s.add_dependency(%q<archive-tar-minitar>, ["~> 0.5.0"])
63
+ s.add_dependency(%q<rye>, ["~> 0.9.0"])
64
+ s.add_dependency(%q<bluecloth>, ["~> 2.1.0"])
65
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
66
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
67
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
68
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
69
+ s.add_dependency(%q<rcov>, [">= 0"])
70
+ end
71
+ else
72
+ s.add_dependency(%q<archive-tar-minitar>, ["~> 0.5.0"])
73
+ s.add_dependency(%q<rye>, ["~> 0.9.0"])
74
+ s.add_dependency(%q<bluecloth>, ["~> 2.1.0"])
75
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
76
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
77
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
78
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
79
+ s.add_dependency(%q<rcov>, [">= 0"])
80
+ end
81
+ end
82
+
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Pocketknife::ExecutionError" do
4
+ describe "when raised while executing without immediate output" do
5
+ before do
6
+ @rye_err = mock(Rye::Err,
7
+ :exit_status => 1,
8
+ :stdout => "hello",
9
+ :stderr => "oh noes!!1!")
10
+ @error = Pocketknife::ExecutionError.new("mynode", "mycommand", @rye_err, false)
11
+ @message = @error.to_s
12
+ end
13
+
14
+ describe "object" do
15
+ subject { @error }
16
+
17
+ its(:node) { should == "mynode" }
18
+ its(:exit_status) { should == 1 }
19
+ its(:cause) { should == @rye_err }
20
+ its(:immediate) { should == false }
21
+ end
22
+
23
+ it "should have a detailed message" do
24
+ @message.should == <<-HERE.chomp
25
+ Failed while executing commands on node 'mynode'
26
+ - COMMAND: mycommand
27
+ - EXIT STATUS: 1
28
+ - STDOUT: hello
29
+ - STDERR: oh noes!!1!
30
+ HERE
31
+ end
32
+ end
33
+
34
+ describe "when raised while executing with immediate output" do
35
+ before do
36
+ @rye_err = mock(Rye::Err,
37
+ :exit_status => 1,
38
+ :stdout => "hello",
39
+ :stderr => "oh noes!!1!")
40
+ @error = Pocketknife::ExecutionError.new("mynode", "mycommand", @rye_err, true)
41
+ @message = @error.to_s
42
+ end
43
+
44
+ describe "object" do
45
+ subject { @error }
46
+
47
+ its(:node) { should == "mynode" }
48
+ its(:exit_status) { should == 1 }
49
+ its(:cause) { should == @rye_err }
50
+ its(:immediate) { should == true }
51
+ end
52
+
53
+ it "should have a detailed message" do
54
+ @message.should == <<-HERE.chomp
55
+ Failed while executing commands on node 'mynode'
56
+ - COMMAND: mycommand
57
+ - EXIT STATUS: 1
58
+ HERE
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "PocketKnife::NodeManager" do
4
+ describe "#hostname_for" do
5
+ before do
6
+ @node_manager = Pocketknife::NodeManager.new(mock(Pocketknife))
7
+ end
8
+
9
+ it "should find a hostname that's the same as the node name" do
10
+ @node_manager.stub(:known_nodes).and_return(["a.host.name"])
11
+ @node_manager.hostname_for("a.host.name").should == "a.host.name"
12
+ end
13
+
14
+ it "should find a hostname when given an abbreviated node name" do
15
+ @node_manager.stub(:known_nodes).and_return(["a.host.name"])
16
+ @node_manager.hostname_for("a.host").should == "a.host.name"
17
+ end
18
+
19
+ it "should find a hostname when given a very abbreviated node name" do
20
+ @node_manager.stub(:known_nodes).and_return(["a.host.name"])
21
+ @node_manager.hostname_for("a").should == "a.host.name"
22
+ end
23
+
24
+ it "should fail to find hostname when given a non-unique abbreviated node name" do
25
+ @node_manager.stub(:known_nodes).and_return(["a.host.name", "a.different.name"])
26
+ begin
27
+ @node_manager.hostname_for("a")
28
+ fail "No exception was thrown!"
29
+ rescue Pocketknife::NoSuchNode => e
30
+ e.message.should == "Can't find unique node named 'a', this matches nodes: a.host.name, a.different.name"
31
+ end
32
+ end
33
+
34
+ it "should fail to find hostname for a node that doesn't exist" do
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,299 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "PocketKnife::Node" do
4
+ def node_factory(name=nil, pocketknife=nil, connection=nil)
5
+ name ||= "mynode"
6
+ pocketknife ||= Pocketknife.new(:verbosity => false)
7
+ node = Pocketknife::Node.new(name, pocketknife)
8
+ node.stub(:connection => connection || true) unless connection == false
9
+ return node
10
+ end
11
+
12
+ describe "::new" do
13
+ before do
14
+ @pocketknife = Pocketknife.new
15
+ @name = "mynode"
16
+ @node = node_factory(@name, @pocketknife)
17
+ end
18
+
19
+ it "should have a name" do
20
+ @node.name.should == @name
21
+ end
22
+
23
+ it "should have a pocketknife" do
24
+ @node.pocketknife.should == @pocketknife
25
+ end
26
+ end
27
+
28
+ describe "#connection" do
29
+ it "should instantiate a new connection" do
30
+ node = node_factory(nil, nil, false)
31
+ rye = mock(Rye::Box)
32
+ rye.should_receive(:disable_safe_mode)
33
+ Rye::Box.should_receive(:new).with("mynode", {:user => "root"}).and_return(rye)
34
+
35
+ node.connection.should == rye
36
+ end
37
+
38
+ it "should return an existing connection" do
39
+ node = node_factory(nil, nil, false)
40
+ rye = mock(Rye::Box)
41
+ rye.should_receive(:disable_safe_mode)
42
+ Rye::Box.should_receive(:new).with("mynode", {:user => "root"}).and_return(rye)
43
+ node.connection.should == rye
44
+
45
+ node.connection.should == rye
46
+ end
47
+ end
48
+
49
+ describe "#has_executable?" do
50
+ before do
51
+ @node = node_factory
52
+ end
53
+
54
+ it "should find an existing executable" do
55
+ @node.connection.should_receive(:execute).with(%{which "chef-solo" && test -x `which "chef-solo"`}).and_return(true)
56
+
57
+ @node.has_executable?("chef-solo").should be_true
58
+ end
59
+
60
+ it "should not find a missing executable" do
61
+ @node.connection.should_receive(:execute).with(%{which "chef-solo" && test -x `which "chef-solo"`}).and_raise(Rye::Err.new("omg"))
62
+
63
+ @node.has_executable?("chef-solo").should be_false
64
+ end
65
+ end
66
+
67
+ describe "#platform" do
68
+ describe "when Ubuntu 10.10" do
69
+ before do
70
+ node = node_factory
71
+ node.connection.stub(:cat => <<-HERE)
72
+ DISTRIB_ID=Ubuntu
73
+ DISTRIB_RELEASE=10.10
74
+ DISTRIB_CODENAME=maverick
75
+ DISTRIB_DESCRIPTION="Ubuntu 10.10"
76
+ HERE
77
+ @platform = node.platform
78
+ end
79
+
80
+ it "should have a distributor" do
81
+ @platform[:distributor].should == "Ubuntu"
82
+ end
83
+
84
+ it "should have a release" do
85
+ @platform[:release].should == "10.10"
86
+ end
87
+
88
+ it "should have a codename" do
89
+ @platform[:codename].should == "maverick"
90
+ end
91
+
92
+ it "should have a version as a floating point number" do
93
+ @platform[:version].should == 10.1
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "#install" do
99
+ it "should do nothing if chef is installed" do
100
+ node = node_factory
101
+ node.should_receive(:has_executable?).with("chef-solo").and_return(true)
102
+ node.pocketknife.should_not_receive(:can_install)
103
+
104
+ node.install
105
+ end
106
+
107
+ it "should only install chef if ruby and rubygems are installed" do
108
+ node = node_factory
109
+
110
+ node.pocketknife.should_receive(:can_install).and_return(true)
111
+
112
+ node.should_receive(:has_executable?).with("chef-solo").and_return(false)
113
+ node.should_receive(:has_executable?).with("ruby").and_return(true)
114
+ node.should_receive(:has_executable?).with("gem").and_return(true)
115
+
116
+ node.should_not_receive(:install_ruby)
117
+ node.should_not_receive(:install_rubygems)
118
+ node.should_receive(:install_chef)
119
+
120
+ node.install
121
+ end
122
+
123
+ it "should install chef and rubygems if only ruby is present" do
124
+ node = node_factory
125
+
126
+ node.pocketknife.should_receive(:can_install).and_return(true)
127
+
128
+ node.should_receive(:has_executable?).with("chef-solo").and_return(false)
129
+ node.should_receive(:has_executable?).with("ruby").and_return(true)
130
+ node.should_receive(:has_executable?).with("gem").and_return(false)
131
+
132
+ node.should_not_receive(:install_ruby)
133
+ node.should_receive(:install_rubygems)
134
+ node.should_receive(:install_chef)
135
+
136
+ node.install
137
+ end
138
+
139
+ it "should install chef, rubygems and ruby if none are present" do
140
+ node = node_factory
141
+
142
+ node.pocketknife.should_receive(:can_install).and_return(true)
143
+
144
+ node.should_receive(:has_executable?).with("chef-solo").and_return(false)
145
+ node.should_receive(:has_executable?).with("ruby").and_return(false)
146
+ node.should_receive(:has_executable?).with("gem").and_return(false)
147
+
148
+ node.should_receive(:install_ruby)
149
+ node.should_receive(:install_rubygems)
150
+ node.should_receive(:install_chef)
151
+
152
+ node.install
153
+ end
154
+ end
155
+
156
+ describe "#install_chef" do
157
+ it "should install chef" do
158
+ node = node_factory
159
+ node.should_receive(:execute).with("gem install --no-rdoc --no-ri chef", true)
160
+
161
+ node.install_chef
162
+ end
163
+ end
164
+
165
+ describe "#install_rubygems" do
166
+ it "should install rubygems" do
167
+ node = node_factory
168
+ node.should_receive(:execute).with(<<-HERE, true)
169
+ cd /root &&
170
+ rm -rf rubygems-1.3.7 rubygems-1.3.7.tgz &&
171
+ wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.7.tgz &&
172
+ tar zxf rubygems-1.3.7.tgz &&
173
+ cd rubygems-1.3.7 &&
174
+ ruby setup.rb --no-format-executable &&
175
+ rm -rf rubygems-1.3.7 rubygems-1.3.7.tgz
176
+ HERE
177
+
178
+ node.install_rubygems
179
+ end
180
+ end
181
+
182
+ describe "#install_ruby" do
183
+ it "should install ruby on Ubuntu" do
184
+ node = node_factory
185
+ node.stub(:platform => {:distributor => "Ubuntu"})
186
+ node.should_receive(:execute).with("DEBIAN_FRONTEND=noninteractive apt-get --yes install ruby ruby-dev libopenssl-ruby irb build-essential wget ssl-cert", true)
187
+
188
+ node.install_ruby
189
+ end
190
+
191
+ it "should install ruby on CentOS" do
192
+ node = node_factory
193
+ node.stub(:platform => {:distributor => "CentOS"})
194
+ node.should_receive(:execute).with("yum -y install ruby ruby-shadow gcc gcc-c++ ruby-devel wget", true)
195
+
196
+ node.install_ruby
197
+ end
198
+ end
199
+
200
+ describe "::prepare_upload" do
201
+ before(:all) do
202
+ @previous = Dir.pwd
203
+ @dir = mkproject
204
+ @node = node_factory
205
+ end
206
+
207
+ after(:all) do
208
+ FileUtils.rm_rf(@dir) if @dir
209
+ Dir.chdir(@previous)
210
+ end
211
+
212
+ def prepare_upload_with_or_without_a_block
213
+ Pocketknife::Node::TMP_SOLO_RB.should_receive(:open)
214
+ Pocketknife::Node::TMP_CHEF_SOLO_APPLY.should_receive(:open)
215
+ Pocketknife::Node::TMP_TARBALL.should_receive(:open)
216
+ end
217
+
218
+ describe "without a block" do
219
+ it "should create a tarball and leave it alone" do
220
+ prepare_upload_with_or_without_a_block
221
+ Pocketknife::Node.should_not_receive(:cleanup_upload)
222
+ Pocketknife::Node.prepare_upload
223
+ end
224
+ end
225
+
226
+ describe "with a block" do
227
+ it "should create a tarball and clean it up" do
228
+ prepare_upload_with_or_without_a_block
229
+ Pocketknife::Node.should_receive(:cleanup_upload)
230
+ Pocketknife::Node.prepare_upload { }
231
+ end
232
+ end
233
+ end
234
+
235
+ describe "::cleanup_upload" do
236
+ it "should cleanup files" do
237
+ mkproject do
238
+ [
239
+ Pocketknife::Node::TMP_SOLO_RB,
240
+ Pocketknife::Node::TMP_CHEF_SOLO_APPLY,
241
+ Pocketknife::Node::TMP_TARBALL
242
+ ].each do |item|
243
+ item.should_receive(:exist?).and_return(true)
244
+ item.should_receive(:unlink)
245
+ end
246
+
247
+ Pocketknife::Node.cleanup_upload
248
+ end
249
+ end
250
+ end
251
+
252
+ describe "#upload" do
253
+ it "should upload configuration" do
254
+ mkproject do
255
+ node = node_factory
256
+
257
+ local_node_json = node.local_node_json_pathname
258
+ local_node_json.open("w") { |h| h.write("{}") }
259
+
260
+ node.should_receive(:execute).with(<<-HERE)
261
+ umask 0377 &&
262
+ rm -rf "/etc/chef" "/var/local/pocketknife" "/var/local/pocketknife/cache" "/usr/local/sbin/chef-solo-apply" "/usr/local/sbin/csa" &&
263
+ mkdir -p "/etc/chef" "/var/local/pocketknife" "/var/local/pocketknife/cache" "/usr/local/sbin"
264
+ HERE
265
+
266
+ node.connection.should_receive(:file_upload).with(local_node_json.to_s, "/etc/chef/node.json")
267
+
268
+ node.connection.should_receive(:file_upload).with(Pocketknife::Node::TMP_TARBALL.to_s, "/var/local/pocketknife/cache/pocketknife.tmp")
269
+
270
+ node.should_receive(:execute).with(<<-HERE, true)
271
+ cd "/var/local/pocketknife/cache" &&
272
+ tar xf "/var/local/pocketknife/cache/pocketknife.tmp" &&
273
+ chmod -R u+rwX,go= . &&
274
+ chown -R root:root . &&
275
+ mv "solo.rb.tmp" "/etc/chef/solo.rb" &&
276
+ mv "chef-solo-apply.tmp" "/usr/local/sbin/chef-solo-apply" &&
277
+ chmod u+x "/usr/local/sbin/chef-solo-apply" &&
278
+ ln -s "chef-solo-apply" "/usr/local/sbin/csa" &&
279
+ rm "/var/local/pocketknife/cache/pocketknife.tmp" &&
280
+ mv * "/var/local/pocketknife"
281
+ HERE
282
+
283
+ node.upload
284
+ end
285
+ end
286
+ end
287
+
288
+ describe "#apply" do
289
+ it "should apply configuration" do
290
+ node = node_factory
291
+ node.pocketknife.should_receive(:verbosity).at_least(:once).and_return(false)
292
+ node.should_receive(:install)
293
+ node.should_receive(:execute).with("chef-solo -j /etc/chef/node.json", true)
294
+ node.stub(:say)
295
+
296
+ node.apply
297
+ end
298
+ end
299
+ end