bosh_cli 1.0.3 → 1.5.0.pre.1113

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/bin/bosh +0 -9
  2. data/lib/cli.rb +69 -64
  3. data/lib/cli/backup_destination_path.rb +33 -0
  4. data/lib/cli/base_command.rb +57 -56
  5. data/lib/cli/blob_manager.rb +12 -12
  6. data/lib/cli/changeset_helper.rb +6 -7
  7. data/lib/cli/client/director.rb +724 -0
  8. data/lib/cli/command_handler.rb +6 -7
  9. data/lib/cli/commands/backup.rb +39 -0
  10. data/lib/cli/commands/biff.rb +42 -21
  11. data/lib/cli/commands/blob_management.rb +1 -1
  12. data/lib/cli/commands/cloudcheck.rb +11 -13
  13. data/lib/cli/commands/deployment.rb +53 -37
  14. data/lib/cli/commands/help.rb +3 -2
  15. data/lib/cli/commands/job_management.rb +67 -103
  16. data/lib/cli/commands/job_rename.rb +6 -8
  17. data/lib/cli/commands/log_management.rb +78 -55
  18. data/lib/cli/commands/maintenance.rb +36 -30
  19. data/lib/cli/commands/misc.rb +72 -51
  20. data/lib/cli/commands/package.rb +2 -2
  21. data/lib/cli/commands/property_management.rb +10 -12
  22. data/lib/cli/commands/release.rb +236 -133
  23. data/lib/cli/commands/snapshot.rb +93 -0
  24. data/lib/cli/commands/ssh.rb +216 -213
  25. data/lib/cli/commands/stemcell.rb +46 -34
  26. data/lib/cli/commands/task.rb +2 -2
  27. data/lib/cli/commands/user.rb +27 -3
  28. data/lib/cli/commands/vm.rb +28 -0
  29. data/lib/cli/commands/vms.rb +81 -23
  30. data/lib/cli/config.rb +6 -2
  31. data/lib/cli/core_ext.rb +31 -30
  32. data/lib/cli/deployment_helper.rb +134 -159
  33. data/lib/cli/deployment_manifest.rb +66 -0
  34. data/lib/cli/deployment_manifest_compiler.rb +0 -3
  35. data/lib/cli/event_log_renderer.rb +10 -10
  36. data/lib/cli/file_with_progress_bar.rb +52 -0
  37. data/lib/cli/job_builder.rb +1 -1
  38. data/lib/cli/job_command_args.rb +23 -0
  39. data/lib/cli/job_property_collection.rb +4 -7
  40. data/lib/cli/job_property_validator.rb +22 -12
  41. data/lib/cli/job_state.rb +54 -0
  42. data/lib/cli/line_wrap.rb +54 -0
  43. data/lib/cli/packaging_helper.rb +10 -10
  44. data/lib/cli/release.rb +18 -15
  45. data/lib/cli/release_builder.rb +9 -4
  46. data/lib/cli/release_compiler.rb +9 -9
  47. data/lib/cli/release_tarball.rb +3 -6
  48. data/lib/cli/resurrection.rb +31 -0
  49. data/lib/cli/runner.rb +56 -30
  50. data/lib/cli/stemcell.rb +25 -10
  51. data/lib/cli/task_log_renderer.rb +1 -1
  52. data/lib/cli/task_tracker.rb +10 -9
  53. data/lib/cli/validation.rb +3 -1
  54. data/lib/cli/version.rb +1 -1
  55. data/lib/cli/version_calc.rb +5 -18
  56. data/lib/cli/versions_index.rb +1 -1
  57. data/lib/cli/vm_state.rb +43 -0
  58. data/lib/cli/yaml_helper.rb +26 -35
  59. metadata +75 -208
  60. data/Rakefile +0 -56
  61. data/lib/cli/director.rb +0 -628
  62. data/spec/assets/biff/bad_gateway_config.yml +0 -28
  63. data/spec/assets/biff/good_simple_config.yml +0 -63
  64. data/spec/assets/biff/good_simple_golden_config.yml +0 -63
  65. data/spec/assets/biff/good_simple_template.erb +0 -69
  66. data/spec/assets/biff/ip_out_of_range.yml +0 -63
  67. data/spec/assets/biff/multiple_subnets_config.yml +0 -40
  68. data/spec/assets/biff/network_only_template.erb +0 -34
  69. data/spec/assets/biff/no_cc_config.yml +0 -27
  70. data/spec/assets/biff/no_range_config.yml +0 -27
  71. data/spec/assets/biff/no_subnet_config.yml +0 -16
  72. data/spec/assets/biff/ok_network_config.yml +0 -30
  73. data/spec/assets/biff/properties_template.erb +0 -6
  74. data/spec/assets/config/atmos/config/final.yml +0 -6
  75. data/spec/assets/config/atmos/config/private.yml +0 -4
  76. data/spec/assets/config/bad-providers/config/final.yml +0 -5
  77. data/spec/assets/config/bad-providers/config/private.yml +0 -4
  78. data/spec/assets/config/deprecation/config/final.yml +0 -5
  79. data/spec/assets/config/deprecation/config/private.yml +0 -2
  80. data/spec/assets/config/local/config/final.yml +0 -5
  81. data/spec/assets/config/local/config/private.yml +0 -1
  82. data/spec/assets/config/s3/config/final.yml +0 -5
  83. data/spec/assets/config/s3/config/private.yml +0 -5
  84. data/spec/assets/config/swift-hp/config/final.yml +0 -6
  85. data/spec/assets/config/swift-hp/config/private.yml +0 -7
  86. data/spec/assets/config/swift-rackspace/config/final.yml +0 -6
  87. data/spec/assets/config/swift-rackspace/config/private.yml +0 -6
  88. data/spec/assets/deployment.MF +0 -0
  89. data/spec/assets/plugins/bosh/cli/commands/echo.rb +0 -43
  90. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +0 -24
  91. data/spec/assets/release/jobs/cacher.tgz +0 -0
  92. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  93. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  94. data/spec/assets/release/jobs/cacher/job.MF +0 -6
  95. data/spec/assets/release/jobs/cacher/monit +0 -1
  96. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  97. data/spec/assets/release/jobs/cleaner/job.MF +0 -4
  98. data/spec/assets/release/jobs/cleaner/monit +0 -1
  99. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  100. data/spec/assets/release/jobs/sweeper/config/test.conf +0 -1
  101. data/spec/assets/release/jobs/sweeper/job.MF +0 -5
  102. data/spec/assets/release/jobs/sweeper/monit +0 -1
  103. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  104. data/spec/assets/release/packages/stuff.tgz +0 -0
  105. data/spec/assets/release/release.MF +0 -17
  106. data/spec/assets/release_invalid_checksum.tgz +0 -0
  107. data/spec/assets/release_invalid_jobs.tgz +0 -0
  108. data/spec/assets/release_no_name.tgz +0 -0
  109. data/spec/assets/release_no_version.tgz +0 -0
  110. data/spec/assets/stemcell/image +0 -1
  111. data/spec/assets/stemcell/stemcell.MF +0 -6
  112. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  113. data/spec/assets/stemcell_no_image.tgz +0 -0
  114. data/spec/assets/valid_release.tgz +0 -0
  115. data/spec/assets/valid_stemcell.tgz +0 -0
  116. data/spec/spec_helper.rb +0 -28
  117. data/spec/unit/base_command_spec.rb +0 -87
  118. data/spec/unit/biff_spec.rb +0 -172
  119. data/spec/unit/blob_manager_spec.rb +0 -288
  120. data/spec/unit/cache_spec.rb +0 -36
  121. data/spec/unit/cli_commands_spec.rb +0 -356
  122. data/spec/unit/config_spec.rb +0 -125
  123. data/spec/unit/core_ext_spec.rb +0 -81
  124. data/spec/unit/dependency_helper_spec.rb +0 -52
  125. data/spec/unit/deployment_manifest_compiler_spec.rb +0 -63
  126. data/spec/unit/deployment_manifest_spec.rb +0 -153
  127. data/spec/unit/director_spec.rb +0 -471
  128. data/spec/unit/director_task_spec.rb +0 -48
  129. data/spec/unit/event_log_renderer_spec.rb +0 -171
  130. data/spec/unit/hash_changeset_spec.rb +0 -73
  131. data/spec/unit/job_builder_spec.rb +0 -455
  132. data/spec/unit/job_property_collection_spec.rb +0 -111
  133. data/spec/unit/job_property_validator_spec.rb +0 -7
  134. data/spec/unit/job_rename_spec.rb +0 -200
  135. data/spec/unit/package_builder_spec.rb +0 -593
  136. data/spec/unit/release_builder_spec.rb +0 -120
  137. data/spec/unit/release_spec.rb +0 -173
  138. data/spec/unit/release_tarball_spec.rb +0 -29
  139. data/spec/unit/runner_spec.rb +0 -7
  140. data/spec/unit/ssh_spec.rb +0 -84
  141. data/spec/unit/stemcell_spec.rb +0 -17
  142. data/spec/unit/task_tracker_spec.rb +0 -131
  143. data/spec/unit/version_calc_spec.rb +0 -27
  144. data/spec/unit/versions_index_spec.rb +0 -144
