bosh_cli 0.16

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 (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