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,469 @@
1
+ # Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Release < Base
5
+ DEFAULT_RELEASE_NAME = "bosh-release"
6
+
7
+ include Bosh::Cli::DependencyHelper
8
+ include Bosh::Cli::VersionCalc
9
+
10
+ def init(base=nil, *options)
11
+ flags = options.inject({}) { |h, option| h[option] = true; h }
12
+ git = flags.delete("--git")
13
+
14
+ if base
15
+ FileUtils.mkdir_p(base) unless Dir.exist?(base)
16
+ Dir.chdir(base)
17
+ end
18
+
19
+ err("Release already initialized") if in_release_dir?
20
+
21
+ git_init if git
22
+
23
+ %w[jobs packages src blobs].each do |dir|
24
+ FileUtils.mkdir(dir)
25
+ end
26
+
27
+ # initialize an empty blob_index file
28
+ blobs = {}
29
+ File.open("blob_index.yml", "w") do |f|
30
+ YAML.dump(blobs, f)
31
+ end
32
+
33
+ say("Release directory initialized".green)
34
+ end
35
+
36
+ def git_init
37
+ out = %x{git init 2>&1}
38
+ if $? != 0
39
+ say("error running 'git init':\n#{out}")
40
+ else
41
+ File.open(".gitignore", "w") do |f|
42
+ f.puts("config/dev.yml\nconfig/private.yml")
43
+ f.puts("blobs\nreleases/*.tgz")
44
+ f.puts("dev_releases\n.dev_builds")
45
+ f.puts(".final_builds/jobs/**/*.tgz")
46
+ f.puts(".final_builds/packages/**/*.tgz")
47
+ end
48
+ end
49
+ rescue Errno::ENOENT
50
+ say("Unable to run 'git init'".red)
51
+ end
52
+
53
+ def verify(tarball_path, *options)
54
+ tarball = Bosh::Cli::ReleaseTarball.new(tarball_path)
55
+
56
+ say("\nVerifying release...")
57
+ tarball.validate
58
+ nl
59
+
60
+ if tarball.valid?
61
+ say("'%s' is a valid release" % [tarball_path] )
62
+ else
63
+ say("'%s' is not a valid release:" % [tarball_path] )
64
+ for error in tarball.errors
65
+ say("- %s" % [error])
66
+ end
67
+ end
68
+ end
69
+
70
+ def upload(release_file = nil)
71
+ auth_required
72
+
73
+ if release_file.nil?
74
+ check_if_release_dir
75
+ release_file = release.latest_release_filename
76
+ if release_file.nil?
77
+ err("The information about latest generated release is missing, " +
78
+ "please provide release filename")
79
+ end
80
+ unless confirmed?("Upload release " +
81
+ "`#{File.basename(release_file).green}' " +
82
+ "to `#{target_name.green}'")
83
+ err("Canceled upload")
84
+ end
85
+ end
86
+
87
+ file_type = `file --mime-type -b '#{release_file}'`
88
+
89
+ if file_type =~ /text\/(plain|yaml)/
90
+ upload_manifest(release_file)
91
+ else # Just assume tarball
92
+ upload_tarball(release_file)
93
+ end
94
+ end
95
+
96
+ def upload_manifest(manifest_path)
97
+ manifest = load_yaml_file(manifest_path)
98
+ remote_release = get_remote_release(manifest["name"]) rescue nil
99
+ blobstore = release.blobstore
100
+ tmpdir = Dir.mktmpdir
101
+
102
+ at_exit { FileUtils.rm_rf(tmpdir) }
103
+
104
+ compiler = Bosh::Cli::ReleaseCompiler.new(manifest_path,
105
+ blobstore, remote_release)
106
+ need_repack = true
107
+
108
+ unless compiler.exists?
109
+ compiler.tarball_path = File.join(tmpdir, "release.tgz")
110
+ compiler.compile
111
+ need_repack = false
112
+ end
113
+ upload_tarball(compiler.tarball_path, :repack => need_repack)
114
+ end
115
+
116
+ def upload_tarball(tarball_path, options = {})
117
+ tarball = Bosh::Cli::ReleaseTarball.new(tarball_path)
118
+ # Trying to repack release by default
119
+ repack = options.has_key?(:repack) ? !!options[:repack] : true
120
+
121
+ say("\nVerifying release...")
122
+ tarball.validate(:allow_sparse => true)
123
+ nl
124
+
125
+ unless tarball.valid?
126
+ err("Release is invalid, please fix, verify and upload again")
127
+ end
128
+
129
+ begin
130
+ remote_release = get_remote_release(tarball.release_name)
131
+ if remote_release["versions"].include?(tarball.version)
132
+ err("This release version has already been uploaded")
133
+ end
134
+
135
+ if repack
136
+ say("Checking if can repack release for faster upload...")
137
+ repacked_path = tarball.repack(remote_release)
138
+ if repacked_path.nil?
139
+ say("Uploading the whole release".green)
140
+ else
141
+ say("Release repacked " +
142
+ "(new size is #{pretty_size(repacked_path)})".green)
143
+ tarball_path = repacked_path
144
+ end
145
+ end
146
+ rescue Bosh::Cli::DirectorError
147
+ # It's OK for director to choke on getting
148
+ # a release info (think new releases)
149
+ end
150
+
151
+ say("\nUploading release...\n")
152
+ status, message = director.upload_release(tarball_path)
153
+
154
+ responses = {
155
+ :done => "Release uploaded and updated",
156
+ :non_trackable => "Uploaded release but director at #{target} " +
157
+ "doesn't support update tracking",
158
+ :track_timeout => "Uploaded release but timed out out " +
159
+ "while tracking status",
160
+ :error => "Uploaded release but received an error while tracking status"
161
+ }
162
+
163
+ say(responses[status] || "Cannot upload release: #{message}")
164
+ end
165
+
166
+ def create(*options)
167
+ check_if_release_dir
168
+ if options.size == 1 && File.file?(options[0])
169
+ create_from_manifest(options[0])
170
+ release_filename = options[0]
171
+ else
172
+ release_filename = create_from_spec(*options)
173
+ end
174
+
175
+ if release_filename
176
+ release.latest_release_filename = release_filename
177
+ release.save_config
178
+ end
179
+ end
180
+
181
+ def create_from_manifest(manifest_file)
182
+ say("Recreating release from the manifest")
183
+ Bosh::Cli::ReleaseCompiler.compile(manifest_file, release.blobstore)
184
+ end
185
+
186
+ def create_from_spec(*options)
187
+ flags = options.inject({}) { |h, option| h[option] = true; h }
188
+
189
+ final = flags.delete("--final")
190
+ force = flags.delete("--force")
191
+ manifest_only = !flags.delete("--with-tarball")
192
+ dry_run = flags.delete("--dry-run")
193
+
194
+ if final && !release.has_blobstore_secret?
195
+ say("Can't create final release without blobstore secret".red)
196
+ exit(1)
197
+ end
198
+
199
+ if flags.size > 0
200
+ say("Unknown flags: #{flags.keys.join(", ")}".red)
201
+ show_usage
202
+ exit(1)
203
+ end
204
+
205
+ if !force
206
+ check_dirty_blobs
207
+ check_if_dirty_state
208
+ end
209
+
210
+ confirmation = "Are you sure you want to " +
211
+ "generate #{'final'.red} version? "
212
+
213
+ if final && !dry_run && !confirmed?(confirmation)
214
+ say("Canceled release generation".green)
215
+ exit(1)
216
+ end
217
+
218
+ packages = []
219
+ jobs = []
220
+
221
+ if final
222
+ header("Building FINAL release".green)
223
+ release_name = release.final_name
224
+ else
225
+ release_name = release.dev_name
226
+ header("Building DEV release".green)
227
+ end
228
+
229
+ if version_greater(release.min_cli_version, Bosh::Cli::VERSION)
230
+ err("You should use CLI >= #{release.min_cli_version} " +
231
+ "with this release, you have #{Bosh::Cli::VERSION}")
232
+ end
233
+
234
+ if release_name.blank?
235
+ confirmation = "Please enter %s release name: " % [
236
+ final ? "final" : "development"]
237
+ name = interactive? ? ask(confirmation).to_s : DEFAULT_RELEASE_NAME
238
+ err("Canceled release creation, no name given") if name.blank?
239
+ if final
240
+ release.final_name = name
241
+ else
242
+ release.dev_name = name
243
+ end
244
+ release.save_config
245
+ end
246
+
247
+ header("Building packages")
248
+ Dir[File.join(work_dir, "packages", "*", "spec")].each do |package_spec|
249
+ package = Bosh::Cli::PackageBuilder.new(package_spec, work_dir,
250
+ final, release.blobstore)
251
+ package.dry_run = dry_run
252
+ say("Building #{package.name.green}...")
253
+ package.build
254
+ packages << package
255
+ nl
256
+ end
257
+
258
+ if packages.size > 0
259
+ package_index = packages.inject({}) do |index, package|
260
+ index[package.name] = package.dependencies
261
+ index
262
+ end
263
+ sorted_packages = tsort_packages(package_index)
264
+ header("Resolving dependencies")
265
+ say("Dependencies resolved, correct build order is:")
266
+ for package_name in sorted_packages
267
+ say("- %s" % [package_name])
268
+ end
269
+ nl
270
+ end
271
+
272
+ built_package_names = packages.map { |package| package.name }
273
+
274
+ header("Building jobs")
275
+ Dir[File.join(work_dir, "jobs", "*")].each do |job_dir|
276
+ next unless File.directory?(job_dir)
277
+ prepare_script = File.join(job_dir, "prepare")
278
+ job_spec = File.join(job_dir, "spec")
279
+
280
+ if File.exists?(prepare_script)
281
+ say("Found prepare script in `#{File.basename(job_dir)}'")
282
+ Bosh::Cli::JobBuilder.run_prepare_script(prepare_script)
283
+ end
284
+
285
+ job = Bosh::Cli::JobBuilder.new(job_spec, work_dir, final,
286
+ release.blobstore, built_package_names)
287
+ job.dry_run = dry_run
288
+ say("Building #{job.name.green}...")
289
+ job.build
290
+ jobs << job
291
+ nl
292
+ end
293
+
294
+ builder = Bosh::Cli::ReleaseBuilder.new(release, packages,
295
+ jobs, :final => final)
296
+
297
+ unless dry_run
298
+ if manifest_only
299
+ builder.build(:generate_tarball => false)
300
+ else
301
+ builder.build(:generate_tarball => true)
302
+ end
303
+ end
304
+
305
+ header("Release summary")
306
+ show_summary(builder)
307
+ nl
308
+
309
+ return nil if dry_run
310
+
311
+ say("Release version: #{builder.version.to_s.green}")
312
+ say("Release manifest: #{builder.manifest_path.green}")
313
+
314
+ unless manifest_only
315
+ say("Release tarball (#{pretty_size(builder.tarball_path)}): " +
316
+ builder.tarball_path.green)
317
+ end
318
+
319
+ release.min_cli_version = Bosh::Cli::VERSION
320
+ release.save_config
321
+
322
+ builder.manifest_path
323
+ end
324
+
325
+ def reset
326
+ check_if_release_dir
327
+
328
+ say("Your dev release environment will be completely reset".red)
329
+ if confirmed?
330
+ say("Removing dev_builds index...")
331
+ FileUtils.rm_rf(".dev_builds")
332
+ say("Clearing dev name...")
333
+ release.dev_name = nil
334
+ release.save_config
335
+ say("Removing dev tarballs...")
336
+ FileUtils.rm_rf("dev_releases")
337
+
338
+ say("Release has been reset".green)
339
+ else
340
+ say("Canceled")
341
+ end
342
+ end
343
+
344
+ def list
345
+ auth_required
346
+ releases = director.list_releases.sort do |r1, r2|
347
+ r1["name"] <=> r2["name"]
348
+ end
349
+
350
+ err("No releases") if releases.empty?
351
+
352
+ releases_table = table do |t|
353
+ t.headings = "Name", "Versions"
354
+ releases.each do |r|
355
+ versions = r["versions"].sort do |v1, v2|
356
+ version_cmp(v1, v2)
357
+ end
358
+
359
+ t << [r["name"], versions.join(", ")]
360
+ end
361
+ end
362
+
363
+ nl
364
+ say(releases_table)
365
+ nl
366
+ say("Releases total: %d" % releases.size)
367
+ end
368
+
369
+ def delete(name, *options)
370
+ auth_required
371
+ force = options.include?("--force")
372
+ options.delete("--force")
373
+ version = options.shift
374
+
375
+ desc = "release `#{name}'"
376
+ desc << " version #{version}" if version
377
+
378
+ if force
379
+ say("Deleting #{desc} (FORCED DELETE, WILL IGNORE ERRORS)".red)
380
+ elsif options.size > 0
381
+ err("Unknown option, currently only '--force' is supported")
382
+ else
383
+ say("Deleting #{desc}".red)
384
+ end
385
+
386
+ if confirmed?
387
+ status, body = director.delete_release(name, :force => force,
388
+ :version => version)
389
+ responses = {
390
+ :done => "Deleted #{desc}",
391
+ :non_trackable => "Started deleting release but director " +
392
+ "at '#{target}' doesn't support " +
393
+ "deployment tracking",
394
+ :track_timeout => "Started deleting release but timed out out " +
395
+ "while tracking status",
396
+ :error => "Started deleting release but received an error " +
397
+ "while tracking status",
398
+ }
399
+
400
+ say(responses[status] || "Cannot delete release: #{body}")
401
+ else
402
+ say("Canceled deleting release".green)
403
+ end
404
+ end
405
+
406
+ private
407
+
408
+ def show_summary(builder)
409
+ packages_table = table do |t|
410
+ t.headings = %w(Name Version Notes Fingerprint)
411
+ builder.packages.each do |package|
412
+ t << artefact_summary(package)
413
+ end
414
+ end
415
+
416
+ jobs_table = table do |t|
417
+ t.headings = %w(Name Version Notes Fingerprint)
418
+ builder.jobs.each do |job|
419
+ t << artefact_summary(job)
420
+ end
421
+ end
422
+
423
+ say("Packages")
424
+ say(packages_table)
425
+ nl
426
+ say("Jobs")
427
+ say(jobs_table)
428
+
429
+ affected_jobs = builder.affected_jobs
430
+
431
+ if affected_jobs.size > 0
432
+ nl
433
+ say("Jobs affected by changes in this release")
434
+
435
+ affected_jobs_table = table do |t|
436
+ t.headings = %w(Name Version)
437
+ affected_jobs.each do |job|
438
+ t << [job.name, job.version]
439
+ end
440
+ end
441
+
442
+ say(affected_jobs_table)
443
+ end
444
+ end
445
+
446
+ def artefact_summary(artefact)
447
+ result = []
448
+ result << artefact.name
449
+ result << artefact.version
450
+ result << artefact.notes.join(", ")
451
+ result << artefact.fingerprint
452
+ result
453
+ end
454
+
455
+ def get_remote_release(name)
456
+ release = director.get_release(name)
457
+
458
+ unless release.is_a?(Hash) &&
459
+ release.has_key?("jobs") &&
460
+ release.has_key?("packages")
461
+ raise Bosh::Cli::DirectorError,
462
+ "Cannot find version, jobs and packages info " +
463
+ "in the director response, maybe old director?"
464
+ end
465
+
466
+ release
467
+ end
468
+ end
469
+ end