@@ -5,7 +5,6 @@ module Bosh::Cli
5
5
  include VersionCalc
6
6
 
7
7
  def prepare_deployment_manifest(options = {})
8
- # TODO: extract to helper class
9
8
  deployment_required
10
9
  manifest_filename = deployment
11
10
 
@@ -16,11 +15,11 @@ module Bosh::Cli
16
15
  manifest = load_yaml_file(manifest_filename)
17
16
  manifest_yaml = File.read(manifest_filename)
18
17
 
19
- if manifest["name"].blank?
20
- err("Deployment name not found in the deployment manifest")
18
+ if manifest['name'].blank?
19
+ err('Deployment name not found in the deployment manifest')
21
20
  end
22
21
 
23
- if manifest["target"]
22
+ if manifest['target']
24
23
  err(manifest_target_upgrade_notice)
25
24
  end
26
25
 
@@ -29,40 +28,40 @@ module Bosh::Cli
29
28
  properties = {}
30
29
 
31
30
  begin
32
- say("Getting deployment properties from director...")
33
- properties = director.list_properties(manifest["name"])
31
+ say('Getting deployment properties from director...')
32
+ properties = director.list_properties(manifest['name'])
34
33
  rescue Bosh::Cli::DirectorError
35
- say("Unable to get properties list from director, " +
36
- "trying without it...")
34
+ say('Unable to get properties list from director, ' +
35
+ 'trying without it...')
37
36
  end
