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