tenderloin 0.2.0

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.
Files changed (73) hide show
  1. data/LICENCE +21 -0
  2. data/README.md +50 -0
  3. data/Version +1 -0
  4. data/bin/loin +5 -0
  5. data/config/default.rb +26 -0
  6. data/lib/tenderloin/actions/base.rb +93 -0
  7. data/lib/tenderloin/actions/box/add.rb +22 -0
  8. data/lib/tenderloin/actions/box/destroy.rb +14 -0
  9. data/lib/tenderloin/actions/box/download.rb +63 -0
  10. data/lib/tenderloin/actions/box/unpackage.rb +46 -0
  11. data/lib/tenderloin/actions/runner.rb +138 -0
  12. data/lib/tenderloin/actions/vm/boot.rb +52 -0
  13. data/lib/tenderloin/actions/vm/destroy.rb +18 -0
  14. data/lib/tenderloin/actions/vm/halt.rb +14 -0
  15. data/lib/tenderloin/actions/vm/import.rb +32 -0
  16. data/lib/tenderloin/actions/vm/move_hard_drive.rb +53 -0
  17. data/lib/tenderloin/actions/vm/provision.rb +71 -0
  18. data/lib/tenderloin/actions/vm/reload.rb +17 -0
  19. data/lib/tenderloin/actions/vm/shared_folders.rb +47 -0
  20. data/lib/tenderloin/actions/vm/start.rb +17 -0
  21. data/lib/tenderloin/actions/vm/up.rb +55 -0
  22. data/lib/tenderloin/box.rb +143 -0
  23. data/lib/tenderloin/busy.rb +73 -0
  24. data/lib/tenderloin/cli.rb +59 -0
  25. data/lib/tenderloin/commands.rb +154 -0
  26. data/lib/tenderloin/config.rb +144 -0
  27. data/lib/tenderloin/downloaders/base.rb +13 -0
  28. data/lib/tenderloin/downloaders/file.rb +21 -0
  29. data/lib/tenderloin/downloaders/http.rb +47 -0
  30. data/lib/tenderloin/env.rb +156 -0
  31. data/lib/tenderloin/fusion_vm.rb +85 -0
  32. data/lib/tenderloin/ssh.rb +49 -0
  33. data/lib/tenderloin/util.rb +51 -0
  34. data/lib/tenderloin/vm.rb +63 -0
  35. data/lib/tenderloin/vmx_file.rb +28 -0
  36. data/lib/tenderloin.rb +14 -0
  37. data/script/tenderloin-ssh-expect.sh +23 -0
  38. data/templates/Tenderfile +8 -0
  39. data/test/tenderloin/actions/base_test.rb +32 -0
  40. data/test/tenderloin/actions/box/add_test.rb +37 -0
  41. data/test/tenderloin/actions/box/destroy_test.rb +18 -0
  42. data/test/tenderloin/actions/box/download_test.rb +118 -0
  43. data/test/tenderloin/actions/box/unpackage_test.rb +100 -0
  44. data/test/tenderloin/actions/runner_test.rb +262 -0
  45. data/test/tenderloin/actions/vm/boot_test.rb +55 -0
  46. data/test/tenderloin/actions/vm/destroy_test.rb +24 -0
  47. data/test/tenderloin/actions/vm/down_test.rb +32 -0
  48. data/test/tenderloin/actions/vm/export_test.rb +88 -0
  49. data/test/tenderloin/actions/vm/forward_ports_test.rb +50 -0
  50. data/test/tenderloin/actions/vm/halt_test.rb +27 -0
  51. data/test/tenderloin/actions/vm/import_test.rb +36 -0
  52. data/test/tenderloin/actions/vm/move_hard_drive_test.rb +108 -0
  53. data/test/tenderloin/actions/vm/package_test.rb +181 -0
  54. data/test/tenderloin/actions/vm/provision_test.rb +103 -0
  55. data/test/tenderloin/actions/vm/reload_test.rb +44 -0
  56. data/test/tenderloin/actions/vm/resume_test.rb +27 -0
  57. data/test/tenderloin/actions/vm/shared_folders_test.rb +117 -0
  58. data/test/tenderloin/actions/vm/start_test.rb +28 -0
  59. data/test/tenderloin/actions/vm/suspend_test.rb +27 -0
  60. data/test/tenderloin/actions/vm/up_test.rb +98 -0
  61. data/test/tenderloin/box_test.rb +139 -0
  62. data/test/tenderloin/busy_test.rb +83 -0
  63. data/test/tenderloin/commands_test.rb +269 -0
  64. data/test/tenderloin/config_test.rb +123 -0
  65. data/test/tenderloin/downloaders/base_test.rb +20 -0
  66. data/test/tenderloin/downloaders/file_test.rb +32 -0
  67. data/test/tenderloin/downloaders/http_test.rb +40 -0
  68. data/test/tenderloin/env_test.rb +345 -0
  69. data/test/tenderloin/ssh_test.rb +103 -0
  70. data/test/tenderloin/util_test.rb +64 -0
  71. data/test/tenderloin/vm_test.rb +89 -0
  72. data/test/test_helper.rb +92 -0
  73. metadata +241 -0