38
37
 
39
- say("Compiling deployment manifest...")
38
+ say('Compiling deployment manifest...')
40
39
  compiler.properties = properties.inject({}) do |hash, property|
41
- hash[property["name"]] = property["value"]
40
+ hash[property['name']] = property['value']
42
41
  hash
43
42
  end
44
43
 
45
- manifest = YAML.load(compiler.result)
44
+ manifest = Psych.load(compiler.result)
46
45
  end
47
46
 
48
- if manifest["name"].blank? || manifest["director_uuid"].blank?
47
+ if manifest['name'].blank? || manifest['director_uuid'].blank?
49
48
  err("Invalid manifest `#{File.basename(deployment)}': " +
50
- "name and director UUID are required")
49
+ 'name and director UUID are required')
51
50
  end
52
51
 
53
- if director.uuid != manifest["director_uuid"]
52
+ if director.uuid != manifest['director_uuid']
54
53
  err("Target director UUID doesn't match UUID from deployment manifest")
55
54
  end
56
55
 
57
- if manifest["release"].blank? && manifest["releases"].blank?
56
+ if manifest['release'].blank? && manifest['releases'].blank?
58
57
  err("Deployment manifest doesn't have release information: '" +
59
- "please add 'release' or 'releases' section")
58
+ "please add 'release' or 'releases' section")
60
59
  end
61
60
 
62
61
  resolve_release_aliases(manifest)
63
62
  resolve_stemcell_aliases(manifest)
64
63
 
65
- options[:yaml] ? YAML.dump(manifest) : manifest
64
+ options[:yaml] ? Psych.dump(manifest) : manifest
66
65
  end
67
66
 
68
67
  # Check if the 2 deployments are different.
@@ -91,7 +90,7 @@ module Bosh::Cli
91
90
  # if something goes wrong, so it doesn't need to have
92
91
  # a meaningful return value.
93
92
  # @return Boolean Were there any changes in deployment manifest?
94
- def inspect_deployment_changes(manifest, options = { })
93
+ def inspect_deployment_changes(manifest, options = {})
95
94
  show_empty_changeset = true
