bosh_cli 0.16
Sign up to get free protection for your applications and to get access to all the features.
- data/README +4 -0
- data/Rakefile +55 -0
- data/bin/bosh +17 -0
- data/lib/cli.rb +76 -0
- data/lib/cli/cache.rb +44 -0
- data/lib/cli/changeset_helper.rb +142 -0
- data/lib/cli/command_definition.rb +52 -0
- data/lib/cli/commands/base.rb +245 -0
- data/lib/cli/commands/biff.rb +300 -0
- data/lib/cli/commands/blob.rb +125 -0
- data/lib/cli/commands/cloudcheck.rb +169 -0
- data/lib/cli/commands/deployment.rb +147 -0
- data/lib/cli/commands/job.rb +42 -0
- data/lib/cli/commands/job_management.rb +117 -0
- data/lib/cli/commands/log_management.rb +81 -0
- data/lib/cli/commands/maintenance.rb +131 -0
- data/lib/cli/commands/misc.rb +240 -0
- data/lib/cli/commands/package.rb +112 -0
- data/lib/cli/commands/property_management.rb +125 -0
- data/lib/cli/commands/release.rb +469 -0
- data/lib/cli/commands/ssh.rb +271 -0
- data/lib/cli/commands/stemcell.rb +184 -0
- data/lib/cli/commands/task.rb +213 -0
- data/lib/cli/commands/user.rb +28 -0
- data/lib/cli/commands/vms.rb +53 -0
- data/lib/cli/config.rb +154 -0
- data/lib/cli/core_ext.rb +145 -0
- data/lib/cli/dependency_helper.rb +62 -0
- data/lib/cli/deployment_helper.rb +263 -0
- data/lib/cli/deployment_manifest_compiler.rb +28 -0
- data/lib/cli/director.rb +633 -0
- data/lib/cli/director_task.rb +64 -0
- data/lib/cli/errors.rb +48 -0
- data/lib/cli/event_log_renderer.rb +351 -0
- data/lib/cli/job_builder.rb +226 -0
- data/lib/cli/package_builder.rb +254 -0
- data/lib/cli/packaging_helper.rb +248 -0
- data/lib/cli/release.rb +176 -0
- data/lib/cli/release_builder.rb +215 -0
- data/lib/cli/release_compiler.rb +178 -0
- data/lib/cli/release_tarball.rb +272 -0
- data/lib/cli/runner.rb +771 -0
- data/lib/cli/stemcell.rb +83 -0
- data/lib/cli/task_log_renderer.rb +40 -0
- data/lib/cli/templates/help_message.erb +75 -0
- data/lib/cli/validation.rb +42 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/version_calc.rb +48 -0
- data/lib/cli/versions_index.rb +126 -0
- data/lib/cli/yaml_helper.rb +62 -0
- data/spec/assets/biff/bad_gateway_config.yml +28 -0
- data/spec/assets/biff/good_simple_config.yml +63 -0
- data/spec/assets/biff/good_simple_golden_config.yml +63 -0
- data/spec/assets/biff/good_simple_template.erb +69 -0
- data/spec/assets/biff/multiple_subnets_config.yml +40 -0
- data/spec/assets/biff/network_only_template.erb +34 -0
- data/spec/assets/biff/no_cc_config.yml +27 -0
- data/spec/assets/biff/no_range_config.yml +27 -0
- data/spec/assets/biff/no_subnet_config.yml +16 -0
- data/spec/assets/biff/ok_network_config.yml +30 -0
- data/spec/assets/biff/properties_template.erb +6 -0
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
- data/spec/assets/release/jobs/cacher.tgz +0 -0
- data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
- data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
- data/spec/assets/release/jobs/cacher/job.MF +6 -0
- data/spec/assets/release/jobs/cacher/monit +1 -0
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +4 -0
- data/spec/assets/release/jobs/cleaner/monit +1 -0
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
- data/spec/assets/release/jobs/sweeper/job.MF +5 -0
- data/spec/assets/release/jobs/sweeper/monit +1 -0
- data/spec/assets/release/packages/mutator.tar.gz +0 -0
- data/spec/assets/release/packages/stuff.tgz +0 -0
- data/spec/assets/release/release.MF +17 -0
- data/spec/assets/release_invalid_checksum.tgz +0 -0
- data/spec/assets/release_invalid_jobs.tgz +0 -0
- data/spec/assets/release_no_name.tgz +0 -0
- data/spec/assets/release_no_version.tgz +0 -0
- data/spec/assets/stemcell/image +1 -0
- data/spec/assets/stemcell/stemcell.MF +6 -0
- data/spec/assets/stemcell_invalid_mf.tgz +0 -0
- data/spec/assets/stemcell_no_image.tgz +0 -0
- data/spec/assets/valid_release.tgz +0 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/base_command_spec.rb +66 -0
- data/spec/unit/biff_spec.rb +135 -0
- data/spec/unit/cache_spec.rb +36 -0
- data/spec/unit/cli_commands_spec.rb +481 -0
- data/spec/unit/config_spec.rb +139 -0
- data/spec/unit/core_ext_spec.rb +77 -0
- data/spec/unit/dependency_helper_spec.rb +52 -0
- data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
- data/spec/unit/director_spec.rb +511 -0
- data/spec/unit/director_task_spec.rb +48 -0
- data/spec/unit/event_log_renderer_spec.rb +171 -0
- data/spec/unit/hash_changeset_spec.rb +73 -0
- data/spec/unit/job_builder_spec.rb +454 -0
- data/spec/unit/package_builder_spec.rb +567 -0
- data/spec/unit/release_builder_spec.rb +65 -0
- data/spec/unit/release_spec.rb +66 -0
- data/spec/unit/release_tarball_spec.rb +33 -0
- data/spec/unit/runner_spec.rb +140 -0
- data/spec/unit/ssh_spec.rb +78 -0
- data/spec/unit/stemcell_spec.rb +17 -0
- data/spec/unit/version_calc_spec.rb +27 -0
- data/spec/unit/versions_index_spec.rb +132 -0
- metadata +338 -0
data/lib/cli/core_ext.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module BoshExtensions
|
4
|
+
|
5
|
+
def say(message, sep = "\n")
|
6
|
+
return unless Bosh::Cli::Config.output && message
|
7
|
+
message = message.dup.to_s
|
8
|
+
sep = "" if message[-1..-1] == sep
|
9
|
+
Bosh::Cli::Config.output.print("#{$indent}#{message}#{sep}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_indent(indent)
|
13
|
+
old_indent, $indent = $indent, old_indent.to_s + indent.to_s
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
$indent = old_indent
|
17
|
+
end
|
18
|
+
|
19
|
+
def header(message, filler = '-')
|
20
|
+
say("\n")
|
21
|
+
say(message)
|
22
|
+
say(filler.to_s * message.size)
|
23
|
+
end
|
24
|
+
|
25
|
+
def nl(count = 1)
|
26
|
+
say("\n" * count)
|
27
|
+
end
|
28
|
+
|
29
|
+
def err(message)
|
30
|
+
raise Bosh::Cli::CliExit.new message
|
31
|
+
end
|
32
|
+
|
33
|
+
def quit(message = nil)
|
34
|
+
say(message)
|
35
|
+
raise Bosh::Cli::GracefulExit, message
|
36
|
+
end
|
37
|
+
|
38
|
+
def blank?
|
39
|
+
self.to_s.blank?
|
40
|
+
end
|
41
|
+
|
42
|
+
def pretty_size(what, prec=1)
|
43
|
+
if what.is_a?(String) && File.exists?(what)
|
44
|
+
size = File.size(what)
|
45
|
+
else
|
46
|
+
size = what.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
return "NA" unless size
|
50
|
+
return "#{size}B" if size < 1024
|
51
|
+
return sprintf("%.#{prec}fK", size/1024.0) if size < (1024*1024)
|
52
|
+
if size < (1024*1024*1024)
|
53
|
+
return sprintf("%.#{prec}fM", size/(1024.0*1024.0))
|
54
|
+
end
|
55
|
+
sprintf("%.#{prec}fG", size/(1024.0*1024.0*1024.0))
|
56
|
+
end
|
57
|
+
|
58
|
+
def pluralize(number, singular, plural = nil)
|
59
|
+
plural = plural || "#{singular}s"
|
60
|
+
number == 1 ? "1 #{singular}" : "#{number} #{plural}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_time(time)
|
64
|
+
ts = time.to_i
|
65
|
+
sprintf("%02d:%02d:%02d", ts / 3600, (ts / 60) % 60, ts % 60);
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_yaml_file(path, expected_type = Hash)
|
69
|
+
err("Cannot find file `#{path}'") unless File.exists?(path)
|
70
|
+
yaml = YAML.load_file(path)
|
71
|
+
|
72
|
+
if expected_type && !yaml.is_a?(expected_type)
|
73
|
+
err("Incorrect file format in `#{path}', #{expected_type} expected")
|
74
|
+
end
|
75
|
+
|
76
|
+
Bosh::Cli::YamlHelper.check_duplicate_keys(path)
|
77
|
+
|
78
|
+
yaml
|
79
|
+
rescue SystemCallError => e
|
80
|
+
err("Cannot load YAML file at `#{path}': #{e}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def dump_yaml_to_file(obj, file)
|
84
|
+
yaml = YAML.dump(obj)
|
85
|
+
file.write(yaml.gsub(" \n", "\n"))
|
86
|
+
file.flush
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module BoshStringExtensions
|
91
|
+
|
92
|
+
COLOR_CODES = {
|
93
|
+
:red => "\e[0m\e[31m",
|
94
|
+
:green => "\e[0m\e[32m",
|
95
|
+
:yellow => "\e[0m\e[33m"
|
96
|
+
}
|
97
|
+
|
98
|
+
def red
|
99
|
+
colorize(:red)
|
100
|
+
end
|
101
|
+
|
102
|
+
def green
|
103
|
+
colorize(:green)
|
104
|
+
end
|
105
|
+
|
106
|
+
def yellow
|
107
|
+
colorize(:yellow)
|
108
|
+
end
|
109
|
+
|
110
|
+
def colorize(color_code)
|
111
|
+
if Bosh::Cli::Config.colorize && COLOR_CODES[color_code]
|
112
|
+
"#{COLOR_CODES[color_code]}#{self}\e[0m"
|
113
|
+
else
|
114
|
+
self
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def blank?
|
119
|
+
self =~ /^\s*$/
|
120
|
+
end
|
121
|
+
|
122
|
+
def bosh_valid_id?
|
123
|
+
self =~ Bosh::Cli::Config::VALID_ID
|
124
|
+
end
|
125
|
+
|
126
|
+
def truncate(limit = 30)
|
127
|
+
return "" if self.blank?
|
128
|
+
etc = "..."
|
129
|
+
stripped = self.strip[0..limit]
|
130
|
+
if stripped.length > limit
|
131
|
+
stripped.gsub(/\s+?(\S+)?$/, "") + etc
|
132
|
+
else
|
133
|
+
stripped
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
class Object
|
140
|
+
include BoshExtensions
|
141
|
+
end
|
142
|
+
|
143
|
+
class String
|
144
|
+
include BoshStringExtensions
|
145
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::Cli
|
4
|
+
module DependencyHelper
|
5
|
+
|
6
|
+
# Expects package dependency graph
|
7
|
+
# { "A" => ["B", "C"], "B" => ["C", "D"] }
|
8
|
+
def tsort_packages(map)
|
9
|
+
resolved = Set.new
|
10
|
+
in_degree = { }
|
11
|
+
graph = { }
|
12
|
+
|
13
|
+
map.keys.sort.each do |package|
|
14
|
+
dependencies = map[package]
|
15
|
+
graph[package] ||= Set.new
|
16
|
+
in_degree[package] = dependencies.size
|
17
|
+
|
18
|
+
resolved << package if in_degree[package] == 0
|
19
|
+
|
20
|
+
# Reverse edges to avoid dfs
|
21
|
+
dependencies.each do |dependency|
|
22
|
+
unless map.has_key?(dependency)
|
23
|
+
raise MissingDependency, ("Package '%s' depends on " +
|
24
|
+
"missing package '%s'") % [package, dependency]
|
25
|
+
end
|
26
|
+
|
27
|
+
graph[dependency] ||= Set.new
|
28
|
+
graph[dependency] << package
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
sorted = []
|
33
|
+
|
34
|
+
until resolved.empty?
|
35
|
+
p = resolved.first
|
36
|
+
resolved.delete(p)
|
37
|
+
|
38
|
+
sorted << p
|
39
|
+
|
40
|
+
graph[p].each do |v|
|
41
|
+
in_degree[v] -= 1
|
42
|
+
resolved << v if in_degree[v] == 0
|
43
|
+
end
|
44
|
+
graph[p].clear
|
45
|
+
end
|
46
|
+
|
47
|
+
# each_pair gives different (correct) results in 1.8 in 1.9,
|
48
|
+
# stabilizing for tests
|
49
|
+
graph.keys.sort.each do |v|
|
50
|
+
e = graph[v]
|
51
|
+
unless e.empty?
|
52
|
+
raise CircularDependency, ("Cannot resolve dependencies for '%s': " +
|
53
|
+
"circular dependency with '%s'") % [v, e.first]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
sorted
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
2
|
+
|
3
|
+
module Bosh::Cli
|
4
|
+
module DeploymentHelper
|
5
|
+
|
6
|
+
def prepare_deployment_manifest(options = {})
|
7
|
+
# TODO: extract to helper class
|
8
|
+
deployment_required
|
9
|
+
manifest_filename = deployment
|
10
|
+
|
11
|
+
unless File.exists?(manifest_filename)
|
12
|
+
err("Cannot find deployment manifest in `#{manifest_filename}'")
|
13
|
+
end
|
14
|
+
|
15
|
+
manifest = load_yaml_file(manifest_filename)
|
16
|
+
manifest_yaml = File.read(manifest_filename)
|
17
|
+
|
18
|
+
if manifest["name"].blank?
|
19
|
+
err("Deployment name not found in the deployment manifest")
|
20
|
+
end
|
21
|
+
|
22
|
+
if manifest["target"]
|
23
|
+
err(manifest_target_upgrade_notice)
|
24
|
+
end
|
25
|
+
|
26
|
+
if options[:resolve_properties]
|
27
|
+
compiler = DeploymentManifestCompiler.new(manifest_yaml)
|
28
|
+
properties = {}
|
29
|
+
|
30
|
+
begin
|
31
|
+
say("Getting deployment properties from director...")
|
32
|
+
properties = director.list_properties(manifest["name"])
|
33
|
+
rescue Bosh::Cli::DirectorError
|
34
|
+
say("Unable to get properties list from director, " +
|
35
|
+
"trying without it...")
|
36
|
+
end
|
37
|
+
|
38
|
+
say("Compiling deployment manifest...")
|
39
|
+
compiler.properties = properties.inject({}) do |hash, property|
|
40
|
+
hash[property["name"]] = property["value"]
|
41
|
+
hash
|
42
|
+
end
|
43
|
+
|
44
|
+
manifest_yaml = compiler.result
|
45
|
+
manifest = YAML.load(manifest_yaml)
|
46
|
+
end
|
47
|
+
|
48
|
+
if manifest["name"].blank? || manifest["release"].blank? ||
|
49
|
+
manifest["director_uuid"].blank?
|
50
|
+
err("Invalid manifest `#{File.basename(deployment)}': " +
|
51
|
+
"name, release and director UUID are all required")
|
52
|
+
end
|
53
|
+
|
54
|
+
options[:yaml] ? manifest_yaml : manifest
|
55
|
+
end
|
56
|
+
|
57
|
+
# Interactive walkthrough of deployment changes,
|
58
|
+
# expected to bail out of CLI using 'cancel_deployment'
|
59
|
+
# if something goes wrong, so it doesn't need to have
|
60
|
+
# a meaningful return value.
|
61
|
+
# @return Boolean Were there any changes in deployment manifest?
|
62
|
+
def inspect_deployment_changes(manifest, options = { })
|
63
|
+
show_empty_changeset = true
|
64
|
+
|
65
|
+
if options.has_key?(:show_empty_changeset)
|
66
|
+
show_empty_changeset = options[:show_empty_changeset]
|
67
|
+
end
|
68
|
+
|
69
|
+
manifest = manifest.dup
|
70
|
+
current_deployment = director.get_deployment(manifest["name"])
|
71
|
+
|
72
|
+
# We cannot retrieve current manifest until there was at least one
|
73
|
+
# successful deployment. There used to be a warning about that
|
74
|
+
# but it turned out to be confusing to many users and thus has
|
75
|
+
# been removed.
|
76
|
+
return if current_deployment["manifest"].nil?
|
77
|
+
current_manifest = YAML.load(current_deployment["manifest"])
|
78
|
+
|
79
|
+
unless current_manifest.is_a?(Hash)
|
80
|
+
err("Current deployment manifest format is invalid, " +
|
81
|
+
"check if director works properly")
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO: validate new deployment manifest
|
85
|
+
diff = Bosh::Cli::HashChangeset.new
|
86
|
+
diff.add_hash(normalize_deployment_manifest(manifest), :new)
|
87
|
+
diff.add_hash(normalize_deployment_manifest(current_manifest), :old)
|
88
|
+
@_diff_key_visited = { "name" => 1, "director_uuid" => 1 }
|
89
|
+
|
90
|
+
say("Detecting changes in deployment...".green)
|
91
|
+
nl
|
92
|
+
|
93
|
+
if !diff.changed? && !show_empty_changeset
|
94
|
+
return false
|
95
|
+
end
|
96
|
+
|
97
|
+
print_summary(diff, :release)
|
98
|
+
|
99
|
+
if diff[:release][:name].changed?
|
100
|
+
say("Release name has changed: %s -> %s".red % [
|
101
|
+
diff[:release][:name].old, diff[:release][:name].new])
|
102
|
+
unless confirmed?("This is very serious and potentially destructive " +
|
103
|
+
"change. ARE YOU SURE YOU WANT TO DO IT?")
|
104
|
+
cancel_deployment
|
105
|
+
end
|
106
|
+
elsif diff[:release][:version].changed?
|
107
|
+
say("Release version has changed: %s -> %s".yellow % [
|
108
|
+
diff[:release][:version].old, diff[:release][:version].new])
|
109
|
+
unless confirmed?("Are you sure you want to deploy this version?")
|
110
|
+
cancel_deployment
|
111
|
+
end
|
112
|
+
end
|
113
|
+
nl
|
114
|
+
|
115
|
+
print_summary(diff, :compilation)
|
116
|
+
nl
|
117
|
+
|
118
|
+
print_summary(diff, :update)
|
119
|
+
nl
|
120
|
+
|
121
|
+
print_summary(diff, :resource_pools)
|
122
|
+
|
123
|
+
old_stemcells = Set.new
|
124
|
+
new_stemcells = Set.new
|
125
|
+
|
126
|
+
diff[:resource_pools].each do |pool|
|
127
|
+
old_stemcells << {
|
128
|
+
:name => pool[:stemcell][:name].old,
|
129
|
+
:version => pool[:stemcell][:version].old
|
130
|
+
}
|
131
|
+
new_stemcells << {
|
132
|
+
:name => pool[:stemcell][:name].new,
|
133
|
+
:version => pool[:stemcell][:version].new
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
if old_stemcells != new_stemcells
|
138
|
+
unless confirmed?("Stemcell update has been detected. " +
|
139
|
+
"Are you sure you want to update stemcells?")
|
140
|
+
cancel_deployment
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if old_stemcells.size != new_stemcells.size
|
145
|
+
say("Stemcell update seems to be inconsistent with current " +
|
146
|
+
"deployment. Please carefully review changes above.".red)
|
147
|
+
unless confirmed?("Are you sure this configuration is correct?")
|
148
|
+
cancel_deployment
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
nl
|
153
|
+
print_summary(diff, :networks)
|
154
|
+
nl
|
155
|
+
print_summary(diff, :jobs)
|
156
|
+
nl
|
157
|
+
print_summary(diff, :properties)
|
158
|
+
nl
|
159
|
+
|
160
|
+
diff.keys.each do |key|
|
161
|
+
unless @_diff_key_visited[key]
|
162
|
+
print_summary(diff, key)
|
163
|
+
nl
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
diff.changed?
|
168
|
+
rescue Bosh::Cli::DeploymentNotFound
|
169
|
+
say("Cannot get current deployment information from director, " +
|
170
|
+
"possibly a new deployment".red)
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def find_deployment(name)
|
177
|
+
if File.exists?(name)
|
178
|
+
File.expand_path(name)
|
179
|
+
else
|
180
|
+
File.expand_path(File.join(work_dir, "deployments", "#{name}.yml"))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def cancel_deployment
|
185
|
+
quit("Deployment canceled".red)
|
186
|
+
end
|
187
|
+
|
188
|
+
def manifest_error(err)
|
189
|
+
err("Deployment manifest error: #{err}")
|
190
|
+
end
|
191
|
+
|
192
|
+
def manifest_target_upgrade_notice
|
193
|
+
<<-EOS.gsub(/^\s*/, "").gsub(/\n$/, "")
|
194
|
+
Please upgrade your deployment manifest to use director UUID instead
|
195
|
+
of target. Just replace 'target' key with 'director_uuid' key in your
|
196
|
+
manifest. You can get your director UUID by targeting your director
|
197
|
+
with 'bosh target' and running 'bosh status' command afterwards.
|
198
|
+
EOS
|
199
|
+
end
|
200
|
+
|
201
|
+
def print_summary(diff, key, title = nil)
|
202
|
+
title ||= key.to_s.gsub(/[-_]/, " ").capitalize
|
203
|
+
|
204
|
+
say(title.green)
|
205
|
+
summary = diff[key].summary
|
206
|
+
if summary.empty?
|
207
|
+
say("No changes")
|
208
|
+
else
|
209
|
+
say(summary.join("\n"))
|
210
|
+
end
|
211
|
+
@_diff_key_visited[key.to_s] = 1
|
212
|
+
end
|
213
|
+
|
214
|
+
def normalize_deployment_manifest(manifest)
|
215
|
+
normalized = manifest.dup
|
216
|
+
|
217
|
+
%w(networks jobs resource_pools).each do |section|
|
218
|
+
unless normalized[section].kind_of?(Array)
|
219
|
+
manifest_error("#{section} is expected to be an array")
|
220
|
+
end
|
221
|
+
|
222
|
+
normalized[section] = normalized[section].inject({}) do |acc, e|
|
223
|
+
if e["name"].blank?
|
224
|
+
manifest_error("missing name for one of entries in '#{section}'")
|
225
|
+
end
|
226
|
+
if acc.has_key?(e["name"])
|
227
|
+
manifest_error("duplicate entry '#{e['name']}' in '#{section}'")
|
228
|
+
end
|
229
|
+
acc[e["name"]] = e
|
230
|
+
acc
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
normalized["networks"].each do |network_name, network|
|
235
|
+
# VIP and dynamic networks do not require subnet,
|
236
|
+
# but if it's there we can run some sanity checks
|
237
|
+
next unless network.has_key?("subnets")
|
238
|
+
|
239
|
+
unless network["subnets"].kind_of?(Array)
|
240
|
+
manifest_error("network subnets is expected to be an array")
|
241
|
+
end
|
242
|
+
|
243
|
+
subnets = network["subnets"].inject({}) do |acc, e|
|
244
|
+
if e["range"].blank?
|
245
|
+
manifest_error("missing range for one of subnets " +
|
246
|
+
"in '#{network_name}'")
|
247
|
+
end
|
248
|
+
if acc.has_key?(e["range"])
|
249
|
+
manifest_error("duplicate network range '#{e['range']}' " +
|
250
|
+
"in '#{network}'")
|
251
|
+
end
|
252
|
+
acc[e["range"]] = e
|
253
|
+
acc
|
254
|
+
end
|
255
|
+
|
256
|
+
normalized["networks"][network_name]["subnets"] = subnets
|
257
|
+
end
|
258
|
+
|
259
|
+
normalized
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|