@@ -0,0 +1,269 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class CommandsTest < Test::Unit::TestCase
4
+ setup do
5
+ Tenderloin::Env.stubs(:load!)
6
+
7
+ @persisted_vm = mock("persisted_vm")
8
+ @persisted_vm.stubs(:execute!)
9
+ Tenderloin::Env.stubs(:persisted_vm).returns(@persisted_vm)
10
+ Tenderloin::Env.stubs(:require_persisted_vm)
11
+ end
12
+
13
+ context "init" do
14
+ setup do
15
+ FileUtils.stubs(:cp)
16
+ @rootfile_path = File.join(Dir.pwd, Tenderloin::Env::ROOTFILE_NAME)
17
+ @template_path = File.join(PROJECT_ROOT, "templates", Tenderloin::Env::ROOTFILE_NAME)
18
+ end
19
+
20
+ should "error and exit if a rootfile already exists" do
21
+ File.expects(:exist?).with(@rootfile_path).returns(true)
22
+ Tenderloin::Commands.expects(:error_and_exit).once
23
+ Tenderloin::Commands.init
24
+ end
25
+
26
+ should "copy the templated rootfile to the current path" do
27
+ File.expects(:exist?).with(@rootfile_path).returns(false)
28
+ FileUtils.expects(:cp).with(@template_path, @rootfile_path).once
29
+ Tenderloin::Commands.init
30
+ end
31
+ end
32
+
33
+ context "up" do
34
+ setup do
35
+ Tenderloin::Env.stubs(:persisted_vm).returns(nil)
36
+ Tenderloin::VM.stubs(:execute!)
37
+ Tenderloin::Env.stubs(:require_box)
38
+ end
39
+
40
+ should "require load the environment" do
41
+ Tenderloin::Env.expects(:load!).once
42
+ Tenderloin::Commands.up
43
+ end
44
+
45
+ should "require a box" do
46
+ Tenderloin::Env.expects(:require_box).once
47
+ Tenderloin::Commands.up
48
+ end
49
+
50
+ should "call the up action on VM if it doesn't exist" do
51
+ Tenderloin::VM.expects(:execute!).with(Tenderloin::Actions::VM::Up).once
52
+ Tenderloin::Commands.up
53
+ end
54
+
55
+ should "call start on the persisted vm if it exists" do
56
+ Tenderloin::Env.stubs(:persisted_vm).returns(@persisted_vm)
57
+ @persisted_vm.expects(:start).once
58
+ Tenderloin::VM.expects(:execute!).never
59
+ Tenderloin::Commands.up
60
+ end
61
+ end
62
+
63
+ context "down" do
64
+ setup do
65
+ @persisted_vm.stubs(:destroy)
66
+ end
67
+
68
+ should "require a persisted VM" do
69
+ Tenderloin::Env.expects(:require_persisted_vm).once
70
+ Tenderloin::Commands.down
71
+ end
72
+
73
+ should "destroy the persisted VM and the VM image" do
74
+ @persisted_vm.expects(:destroy).once
75
+ Tenderloin::Commands.down
76
+ end
77
+ end
78
+
79
+ context "reload" do
80
+ should "require a persisted VM" do
81
+ Tenderloin::Env.expects(:require_persisted_vm).once
82
+ Tenderloin::Commands.reload
83
+ end
84
+
85
+ should "call the `reload` action on the VM" do
86
+ @persisted_vm.expects(:execute!).with(Tenderloin::Actions::VM::Reload).once
87
+ Tenderloin::Commands.reload
88
+ end
89
+ end
90
+
91
+ context "ssh" do
92
+ setup do
93
+ Tenderloin::SSH.stubs(:connect)
94
+ end
95
+
96
+ should "require a persisted VM" do
97
+ Tenderloin::Env.expects(:require_persisted_vm).once
98
+ Tenderloin::Commands.ssh
99
+ end
100
+
101
+ should "connect to SSH" do
102
+ Tenderloin::SSH.expects(:connect).once
103
+ Tenderloin::Commands.ssh
104
+ end
105
+ end
106
+
107
+ context "halt" do
108
+ should "require a persisted VM" do
109
+ Tenderloin::Env.expects(:require_persisted_vm).once
110
+ Tenderloin::Commands.halt
111
+ end
112
+
113
+ should "call the `halt` action on the VM" do
114
+ @persisted_vm.expects(:execute!).with(Tenderloin::Actions::VM::Halt).once
115
+ Tenderloin::Commands.halt
116
+ end
117
+ end
118
+
119
+ context "suspend" do
120
+ setup do
121
+ @persisted_vm.stubs(:suspend)
122
+ @persisted_vm.stubs(:saved?).returns(false)
123
+ end
124
+
125
+ should "require a persisted VM" do
126
+ Tenderloin::Env.expects(:require_persisted_vm).once
127
+ Tenderloin::Commands.suspend
128
+ end
129
+
130
+ should "suspend the VM" do
131
+ @persisted_vm.expects(:suspend).once
132
+ Tenderloin::Commands.suspend
133
+ end
134
+ end
135
+
136
+ context "resume" do
137
+ setup do
138
+ @persisted_vm.stubs(:resume)
139
+ @persisted_vm.stubs(:saved?).returns(true)
140
+ end
141
+
142
+ should "require a persisted VM" do
143
+ Tenderloin::Env.expects(:require_persisted_vm).once
144
+ Tenderloin::Commands.resume
145
+ end
146
+
147
+ should "save the state of the VM" do
148
+ @persisted_vm.expects(:resume).once
149
+ Tenderloin::Commands.resume
150
+ end
151
+ end
152
+
153
+ context "package" do
154
+ setup do
155
+ @persisted_vm.stubs(:package)
156
+ @persisted_vm.stubs(:powered_off?).returns(true)
157
+ end
158
+
159
+ should "require a persisted vm" do
160
+ Tenderloin::Env.expects(:require_persisted_vm).once
161
+ Tenderloin::Commands.package
162
+ end
163
+
164
+ should "error and exit if the VM is not powered off" do
165
+ @persisted_vm.stubs(:powered_off?).returns(false)
166
+ Tenderloin::Commands.expects(:error_and_exit).once
167
+ @persisted_vm.expects(:package).never
168
+ Tenderloin::Commands.package
169
+ end
170
+
171
+ should "call package on the persisted VM" do
172
+ @persisted_vm.expects(:package).once
173
+ Tenderloin::Commands.package
174
+ end
175
+
176
+ should "pass the out path and include_files to the package method" do
177
+ out_path = mock("out_path")
178
+ include_files = mock("include_files")
179
+ @persisted_vm.expects(:package).with(out_path, include_files).once
180
+ Tenderloin::Commands.package(out_path, include_files)
181
+ end
182
+
183
+ should "default to an empty array when not include_files are specified" do
184
+ out_path = mock("out_path")
185
+ @persisted_vm.expects(:package).with(out_path, []).once
186
+ Tenderloin::Commands.package(out_path)
187
+ end
188
+ end
189
+
190
+ context "box" do
191
+ setup do
192
+ Tenderloin::Commands.stubs(:box_foo)
193
+ Tenderloin::Commands.stubs(:box_add)
194
+ Tenderloin::Commands.stubs(:box_remove)
195
+ end
196
+
197
+ should "load the environment" do
198
+ Tenderloin::Env.expects(:load!).once
199
+ Tenderloin::Commands.box(["add"])
200
+ end
201
+
202
+ should "error and exit if the first argument is not 'add' or 'remove'" do
203
+ Tenderloin::Commands.expects(:error_and_exit).once
204
+ Tenderloin::Commands.box(["foo"])
205
+ end
206
+
207
+ should "not error and exit if the first argument is 'add' or 'remove'" do
208
+ commands = ["add", "remove"]
209
+
210
+ commands.each do |command|
211
+ Tenderloin::Commands.expects(:error_and_exit).never
212
+ Tenderloin::Commands.expects("box_#{command}".to_sym).once
213
+ Tenderloin::Commands.box([command])
214
+ end
215
+ end
216
+
217
+ should "forward any additional arguments" do
218
+ Tenderloin::Commands.expects(:box_add).with(1,2,3).once
219
+ Tenderloin::Commands.box(["add",1,2,3])
220
+ end
221
+ end
222
+
223
+ context "box list" do
224
+ setup do
225
+ @boxes = ["foo", "bar"]
226
+
227
+ Tenderloin::Box.stubs(:all).returns(@boxes)
228
+ Tenderloin::Commands.stubs(:puts)
229
+ end
230
+
231
+ should "call all on box and sort the results" do
232
+ @all = mock("all")
233
+ @all.expects(:sort).returns(@boxes)
234
+ Tenderloin::Box.expects(:all).returns(@all)
235
+ Tenderloin::Commands.box_list
236
+ end
237
+ end
238
+
239
+ context "box add" do
240
+ setup do
241
+ @name = "foo"
242
+ @path = "bar"
243
+ end
244
+
245
+ should "execute the add action with the name and path" do
246
+ Tenderloin::Box.expects(:add).with(@name, @path).once
247
+ Tenderloin::Commands.box_add(@name, @path)
248
+ end
249
+ end
250
+
251
+ context "box remove" do
252
+ setup do
253
+ @name = "foo"
254
+ end
255
+
256
+ should "error and exit if the box doesn't exist" do
257
+ Tenderloin::Box.expects(:find).returns(nil)
258
+ Tenderloin::Commands.expects(:error_and_exit).once
259
+ Tenderloin::Commands.box_remove(@name)
260
+ end
261
+
262
+ should "call destroy on the box if it exists" do
263
+ @box = mock("box")
264
+ Tenderloin::Box.expects(:find).with(@name).returns(@box)
265
+ @box.expects(:destroy).once
266
+ Tenderloin::Commands.box_remove(@name)
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,123 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class ConfigTest < Test::Unit::TestCase
4
+ context "resetting" do
5
+ setup do
6
+ Tenderloin::Config.run { |config| }
7
+ Tenderloin::Config.execute!
8
+ end
9
+
10
+ should "return the same config object typically" do
11
+ config = Tenderloin::Config.config
12
+ assert config.equal?(Tenderloin::Config.config)
13
+ end
14
+
15
+ should "create a new object if cleared" do
16
+ config = Tenderloin::Config.config
17
+ Tenderloin::Config.reset!
18
+ assert !config.equal?(Tenderloin::Config.config)
19
+ end
20
+
21
+ should "empty the runners" do
22
+ assert !Tenderloin::Config.config_runners.empty?
23
+ Tenderloin::Config.reset!
24
+ assert Tenderloin::Config.config_runners.empty?
25
+ end
26
+ end
27
+
28
+ context "accessing configuration" do
29
+ setup do
30
+ Tenderloin::Config.run { |config| }
31
+ Tenderloin::Config.execute!
32
+ end
33
+
34
+ should "forward config to the class method" do
35
+ assert_equal Tenderloin.config, Tenderloin::Config.config
36
+ end
37
+ end
38
+
39
+ context "initializing" do
40
+ teardown do
41
+ Tenderloin::Config.instance_variable_set(:@config_runners, nil)
42
+ Tenderloin::Config.instance_variable_set(:@config, nil)
43
+ end
44
+
45
+ should "not run the blocks right away" do
46
+ obj = mock("obj")
47
+ obj.expects(:foo).never
48
+ Tenderloin::Config.run { |config| obj.foo }
49
+ Tenderloin::Config.run { |config| obj.foo }
50
+ Tenderloin::Config.run { |config| obj.foo }
51
+ end
52
+
53
+ should "run the blocks when execute! is ran" do
54
+ obj = mock("obj")
55
+ obj.expects(:foo).times(2)
56
+ Tenderloin::Config.run { |config| obj.foo }
57
+ Tenderloin::Config.run { |config| obj.foo }
58
+ Tenderloin::Config.execute!
59
+ end
60
+
61
+ should "run the blocks with the same config object" do
62
+ Tenderloin::Config.run { |config| assert config }
63
+ Tenderloin::Config.run { |config| assert config }
64
+ Tenderloin::Config.execute!
65
+ end
66
+
67
+ should "not be loaded, initially" do
68
+ assert !Tenderloin::Config.config.loaded?
69
+ end
70
+
71
+ should "be loaded after running" do
72
+ Tenderloin::Config.run {}
73
+ Tenderloin::Config.execute!
74
+ assert Tenderloin::Config.config.loaded?
75
+ end
76
+ end
77
+
78
+ context "base class" do
79
+ setup do
80
+ @base = Tenderloin::Config::Base.new
81
+ end
82
+
83
+ should "forward [] access to methods" do
84
+ @base.expects(:foo).once
85
+ @base[:foo]
86
+ end
87
+
88
+ should "return a hash of instance variables" do
89
+ data = { :foo => "bar", :bar => "baz" }
90
+
91
+ data.each do |iv, value|
92
+ @base.instance_variable_set("@#{iv}".to_sym, value)
93
+ end
94
+
95
+ result = @base.instance_variables_hash
96
+ assert_equal data.length, result.length
97
+
98
+ data.each do |iv, value|
99
+ assert_equal value, result[iv]
100
+ end
101
+ end
102
+
103
+ should "convert instance variable hash to json" do
104
+ @json = mock("json")
105
+ @iv_hash = mock("iv_hash")
106
+ @iv_hash.expects(:to_json).once.returns(@json)
107
+ @base.expects(:instance_variables_hash).returns(@iv_hash)
108
+ assert_equal @json, @base.to_json
109
+ end
110
+ end
111
+
112
+ context "chef config" do
113
+ setup do
114
+ @config = Tenderloin::Config::ChefConfig.new
115
+ @config.json = "HEY"
116
+ end
117
+
118
+ should "not include the 'json' key in the config dump" do
119
+ result = JSON.parse(@config.to_json)
120
+ assert !result.has_key?("json")
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,20 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class BaseDownloaderTest < Test::Unit::TestCase
4
+ should "include the util class so subclasses have access to it" do
5
+ assert Tenderloin::Downloaders::Base.include?(Tenderloin::Util)
6
+ end
7
+
8
+ context "base instance" do
9
+ setup do
10
+ @base = Tenderloin::Downloaders::Base.new
11
+ end
12
+
13
+ should "implement prepare which does nothing" do
14
+ assert_nothing_raised do
15
+ assert @base.respond_to?(:download!)
16
+ @base.download!("source", "destination")
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class FileDownloaderTest < Test::Unit::TestCase
4
+ setup do
5
+ @downloader, @tempfile = mock_downloader(Tenderloin::Downloaders::File)
6
+ @uri = "foo.box"
7
+ end
8
+
9
+ context "downloading" do
10
+ setup do
11
+ @file = mock("file")
12
+ @file.stubs(:read)
13
+ @file.stubs(:eof?).returns(false)
14
+ @downloader.stubs(:open).yields(@file)
15
+ end
16
+
17
+ should "open with the given uri" do
18
+ @downloader.expects(:open).with(@uri).once
19
+ @downloader.download!(@uri, @tempfile)
20
+ end
21
+
22
+ should "buffer the read from the file and write to the tempfile" do
23
+ data = mock("data")
24
+ write_seq = sequence("write_seq")
25
+ @file.stubs(:eof?).returns(false).in_sequence(write_seq)
26
+ @file.expects(:read).returns(data).in_sequence(write_seq)
27
+ @tempfile.expects(:write).with(data).in_sequence(write_seq)
28
+ @file.stubs(:eof?).returns(true).in_sequence(write_seq)
29
+ @downloader.download!(@uri, @tempfile)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
2
+
3
+ class HttpDownloaderTest < Test::Unit::TestCase
4
+ setup do
5
+ @downloader, @tempfile = mock_downloader(Tenderloin::Downloaders::HTTP)
6
+ @downloader.stubs(:report_progress)
7
+ @downloader.stubs(:complete_progress)
8
+ @uri = "foo.box"
9
+ end
10
+
11
+ context "downloading" do
12
+ setup do
13
+ @parsed_uri = mock("parsed")
14
+ URI.stubs(:parse).with(@uri).returns(@parsed_uri)
15
+ end
16
+
17
+ should "parse the URI and use that parsed URI for Net::HTTP" do
18
+ URI.expects(:parse).with(@uri).returns(@parsed_uri).once
19
+ Net::HTTP.expects(:get_response).with(@parsed_uri).once
20
+ @downloader.download!(@uri, @tempfile)
21
+ end
22
+
23
+ should "read the body of the response and place each segment into the file" do
24
+ response = mock("response")
25
+ response.stubs(:content_length)
26
+ segment = mock("segment")
27
+ segment.stubs(:length).returns(7)
28
+
29
+ Net::HTTP.stubs(:get_response).yields(response)
30
+ response.expects(:read_body).once.yields(segment)
31
+ @tempfile.expects(:write).with(segment).once
32
+
33
+ @downloader.download!(@uri, @tempfile)
34
+ end
35
+ end
36
+
37
+ context "reporting progress" do
38
+ # TODO: Testing for this, probably
39
+ end
40
+ end