96
95
 
97
96
  if options.has_key?(:show_empty_changeset)
@@ -99,27 +98,26 @@ module Bosh::Cli
99
98
  end
100
99
 
101
100
  manifest = manifest.dup
102
- current_deployment = director.get_deployment(manifest["name"])
101
+ current_deployment = director.get_deployment(manifest['name'])
103
102
 
104
103
  # We cannot retrieve current manifest until there was at least one
105
104
  # successful deployment. There used to be a warning about that
106
105
  # but it turned out to be confusing to many users and thus has
107
106
  # been removed.
108
- return if current_deployment["manifest"].nil?
109
- current_manifest = YAML.load(current_deployment["manifest"])
107
+ return if current_deployment['manifest'].nil?
108
+ current_manifest = Psych.load(current_deployment['manifest'])
110
109
 
111
110
  unless current_manifest.is_a?(Hash)
112
- err("Current deployment manifest format is invalid, " +
113
- "check if director works properly")
111
+ err('Current deployment manifest format is invalid, ' +
112
+ 'check if director works properly')
114
113
  end
115
114
 
116
- # TODO: validate new deployment manifest
117
115
  diff = Bosh::Cli::HashChangeset.new
118
116
  diff.add_hash(normalize_deployment_manifest(manifest), :new)
119
117
  diff.add_hash(normalize_deployment_manifest(current_manifest), :old)
120
- @_diff_key_visited = { "name" => 1, "director_uuid" => 1 }
118
+ @_diff_key_visited = { 'name' => 1, 'director_uuid' => 1 }
121
119
 
122
- say("Detecting changes in deployment...".green)
120
+ say('Detecting changes in deployment...'.make_green)
123
121
  nl
124
122
 
125
123
  if !diff.changed? && !show_empty_changeset
@@ -163,16 +161,16 @@ module Bosh::Cli
163
161
  end
164
162
 
165
163
  if old_stemcells != new_stemcells
166
- unless confirmed?("Stemcell update has been detected. " +
167
- "Are you sure you want to update stemcells?")
164
+ unless confirmed?('Stemcell update has been detected. ' +
165
+ 'Are you sure you want to update stemcells?')
168
166
  cancel_deployment
169
167
  end
170
168
  end
171
169
 
172
170
  if old_stemcells.size != new_stemcells.size
173
- say("Stemcell update seems to be inconsistent with current ".red +
174
- "deployment. Please carefully review changes above.".red)
175
- unless confirmed?("Are you sure this configuration is correct?")
171
+ say('Stemcell update seems to be inconsistent with current '.make_red +
172
+ 'deployment. Please carefully review changes above.'.make_red)
173
+ unless confirmed?('Are you sure this configuration is correct?')
176
174
  cancel_deployment
177
175
  end
178
176
  end
@@ -194,31 +192,102 @@ module Bosh::Cli
194
192
 
195
193
  diff.changed?
196
194
  rescue Bosh::Cli::ResourceNotFound
197
- say("Cannot get current deployment information from director, " +
198
- "possibly a new deployment".red)
195
+ say('Cannot get current deployment information from director, ' +
196
+ 'possibly a new deployment'.make_red)
199
197
  true
200
198
  end
201
199
 
202
- private
200
+ def latest_release_versions
201
+ @_latest_release_versions ||= begin
202
+ director.list_releases.inject({}) do |hash, release|
203
+ name = release['name']
204
+ versions = release['versions'] || release['release_versions'].map { |release_version| release_version['version'] }
205
+ latest_version = versions.map { |v| Bosh::Common::VersionNumber.new(v) }.max
206
+ hash[name] = latest_version.to_s
207
+ hash
208
+ end
209
+ end
210
+ end
203
211
 
