pocketknife 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.
@@ -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