apcera-stager-api 0.2.5

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +15 -0
  4. data/CHANGELOG.md +17 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +21 -0
  7. data/README.md +86 -0
  8. data/Rakefile +8 -0
  9. data/apcera-stager-api-contrib.gemspec +25 -0
  10. data/lib/apcera-stager-api.rb +1 -0
  11. data/lib/apcera/stager/error.rb +8 -0
  12. data/lib/apcera/stager/loader.rb +11 -0
  13. data/lib/apcera/stager/stager.rb +335 -0
  14. data/spec/apcera/stager/stager_spec.rb +571 -0
  15. data/spec/fixtures/cassettes/complete.yml +55 -0
  16. data/spec/fixtures/cassettes/dependencies_add.yml +101 -0
  17. data/spec/fixtures/cassettes/dependencies_remove.yml +101 -0
  18. data/spec/fixtures/cassettes/done.yml +28 -0
  19. data/spec/fixtures/cassettes/download.yml +37400 -0
  20. data/spec/fixtures/cassettes/environment_add.yml +53 -0
  21. data/spec/fixtures/cassettes/environment_remove.yml +53 -0
  22. data/spec/fixtures/cassettes/fail.yml +28 -0
  23. data/spec/fixtures/cassettes/invalid/complete.yml +55 -0
  24. data/spec/fixtures/cassettes/invalid/dependencies_add.yml +53 -0
  25. data/spec/fixtures/cassettes/invalid/dependencies_remove.yml +53 -0
  26. data/spec/fixtures/cassettes/invalid/done.yml +55 -0
  27. data/spec/fixtures/cassettes/invalid/download.yml +54 -0
  28. data/spec/fixtures/cassettes/invalid/environment_add.yml +53 -0
  29. data/spec/fixtures/cassettes/invalid/environment_remove.yml +53 -0
  30. data/spec/fixtures/cassettes/invalid/fail.yml +28 -0
  31. data/spec/fixtures/cassettes/invalid/metadata.yml +28 -0
  32. data/spec/fixtures/cassettes/invalid/provides_add.yml +53 -0
  33. data/spec/fixtures/cassettes/invalid/provides_remove.yml +53 -0
  34. data/spec/fixtures/cassettes/invalid/relaunch.yml +53 -0
  35. data/spec/fixtures/cassettes/invalid/snapshot.yml +53 -0
  36. data/spec/fixtures/cassettes/invalid/templates_add.yml +53 -0
  37. data/spec/fixtures/cassettes/invalid/templates_remove.yml +53 -0
  38. data/spec/fixtures/cassettes/invalid/upload.yml +55 -0
  39. data/spec/fixtures/cassettes/metadata.yml +51 -0
  40. data/spec/fixtures/cassettes/provides_add.yml +53 -0
  41. data/spec/fixtures/cassettes/provides_remove.yml +53 -0
  42. data/spec/fixtures/cassettes/relaunch.yml +28 -0
  43. data/spec/fixtures/cassettes/snapshot.yml +28 -0
  44. data/spec/fixtures/cassettes/templates_add.yml +53 -0
  45. data/spec/fixtures/cassettes/templates_remove.yml +53 -0
  46. data/spec/fixtures/cassettes/upload.yml +55 -0
  47. data/spec/spec_helper.rb +31 -0
  48. data/spec/tmp/.gitkeep +0 -0
  49. metadata +190 -0