204
- def find_deployment(name)
205
- if File.exists?(name)
206
- File.expand_path(name)
207
- else
208
- File.expand_path(File.join(work_dir, "deployments", "#{name}.yml"))
212
+ # @param [Hash] manifest Deployment manifest (will be modified)
213
+ # @return [void]
214
+ def resolve_release_aliases(manifest)
215
+ releases = manifest['releases'] || [manifest['release']]
216
+
217
+ releases.each do |release|
218
+ if release['version'] == 'latest'
219
+ latest_release_version = latest_release_versions[release['name']]
220
+ unless latest_release_version
221
+ err("Release '#{release['name']}' not found on director. Unable to resolve 'latest' alias in manifest.")
222
+ end
223
+ release['version'] = latest_release_version
224
+ end
225
+
226
+ if release['version'].to_i.to_s == release['version']
227
+ release['version'] = release['version'].to_i
228
+ end
229
+ end
230
+ end
231
+
232
+ def job_unique_in_deployment?(job_name)
233
+ job = find_job(job_name)
234
+ job.fetch('instances') == 1 if job
235
+ end
236
+
237
+ def job_exists_in_deployment?(job_name)
238
+ !!find_job(job_name)
239
+ end
240
+
241
+ def job_must_exist_in_deployment(job)
242
+ err("Job `#{job}' doesn't exist") unless job_exists_in_deployment?(job)
243
+ end
244
+
245
+ def prompt_for_job_and_index
246
+ jobs_list = jobs_and_indexes
247
+
248
+ return jobs_list.first if jobs_list.size == 1
249
+
250
+ choose do |menu|
251
+ menu.prompt = 'Choose an instance: '
252
+ jobs_list.each do |job_name, index|
253
+ menu.choice("#{job_name}/#{index}") { [job_name, index] }
254
+ end
255
+ end
256
+ end
257
+
258
+ def jobs_and_indexes
259
+ jobs = prepare_deployment_manifest.fetch('jobs')
260
+
261
+ jobs.inject([]) do |jobs_and_indexes, job|
262
+ job_name = job.fetch('name')
263
+ 0.upto(job.fetch('instances').to_i - 1) do |index|
264
+ jobs_and_indexes << [job_name, index]
265
+ end
266
+ jobs_and_indexes
209
267
  end
210
268
  end
211
269
 
212
270
  def cancel_deployment
213
- quit("Deployment canceled".red)
271
+ quit('Deployment canceled'.make_red)
272
+ end
273
+
274
+ private
275
+
276
+ def find_job(job_name)
277
+ jobs = prepare_deployment_manifest.fetch('jobs')
278
+ jobs.find { |job| job.fetch('name') == job_name }
214
279
  end
215
280
 
216
- def manifest_error(err)
217
- err("Deployment manifest error: #{err}")
281
+ def find_deployment(name)
282
+ if File.exists?(name)
283
+ File.expand_path(name)
284
+ else
285
+ File.expand_path(File.join(work_dir, 'deployments', "#{name}.yml"))
286
+ end
218
287
  end
219
288
 
220
289
  def manifest_target_upgrade_notice
221
- <<-EOS.gsub(/^\s*/, "").gsub(/\n$/, "")
290
+ <<-EOS.gsub(/^\s*/, '').gsub(/\n$/, '')
222
291
  Please upgrade your deployment manifest to use director UUID instead
223
292
  of target. Just replace 'target' key with 'director_uuid' key in your
224
293
  manifest. You can get your director UUID by targeting your director
@@ -227,130 +296,53 @@ module Bosh::Cli
227
296
  end
228
297
 
229
298
  def print_summary(diff, key, title = nil)
230
- title ||= key.to_s.gsub(/[-_]/, " ").capitalize
299
+ title ||= key.to_s.gsub(/[-_]/, ' ').capitalize
231
300
 
232
- say(title.green)
301
+ say(title.make_green)
233
302
  summary = diff[key].summary
234
303
  if summary.empty?
235
- say("No changes")
304
+ say('No changes')
236
305
  else
237
306
  say(summary.join("\n"))
238
307
  end
239
308
  @_diff_key_visited[key.to_s] = 1
240
309
  end
241
310
 
