bosh_cli 0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/README +4 -0
  2. data/Rakefile +55 -0
  3. data/bin/bosh +17 -0
  4. data/lib/cli.rb +76 -0
  5. data/lib/cli/cache.rb +44 -0
  6. data/lib/cli/changeset_helper.rb +142 -0
  7. data/lib/cli/command_definition.rb +52 -0
  8. data/lib/cli/commands/base.rb +245 -0
  9. data/lib/cli/commands/biff.rb +300 -0
  10. data/lib/cli/commands/blob.rb +125 -0
  11. data/lib/cli/commands/cloudcheck.rb +169 -0
  12. data/lib/cli/commands/deployment.rb +147 -0
  13. data/lib/cli/commands/job.rb +42 -0
  14. data/lib/cli/commands/job_management.rb +117 -0
  15. data/lib/cli/commands/log_management.rb +81 -0
  16. data/lib/cli/commands/maintenance.rb +131 -0
  17. data/lib/cli/commands/misc.rb +240 -0
  18. data/lib/cli/commands/package.rb +112 -0
  19. data/lib/cli/commands/property_management.rb +125 -0
  20. data/lib/cli/commands/release.rb +469 -0
  21. data/lib/cli/commands/ssh.rb +271 -0
  22. data/lib/cli/commands/stemcell.rb +184 -0
  23. data/lib/cli/commands/task.rb +213 -0
  24. data/lib/cli/commands/user.rb +28 -0
  25. data/lib/cli/commands/vms.rb +53 -0
  26. data/lib/cli/config.rb +154 -0
  27. data/lib/cli/core_ext.rb +145 -0
  28. data/lib/cli/dependency_helper.rb +62 -0
  29. data/lib/cli/deployment_helper.rb +263 -0
  30. data/lib/cli/deployment_manifest_compiler.rb +28 -0
  31. data/lib/cli/director.rb +633 -0
  32. data/lib/cli/director_task.rb +64 -0
  33. data/lib/cli/errors.rb +48 -0
  34. data/lib/cli/event_log_renderer.rb +351 -0
  35. data/lib/cli/job_builder.rb +226 -0
  36. data/lib/cli/package_builder.rb +254 -0
  37. data/lib/cli/packaging_helper.rb +248 -0
  38. data/lib/cli/release.rb +176 -0
  39. data/lib/cli/release_builder.rb +215 -0
  40. data/lib/cli/release_compiler.rb +178 -0
  41. data/lib/cli/release_tarball.rb +272 -0
  42. data/lib/cli/runner.rb +771 -0
  43. data/lib/cli/stemcell.rb +83 -0
  44. data/lib/cli/task_log_renderer.rb +40 -0
  45. data/lib/cli/templates/help_message.erb +75 -0
  46. data/lib/cli/validation.rb +42 -0
  47. data/lib/cli/version.rb +7 -0
  48. data/lib/cli/version_calc.rb +48 -0
  49. data/lib/cli/versions_index.rb +126 -0
  50. data/lib/cli/yaml_helper.rb +62 -0
  51. data/spec/assets/biff/bad_gateway_config.yml +28 -0
  52. data/spec/assets/biff/good_simple_config.yml +63 -0
  53. data/spec/assets/biff/good_simple_golden_config.yml +63 -0
  54. data/spec/assets/biff/good_simple_template.erb +69 -0
  55. data/spec/assets/biff/multiple_subnets_config.yml +40 -0
  56. data/spec/assets/biff/network_only_template.erb +34 -0
  57. data/spec/assets/biff/no_cc_config.yml +27 -0
  58. data/spec/assets/biff/no_range_config.yml +27 -0
  59. data/spec/assets/biff/no_subnet_config.yml +16 -0
  60. data/spec/assets/biff/ok_network_config.yml +30 -0
  61. data/spec/assets/biff/properties_template.erb +6 -0
  62. data/spec/assets/deployment.MF +0 -0
  63. data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
  64. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
  65. data/spec/assets/release/jobs/cacher.tgz +0 -0
  66. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  67. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  68. data/spec/assets/release/jobs/cacher/job.MF +6 -0
  69. data/spec/assets/release/jobs/cacher/monit +1 -0
  70. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  71. data/spec/assets/release/jobs/cleaner/job.MF +4 -0
  72. data/spec/assets/release/jobs/cleaner/monit +1 -0
  73. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  74. data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
  75. data/spec/assets/release/jobs/sweeper/job.MF +5 -0
  76. data/spec/assets/release/jobs/sweeper/monit +1 -0
  77. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  78. data/spec/assets/release/packages/stuff.tgz +0 -0
  79. data/spec/assets/release/release.MF +17 -0
  80. data/spec/assets/release_invalid_checksum.tgz +0 -0
  81. data/spec/assets/release_invalid_jobs.tgz +0 -0
  82. data/spec/assets/release_no_name.tgz +0 -0
  83. data/spec/assets/release_no_version.tgz +0 -0
  84. data/spec/assets/stemcell/image +1 -0
  85. data/spec/assets/stemcell/stemcell.MF +6 -0
  86. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  87. data/spec/assets/stemcell_no_image.tgz +0 -0
  88. data/spec/assets/valid_release.tgz +0 -0
  89. data/spec/assets/valid_stemcell.tgz +0 -0
  90. data/spec/spec_helper.rb +25 -0
  91. data/spec/unit/base_command_spec.rb +66 -0
  92. data/spec/unit/biff_spec.rb +135 -0
  93. data/spec/unit/cache_spec.rb +36 -0
  94. data/spec/unit/cli_commands_spec.rb +481 -0
  95. data/spec/unit/config_spec.rb +139 -0
  96. data/spec/unit/core_ext_spec.rb +77 -0
  97. data/spec/unit/dependency_helper_spec.rb +52 -0
  98. data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
  99. data/spec/unit/director_spec.rb +511 -0
  100. data/spec/unit/director_task_spec.rb +48 -0
  101. data/spec/unit/event_log_renderer_spec.rb +171 -0
  102. data/spec/unit/hash_changeset_spec.rb +73 -0
  103. data/spec/unit/job_builder_spec.rb +454 -0
  104. data/spec/unit/package_builder_spec.rb +567 -0
  105. data/spec/unit/release_builder_spec.rb +65 -0
  106. data/spec/unit/release_spec.rb +66 -0
  107. data/spec/unit/release_tarball_spec.rb +33 -0
  108. data/spec/unit/runner_spec.rb +140 -0
  109. data/spec/unit/ssh_spec.rb +78 -0
  110. data/spec/unit/stemcell_spec.rb +17 -0
  111. data/spec/unit/version_calc_spec.rb +27 -0
  112. data/spec/unit/versions_index_spec.rb +132 -0
  113. metadata +338 -0
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::DirectorTask do
6
+
7
+ before :each do
8
+ @director = Bosh::Cli::Director.new("http://target", "user", "pass")
9
+ end
10
+
11
+ it "tracks partial output responses from director" do
12
+ @task = Bosh::Cli::DirectorTask.new(@director, 10)
13
+
14
+ @director.stub!(:get).
15
+ with("/tasks/10/output", nil, nil, "Range" => "bytes=0-").
16
+ and_return([206, "test\nout", { :content_range => "bytes 0-7/100" }])
17
+
18
+ @director.stub!(:get).
19
+ with("/tasks/10/output", nil, nil, "Range" => "bytes=8-").
20
+ and_return([206, "put", { :content_range => "bytes 8-10/100" }])
21
+
22
+ @director.stub!(:get).
23
+ with("/tasks/10/output", nil, nil, "Range" => "bytes=11-").
24
+ and_return([206, " success\n", { :content_range => "bytes 11-19/100" }])
25
+
26
+ @director.stub!(:get).
27
+ with("/tasks/10/output", nil, nil, "Range" => "bytes=20-").
28
+ and_return([416, "done", {}])
29
+
30
+ @task.output.should == "test\n"
31
+ @task.output.should == nil # No newline yet
32
+ @task.output.should == "output success\n" # Got a newline
33
+ @task.output.should == "done\n" # Flushed
34
+ end
35
+
36
+ it "supports explicit output flush" do
37
+ @task = Bosh::Cli::DirectorTask.new(@director, 10)
38
+
39
+ @director.stub!(:get).
40
+ with("/tasks/10/output", nil, nil, "Range" => "bytes=0-").
41
+ and_return([206, "test\nout", { :content_range => "bytes 0-7/100" }])
42
+
43
+ @task.output.should == "test\n"
44
+ @task.flush_output.should == "out\n"
45
+ # Nothing in buffer at this point
46
+ @task.flush_output.should == nil
47
+ end
48
+ end
@@ -0,0 +1,171 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::EventLogRenderer do
6
+
7
+ def make_event(stage, task, index, total,
8
+ state = "started", tags = [], progress = 0)
9
+ event = {
10
+ "time" => Time.now.to_i,
11
+ "stage" => stage,
12
+ "task" => task,
13
+ "index" => index,
14
+ "total" => total,
15
+ "state" => state,
16
+ "tags" => tags,
17
+ "progress" => progress
18
+ }
19
+ JSON.generate(event)
20
+ end
21
+
22
+ def make_renderer(*args)
23
+ Bosh::Cli::EventLogRenderer.new(*args)
24
+ end
25
+
26
+ it "allows adding events" do
27
+ renderer = make_renderer
28
+ renderer.add_event(make_event("Preparing", "Binding release",
29
+ 1, 9, "started"))
30
+ renderer.add_event(make_event("Preparing", "Binding existing deployment",
31
+ 2, 9, "started"))
32
+ renderer.events_count.should == 2
33
+ end
34
+
35
+ it "silently ignores malformed events" do
36
+ renderer = make_renderer
37
+ renderer.add_event(make_event(nil, "Binding release", 1, 9, nil, []))
38
+ renderer.add_event(make_event("Preparing", "Binding existing deployment",
39
+ 2, nil, nil))
40
+ renderer.add_event(JSON.generate("a" => "b"))
41
+ renderer.events_count.should == 0
42
+ end
43
+
44
+ it "sets current stage based on the most recent event " +
45
+ "but ignores events from non-current stages" do
46
+ renderer = make_renderer
47
+ renderer.add_event(make_event("Preparing", "Binding release", 1, 9))
48
+ renderer.current_stage.should == "Preparing"
49
+ renderer.add_event(make_event("Preparing", "Binding existing deployment",
50
+ 2, 9))
51
+ renderer.current_stage.should == "Preparing"
52
+ renderer.add_event(make_event("Updating resource pool",
53
+ "Deleting outdated VM", 1, 5))
54
+ renderer.current_stage.should == "Updating resource pool"
55
+ renderer.events_count.should == 3
56
+ renderer.add_event(make_event("Preparing", "Some additional stuff", 3, 9))
57
+ renderer.current_stage.should == "Updating resource pool"
58
+ renderer.events_count.should == 3
59
+ renderer.add_event(make_event("Updating job router", "Canary update", 1, 1))
60
+ renderer.current_stage.should == "Updating job router"
61
+ renderer.events_count.should == 4
62
+ end
63
+
64
+ it "can render event log with progress bar" do
65
+ buf = StringIO.new
66
+ Bosh::Cli::Config.output = buf
67
+
68
+ renderer = make_renderer
69
+ renderer.add_event(make_event("Preparing", "Binding release", 1, 9))
70
+
71
+ lines = renderer.render.split("\n")
72
+
73
+ lines.count.should == 3
74
+ lines[1].should == "Preparing"
75
+ lines[2].should =~ /Binding release/
76
+ lines[2].should =~ /\|\s+\| 0\/9/
77
+
78
+ renderer.add_event(make_event("Preparing", "Moving stuff", 2, 9))
79
+
80
+ lines = renderer.render.split("\n")
81
+ lines.count.should == 1
82
+ lines[0].should =~ /Binding release/
83
+ lines[0].should =~ /\|\s+\| 0\/9/
84
+
85
+ renderer.add_event(make_event("Preparing", "Moving stuff",
86
+ 2, 9, "finished"))
87
+
88
+ lines = renderer.render.split("\n")
89
+ lines.count.should == 2
90
+ lines[0].should =~ /moving stuff/
91
+ lines[1].should =~ /Binding release/
92
+ lines[1].should =~ /\|o+\s+\| 1\/9/
93
+
94
+ # throwing in out-of-order event
95
+ renderer.add_event(make_event("Preparing", "Binding release",
96
+ 1, 9, "finished"))
97
+
98
+ (3..9).each do |i|
99
+ renderer.add_event(make_event("Preparing", "event #{i}",
100
+ i, 9))
101
+ renderer.add_event(make_event("Preparing", "event #{i}",
102
+ i, 9, "finished"))
103
+ end
104
+
105
+ lines = renderer.render.split("\n")
106
+ lines.count.should == 9
107
+ lines[-1].should =~ /\|o+\| 9\/9/
108
+
109
+ renderer.add_event(make_event("Updating", "prepare update",
110
+ 1, 2, "started", ["stuff", "thing"]))
111
+
112
+ lines = renderer.render.split("\n")
113
+ lines.count.should == 4
114
+
115
+ lines[0].should =~ /Done/
116
+ lines[0].should =~ /9\/9/
117
+ lines[1].should == ""
118
+ lines[2].should == "Updating %s" % ["stuff, thing".green]
119
+ lines[3].should =~ /0\/2/
120
+
121
+ lines = renderer.finish(:done).split("\n")
122
+ lines[0].should =~ /Done/
123
+ lines[0].should =~ /2\/2/
124
+ end
125
+
126
+ it "renders erorr state properly" do
127
+ buf = StringIO.new
128
+ Bosh::Cli::Config.output = buf
129
+
130
+ renderer = make_renderer
131
+ renderer.add_event(make_event("Preparing", "Binding release", 1, 9))
132
+ renderer.add_event(make_event("Preparing", "Moving stuff", 2, 9))
133
+ renderer.add_event(make_event("Preparing", "Moving stuff", 2, 9, "finished"))
134
+ renderer.add_event(make_event("Updating", "prepare update",
135
+ 1, 2, "started", ["stuff", "thing"]))
136
+
137
+ lines = renderer.finish(:error).split("\n")
138
+ lines[-1].should =~ /Error/
139
+ lines[-1].should =~ /0\/2/
140
+ end
141
+
142
+ it "supports tracking individual tasks progress" do
143
+ renderer = make_renderer
144
+ renderer.add_event(make_event("Preparing", "Binding release",
145
+ 1, 2, "started", [], 0))
146
+ renderer.add_event(make_event("Preparing", "Binding release",
147
+ 1, 2, "in_progress", [], 25))
148
+
149
+ lines = renderer.render.split("\n")
150
+
151
+ lines[1].should =~ /Preparing/
152
+ lines[2].should =~ /\|o+\s+\| 0\/2/
153
+ lines[2].should =~ /Binding release/
154
+
155
+ renderer.add_event(make_event("Preparing", "Binding release",
156
+ 1, 2, "in_progress", [], 50))
157
+
158
+ lines = renderer.render.split("\n")
159
+
160
+ lines[0].should =~ /\|o+\s+\| 0\/2/
161
+ lines[0].should =~ /Binding release/
162
+
163
+ renderer.add_event(make_event("Preparing", "Binding release",
164
+ 1, 2, "finished", []))
165
+
166
+ lines = renderer.render.split("\n")
167
+ lines[1].should_not =~ /Binding release/
168
+ lines[1].should =~ /\|o+\s+\| 1\/2/
169
+ end
170
+
171
+ end
@@ -0,0 +1,73 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::HashChangeset do
6
+
7
+ def changeset(m1, m2)
8
+ cs = Bosh::Cli::HashChangeset.new
9
+ cs.add_hash(m1, :old)
10
+ cs.add_hash(m2, :new)
11
+ cs
12
+ end
13
+
14
+ it "contains changeset for two hashes" do
15
+ m1 = {
16
+ :foo => {
17
+ :bar => 1,
18
+ :baz => "purr",
19
+ :nc => "nc",
20
+ :properties => {
21
+ :a => "1",
22
+ :b => "2",
23
+ :c => "3"
24
+ },
25
+ :arr => %w(a b c)
26
+ },
27
+ :arr => [1, 2, 3],
28
+ :zb => { :a => 2, :b => 3}
29
+ }
30
+
31
+ m2 = {
32
+ :foo => {
33
+ :bar => 2,
34
+ :baz => "meow",
35
+ :nc => "nc",
36
+ :properties => {
37
+ :a => "2",
38
+ :d => "7",
39
+ :c => 3
40
+ },
41
+ :arr => %w(a b c d e)
42
+ },
43
+ :arr => [1, 2, 3],
44
+ :zb => "test"
45
+ }
46
+
47
+ mc = changeset(m1, m2)
48
+
49
+ mc[:foo][:bar].changed?.should be_true
50
+ mc[:foo][:bar].old.should == 1
51
+ mc[:foo][:bar].new.should == 2
52
+
53
+ mc[:foo][:baz].changed?.should be_true
54
+ mc[:foo][:baz].old.should == "purr"
55
+ mc[:foo][:baz].new.should == "meow"
56
+
57
+ mc[:foo][:nc].changed?.should be_false
58
+ mc[:foo][:nc].same?.should be_true
59
+ mc[:foo][:nc].old.should == "nc"
60
+ mc[:foo][:nc].new.should == "nc"
61
+
62
+ mc[:foo][:properties][:a].changed?.should be_true
63
+ mc[:foo][:properties][:b].removed?.should be_true
64
+ mc[:foo][:properties][:c].mismatch?.should be_true
65
+ mc[:foo][:properties][:d].added?.should be_true
66
+
67
+ mc[:foo][:arr].changed?.should be_true
68
+ mc[:arr].same?.should be_true
69
+
70
+ mc[:zb].mismatch?.should be_true
71
+ end
72
+
73
+ end
@@ -0,0 +1,454 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ require "spec_helper"
4
+
5
+ describe Bosh::Cli::JobBuilder do
6
+
7
+ before(:each) do
8
+ @release_dir = Dir.mktmpdir
9
+ end
10
+
11
+ def new_builder(name, packages = [], templates = { }, built_packages = [],
12
+ create_spec = true, final = false, blobstore = mock("blobstore"))
13
+ # Workaround for Hash requirement
14
+ if templates.is_a?(Array)
15
+ templates = templates.inject({ }) { |h, e| h[e] = e; h }
16
+ end
17
+
18
+ spec = {
19
+ "name" => name,
20
+ "packages" => packages,
21
+ "templates" => templates
22
+ }
23
+ add_spec(name) if create_spec
24
+
25
+ Bosh::Cli::JobBuilder.new(spec, @release_dir,
26
+ final, blobstore, built_packages)
27
+ end
28
+
29
+ def add_file(job_name, file, contents = nil)
30
+ job_dir = File.join(@release_dir, "jobs", job_name)
31
+ file_path = File.join(job_dir, file)
32
+ FileUtils.mkdir_p(File.dirname(file_path))
33
+ FileUtils.touch(file_path)
34
+ if contents
35
+ File.open(file_path, "w") { |f| f.write(contents) }
36
+ end
37
+ end
38
+
39
+ def add_spec(job_name)
40
+ add_file(job_name, "spec")
41
+ end
42
+
43
+ def add_monit(job_name, file="monit")
44
+ add_file(job_name, file)
45
+ end
46
+
47
+ def add_templates(job_name, *files)
48
+ job_template_path = File.join(@release_dir, "jobs", job_name, "templates")
49
+ FileUtils.mkdir_p(job_template_path)
50
+
51
+ files.each do |file|
52
+ add_file(job_name, "templates/#{file}")
53
+ end
54
+ end
55
+
56
+ def remove_templates(job_name, *files)
57
+ job_template_path = File.join(@release_dir, "jobs", job_name, "templates")
58
+
59
+ files.each do |file|
60
+ FileUtils.rm_rf(File.join(job_template_path, file))
61
+ end
62
+ end
63
+
64
+ it "creates a new builder" do
65
+ add_templates("foo", "a.conf", "b.yml")
66
+ add_monit("foo")
67
+ builder = new_builder("foo", ["foo", "bar", "baz"],
68
+ ["a.conf", "b.yml"], ["foo", "bar", "baz"])
69
+ builder.packages.should == ["foo", "bar", "baz"]
70
+ builder.templates.should =~ ["a.conf", "b.yml"]
71
+ builder.release_dir.should == @release_dir
72
+ end
73
+
74
+ it "has a fingerprint" do
75
+ add_templates("foo", "a.conf", "b.yml")
76
+ add_monit("foo")
77
+ builder = new_builder("foo", ["foo", "bar"],
78
+ ["a.conf", "b.yml"], ["foo", "bar"])
79
+ builder.fingerprint.should == "457a3618ecca5fcf375e479627eb0a574c63574d"
80
+ end
81
+
82
+ it "has a stable portable fingerprint" do
83
+ add_templates("foo", "a.conf", "b.yml")
84
+ add_monit("foo")
85
+ b1 = new_builder("foo", ["foo", "bar"],
86
+ ["a.conf", "b.yml"], ["foo", "bar"])
87
+ f1 = b1.fingerprint
88
+ b1.reload.fingerprint.should == f1
89
+
90
+ b2 = new_builder("foo", ["foo", "bar"],
91
+ ["a.conf", "b.yml"], ["foo", "bar"])
92
+ b2.fingerprint.should == f1
93
+ end
94
+
95
+ it "changes fingerprint when new template file is added" do
96
+ add_templates("foo", "a.conf", "b.yml")
97
+ add_monit("foo")
98
+
99
+ b1 = new_builder("foo", ["foo", "bar"],
100
+ ["a.conf", "b.yml"], ["foo", "bar"])
101
+ f1 = b1.fingerprint
102
+
103
+ add_templates("foo", "baz")
104
+ b2 = new_builder("foo", ["foo", "bar"],
105
+ ["a.conf", "b.yml", "baz"], ["foo", "bar"])
106
+ b2.fingerprint.should_not == f1
107
+ end
108
+
109
+ it "changes fingerprint when template files is changed" do
110
+ add_templates("foo", "a.conf", "b.yml")
111
+ add_monit("foo")
112
+
113
+ b1 = new_builder("foo", ["foo", "bar"],
114
+ ["a.conf", "b.yml"], ["foo", "bar"])
115
+ f1 = b1.fingerprint
116
+
117
+ add_file("foo", "templates/a.conf", "bzz")
118
+ b1.reload.fingerprint.should_not == f1
119
+ end
120
+
121
+ it "changes fingerprint when new monit file is added" do
122
+ add_templates("foo", "a.conf", "b.yml")
123
+ add_monit("foo", "foo.monit")
124
+
125
+ b1 = new_builder("foo", ["foo", "bar"],
126
+ ["a.conf", "b.yml"], ["foo", "bar"])
127
+ f1 = b1.fingerprint
128
+
129
+ add_monit("foo", "bar.monit")
130
+ b2 = new_builder("foo", ["foo", "bar"],
131
+ ["a.conf", "b.yml"], ["foo", "bar"])
132
+ b2.fingerprint.should_not == f1
133
+ end
134
+
135
+ it "can read template file names from hash" do
136
+ add_templates("foo", "a.conf", "b.yml")
137
+ add_monit("foo")
138
+ builder = new_builder("foo", ["foo", "bar", "baz"],
139
+ { "a.conf" => 1, "b.yml" => 2 },
140
+ ["foo", "bar", "baz"])
141
+ builder.templates.should =~ ["a.conf", "b.yml"]
142
+ end
143
+
144
+ it "whines if name is blank" do
145
+ lambda {
146
+ new_builder("")
147
+ }.should raise_error(Bosh::Cli::InvalidJob, "Job name is missing")
148
+ end
149
+
150
+ it "whines on funny characters in name" do
151
+ lambda {
152
+ new_builder("@#!", [])
153
+ }.should raise_error(Bosh::Cli::InvalidJob,
154
+ "`@#!' is not a valid BOSH identifier")
155
+ end
156
+
157
+ it "whines if some templates are missing" do
158
+ add_templates("foo", "a.conf", "b.conf")
159
+
160
+ lambda {
161
+ new_builder("foo", [], ["a.conf", "b.conf", "c.conf"])
162
+ }.should raise_error(Bosh::Cli::InvalidJob,
163
+ "Some template files required by 'foo' job " +
164
+ "are missing: c.conf")
165
+ end
166
+
167
+ it "whines about extra packages" do
168
+ add_templates("foo", "a.conf", "b.conf")
169
+
170
+ lambda {
171
+ new_builder("foo", [], ["a.conf"], [])
172
+ }.should raise_error(Bosh::Cli::InvalidJob,
173
+ "There are unused template files for job 'foo'" +
174
+ ": b.conf")
175
+ end
176
+
177
+ it "whines if some packages are missing" do
178
+ lambda {
179
+ new_builder("foo", ["foo", "bar", "baz", "app42"], { }, ["foo", "bar"])
180
+ }.should raise_error(Bosh::Cli::InvalidJob,
181
+ "Some packages required by 'foo' job are missing: " +
182
+ "baz, app42")
183
+ end
184
+
185
+ it "whines if there is no spec file" do
186
+ lambda {
187
+ new_builder("foo", ["foo", "bar", "baz", "app42"], { },
188
+ ["foo", "bar", "baz", "app42"], false)
189
+ }.should raise_error(Bosh::Cli::InvalidJob,
190
+ "Cannot find spec file for 'foo'")
191
+ end
192
+
193
+ it "whines if there is no monit file" do
194
+ lambda {
195
+ add_templates("foo", "a.conf", "b.yml")
196
+ new_builder("foo", ["foo", "bar", "baz", "app42"],
197
+ ["a.conf", "b.yml"], ["foo", "bar", "baz", "app42"])
198
+ }.should raise_error(Bosh::Cli::InvalidJob,
199
+ "Cannot find monit file for 'foo'")
200
+
201
+ add_monit("foo")
202
+ lambda {
203
+ new_builder("foo", ["foo", "bar", "baz", "app42"],
204
+ ["a.conf", "b.yml"], ["foo", "bar", "baz", "app42"])
205
+ }.should_not raise_error
206
+ end
207
+
208
+ it "supports preparation script" do
209
+ spec = {
210
+ "name" => "foo",
211
+ "packages" => ["bar", "baz"],
212
+ "templates" => ["a.conf", "b.yml"]
213
+ }
214
+ spec_yaml = YAML.dump(spec)
215
+
216
+ script = <<-SCRIPT.gsub(/^\s*/, "")
217
+ #!/bin/sh
218
+ mkdir templates
219
+ touch templates/a.conf
220
+ touch templates/b.yml
221
+ echo '#{spec_yaml}' > spec
222
+ touch monit
223
+ SCRIPT
224
+
225
+ add_file("foo", "prepare", script)
226
+ script_path = File.join(@release_dir, "jobs", "foo", "prepare")
227
+ FileUtils.chmod(0755, script_path)
228
+ Bosh::Cli::JobBuilder.run_prepare_script(script_path)
229
+
230
+ builder = new_builder("foo", ["bar", "baz"], ["a.conf", "b.yml"],
231
+ ["foo", "bar", "baz", "app42"], false)
232
+ builder.copy_files.should == 4
233
+
234
+ Dir.chdir(builder.build_dir) do
235
+ File.directory?("templates").should be_true
236
+ ["templates/a.conf", "templates/b.yml"].each do |file|
237
+ File.file?(file).should be_true
238
+ end
239
+ File.file?("job.MF").should be_true
240
+ File.read("job.MF").should == File.read(
241
+ File.join(@release_dir, "jobs", "foo", "spec"))
242
+ File.exists?("monit").should be_true
243
+ File.exists?("prepare").should be_false
244
+ end
245
+ end
246
+
247
+ it "copies job files" do
248
+ add_templates("foo", "a.conf", "b.yml")
249
+ add_monit("foo")
250
+ builder = new_builder("foo", ["foo", "bar", "baz", "app42"],
251
+ ["a.conf", "b.yml"], ["foo", "bar", "baz", "app42"])
252
+
253
+ builder.copy_files.should == 4
254
+
255
+ Dir.chdir(builder.build_dir) do
256
+ File.directory?("templates").should be_true
257
+ ["templates/a.conf", "templates/b.yml"].each do |file|
258
+ File.file?(file).should be_true
259
+ end
260
+ File.file?("job.MF").should be_true
261
+ File.read("job.MF").should == File.read(
262
+ File.join(@release_dir, "jobs", "foo", "spec"))
263
+ File.exists?("monit").should be_true
264
+ end
265
+ end
266
+
267
+ it "generates tarball" do
268
+ add_templates("foo", "bar", "baz")
269
+ add_monit("foo")
270
+
271
+ builder = new_builder("foo", ["p1", "p2"], ["bar", "baz"], ["p1", "p2"])
272
+ builder.generate_tarball.should be_true
273
+ end
274
+
275
+ it "supports versioning" do
276
+ add_templates("foo", "bar", "baz")
277
+ add_monit("foo")
278
+
279
+ builder = new_builder("foo", [], ["bar", "baz"], [])
280
+
281
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
282
+ should be_false
283
+ builder.build
284
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
285
+ should be_true
286
+ v1_fingerprint = builder.fingerprint
287
+
288
+ add_templates("foo", "zb.yml")
289
+ builder = new_builder("foo", [], ["bar", "baz", "zb.yml"], [])
290
+ builder.build
291
+
292
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
293
+ should be_true
294
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.2-dev.tgz").
295
+ should be_true
296
+
297
+ remove_templates("foo", "zb.yml")
298
+
299
+ builder = new_builder("foo", [], ["bar", "baz"], [])
300
+ builder.build
301
+ builder.version.should == "0.1-dev"
302
+
303
+ builder.fingerprint.should == v1_fingerprint
304
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
305
+ should be_true
306
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.2-dev.tgz").
307
+ should be_true
308
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.3-dev.tgz").
309
+ should be_false
310
+ end
311
+
312
+ it "can point to either dev or a final version of a job" do
313
+ add_templates("foo", "bar", "baz")
314
+ add_monit("foo")
315
+ fingerprint = "34586bac103c208361ae5dcadd101807110c4546"
316
+
317
+ final_versions = Bosh::Cli::VersionsIndex.new(
318
+ File.join(@release_dir, ".final_builds", "jobs", "foo"))
319
+ dev_versions = Bosh::Cli::VersionsIndex.new(
320
+ File.join(@release_dir, ".dev_builds", "jobs", "foo"))
321
+
322
+ final_versions.add_version(fingerprint,
323
+ { "version" => "4", "blobstore_id" => "12321" },
324
+ "payload")
325
+ dev_versions.add_version(fingerprint,
326
+ { "version" => "0.7-dev" },
327
+ "dev_payload")
328
+
329
+ builder = new_builder("foo", [], ["bar", "baz"], [])
330
+
331
+ builder.fingerprint.should == fingerprint
332
+
333
+ builder.use_final_version
334
+ builder.version.should == "4"
335
+ builder.tarball_path.should == File.join(
336
+ @release_dir, ".final_builds", "jobs", "foo", "4.tgz")
337
+
338
+ builder.use_dev_version
339
+ builder.version.should == "0.7-dev"
340
+ builder.tarball_path.should == File.join(
341
+ @release_dir, ".dev_builds", "jobs", "foo", "0.7-dev.tgz")
342
+ end
343
+
344
+ it "bumps major dev version in sync with final version" do
345
+ add_templates("foo", "bar", "baz")
346
+ add_monit("foo")
347
+
348
+ builder = new_builder("foo", [], ["bar", "baz"], [])
349
+ builder.build
350
+
351
+ builder.version.should == "0.1-dev"
352
+
353
+ blobstore = mock("blobstore")
354
+ blobstore.should_receive(:create).and_return("object_id")
355
+ final_builder = new_builder("foo", [], ["bar", "baz"], [],
356
+ true, true, blobstore)
357
+ final_builder.build
358
+
359
+ final_builder.version.should == 1
360
+
361
+ add_templates("foo", "bzz")
362
+ builder2 = new_builder("foo", [], ["bar", "baz", "bzz"], [])
363
+ builder2.build
364
+ builder2.version.should == "1.1-dev"
365
+ end
366
+
367
+ it "whines on attempt to create final build if not matched " +
368
+ "by existing final or dev build" do
369
+ add_templates("foo", "bar", "baz")
370
+ add_monit("foo")
371
+
372
+ blobstore = mock("blobstore")
373
+
374
+ final_builder = new_builder("foo", [], ["bar", "baz"],
375
+ [], true, true, blobstore)
376
+ lambda {
377
+ final_builder.build
378
+ }.should raise_error(Bosh::Cli::CliExit)
379
+
380
+ dev_builder = new_builder("foo", [], ["bar", "baz"],
381
+ [], true, false, blobstore)
382
+ dev_builder.build
383
+
384
+ final_builder2 = new_builder("foo", [], ["bar", "baz"],
385
+ [], true, true, blobstore)
386
+ blobstore.should_receive(:create)
387
+ final_builder2.build
388
+
389
+ add_templates("foo", "bzz")
390
+ final_builder3 = new_builder("foo", [], ["bar", "baz", "bzz"],
391
+ [], true, true, blobstore)
392
+
393
+ lambda {
394
+ final_builder3.build
395
+ }.should raise_error(Bosh::Cli::CliExit)
396
+ end
397
+
398
+ it "allows template subdirectories" do
399
+ add_templates("foo", "foo/bar", "bar/baz")
400
+ add_monit("foo")
401
+
402
+ blobstore = mock("blobstore")
403
+ builder = new_builder("foo", [], ["foo/bar", "bar/baz"],
404
+ [], true, false, blobstore)
405
+ builder.build
406
+
407
+ Dir.chdir(builder.build_dir) do
408
+ File.directory?("templates").should be_true
409
+ ["templates/foo/bar", "templates/bar/baz"].each do |file|
410
+ File.file?(file).should be_true
411
+ end
412
+ end
413
+ end
414
+
415
+ it "supports dry run" do
416
+ add_templates("foo", "bar", "baz")
417
+ add_monit("foo")
418
+
419
+ builder = new_builder("foo", [], ["bar", "baz"], [])
420
+ builder.dry_run = true
421
+ builder.build
422
+
423
+ builder.version.should == "0.1-dev"
424
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
425
+ should be_false
426
+
427
+ builder.dry_run = false
428
+ builder.reload.build
429
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
430
+ should be_true
431
+
432
+ blobstore = mock("blobstore")
433
+ blobstore.should_not_receive(:create)
434
+ final_builder = new_builder("foo", [], ["bar", "baz"], [],
435
+ true, true, blobstore)
436
+ final_builder.dry_run = true
437
+ final_builder.build
438
+
439
+ # Shouldn't be promoted during dry run:
440
+ final_builder.version.should == "0.1-dev"
441
+ File.exists?(@release_dir + "/.final_builds/jobs/foo/1.tgz").should be_false
442
+
443
+ add_templates("foo", "bzz")
444
+ builder2 = new_builder("foo", [], ["bar", "baz", "bzz"], [])
445
+ builder2.dry_run = true
446
+ builder2.build
447
+ builder2.version.should == "0.2-dev"
448
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.1-dev.tgz").
449
+ should be_true
450
+ File.exists?(@release_dir + "/.dev_builds/jobs/foo/0.2-dev.tgz").
451
+ should be_false
452
+ end
453
+
454
+ end