@@ -0,0 +1,571 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apcera::Stager do
4
+ before do
5
+ @appdir = "site"
6
+ @stager_url = "http://example.com"
7
+ end
8
+
9
+ describe "initialize" do
10
+ it "should raise an exception when initialized without a stager url" do
11
+ expect { Apcera::Stager.new }.to raise_error(Apcera::Error::StagerURLRequired)
12
+ end
13
+
14
+ it "should initialize with the stager url passed as an argument" do
15
+ stager = Apcera::Stager.new({:stager_url => @stager_url})
16
+ stager.class.should == Apcera::Stager
17
+ stager.stager_url.should == @stager_url
18
+ end
19
+
20
+ it "should initialize when the ENV variable STAGER_URL is present" do
21
+ begin
22
+ ENV["STAGER_URL"] = @stager_url
23
+ stager = Apcera::Stager.new
24
+ stager.class.should == Apcera::Stager
25
+ stager.stager_url.should == @stager_url
26
+ ensure
27
+ ENV["STAGER_URL"] = nil
28
+ end
29
+ end
30
+ end
31
+
32
+ context do
33
+ before do
34
+ @stager = Apcera::Stager.new({:stager_url => @stager_url})
35
+
36
+ # Best way to get our current path is to get the gem_dir!
37
+ spec = Gem::Specification.find_by_name("apcera-stager-api").gem_dir
38
+
39
+ # Lets write files in spec/tmp for tests!
40
+ @stager.root_path = File.join(spec, "spec", "tmp")
41
+ @stager.pkg_path = File.join(@stager.root_path, "pkg.tar.gz")
42
+ @stager.updated_pkg_path = File.join(@stager.root_path, "updated.tar.gz")
43
+ @stager.system_options = { :out => "/dev/null", :err => "/dev/null" }
44
+
45
+ # We don't want to exit in tests.
46
+ @stager.stub(:exit0r)
47
+ @stager.stub(:output_error)
48
+ @stager.stub(:output)
49
+ end
50
+
51
+ after do
52
+ # Don't trust urls above, they could be changed in tests.
53
+ # This does an actual delete and we want to be specific.
54
+ spec = Gem::Specification.find_by_name("apcera-stager-api").gem_dir
55
+ test_files = File.join(spec, "spec", "tmp", "*")
56
+
57
+ # Remove the test files.
58
+ FileUtils.rm_rf Dir.glob(test_files)
59
+ end
60
+
61
+ context "setup_chroot" do
62
+ it "should setup a working chrooted environment in /stagerfs" do
63
+ @stager.should_receive(:execute).with("sudo mkdir -p /stagerfs/etc")
64
+ @stager.should_receive(:execute).with("sudo cp /etc/resolv.conf /stagerfs/etc/resolv.conf")
65
+ @stager.should_receive(:execute).with("sudo mkdir -p /stagerfs/proc")
66
+ @stager.should_receive(:execute).with("sudo mount --bind /proc /stagerfs/proc")
67
+ @stager.should_receive(:execute).with("sudo mkdir -p /stagerfs/dev")
68
+ @stager.should_receive(:execute).with("sudo mount --rbind /dev /stagerfs/dev")
69
+
70
+ @stager.setup_chroot
71
+ end
72
+ end
73
+
74
+ context "download" do
75
+ it "should download the app package to pkg.tar.gz" do
76
+ VCR.use_cassette('download') do
77
+ @stager.download
78
+ end
79
+ File.exist?(@stager.pkg_path).should == true
80
+ end
81
+
82
+ it "should bubble errors to fail" do
83
+ @stager.should_receive(:exit0r).with(1) { raise }
84
+
85
+ VCR.use_cassette('invalid/download') do
86
+ expect { @stager.download }.to raise_error(Apcera::Error::DownloadError, "package download failed.\n")
87
+ end
88
+ end
89
+ end
90
+
91
+ context "extract" do
92
+ before do
93
+ VCR.use_cassette('download') do
94
+ @stager.download
95
+ end
96
+ end
97
+
98
+ it "should decompress the package to a supplied path" do
99
+ @stager.extract(@appdir)
100
+ expected_path = File.join(@stager.root_path, @appdir)
101
+ File.exist?(expected_path).should == true
102
+ @stager.app_path.should == expected_path
103
+ end
104
+
105
+ it "should bubble errors to fail" do
106
+ @stager.should_receive(:exit0r).with(1) { raise }
107
+
108
+ err = Apcera::Error::ExecuteError.new
109
+ @stager.should_receive(:execute_app).with("tar -zxf #{@stager.pkg_path}").and_raise(err)
110
+
111
+ expect { @stager.extract(@appdir) }.to raise_error(err.class)
112
+ end
113
+ end
114
+
115
+ context "execute" do
116
+ before do
117
+ VCR.use_cassette('download') do
118
+ @stager.download
119
+ end
120
+
121
+ @stager.extract(@appdir)
122
+ end
123
+
124
+ it "should execute commands with clean bundler environment" do
125
+ Bundler.should_receive(:with_clean_env).at_least(:once).and_yield
126
+
127
+ @stager.execute("cat thing").should == nil
128
+ @stager.execute("cat #{File.join(@stager.app_path, "app", "Gemfile")}").should == true
129
+ end
130
+
131
+ it "should bubble errors to fail" do
132
+ @stager.should_receive(:exit0r).with(1) { raise }
133
+
134
+ cmd = "cat thing"
135
+ expect {@stager.execute(cmd) }.to raise_error(Apcera::Error::ExecuteError, "failed to execute: #{cmd}.\n")
136
+ end
137
+ end
138
+
139
+ context "execute_app" do
140
+ before do
141
+ VCR.use_cassette('download') do
142
+ @stager.download
143
+ end
144
+ end
145
+
146
+ it "should execute commands in app dir with clean bundler environment" do
147
+ Bundler.should_receive(:with_clean_env).at_least(:once).and_yield
148
+
149
+ @stager.extract(@appdir)
150
+
151
+ @stager.execute_app("cat thing").should == nil
152
+ @stager.execute_app("cat #{File.join("app", "Gemfile")}").should == true
153
+ end
154
+
155
+ it "should bubble errors to fail when app path is missing (no extract)" do
156
+ @stager.should_receive(:exit0r).with(1) { raise }
157
+
158
+ cmd = "cat thing"
159
+ expect {@stager.execute_app(cmd) }.to raise_error(Apcera::Error::AppPathError, "app path not set, please run extract!\n")
160
+ end
161
+
162
+ it "should bubble errors to fail" do
163
+ @stager.should_receive(:exit0r).with(1) { raise }
164
+
165
+ @stager.extract(@appdir)
166
+
167
+ cmd = "cat thing"
168
+ expect {@stager.execute_app(cmd) }.to raise_error(Apcera::Error::ExecuteError, "failed to execute: #{cmd}.\n")
169
+ end
170
+ end
171
+
172
+ context "upload" do
173
+ it "should compress a new package and send to the staging coordinator" do
174
+ VCR.use_cassette('download') do
175
+ @stager.download
176
+ end
177
+
178
+ @stager.extract(@appdir)
179
+
180
+ VCR.use_cassette('upload') do
181
+ @stager.upload
182
+ end
183
+
184
+ File.exist?(@stager.updated_pkg_path).should be_true
185
+ end
186
+
187
+ it "should compress using tar czf" do
188
+ VCR.use_cassette('download') do
189
+ @stager.download
190
+ end
191
+
192
+ @stager.extract(@appdir)
193
+
194
+ @stager.should_receive(:execute_app).with("cd #{@stager.app_path}/.. && tar czf #{@stager.updated_pkg_path} #{@appdir}").and_return
195
+
196
+ @stager.upload
197
+ end
198
+
199
+ it "should upload the original package when the app wasn't extracted" do
200
+ VCR.use_cassette('download') do
201
+ @stager.download
202
+ end
203
+
204
+ @stager.should_not_receive(:download)
205
+ @stager.should_receive(:upload_file).with(@stager.pkg_path).and_return
206
+
207
+ @stager.upload
208
+ end
209
+
210
+ it "should upload the original package when the app wasn't downloaded or extracted" do
211
+ @stager.should_receive(:download).and_return
212
+ @stager.should_receive(:upload_file).with(@stager.pkg_path).and_return
213
+
214
+ @stager.upload
215
+ end
216
+
217
+ it "should bubble errors to fail" do
218
+ VCR.use_cassette('download') do
219
+ @stager.download
220
+ end
221
+
222
+ @stager.should_receive(:exit0r).with(1) { raise }
223
+
224
+ @stager.extract(@appdir)
225
+
226
+ VCR.use_cassette('invalid/upload') do
227
+ expect { @stager.upload }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
228
+ end
229
+ end
230
+ end
231
+
232
+ context "complete" do
233
+ before do
234
+ VCR.use_cassette('download') do
235
+ @stager.download
236
+ end
237
+
238
+ @stager.extract(@appdir)
239
+ end
240
+
241
+ it "should compress a new package and send to the staging coordinator then be done" do
242
+ VCR.use_cassette('complete') do
243
+ @stager.complete
244
+ end
245
+
246
+ File.exist?(File.join(@stager.root_path, "#{@appdir}.tar.gz"))
247
+ end
248
+
249
+ it "should bubble errors to fail" do
250
+ @stager.should_receive(:exit0r).with(1) { raise }
251
+
252
+ VCR.use_cassette('invalid/complete') do
253
+ expect { @stager.complete }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
254
+ end
255
+ end
256
+ end
257
+
258
+ context "done" do
259
+ it "should send done to the staging coordinator" do
260
+ @stager.should_receive(:exit0r).with(0)
261
+
262
+ VCR.use_cassette('done') do
263
+ @stager.done
264
+ end
265
+ end
266
+
267
+ it "should bubble errors to fail" do
268
+ @stager.should_receive(:exit0r).with(1) { raise }
269
+
270
+ VCR.use_cassette('invalid/done') do
271
+ expect { @stager.done }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
272
+ end
273
+ end
274
+ end
275
+
276
+ context "snapshot" do
277
+ before do
278
+ VCR.use_cassette('download') do
279
+ @stager.download
280
+ end
281
+
282
+ @stager.extract(@appdir)
283
+ end
284
+
285
+ it "should send a snapshot request to the staging coordinator" do
286
+
287
+ VCR.use_cassette('snapshot') do
288
+ @stager.snapshot
289
+ end
290
+ VCR.use_cassette('done') do
291
+ @stager.done
292
+ end
293
+ end
294
+
295
+ it "should bubble errors to fail" do
296
+ @stager.should_receive(:exit0r).with(1) { raise }
297
+
298
+ VCR.use_cassette('invalid/snapshot') do
299
+ expect { @stager.snapshot }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
300
+ end
301
+ end
302
+ end
303
+
304
+ context "fail" do
305
+ it "should have a fail hook that exits with exit code 1" do
306
+ # Make sure we don't exit and that we called exit 1.
307
+ @stager.should_receive(:exit0r).with(1)
308
+
309
+ VCR.use_cassette('fail') do
310
+ @stager.fail.should == "OK"
311
+ end
312
+ end
313
+ end
314
+
315
+ context "meta" do
316
+ it "should recieve package metadata and cache it" do
317
+ VCR.use_cassette('metadata') do
318
+ @stager.meta.class.should == Hash
319
+ end
320
+ end
321
+
322
+ it "should throw errors" do
323
+ VCR.use_cassette('invalid/metadata') do
324
+ expect { @stager.meta }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
325
+ end
326
+ end
327
+ end
328
+
329
+ context "relaunch" do
330
+ it "should allow you to trigger a stager relaunch" do
331
+ @stager.should_receive(:exit0r).with(0) { 0 }
332
+
333
+ VCR.use_cassette('relaunch') do
334
+ @stager.relaunch.should == 0
335
+ end
336
+ end
337
+
338
+ it "should bubble errors to fail" do
339
+ @stager.should_receive(:exit0r).with(1) { raise }
340
+
341
+ VCR.use_cassette('invalid/relaunch') do
342
+ expect { @stager.relaunch }.to raise_error(RestClient::ResourceNotFound, "404 Resource Not Found")
343
+ end
344
+ end
345
+ end
346
+
347
+ context "start_command" do
348
+ it "should return the package start command" do
349
+ VCR.use_cassette('metadata') do
350
+ @stager.start_command.should == "./startme"
351
+ end
352
+ end
353
+
354
+ it "should allow you to set the start command" do
355
+ VCR.use_cassette('environment_add') do
356
+ @stager.start_command = "./startme"
357
+ end
358
+ end
359
+ end
360
+
361
+ context "start_path" do
362
+ it "should return the package start path" do
363
+ VCR.use_cassette('metadata') do
364
+ @stager.start_path.should == "/app"
365
+ end
366
+ end
367
+
368
+ it "should allow you to set the start path" do
369
+ VCR.use_cassette('environment_add') do
370
+ @stager.start_path = "/app"
371
+ end
372
+ end
373
+ end
374
+
375
+ context "environment_add" do
376
+ it "should add an environment variable" do
377
+ VCR.use_cassette('environment_add') do
378
+ @stager.environment_add("TEST_VAR", "foo")
379
+ end
380
+ end
381
+
382
+ it "should bubble errors to fail" do
383
+ @stager.should_receive(:exit0r).with(1) { raise }
384
+
385
+ VCR.use_cassette('invalid/environment_add') do
386
+ expect { @stager.environment_add("TEST_VAR", "foo") }.to raise_error
387
+ end
388
+ end
389
+ end
390
+
391
+ context "environment_remove" do
392
+ it "should environment_remove an environment variable" do
393
+ VCR.use_cassette('environment_remove') do
394
+ @stager.environment_remove("TEST_VAR")
395
+ end
396
+ end
397
+
398
+ it "should bubble errors to fail" do
399
+ @stager.should_receive(:exit0r).with(1) { raise }
400
+
401
+ VCR.use_cassette('invalid/environment_remove') do
402
+ expect { @stager.environment_remove("TEST_VAR") }.to raise_error
403
+ end
404
+ end
405
+ end
406
+
407
+ context "provides_add" do
408
+ it "should add to its list of provides" do
409
+ VCR.use_cassette('provides_add') do
410
+ @stager.provides_add("os", "linux")
411
+ end
412
+ end
413
+
414
+ it "should bubble errors to fail" do
415
+ @stager.should_receive(:exit0r).with(1) { raise }
416
+
417
+ VCR.use_cassette('invalid/provides_add') do
418
+ expect { @stager.provides_add("os", "linux") }.to raise_error
419
+ end
420
+ end
421
+ end
422
+
423
+ context "provides_remove" do
424
+ it "should remove from its list of provides" do
425
+ VCR.use_cassette('provides_remove') do
426
+ @stager.provides_remove("os", "linux")
427
+ end
428
+ end
429
+
430
+ it "should bubble errors to fail" do
431
+ @stager.should_receive(:exit0r).with(1) { raise }
432
+
433
+ VCR.use_cassette('invalid/provides_remove') do
434
+ expect { @stager.provides_remove("os", "linux") }.to raise_error
435
+ end
436
+ end
437
+ end
438
+
439
+ context "dependencies_add" do
440
+ it "should add to its list of dependencies" do
441
+ VCR.use_cassette('dependencies_add') do
442
+ @stager.dependencies_add("os", "someos").should == true
443
+ end
444
+ end
445
+
446
+ it "should return false if the dependency is already there" do
447
+ VCR.use_cassette('dependencies_add') do
448
+ @stager.dependencies_add("os", "linux").should == false
449
+ end
450
+ end
451
+
452
+ it "should bubble errors to fail" do
453
+ @stager.should_receive(:exit0r).with(1) { raise }
454
+
455
+ VCR.use_cassette('invalid/dependencies_add') do
456
+ expect { @stager.dependencies_add("os", "linux") }.to raise_error
457
+ end
458
+ end
459
+ end
460
+
461
+ context "dependencies_remove" do
462
+ it "should remove from its list of dependencies" do
463
+ VCR.use_cassette('dependencies_remove') do
464
+ @stager.dependencies_remove("os", "linux")
465
+ end
466
+ end
467
+
468
+ it "should return false if the dependency doesn't exist" do
469
+ VCR.use_cassette('dependencies_remove') do
470
+ @stager.dependencies_remove("os", "someos").should == false
471
+ end
472
+ end
473
+
474
+ it "should bubble errors to fail" do
475
+ @stager.should_receive(:exit0r).with(1) { raise }
476
+
477
+ VCR.use_cassette('invalid/dependencies_remove') do
478
+ expect { @stager.dependencies_remove("os", "linux") }.to raise_error
479
+ end
480
+ end
481
+ end
482
+
483
+ context "templates_add" do
484
+ it "should add to its list of templates" do
485
+ VCR.use_cassette('templates_add') do
486
+ @stager.templates_add("/path/to/template")
487
+ end
488
+ end
489
+
490
+ it "should add to its list of templates with delimiters" do
491
+ VCR.use_cassette('templates_add') do
492
+ @stager.templates_add("/path/to/template", "{{", "}}")
493
+ end
494
+ end
495
+
496
+ it "should bubble errors to fail" do
497
+ @stager.should_receive(:exit0r).with(1) { raise }
498
+
499
+ VCR.use_cassette('invalid/templates_add') do
500
+ expect { @stager.templates_add("/path/to/template") }.to raise_error
501
+ end
502
+ end
503
+ end
504
+
505
+ context "templates_remove" do
506
+ it "should remove from its list of templates" do
507
+ VCR.use_cassette('templates_remove') do
508
+ @stager.templates_remove("/path/to/template")
509
+ end
510
+ end
511
+
512
+ it "should remove from its list of templates with delimiters" do
513
+ VCR.use_cassette('templates_remove') do
514
+ @stager.templates_remove("/path/to/template", "{{", "}}")
515
+ end
516
+ end
517
+
518
+ it "should bubble errors to fail" do
519
+ @stager.should_receive(:exit0r).with(1) { raise }
520
+
521
+ VCR.use_cassette('invalid/templates_remove') do
522
+ expect { @stager.templates_remove("/path/to/template") }.to raise_error
523
+ end
524
+ end
525
+ end
526
+
527
+ context "output" do
528
+ before do
529
+ @stager.unstub(:output)
530
+ end
531
+
532
+ it "should print to stdout" do
533
+ $stdout.should_receive(:puts).with("test")
534
+ @stager.output("test")
535
+ end
536
+ end
537
+
538
+ context "output_error" do
539
+ before do
540
+ @stager.unstub(:output_error)
541
+ end
542
+
543
+ it "should print to stderr" do
544
+ $stderr.should_receive(:puts).with("test")
545
+ @stager.output_error("test")
546
+ end
547
+ end
548
+
549
+ context "exit0r" do
550
+ before do
551
+ @stager.unstub(:exit0r)
552
+ end
553
+
554
+ it "should wrap successful exit" do
555
+ begin
556
+ @stager.exit0r(0)
557
+ rescue SystemExit => e
558
+ e.status.should == 0
559
+ end
560
+ end
561
+
562
+ it "should wrap errored exit" do
563
+ begin
564
+ @stager.exit0r(1)
565
+ rescue SystemExit => e
566
+ e.status.should == 1
567
+ end
568
+ end
569
+ end
570
+ end
571
+ end