242
- def normalize_deployment_manifest(manifest)
243
- normalized = manifest.dup
244
-
245
- %w(releases networks jobs resource_pools).each do |section|
246
- normalized[section] ||= []
247
-
248
- unless normalized[section].kind_of?(Array)
249
- manifest_error("#{section} is expected to be an array")
250
- end
251
-
252
- normalized[section] = normalized[section].inject({}) do |acc, e|
253
- if e["name"].blank?
254
- manifest_error("missing name for one of entries in '#{section}'")
255
- end
256
- if acc.has_key?(e["name"])
257
- manifest_error("duplicate entry '#{e['name']}' in '#{section}'")
258
- end
259
- acc[e["name"]] = e
260
- acc
261
- end
262
- end
263
-
264
- normalized["networks"].each do |network_name, network|
265
- # VIP and dynamic networks do not require subnet,
266
- # but if it's there we can run some sanity checks
267
- next unless network.has_key?("subnets")
268
-
269
- unless network["subnets"].kind_of?(Array)
270
- manifest_error("network subnets is expected to be an array")
271
- end
272
-
273
- subnets = network["subnets"].inject({}) do |acc, e|
274
- if e["range"].blank?
275
- manifest_error("missing range for one of subnets " +
276
- "in '#{network_name}'")
277
- end
278
- if acc.has_key?(e["range"])
279
- manifest_error("duplicate network range '#{e['range']}' " +
280
- "in '#{network}'")
281
- end
282
- acc[e["range"]] = e
283
- acc
284
- end
285
-
286
- normalized["networks"][network_name]["subnets"] = subnets
287
- end
288
-
289
- normalized
311
+ def normalize_deployment_manifest(manifest_hash)
312
+ DeploymentManifest.new(manifest_hash).normalize
290
313
  end
291
314
 
292
315
  def warn_about_release_changes(release_diff)
293
316
  if release_diff[:name].changed?
