bosh_cli 1.0.3 → 1.5.0.pre.1113

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