294
- say("Release name has changed: %s -> %s".red % [
317
+ say('Release name has changed: %s -> %s'.make_red % [
295
318
  release_diff[:name].old, release_diff[:name].new])
296
- unless confirmed?("This is very serious and potentially destructive " +
297
- "change. ARE YOU SURE YOU WANT TO DO IT?")
319
+ unless confirmed?('This is very serious and potentially destructive ' +
320
+ 'change. ARE YOU SURE YOU WANT TO DO IT?')
298
321
  cancel_deployment
299
322
  end
300
323
  elsif release_diff[:version].changed?
301
- say("Release version has changed: %s -> %s".yellow % [
324
+ say('Release version has changed: %s -> %s'.make_yellow % [
302
325
  release_diff[:version].old, release_diff[:version].new])
303
- unless confirmed?("Are you sure you want to deploy this version?")
326
+ unless confirmed?('Are you sure you want to deploy this version?')
304
327
  cancel_deployment
305
328
  end
306
329
  end
307
330
  end
308
331
 
309
- # @param [Hash] manifest Deployment manifest (will be modified)
310
- # @return [void]
311
- def resolve_release_aliases(manifest)
312
- if manifest["release"]
313
- if manifest["releases"]
314
- err("Manifest has both `release' and `releases', please fix it")
315
- end
316
- releases = [manifest["release"]]
317
- else
318
- releases = manifest["releases"]
319
- end
320
-
321
- unless releases.is_a?(Array)
322
- err("Invalid release spec in the deployment manifest")
323
- end
324
-
325
- releases.each do |release|
326
- if release["version"] == "latest"
327
- latest_version = latest_releases[release["name"]]
328
- if latest_version.nil?
329
- err("Latest version for release `#{release["name"]}' is unknown")
330
- end
331
- # Avoiding Fixnum -> String noise in diff
332
- if latest_version.to_s == latest_version.to_i.to_s
333
- latest_version = latest_version.to_i
334
- end
335
- release["version"] = latest_version
336
- end
337
- end
338
- end
339
-
340
332
  # @param [Hash] manifest Deployment manifest (will be modified)
341
333
  # @return [void]
342
334
  def resolve_stemcell_aliases(manifest)
343
- return if manifest["resource_pools"].nil?
335
+ return if manifest['resource_pools'].nil?
344
336
 
345
- manifest["resource_pools"].each do |rp|
346
- stemcell = rp["stemcell"]
337
+ manifest['resource_pools'].each do |rp|
338
+ stemcell = rp['stemcell']
347
339
  unless stemcell.is_a?(Hash)
348
- err("Invalid stemcell spec in the deployment manifest")
340
+ err('Invalid stemcell spec in the deployment manifest')
349
341
  end
350
- if stemcell["version"] == "latest"
351
- latest_version = latest_stemcells[stemcell["name"]]
342
+ if stemcell['version'] == 'latest'
343
+ latest_version = latest_stemcells[stemcell['name']]
352
344
  if latest_version.nil?
353
- err("Latest version for stemcell `#{stemcell["name"]}' is unknown")
345
+ err("Latest version for stemcell `#{stemcell['name']}' is unknown")
354
346
  end
355
347
  # Avoiding {Float,Fixnum} -> String noise in diff
356
348
  if latest_version.to_s == latest_version.to_f.to_s
@@ -358,7 +350,7 @@ module Bosh::Cli
358
350
  elsif latest_version.to_s == latest_version.to_i.to_s
359
351
  latest_version = latest_version.to_i
360
352
  end
361
- stemcell["version"] = latest_version
353
+ stemcell['version'] = latest_version
362
354
  end
363
355
  end
364
356
  end
@@ -367,11 +359,11 @@ module Bosh::Cli
367
359
  def latest_stemcells
368
360
  @_latest_stemcells ||= begin
369
361
  stemcells = director.list_stemcells.inject({}) do |hash, stemcell|
370
- unless stemcell.is_a?(Hash) && stemcell["name"] && stemcell["version"]
371
- err("Invalid director stemcell list format")
362
+ unless stemcell.is_a?(Hash) && stemcell['name'] && stemcell['version']
363
+ err('Invalid director stemcell list format')
372
364
  end
373
- hash[stemcell["name"]] ||= []
374
- hash[stemcell["name"]] << stemcell["version"]
365
+ hash[stemcell['name']] ||= []
366
+ hash[stemcell['name']] << stemcell['version']
375
367
  hash
376
368
  end
377
369
 
@@ -381,22 +373,5 @@ module Bosh::Cli
381
373
  end
382
374
  end
383
375
  end
384
-
385
- # @return [Array]
386
- def latest_releases
387
- @_latest_releases ||= begin
388
- director.list_releases.inject({}) do |hash, release|
389
- unless release["name"].is_a?(String) &&
390
- release["versions"].is_a?(Array)
391
- err("Invalid director release list format")
392
- end
393
- hash[release["name"]] = release["versions"].sort { |v1, v2|
394
- version_cmp(v2, v1)
395
- }.first
396
- hash
397
- end
398
- end
399
- end
400
-
401
376
  end
402
377
  end
@@ -0,0 +1,66 @@
1
+ require 'common/deep_copy'
2
+
3
+ module Bosh::Cli
4
+ class DeploymentManifest
5
+ def initialize(manifest_hash)
6
+ @manifest_hash = manifest_hash
7
+ end
8
+
9
+ def normalize
10
+ normalized = Bosh::Common::DeepCopy.copy(manifest_hash)
11
+
12
+ %w(releases networks jobs resource_pools).each do |section|
13
+ normalized[section] ||= []
14
+
15
+ unless normalized[section].kind_of?(Array)
16
+ manifest_error("#{section} is expected to be an array")
17
+ end
18
+
19
+ normalized[section] = normalized[section].inject({}) do |acc, e|
20
+ if e["name"].blank?
21
+ manifest_error("missing name for one of entries in '#{section}'")
22
+ end
23
+ if acc.has_key?(e["name"])
24
+ manifest_error("duplicate entry '#{e['name']}' in '#{section}'")
25
+ end
26
+ acc[e["name"]] = e
27
+ acc
28
+ end
29
+ end
30
+
31
+ normalized["networks"].each do |network_name, network|
32
+ # VIP and dynamic networks do not require subnet,
33
+ # but if it's there we can run some sanity checks
34
+ next unless network.has_key?("subnets")
35
+
36
+ unless network["subnets"].kind_of?(Array)
37
+ manifest_error("network subnets is expected to be an array")
38
+ end
39
+
40
+ subnets = network["subnets"].inject({}) do |acc, e|
41
+ if e["range"].blank?
42
+ manifest_error("missing range for one of subnets " +
43
+ "in '#{network_name}'")
44
+ end
45
+ if acc.has_key?(e["range"])
46
+ manifest_error("duplicate network range '#{e['range']}' " +
47
+ "in '#{network}'")
48
+ end
49
+ acc[e["range"]] = e
50
+ acc
51
+ end
52
+
53
+ normalized["networks"][network_name]["subnets"] = subnets
54
+ end
55
+
56
+ normalized
57
+ end
58
+
59
+ private
60
+ attr_reader :manifest_hash
61
+
62
+ def manifest_error(err)
63
+ err("Deployment manifest error: #{err}")
64
+ end
65
+ end
66
+ end