morpheus-cli 3.5.2 → 3.5.3
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.
- checksums.yaml +4 -4
- data/lib/morpheus/api/api_client.rb +16 -0
- data/lib/morpheus/api/blueprints_interface.rb +84 -0
- data/lib/morpheus/api/execution_request_interface.rb +33 -0
- data/lib/morpheus/api/instances_interface.rb +21 -0
- data/lib/morpheus/api/packages_interface.rb +25 -5
- data/lib/morpheus/api/processes_interface.rb +34 -0
- data/lib/morpheus/api/roles_interface.rb +7 -0
- data/lib/morpheus/api/servers_interface.rb +8 -0
- data/lib/morpheus/api/user_settings_interface.rb +76 -0
- data/lib/morpheus/cli.rb +5 -1
- data/lib/morpheus/cli/alias_command.rb +1 -1
- data/lib/morpheus/cli/app_templates.rb +2 -1
- data/lib/morpheus/cli/apps.rb +173 -19
- data/lib/morpheus/cli/blueprints_command.rb +2134 -0
- data/lib/morpheus/cli/cli_command.rb +3 -1
- data/lib/morpheus/cli/clouds.rb +4 -10
- data/lib/morpheus/cli/coloring_command.rb +14 -8
- data/lib/morpheus/cli/containers_command.rb +92 -5
- data/lib/morpheus/cli/execution_request_command.rb +313 -0
- data/lib/morpheus/cli/hosts.rb +188 -7
- data/lib/morpheus/cli/instances.rb +472 -9
- data/lib/morpheus/cli/login.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +8 -0
- data/lib/morpheus/cli/mixins/processes_helper.rb +134 -0
- data/lib/morpheus/cli/option_types.rb +21 -16
- data/lib/morpheus/cli/packages_command.rb +469 -17
- data/lib/morpheus/cli/processes_command.rb +313 -0
- data/lib/morpheus/cli/remote.rb +20 -9
- data/lib/morpheus/cli/roles.rb +186 -6
- data/lib/morpheus/cli/shell.rb +10 -1
- data/lib/morpheus/cli/tasks.rb +4 -1
- data/lib/morpheus/cli/user_settings_command.rb +431 -0
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whoami.rb +1 -1
- data/lib/morpheus/formatters.rb +14 -0
- data/lib/morpheus/morpkg.rb +119 -0
- data/morpheus-cli.gemspec +1 -0
- metadata +26 -2
@@ -0,0 +1,2134 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
require 'morpheus/cli/mixins/provisioning_helper'
|
3
|
+
|
4
|
+
class Morpheus::Cli::BlueprintsCommand
|
5
|
+
include Morpheus::Cli::CliCommand
|
6
|
+
include Morpheus::Cli::ProvisioningHelper
|
7
|
+
set_command_name :'blueprints'
|
8
|
+
register_subcommands :list, :get, :add, :update, :remove
|
9
|
+
register_subcommands :duplicate
|
10
|
+
register_subcommands :'upload-image' => :upload_image
|
11
|
+
register_subcommands :'update-permissions' => :update_permissions
|
12
|
+
register_subcommands :'available-tiers'
|
13
|
+
register_subcommands :'add-tier', :'update-tier', :'remove-tier', :'connect-tiers', :'disconnect-tiers'
|
14
|
+
register_subcommands :'add-instance'
|
15
|
+
#register_subcommands :'update-instance'
|
16
|
+
register_subcommands :'remove-instance'
|
17
|
+
register_subcommands :'add-instance-config'
|
18
|
+
#register_subcommands :'update-instance-config'
|
19
|
+
register_subcommands :'remove-instance-config'
|
20
|
+
# alias_subcommand :details, :get
|
21
|
+
# set_default_subcommand :list
|
22
|
+
|
23
|
+
def initialize()
|
24
|
+
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(opts)
|
28
|
+
@api_client = establish_remote_appliance_connection(opts)
|
29
|
+
@blueprints_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).blueprints
|
30
|
+
@groups_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).groups
|
31
|
+
@instances_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instances
|
32
|
+
@instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
|
33
|
+
@options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
|
34
|
+
@active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle(args)
|
38
|
+
handle_subcommand(args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def list(args)
|
42
|
+
options = {}
|
43
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
44
|
+
opts.banner = subcommand_usage()
|
45
|
+
build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
46
|
+
opts.footer = "List blueprints."
|
47
|
+
end
|
48
|
+
optparse.parse!(args)
|
49
|
+
connect(options)
|
50
|
+
begin
|
51
|
+
params = {}
|
52
|
+
[:phrase, :offset, :max, :sort, :direction].each do |k|
|
53
|
+
params[k] = options[k] unless options[k].nil?
|
54
|
+
end
|
55
|
+
if options[:dry_run]
|
56
|
+
print_dry_run @blueprints_interface.dry.list(params)
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
json_response = @blueprints_interface.list(params)
|
61
|
+
blueprints = json_response['blueprints']
|
62
|
+
|
63
|
+
if options[:json]
|
64
|
+
puts as_json(json_response, options, "blueprints")
|
65
|
+
return 0
|
66
|
+
elsif options[:csv]
|
67
|
+
puts records_as_csv(json_response['blueprints'], options)
|
68
|
+
return 0
|
69
|
+
elsif options[:yaml]
|
70
|
+
puts as_yaml(json_response, options, "blueprints")
|
71
|
+
return 0
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
title = "Morpheus Blueprints"
|
76
|
+
subtitles = []
|
77
|
+
if params[:phrase]
|
78
|
+
subtitles << "Search: #{params[:phrase]}".strip
|
79
|
+
end
|
80
|
+
print_h1 title, subtitles
|
81
|
+
if blueprints.empty?
|
82
|
+
print cyan,"No blueprints found.",reset,"\n"
|
83
|
+
else
|
84
|
+
print_blueprints_table(blueprints, options)
|
85
|
+
print_results_pagination(json_response)
|
86
|
+
end
|
87
|
+
print reset,"\n"
|
88
|
+
return 0
|
89
|
+
|
90
|
+
rescue RestClient::Exception => e
|
91
|
+
print_rest_exception(e, options)
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(args)
|
97
|
+
options = {}
|
98
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
99
|
+
opts.banner = subcommand_usage("[id]")
|
100
|
+
opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
|
101
|
+
options[:show_config] = true
|
102
|
+
end
|
103
|
+
build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
|
104
|
+
opts.footer = "Get details about a blueprint.\n" +
|
105
|
+
"[id] is required. This is the name or id of a blueprint."
|
106
|
+
end
|
107
|
+
optparse.parse!(args)
|
108
|
+
if args.count < 1
|
109
|
+
puts optparse
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
connect(options)
|
113
|
+
begin
|
114
|
+
if options[:dry_run]
|
115
|
+
if args[0].to_s =~ /\A\d{1,}\Z/
|
116
|
+
print_dry_run @blueprints_interface.dry.get(args[0].to_i)
|
117
|
+
else
|
118
|
+
print_dry_run @blueprints_interface.dry.list({name:args[0]})
|
119
|
+
end
|
120
|
+
return
|
121
|
+
end
|
122
|
+
blueprint = find_blueprint_by_name_or_id(args[0])
|
123
|
+
exit 1 if blueprint.nil?
|
124
|
+
|
125
|
+
json_response = {'blueprint' => blueprint} # skip redundant request
|
126
|
+
#json_response = @blueprints_interface.get(blueprint['id'])
|
127
|
+
blueprint = json_response['blueprint']
|
128
|
+
|
129
|
+
if options[:show_config]
|
130
|
+
#print_h2 "RAW"
|
131
|
+
if options[:json]
|
132
|
+
print cyan
|
133
|
+
print "// JSON config for Morpheus Blueprint: #{blueprint['name']}","\n"
|
134
|
+
print reset
|
135
|
+
puts as_json(blueprint["config"])
|
136
|
+
else
|
137
|
+
print cyan
|
138
|
+
print "# YAML config for Morpheus Blueprint: #{blueprint['name']}","\n"
|
139
|
+
print reset
|
140
|
+
puts as_yaml(blueprint["config"])
|
141
|
+
end
|
142
|
+
return 0
|
143
|
+
end
|
144
|
+
|
145
|
+
if options[:json]
|
146
|
+
puts as_json(json_response, options, "blueprint")
|
147
|
+
return 0
|
148
|
+
elsif options[:yaml]
|
149
|
+
puts as_yaml(json_response, options, "blueprint")
|
150
|
+
return 0
|
151
|
+
elsif options[:csv]
|
152
|
+
puts records_as_csv([json_response['blueprint']], options)
|
153
|
+
return 0
|
154
|
+
end
|
155
|
+
|
156
|
+
print_h1 "Blueprint Details"
|
157
|
+
|
158
|
+
print_blueprint_details(blueprint)
|
159
|
+
|
160
|
+
if blueprint['resourcePermission'].nil?
|
161
|
+
print "\n", "No group access found", "\n"
|
162
|
+
else
|
163
|
+
print_h2 "Group Access"
|
164
|
+
rows = []
|
165
|
+
if blueprint['resourcePermission']['allSites'] || blueprint['resourcePermission']['all']
|
166
|
+
rows.push({"name" => 'All'})
|
167
|
+
end
|
168
|
+
if blueprint['resourcePermission']['sites']
|
169
|
+
blueprint['resourcePermission']['sites'].each do |site|
|
170
|
+
rows.push(site)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
rows = rows.collect do |site|
|
174
|
+
{group: site['name'], default: site['default'] ? 'Yes' : ''}
|
175
|
+
end
|
176
|
+
# columns = [:group, :default]
|
177
|
+
columns = [:group]
|
178
|
+
print cyan
|
179
|
+
print as_pretty_table(rows, columns)
|
180
|
+
end
|
181
|
+
|
182
|
+
print reset,"\n"
|
183
|
+
|
184
|
+
rescue RestClient::Exception => e
|
185
|
+
print_rest_exception(e, options)
|
186
|
+
exit 1
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def add(args)
|
191
|
+
options = {}
|
192
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
193
|
+
opts.banner = subcommand_usage("[name] [options]")
|
194
|
+
opts.on('--config JSON', String, "Blueprint Config JSON") do |val|
|
195
|
+
options['config'] = JSON.parse(val.to_s)
|
196
|
+
end
|
197
|
+
opts.on('--config-yaml YAML', String, "Blueprint Config YAML") do |val|
|
198
|
+
options['config'] = YAML.load(val.to_s)
|
199
|
+
end
|
200
|
+
opts.on('--config-file FILE', String, "Blueprint Config from a local JSON or YAML file") do |val|
|
201
|
+
options['configFile'] = val.to_s
|
202
|
+
end
|
203
|
+
build_option_type_options(opts, options, add_blueprint_option_types(false))
|
204
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote])
|
205
|
+
opts.footer = "Create a new blueprint.\n" +
|
206
|
+
"[name] is optional and can be passed as --name or inside the config instead."
|
207
|
+
"[--config] or [--config-file] can be used to define the blueprint."
|
208
|
+
end
|
209
|
+
optparse.parse!(args)
|
210
|
+
if args.count > 1
|
211
|
+
print_error Morpheus::Terminal.angry_prompt
|
212
|
+
puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
213
|
+
return 1
|
214
|
+
end
|
215
|
+
options[:options] ||= {}
|
216
|
+
if args[0] && !options[:options]['name']
|
217
|
+
options[:options]['name'] = args[0]
|
218
|
+
end
|
219
|
+
connect(options)
|
220
|
+
begin
|
221
|
+
request_payload = nil
|
222
|
+
config_payload = {}
|
223
|
+
if options['config']
|
224
|
+
config_payload = options['config']
|
225
|
+
request_payload = config_payload
|
226
|
+
elsif options['configFile']
|
227
|
+
config_file = File.expand_path(options['configFile'])
|
228
|
+
if !File.exists?(config_file) || !File.file?(config_file)
|
229
|
+
print_red_alert "File not found: #{config_file}"
|
230
|
+
return false
|
231
|
+
end
|
232
|
+
if config_file =~ /\.ya?ml\Z/
|
233
|
+
config_payload = YAML.load_file(config_file)
|
234
|
+
else
|
235
|
+
config_payload = JSON.parse(File.read(config_file))
|
236
|
+
end
|
237
|
+
request_payload = config_payload
|
238
|
+
else
|
239
|
+
params = Morpheus::Cli::OptionTypes.prompt(add_blueprint_option_types, options[:options], @api_client, options[:params])
|
240
|
+
blueprint_payload = params.select {|k,v| ['name', 'description', 'category'].include?(k) }
|
241
|
+
# expects no namespace, just the config
|
242
|
+
request_payload = blueprint_payload
|
243
|
+
end
|
244
|
+
|
245
|
+
if options[:dry_run]
|
246
|
+
print_dry_run @blueprints_interface.dry.create(request_payload)
|
247
|
+
return
|
248
|
+
end
|
249
|
+
|
250
|
+
json_response = @blueprints_interface.create(request_payload)
|
251
|
+
|
252
|
+
if options[:json]
|
253
|
+
print JSON.pretty_generate(json_response)
|
254
|
+
print "\n"
|
255
|
+
else
|
256
|
+
blueprint = json_response["blueprint"]
|
257
|
+
print_green_success "Added blueprint #{blueprint['name']}"
|
258
|
+
if !options[:no_prompt]
|
259
|
+
if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add a tier now?", options.merge({default: false}))
|
260
|
+
add_tier([blueprint['id']])
|
261
|
+
while ::Morpheus::Cli::OptionTypes::confirm("Add another tier?", options.merge({default: false})) do
|
262
|
+
add_tier([blueprint['id']])
|
263
|
+
end
|
264
|
+
else
|
265
|
+
# print details
|
266
|
+
get([blueprint['id']])
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
rescue RestClient::Exception => e
|
272
|
+
print_rest_exception(e, options)
|
273
|
+
exit 1
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def update(args)
|
278
|
+
options = {}
|
279
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
280
|
+
opts.banner = subcommand_usage("[id] [options]")
|
281
|
+
opts.on('--config JSON', String, "Blueprint Config JSON") do |val|
|
282
|
+
options['config'] = JSON.parse(val.to_s)
|
283
|
+
end
|
284
|
+
opts.on('--config-yaml YAML', String, "Blueprint Config YAML") do |val|
|
285
|
+
options['config'] = YAML.load(val.to_s)
|
286
|
+
end
|
287
|
+
opts.on('--config-file FILE', String, "Blueprint Config from a local JSON or YAML file") do |val|
|
288
|
+
options['configFile'] = val.to_s
|
289
|
+
end
|
290
|
+
build_option_type_options(opts, options, update_blueprint_option_types(false))
|
291
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
|
292
|
+
opts.footer = "Update a blueprint.\n" +
|
293
|
+
"[id] is required. This is the name or id of a blueprint.\n" +
|
294
|
+
"[options] Available options include --name and --description. This will update only the specified values.\n" +
|
295
|
+
"[--config] or [--config-file] can be used to replace the entire blueprint."
|
296
|
+
end
|
297
|
+
optparse.parse!(args)
|
298
|
+
|
299
|
+
if args.count < 1
|
300
|
+
puts optparse
|
301
|
+
exit 1
|
302
|
+
end
|
303
|
+
|
304
|
+
connect(options)
|
305
|
+
|
306
|
+
begin
|
307
|
+
|
308
|
+
blueprint = find_blueprint_by_name_or_id(args[0])
|
309
|
+
exit 1 if blueprint.nil?
|
310
|
+
|
311
|
+
request_payload = nil
|
312
|
+
config_payload = {}
|
313
|
+
if options['config']
|
314
|
+
config_payload = options['config']
|
315
|
+
request_payload = config_payload
|
316
|
+
elsif options['configFile']
|
317
|
+
config_file = options['configFile']
|
318
|
+
if !File.exists?(config_file)
|
319
|
+
print_red_alert "File not found: #{config_file}"
|
320
|
+
return false
|
321
|
+
end
|
322
|
+
if config_file =~ /\.ya?ml\Z/
|
323
|
+
config_payload = YAML.load_file(config_file)
|
324
|
+
else
|
325
|
+
config_payload = JSON.parse(File.read(config_file))
|
326
|
+
end
|
327
|
+
request_payload = config_payload
|
328
|
+
else
|
329
|
+
# update just name,description,category
|
330
|
+
# preserve all other attributes of the config..
|
331
|
+
|
332
|
+
#params = Morpheus::Cli::OptionTypes.prompt(update_blueprint_option_types, options[:options], @api_client, options[:params])
|
333
|
+
params = options[:options] || {}
|
334
|
+
|
335
|
+
if params.empty?
|
336
|
+
# print_red_alert "Specify atleast one option to update"
|
337
|
+
print_red_alert "Specify atleast one option to update.\nOr use --config or --config-file to replace the entire config."
|
338
|
+
puts optparse
|
339
|
+
exit 1
|
340
|
+
end
|
341
|
+
|
342
|
+
#puts "parsed params is : #{params.inspect}"
|
343
|
+
blueprint_payload = params.select {|k,v| ['name','description','category'].include?(k) }
|
344
|
+
# expects no namespace, just the config
|
345
|
+
# preserve all other attributes of the config.
|
346
|
+
request_payload = blueprint["config"].merge(blueprint_payload)
|
347
|
+
# todo maybe: put name, description and category at the front.
|
348
|
+
# request_payload = blueprint_payload.merge(blueprint["config"].merge(blueprint_payload))
|
349
|
+
end
|
350
|
+
|
351
|
+
if options[:dry_run]
|
352
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
353
|
+
return
|
354
|
+
end
|
355
|
+
|
356
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
357
|
+
|
358
|
+
if options[:json]
|
359
|
+
print JSON.pretty_generate(json_response)
|
360
|
+
print "\n"
|
361
|
+
else
|
362
|
+
unless options[:quiet]
|
363
|
+
blueprint = json_response['blueprint']
|
364
|
+
print_green_success "Updated blueprint #{blueprint['name']}"
|
365
|
+
details_options = [blueprint['id']]
|
366
|
+
get(details_options)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
rescue RestClient::Exception => e
|
371
|
+
print_rest_exception(e, options)
|
372
|
+
exit 1
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def update_permissions(args)
|
377
|
+
group_access_all = nil
|
378
|
+
group_access_list = nil
|
379
|
+
group_defaults_list = nil
|
380
|
+
options = {}
|
381
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
382
|
+
opts.banner = subcommand_usage("[id] [options]")
|
383
|
+
opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
|
384
|
+
group_access_all = val.to_s == 'on' || val.to_s == 'true' || val == '' || val.nil?
|
385
|
+
end
|
386
|
+
opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
|
387
|
+
if list.size == 1 && list[0] == 'null' # hacky way to clear it
|
388
|
+
group_access_list = []
|
389
|
+
else
|
390
|
+
group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
391
|
+
end
|
392
|
+
end
|
393
|
+
opts.on('--visibility [private|public]', String, "Visibility") do |val|
|
394
|
+
options['visibility'] = val
|
395
|
+
end
|
396
|
+
build_option_type_options(opts, options, update_blueprint_option_types(false))
|
397
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
|
398
|
+
opts.footer = "Update a blueprint permissions.\n" +
|
399
|
+
"[id] is required. This is the name or id of a blueprint."
|
400
|
+
end
|
401
|
+
optparse.parse!(args)
|
402
|
+
|
403
|
+
if args.count != 1
|
404
|
+
puts optparse
|
405
|
+
return 1
|
406
|
+
end
|
407
|
+
|
408
|
+
connect(options)
|
409
|
+
|
410
|
+
begin
|
411
|
+
|
412
|
+
blueprint = find_blueprint_by_name_or_id(args[0])
|
413
|
+
return 1 if blueprint.nil?
|
414
|
+
|
415
|
+
payload = nil
|
416
|
+
if options[:payload]
|
417
|
+
payload = options[:payload]
|
418
|
+
else
|
419
|
+
payload = {
|
420
|
+
'blueprint' => {
|
421
|
+
}
|
422
|
+
}
|
423
|
+
|
424
|
+
# allow arbitrary -O options
|
425
|
+
payload['blueprint'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
426
|
+
|
427
|
+
# Group Access
|
428
|
+
if group_access_all != nil
|
429
|
+
payload['resourcePermissions'] ||= {}
|
430
|
+
payload['resourcePermissions']['all'] = group_access_all
|
431
|
+
end
|
432
|
+
if group_access_list != nil
|
433
|
+
payload['resourcePermissions'] ||= {}
|
434
|
+
payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
|
435
|
+
site = {"id" => site_id.to_i}
|
436
|
+
if group_defaults_list && group_defaults_list.include?(site_id)
|
437
|
+
site["default"] = true
|
438
|
+
end
|
439
|
+
site
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Tenants
|
444
|
+
# if options['tenants']
|
445
|
+
# payload['tenantPermissions'] = {}
|
446
|
+
# payload['tenantPermissions']['accounts'] = options['tenants']
|
447
|
+
# end
|
448
|
+
|
449
|
+
# Visibility
|
450
|
+
if options['visibility'] != nil
|
451
|
+
payload['blueprint']['visibility'] = options['visibility']
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
if options[:dry_run]
|
456
|
+
print_dry_run @blueprints_interface.dry.update_permissions(blueprint['id'], payload)
|
457
|
+
return
|
458
|
+
end
|
459
|
+
|
460
|
+
json_response = @blueprints_interface.update_permissions(blueprint['id'], payload)
|
461
|
+
|
462
|
+
if options[:json]
|
463
|
+
puts JSON.pretty_generate(json_response)
|
464
|
+
else
|
465
|
+
unless options[:quiet]
|
466
|
+
blueprint = json_response['blueprint']
|
467
|
+
print_green_success "Updated permissions for blueprint #{blueprint['name']}"
|
468
|
+
details_options = [blueprint['id']]
|
469
|
+
get(details_options)
|
470
|
+
end
|
471
|
+
end
|
472
|
+
return 0
|
473
|
+
rescue RestClient::Exception => e
|
474
|
+
print_rest_exception(e, options)
|
475
|
+
exit 1
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
|
480
|
+
def upload_image(args)
|
481
|
+
image_type_name = nil
|
482
|
+
options = {}
|
483
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
484
|
+
opts.banner = subcommand_usage("[id] [file]")
|
485
|
+
build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
|
486
|
+
opts.footer = "Upload an image file to be used as the icon for a blueprint.\n" +
|
487
|
+
"[id] is required. This is the name or id of a blueprint.\n" +
|
488
|
+
"[file] is required. This is the local path of a file to upload [png|jpg|svg]."
|
489
|
+
end
|
490
|
+
optparse.parse!(args)
|
491
|
+
if args.count != 2
|
492
|
+
print_error Morpheus::Terminal.angry_prompt
|
493
|
+
puts_error "#{command_name} upload-image expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
494
|
+
return 1
|
495
|
+
end
|
496
|
+
blueprint_name = args[0]
|
497
|
+
filename = File.expand_path(args[1].to_s)
|
498
|
+
image_file = nil
|
499
|
+
if filename && File.file?(filename)
|
500
|
+
# maybe validate it's an image file? [.png|jpg|svg]
|
501
|
+
image_file = File.new(filename, 'rb')
|
502
|
+
else
|
503
|
+
print_red_alert "File not found: #{filename}"
|
504
|
+
# print_error Morpheus::Terminal.angry_prompt
|
505
|
+
# puts_error "bad argument [file] - File not found: #{filename}\n#{optparse}"
|
506
|
+
return 1
|
507
|
+
end
|
508
|
+
connect(options)
|
509
|
+
begin
|
510
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
511
|
+
exit 1 if blueprint.nil?
|
512
|
+
if options[:dry_run]
|
513
|
+
print_dry_run @blueprints_interface.dry.save_image(blueprint['id'], image_file)
|
514
|
+
return 0
|
515
|
+
end
|
516
|
+
unless options[:quiet] || options[:json]
|
517
|
+
print cyan, "Uploading file #{filename} ...", reset, "\n"
|
518
|
+
end
|
519
|
+
json_response = @blueprints_interface.save_image(blueprint['id'], image_file)
|
520
|
+
if options[:json]
|
521
|
+
print JSON.pretty_generate(json_response)
|
522
|
+
elsif !options[:quiet]
|
523
|
+
blueprint = json_response['blueprint']
|
524
|
+
new_image_url = blueprint['image']
|
525
|
+
print cyan, "Updated blueprint #{blueprint['name']} image.\nNew image url is: #{new_image_url}", reset, "\n\n"
|
526
|
+
get([blueprint['id']])
|
527
|
+
end
|
528
|
+
return 0
|
529
|
+
rescue RestClient::Exception => e
|
530
|
+
print_rest_exception(e, options)
|
531
|
+
return 1
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def duplicate(args)
|
536
|
+
options = {}
|
537
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
538
|
+
opts.banner = subcommand_usage("[id] [new name]")
|
539
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
540
|
+
opts.footer = "Duplicate a blueprint." + "\n" +
|
541
|
+
"[id] is required. This is the name or id of a blueprint." + "\n" +
|
542
|
+
"[new name] is required. This is the name for the clone."
|
543
|
+
end
|
544
|
+
optparse.parse!(args)
|
545
|
+
|
546
|
+
if args.count < 1
|
547
|
+
puts optparse
|
548
|
+
exit 1
|
549
|
+
end
|
550
|
+
|
551
|
+
request_payload = {"blueprint" => {}}
|
552
|
+
if args[1]
|
553
|
+
request_payload["blueprint"]["name"] = args[1]
|
554
|
+
end
|
555
|
+
|
556
|
+
connect(options)
|
557
|
+
begin
|
558
|
+
blueprint = find_blueprint_by_name_or_id(args[0])
|
559
|
+
exit 1 if blueprint.nil?
|
560
|
+
# unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to duplicate the blueprint #{blueprint['name']}?")
|
561
|
+
# exit
|
562
|
+
# end
|
563
|
+
if options[:dry_run]
|
564
|
+
print_dry_run @blueprints_interface.dry.duplicate(blueprint['id'], request_payload)
|
565
|
+
return
|
566
|
+
end
|
567
|
+
json_response = @blueprints_interface.duplicate(blueprint['id'], request_payload)
|
568
|
+
|
569
|
+
if options[:json]
|
570
|
+
print JSON.pretty_generate(json_response)
|
571
|
+
print "\n"
|
572
|
+
else
|
573
|
+
new_blueprint = json_response["blueprint"] || {}
|
574
|
+
print_green_success "Created duplicate blueprint '#{new_blueprint['name']}'"
|
575
|
+
#get([new_blueprint["id"]])
|
576
|
+
end
|
577
|
+
|
578
|
+
rescue RestClient::Exception => e
|
579
|
+
print_rest_exception(e, options)
|
580
|
+
exit 1
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
def remove(args)
|
585
|
+
options = {}
|
586
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
587
|
+
opts.banner = subcommand_usage("[id]")
|
588
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
589
|
+
opts.footer = "Delete a blueprint." + "\n" +
|
590
|
+
"[id] is required. This is the name or id of a blueprint."
|
591
|
+
end
|
592
|
+
optparse.parse!(args)
|
593
|
+
|
594
|
+
if args.count < 1
|
595
|
+
puts optparse
|
596
|
+
exit 1
|
597
|
+
end
|
598
|
+
|
599
|
+
connect(options)
|
600
|
+
begin
|
601
|
+
blueprint = find_blueprint_by_name_or_id(args[0])
|
602
|
+
exit 1 if blueprint.nil?
|
603
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the blueprint #{blueprint['name']}?")
|
604
|
+
exit
|
605
|
+
end
|
606
|
+
if options[:dry_run]
|
607
|
+
print_dry_run @blueprints_interface.dry.destroy(blueprint['id'])
|
608
|
+
return
|
609
|
+
end
|
610
|
+
json_response = @blueprints_interface.destroy(blueprint['id'])
|
611
|
+
|
612
|
+
if options[:json]
|
613
|
+
print JSON.pretty_generate(json_response)
|
614
|
+
print "\n"
|
615
|
+
else
|
616
|
+
print_green_success "Removed blueprint #{blueprint['name']}"
|
617
|
+
end
|
618
|
+
|
619
|
+
rescue RestClient::Exception => e
|
620
|
+
print_rest_exception(e, options)
|
621
|
+
exit 1
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
def add_instance(args)
|
626
|
+
options = {}
|
627
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
628
|
+
opts.banner = subcommand_usage("[id] [tier] [instance-type]")
|
629
|
+
# opts.on( '-g', '--group GROUP', "Group" ) do |val|
|
630
|
+
# options[:group] = val
|
631
|
+
# end
|
632
|
+
# opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
|
633
|
+
# options[:cloud] = val
|
634
|
+
# end
|
635
|
+
opts.on('--name VALUE', String, "Instance Name") do |val|
|
636
|
+
options[:instance_name] = val
|
637
|
+
end
|
638
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote])
|
639
|
+
opts.footer = "Update a blueprint, adding an instance." + "\n" +
|
640
|
+
"[id] is required. This is the name or id of a blueprint." + "\n" +
|
641
|
+
"[tier] is required and will be prompted for. This is the name of the tier." + "\n" +
|
642
|
+
"[instance-type] is required and will be prompted for. This is the type of instance."
|
643
|
+
end
|
644
|
+
optparse.parse!(args)
|
645
|
+
|
646
|
+
if args.count < 1
|
647
|
+
print_error Morpheus::Terminal.angry_prompt
|
648
|
+
puts_error "#{command_name} add-instance expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
649
|
+
return 1
|
650
|
+
end
|
651
|
+
|
652
|
+
connect(options)
|
653
|
+
|
654
|
+
begin
|
655
|
+
blueprint_name = args[0]
|
656
|
+
tier_name = args[1]
|
657
|
+
instance_type_code = args[2]
|
658
|
+
# we also need consider when there is multiple instances of the same type in
|
659
|
+
# a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
|
660
|
+
|
661
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
662
|
+
return 1 if blueprint.nil?
|
663
|
+
|
664
|
+
if !tier_name
|
665
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tierName', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Enter the name of the tier'}], options[:options])
|
666
|
+
tier_name = v_prompt['tierName']
|
667
|
+
end
|
668
|
+
|
669
|
+
if !instance_type_code
|
670
|
+
instance_type_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => 'Type', 'optionSource' => 'instanceTypes', 'required' => true, 'description' => 'Select Instance Type.'}],options[:options],api_client,{})
|
671
|
+
instance_type_code = instance_type_prompt['type']
|
672
|
+
end
|
673
|
+
instance_type = find_instance_type_by_code(instance_type_code)
|
674
|
+
return 1 if instance_type.nil?
|
675
|
+
|
676
|
+
tier_config = nil
|
677
|
+
instance_config = nil
|
678
|
+
|
679
|
+
blueprint["config"] ||= {}
|
680
|
+
tiers = blueprint["config"]["tiers"]
|
681
|
+
tiers ||= {}
|
682
|
+
# tier identified by name, case sensitive...
|
683
|
+
if !tiers[tier_name]
|
684
|
+
tiers[tier_name] = {}
|
685
|
+
end
|
686
|
+
tier_config = tiers[tier_name]
|
687
|
+
|
688
|
+
tier_config['instances'] ||= []
|
689
|
+
instance_config = tier_config['instances'].find {|it| it["instance"] && it["instance"]["type"] && it["instance"]["type"] == instance_type["code"] }
|
690
|
+
if !instance_config
|
691
|
+
instance_config = {'instance' => {'type' => instance_type['code']} }
|
692
|
+
tier_config['instances'].push(instance_config)
|
693
|
+
end
|
694
|
+
instance_config['instance'] ||= {}
|
695
|
+
|
696
|
+
# just prompts for Instance Name (optional)
|
697
|
+
instance_name = nil
|
698
|
+
if options[:instance_name]
|
699
|
+
instance_name = options[:instance_name]
|
700
|
+
else
|
701
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Instance Name', 'type' => 'text', 'defaultValue' => instance_config['instance']['name']}])
|
702
|
+
instance_name = v_prompt['name'] || ''
|
703
|
+
end
|
704
|
+
|
705
|
+
if instance_name
|
706
|
+
if instance_name.to_s == 'null'
|
707
|
+
instance_config['instance'].delete('name')
|
708
|
+
# instance_config['instance']['name'] = ''
|
709
|
+
else
|
710
|
+
instance_config['instance']['name'] = instance_name
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
# ok, make api request
|
715
|
+
blueprint["config"]["tiers"] = tiers
|
716
|
+
request_payload = {blueprint: blueprint}
|
717
|
+
|
718
|
+
if options[:dry_run]
|
719
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
720
|
+
return 0
|
721
|
+
end
|
722
|
+
|
723
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
724
|
+
|
725
|
+
if options[:json]
|
726
|
+
puts JSON.pretty_generate(json_response)
|
727
|
+
elsif !options[:quiet]
|
728
|
+
print_green_success "Instance added to blueprint #{blueprint['name']}"
|
729
|
+
# prompt for new instance
|
730
|
+
if !options[:no_prompt]
|
731
|
+
if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add a config now?", options.merge({default: true}))
|
732
|
+
# todo: this needs to work by index, because you can have multiple instances of the same type
|
733
|
+
add_instance_config([blueprint['id'], tier_name, instance_type['code']])
|
734
|
+
while ::Morpheus::Cli::OptionTypes::confirm("Add another config?", options.merge({default: false})) do
|
735
|
+
add_instance_config([blueprint['id'], tier_name, instance_type['code']])
|
736
|
+
end
|
737
|
+
else
|
738
|
+
# print details
|
739
|
+
get([blueprint['name']])
|
740
|
+
end
|
741
|
+
end
|
742
|
+
end
|
743
|
+
return 0
|
744
|
+
|
745
|
+
rescue RestClient::Exception => e
|
746
|
+
print_rest_exception(e, options)
|
747
|
+
return 1
|
748
|
+
end
|
749
|
+
|
750
|
+
end
|
751
|
+
|
752
|
+
def add_instance_config(args)
|
753
|
+
options = {}
|
754
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
755
|
+
opts.banner = subcommand_usage("[id] [tier] [instance]")
|
756
|
+
opts.on( '-g', '--group GROUP', "Group" ) do |val|
|
757
|
+
options[:group] = val
|
758
|
+
end
|
759
|
+
opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
|
760
|
+
options[:cloud] = val
|
761
|
+
end
|
762
|
+
opts.on( '-e', '--env ENVIRONMENT', "Environment" ) do |val|
|
763
|
+
options[:environment] = val
|
764
|
+
end
|
765
|
+
opts.on('--name VALUE', String, "Instance Name") do |val|
|
766
|
+
options[:instance_name] = val
|
767
|
+
end
|
768
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote])
|
769
|
+
opts.footer = "Update a blueprint, adding an instance config." + "\n" +
|
770
|
+
"[id] is required. This is the name or id of a blueprint." + "\n" +
|
771
|
+
"[tier] is required. This is the name of the tier." + "\n" +
|
772
|
+
"[instance] is required. This is the type of instance."
|
773
|
+
end
|
774
|
+
optparse.parse!(args)
|
775
|
+
|
776
|
+
if args.count < 3
|
777
|
+
print_error Morpheus::Terminal.angry_prompt
|
778
|
+
puts_error "Wrong number of arguments"
|
779
|
+
puts_error optparse
|
780
|
+
return 1
|
781
|
+
end
|
782
|
+
|
783
|
+
connect(options)
|
784
|
+
|
785
|
+
begin
|
786
|
+
|
787
|
+
blueprint_name = args[0]
|
788
|
+
tier_name = args[1]
|
789
|
+
instance_type_code = args[2]
|
790
|
+
# we also need consider when there is multiple instances of the same type in
|
791
|
+
# a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
|
792
|
+
|
793
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
794
|
+
return 1 if blueprint.nil?
|
795
|
+
|
796
|
+
instance_type = find_instance_type_by_code(instance_type_code)
|
797
|
+
return 1 if instance_type.nil?
|
798
|
+
|
799
|
+
tier_config = nil
|
800
|
+
instance_config = nil
|
801
|
+
|
802
|
+
blueprint["config"] ||= {}
|
803
|
+
tiers = blueprint["config"]["tiers"]
|
804
|
+
tiers ||= {}
|
805
|
+
# tier identified by name, case sensitive...
|
806
|
+
if !tiers[tier_name]
|
807
|
+
tiers[tier_name] = {}
|
808
|
+
end
|
809
|
+
tier_config = tiers[tier_name]
|
810
|
+
|
811
|
+
tier_config['instances'] ||= []
|
812
|
+
instance_config = tier_config['instances'].find {|it| it["instance"] && it["instance"]["type"] && it["instance"]["type"] == instance_type["code"] }
|
813
|
+
if !instance_config
|
814
|
+
instance_config = {'instance' => {'type' => instance_type['code']} }
|
815
|
+
tier_config['instances'].push(instance_config)
|
816
|
+
end
|
817
|
+
instance_config['instance'] ||= {}
|
818
|
+
|
819
|
+
# group prompt
|
820
|
+
|
821
|
+
# use active group by default
|
822
|
+
options[:group] ||= @active_group_id
|
823
|
+
|
824
|
+
|
825
|
+
# available_groups = get_available_groups()
|
826
|
+
# group_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => get_available_groups(), 'required' => true, 'defaultValue' => @active_group_id}],options[:options],@api_client,{})
|
827
|
+
|
828
|
+
# group_id = group_prompt['group']
|
829
|
+
# the_group = find_group_by_name_or_id_for_provisioning(group_id)
|
830
|
+
|
831
|
+
# # cloud prompt
|
832
|
+
# cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'clouds', 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group_id})
|
833
|
+
# cloud_id = cloud_prompt['cloud']
|
834
|
+
|
835
|
+
# look for existing config for group + cloud
|
836
|
+
|
837
|
+
options[:name_required] = false
|
838
|
+
options[:instance_type_code] = instance_type["code"]
|
839
|
+
|
840
|
+
#options[:options].deep_merge!(specific_config)
|
841
|
+
# this provisioning helper method handles all (most) of the parsing and prompting
|
842
|
+
instance_config_payload = prompt_new_instance(options)
|
843
|
+
|
844
|
+
# strip all empty string and nil, would be problematic for update()
|
845
|
+
instance_config_payload.deep_compact!
|
846
|
+
|
847
|
+
# puts "INSTANCE CONFIG YAML:"
|
848
|
+
# puts as_yaml(instance_config_payload)
|
849
|
+
|
850
|
+
selected_environment = instance_config_payload.delete('instanceContext') || instance_config_payload.delete('environment')
|
851
|
+
# groom provision instance payload for template purposes
|
852
|
+
selected_cloud_id = instance_config_payload.delete('zoneId')
|
853
|
+
selected_site = instance_config_payload['instance'].delete('site')
|
854
|
+
selected_site_id = selected_site['id']
|
855
|
+
|
856
|
+
selected_group = find_group_by_name_or_id_for_provisioning(selected_site_id)
|
857
|
+
selected_cloud = find_cloud_by_name_or_id_for_provisioning(selected_group['id'], selected_cloud_id)
|
858
|
+
|
859
|
+
# store config in environments => env => groups => groupname => clouds => cloudname =>
|
860
|
+
current_config = instance_config
|
861
|
+
if selected_environment.to_s != ''
|
862
|
+
instance_config['environments'] ||= {}
|
863
|
+
instance_config['environments'][selected_environment] ||= {}
|
864
|
+
current_config = instance_config['environments'][selected_environment]
|
865
|
+
end
|
866
|
+
|
867
|
+
current_config['groups'] ||= {}
|
868
|
+
current_config['groups'][selected_group['name']] ||= {}
|
869
|
+
current_config['groups'][selected_group['name']]['clouds'] ||= {}
|
870
|
+
current_config['groups'][selected_group['name']]['clouds'][selected_cloud['name']] = instance_config_payload
|
871
|
+
|
872
|
+
# ok, make api request
|
873
|
+
blueprint["config"]["tiers"] = tiers
|
874
|
+
request_payload = {blueprint: blueprint}
|
875
|
+
|
876
|
+
if options[:dry_run]
|
877
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
878
|
+
return 0
|
879
|
+
end
|
880
|
+
|
881
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
882
|
+
|
883
|
+
if options[:json]
|
884
|
+
puts JSON.pretty_generate(json_response)
|
885
|
+
else
|
886
|
+
print_green_success "Instance added to blueprint #{blueprint['name']}"
|
887
|
+
get([blueprint['name']])
|
888
|
+
end
|
889
|
+
return 0
|
890
|
+
|
891
|
+
rescue RestClient::Exception => e
|
892
|
+
print_rest_exception(e, options)
|
893
|
+
return 1
|
894
|
+
end
|
895
|
+
|
896
|
+
end
|
897
|
+
|
898
|
+
def remove_instance_config(args)
|
899
|
+
instance_index = nil
|
900
|
+
options = {}
|
901
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
902
|
+
opts.banner = subcommand_usage("[id] [tier] [instance] -g GROUP -c CLOUD")
|
903
|
+
opts.on( '-g', '--group GROUP', "Group" ) do |val|
|
904
|
+
options[:group] = val
|
905
|
+
end
|
906
|
+
opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
|
907
|
+
options[:cloud] = val
|
908
|
+
end
|
909
|
+
opts.on( '-e', '--env ENV', "Environment" ) do |val|
|
910
|
+
options[:environment] = val
|
911
|
+
end
|
912
|
+
# opts.on( nil, '--index NUMBER', "The index of the instance to remove, starting with 0." ) do |val|
|
913
|
+
# instance_index = val.to_i
|
914
|
+
# end
|
915
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
916
|
+
opts.footer = "Update a blueprint, removing a specified instance config." + "\n" +
|
917
|
+
"[id] is required. This is the name or id of a blueprint." + "\n" +
|
918
|
+
"[tier] is required. This is the name of the tier." + "\n" +
|
919
|
+
"[instance] is required. This is the type of instance." + "\n" +
|
920
|
+
"The config scope is specified with the -g GROUP, -c CLOUD and -e ENV. The -g and -c options are required."
|
921
|
+
end
|
922
|
+
optparse.parse!(args)
|
923
|
+
|
924
|
+
if args.count < 3
|
925
|
+
print_error Morpheus::Terminal.angry_prompt
|
926
|
+
puts_error "Wrong number of arguments"
|
927
|
+
puts_error optparse
|
928
|
+
return 1
|
929
|
+
end
|
930
|
+
if !options[:group]
|
931
|
+
print_error Morpheus::Terminal.angry_prompt
|
932
|
+
puts_error "Missing required argument -g GROUP"
|
933
|
+
puts_error optparse
|
934
|
+
return 1
|
935
|
+
end
|
936
|
+
if !options[:cloud]
|
937
|
+
print_error Morpheus::Terminal.angry_prompt
|
938
|
+
puts_error "Missing required argument -g CLOUD"
|
939
|
+
puts_error optparse
|
940
|
+
return 1
|
941
|
+
end
|
942
|
+
connect(options)
|
943
|
+
|
944
|
+
begin
|
945
|
+
|
946
|
+
blueprint_name = args[0]
|
947
|
+
tier_name = args[1]
|
948
|
+
instance_type_code = args[2]
|
949
|
+
# we also need consider when there is multiple instances of the same type in
|
950
|
+
# a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
|
951
|
+
|
952
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
953
|
+
return 1 if blueprint.nil?
|
954
|
+
|
955
|
+
instance_type = find_instance_type_by_code(instance_type_code)
|
956
|
+
return 1 if instance_type.nil?
|
957
|
+
|
958
|
+
tier_config = nil
|
959
|
+
# instance_config = nil
|
960
|
+
|
961
|
+
blueprint["config"] ||= {}
|
962
|
+
tiers = blueprint["config"]["tiers"]
|
963
|
+
tiers ||= {}
|
964
|
+
# tier identified by name, case sensitive...
|
965
|
+
if !tiers[tier_name]
|
966
|
+
print_red_alert "Tier not found by name #{tier_name}"
|
967
|
+
return 1
|
968
|
+
end
|
969
|
+
tier_config = tiers[tier_name]
|
970
|
+
|
971
|
+
if !tier_config
|
972
|
+
print_red_alert "Tier not found by name #{tier1_name}!"
|
973
|
+
return 1
|
974
|
+
elsif tier_config['instances'].nil? || tier_config['instances'].empty?
|
975
|
+
print_red_alert "Tier #{tier_name} is empty!"
|
976
|
+
return 1
|
977
|
+
end
|
978
|
+
|
979
|
+
matching_indices = []
|
980
|
+
if tier_config['instances']
|
981
|
+
if instance_index
|
982
|
+
matching_indices = [instance_index].compact
|
983
|
+
else
|
984
|
+
tier_config['instances'].each_with_index do |instance_config, index|
|
985
|
+
is_match = instance_config['instance'] && instance_config['instance']['type'] == instance_type['code']
|
986
|
+
if is_match
|
987
|
+
matching_indices << index
|
988
|
+
end
|
989
|
+
end
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
if matching_indices.size == 0
|
994
|
+
print_red_alert "Instance not found by tier: #{tier_name}, type: #{instance_type_code}"
|
995
|
+
return 1
|
996
|
+
elsif matching_indices.size > 1
|
997
|
+
#print_error Morpheus::Terminal.angry_prompt
|
998
|
+
print_red_alert "More than one instance found by tier: #{tier_name}, type: #{instance_type_code}"
|
999
|
+
puts_error "Try using the --index option to identify the instance you wish to remove."
|
1000
|
+
puts_error optparse
|
1001
|
+
return 1
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
# ok, find the specified config
|
1005
|
+
instance_config = tier_config['instances'][matching_indices[0]]
|
1006
|
+
parent_config = nil
|
1007
|
+
current_config = instance_config
|
1008
|
+
delete_key = nil
|
1009
|
+
|
1010
|
+
config_description = "type: #{instance_type['code']}"
|
1011
|
+
config_description << " environment: #{options[:environment]}" if options[:environment]
|
1012
|
+
config_description << " group: #{options[:group]}" if options[:group]
|
1013
|
+
config_description << " cloud: #{options[:cloud]}" if options[:cloud]
|
1014
|
+
config_description = config_description.strip
|
1015
|
+
|
1016
|
+
|
1017
|
+
# find config in environments => env => groups => groupname => clouds => cloudname =>
|
1018
|
+
if options[:environment]
|
1019
|
+
if current_config && current_config['environments'] && current_config['environments'][options[:environment]]
|
1020
|
+
parent_config = current_config['environments']
|
1021
|
+
delete_key = options[:environment]
|
1022
|
+
current_config = parent_config[delete_key]
|
1023
|
+
else
|
1024
|
+
print_red_alert "Instance config not found for scope #{config_description}"
|
1025
|
+
return 1
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
if options[:group]
|
1029
|
+
if current_config && current_config['groups'] && current_config['groups'][options[:group]]
|
1030
|
+
parent_config = current_config['groups']
|
1031
|
+
delete_key = options[:group]
|
1032
|
+
current_config = parent_config[delete_key]
|
1033
|
+
else
|
1034
|
+
print_red_alert "Instance config not found for scope #{config_description}"
|
1035
|
+
return 1
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
if options[:cloud]
|
1039
|
+
if current_config && current_config['clouds'] && current_config['clouds'][options[:cloud]]
|
1040
|
+
parent_config = current_config['clouds']
|
1041
|
+
delete_key = options[:cloud]
|
1042
|
+
current_config = parent_config[delete_key]
|
1043
|
+
else
|
1044
|
+
print_red_alert "Instance config not found for scope #{config_description}"
|
1045
|
+
return 1
|
1046
|
+
end
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# remove it
|
1050
|
+
parent_config.delete(delete_key)
|
1051
|
+
|
1052
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete this instance config #{config_description} ?")
|
1053
|
+
return 9
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
# ok, make api request
|
1057
|
+
blueprint["config"]["tiers"] = tiers
|
1058
|
+
request_payload = {blueprint: blueprint}
|
1059
|
+
|
1060
|
+
if options[:dry_run]
|
1061
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1062
|
+
return
|
1063
|
+
end
|
1064
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1065
|
+
|
1066
|
+
if options[:json]
|
1067
|
+
puts JSON.pretty_generate(json_response)
|
1068
|
+
else
|
1069
|
+
print_green_success "Removed instance from blueprint."
|
1070
|
+
get([blueprint['id']])
|
1071
|
+
end
|
1072
|
+
return 0
|
1073
|
+
|
1074
|
+
rescue RestClient::Exception => e
|
1075
|
+
print_rest_exception(e, options)
|
1076
|
+
exit 1
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
def update_instance(args)
|
1081
|
+
print_red_alert "NOT YET SUPPORTED"
|
1082
|
+
return 5
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def update_instance_config(args)
|
1086
|
+
print_red_alert "NOT YET SUPPORTED"
|
1087
|
+
return 5
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
def remove_instance(args)
|
1091
|
+
instance_index = nil
|
1092
|
+
options = {}
|
1093
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1094
|
+
opts.banner = subcommand_usage("[id] [tier] [instance]")
|
1095
|
+
# opts.on('--index NUMBER', Number, "Identify Instance by index within tier, starting with 0." ) do |val|
|
1096
|
+
# instance_index = val.to_i
|
1097
|
+
# end
|
1098
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
1099
|
+
end
|
1100
|
+
optparse.parse!(args)
|
1101
|
+
|
1102
|
+
if args.count < 3
|
1103
|
+
print_error Morpheus::Terminal.angry_prompt
|
1104
|
+
puts_error "Wrong number of arguments"
|
1105
|
+
puts_error optparse
|
1106
|
+
return 1
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
connect(options)
|
1110
|
+
|
1111
|
+
begin
|
1112
|
+
|
1113
|
+
blueprint_name = args[0]
|
1114
|
+
tier_name = args[1]
|
1115
|
+
instance_identier = args[2]
|
1116
|
+
|
1117
|
+
# instance_type_code = args[2]
|
1118
|
+
# we also need consider when there is multiple instances of the same type in
|
1119
|
+
# a template/tier.. so maybe split instance_type_code as [type-code]:[index].. or...errr
|
1120
|
+
|
1121
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1122
|
+
return 1 if blueprint.nil?
|
1123
|
+
|
1124
|
+
# instance_type = find_instance_type_by_code(instance_type_code)
|
1125
|
+
# return 1 if instance_type.nil?
|
1126
|
+
|
1127
|
+
tier_config = nil
|
1128
|
+
# instance_config = nil
|
1129
|
+
|
1130
|
+
blueprint["config"] ||= {}
|
1131
|
+
tiers = blueprint["config"]["tiers"]
|
1132
|
+
tiers ||= {}
|
1133
|
+
# tier identified by name, case sensitive...
|
1134
|
+
if !tiers[tier_name]
|
1135
|
+
print_red_alert "Tier not found by name #{tier_name}"
|
1136
|
+
return 1
|
1137
|
+
end
|
1138
|
+
tier_config = tiers[tier_name]
|
1139
|
+
|
1140
|
+
if tier_config['instances'].nil? || tier_config['instances'].empty?
|
1141
|
+
print_red_alert "Tier #{tier_name} is empty!"
|
1142
|
+
return 1
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
# find instance
|
1146
|
+
matching_indices = []
|
1147
|
+
if tier_config['instances']
|
1148
|
+
if instance_identier.to_s =~ /\A\d{1,}\Z/
|
1149
|
+
matching_indices = [instance_identier.to_i].compact
|
1150
|
+
else
|
1151
|
+
tier_config['instances'].each_with_index do |instance_config, index|
|
1152
|
+
if instance_config['instance'] && instance_config['instance']['type'] == instance_identier
|
1153
|
+
matching_indices << index
|
1154
|
+
elsif instance_config['instance'] && instance_config['instance']['name'] == instance_identier
|
1155
|
+
matching_indices << index
|
1156
|
+
end
|
1157
|
+
end
|
1158
|
+
end
|
1159
|
+
end
|
1160
|
+
if matching_indices.size == 0
|
1161
|
+
print_red_alert "Instance not found by tier: #{tier_name}, instance: #{instance_identier}"
|
1162
|
+
return 1
|
1163
|
+
elsif matching_indices.size > 1
|
1164
|
+
#print_error Morpheus::Terminal.angry_prompt
|
1165
|
+
print_red_alert "More than one instance matched tier: #{tier_name}, instance: #{instance_identier}"
|
1166
|
+
puts_error "Instance can be identified type, name or index within the tier."
|
1167
|
+
puts_error optparse
|
1168
|
+
return 1
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
# remove it
|
1172
|
+
tier_config['instances'].delete_at(matching_indices[0])
|
1173
|
+
|
1174
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete this instance #{instance_type_code} instance from tier: #{tier_name}?")
|
1175
|
+
return 9
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# ok, make api request
|
1179
|
+
blueprint["config"]["tiers"] = tiers
|
1180
|
+
request_payload = {blueprint: blueprint}
|
1181
|
+
|
1182
|
+
if options[:dry_run]
|
1183
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1184
|
+
return
|
1185
|
+
end
|
1186
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1187
|
+
|
1188
|
+
if options[:json]
|
1189
|
+
puts JSON.pretty_generate(json_response)
|
1190
|
+
else
|
1191
|
+
print_green_success "Removed instance from blueprint."
|
1192
|
+
get([blueprint['id']])
|
1193
|
+
end
|
1194
|
+
return 0
|
1195
|
+
|
1196
|
+
rescue RestClient::Exception => e
|
1197
|
+
print_rest_exception(e, options)
|
1198
|
+
exit 1
|
1199
|
+
end
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def add_tier(args)
|
1203
|
+
options = {}
|
1204
|
+
boot_order = nil
|
1205
|
+
linked_tiers = nil
|
1206
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1207
|
+
opts.banner = subcommand_usage("[id] [tier]")
|
1208
|
+
opts.on('--name VALUE', String, "Tier Name") do |val|
|
1209
|
+
options[:name] = val
|
1210
|
+
end
|
1211
|
+
opts.on('--bootOrder NUMBER', String, "Boot Order" ) do |val|
|
1212
|
+
boot_order = val
|
1213
|
+
end
|
1214
|
+
opts.on('--linkedTiers x,y,z', Array, "Connected Tiers.") do |val|
|
1215
|
+
linked_tiers = val
|
1216
|
+
end
|
1217
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote])
|
1218
|
+
end
|
1219
|
+
optparse.parse!(args)
|
1220
|
+
|
1221
|
+
if args.count < 1
|
1222
|
+
print_error Morpheus::Terminal.angry_prompt
|
1223
|
+
puts_error "#{command_name} add-tier requires argument: [id]\n#{optparse}"
|
1224
|
+
# puts optparse
|
1225
|
+
return 1
|
1226
|
+
end
|
1227
|
+
blueprint_name = args[0]
|
1228
|
+
tier_name = args[1]
|
1229
|
+
|
1230
|
+
connect(options)
|
1231
|
+
|
1232
|
+
begin
|
1233
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1234
|
+
return 1 if blueprint.nil?
|
1235
|
+
|
1236
|
+
blueprint["config"] ||= {}
|
1237
|
+
blueprint["config"]["tiers"] ||= {}
|
1238
|
+
tiers = blueprint["config"]["tiers"]
|
1239
|
+
|
1240
|
+
# prompt new tier
|
1241
|
+
# Name
|
1242
|
+
# {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1, 'description' => 'A unique name for the blueprint.'},
|
1243
|
+
# {'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'displayOrder' => 2, 'description' => 'Boot Order'}
|
1244
|
+
if !tier_name
|
1245
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Enter the name of the tier'}], options[:options])
|
1246
|
+
tier_name = v_prompt['name']
|
1247
|
+
end
|
1248
|
+
# case insensitive match
|
1249
|
+
existing_tier_names = tiers.keys
|
1250
|
+
matching_tier_name = existing_tier_names.find {|k| k.downcase == tier_name.downcase }
|
1251
|
+
if matching_tier_name
|
1252
|
+
# print_red_alert "Tier #{tier_name} already exists"
|
1253
|
+
# return 1
|
1254
|
+
print cyan,"Tier #{tier_name} already exists.",reset,"\n"
|
1255
|
+
return 0
|
1256
|
+
end
|
1257
|
+
# idempotent
|
1258
|
+
if !tiers[tier_name]
|
1259
|
+
tiers[tier_name] = {'instances' => []}
|
1260
|
+
end
|
1261
|
+
tier = tiers[tier_name]
|
1262
|
+
|
1263
|
+
# Boot Order
|
1264
|
+
if !boot_order
|
1265
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'description' => 'Sequence order for starting app instances by tier. 0-N', 'defaultValue' => tier['bootOrder']}], options[:options])
|
1266
|
+
boot_order = v_prompt['bootOrder']
|
1267
|
+
end
|
1268
|
+
if boot_order.to_s == 'null'
|
1269
|
+
tier.delete('bootOrder')
|
1270
|
+
elsif boot_order.to_s != ''
|
1271
|
+
tier['bootOrder'] = boot_order.to_i
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
# Connected Tiers
|
1275
|
+
if !linked_tiers
|
1276
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'linkedTiers', 'fieldLabel' => 'Connected Tiers', 'type' => 'text', 'required' => false, 'description' => 'Names of connected tiers, comma separated', 'defaultValue' => (linked_tiers ? linked_tiers.join(',') : nil)}], options[:options])
|
1277
|
+
linked_tiers = v_prompt['linkedTiers'].to_s.split(',').collect {|it| it.strip }.select {|it| it != ''}
|
1278
|
+
end
|
1279
|
+
if linked_tiers && !linked_tiers.empty?
|
1280
|
+
linked_tiers.each do |other_tier_name|
|
1281
|
+
link_result = link_tiers(tiers, [tier_name, other_tier_name])
|
1282
|
+
# could just re-prompt unless options[:no_prompt]
|
1283
|
+
return 1 if !link_result
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
# ok, make api request
|
1288
|
+
blueprint["config"]["tiers"] = tiers
|
1289
|
+
request_payload = blueprint["config"]
|
1290
|
+
# request_payload = {blueprint: blueprint}
|
1291
|
+
|
1292
|
+
if options[:dry_run]
|
1293
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1294
|
+
return
|
1295
|
+
end
|
1296
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1297
|
+
|
1298
|
+
if options[:json]
|
1299
|
+
puts JSON.pretty_generate(json_response)
|
1300
|
+
elsif !options[:quiet]
|
1301
|
+
print_green_success "Added tier #{tier_name}"
|
1302
|
+
# prompt for new instance
|
1303
|
+
if !options[:no_prompt]
|
1304
|
+
if ::Morpheus::Cli::OptionTypes::confirm("Would you like to add an instance now?", options.merge({default: true}))
|
1305
|
+
add_instance([blueprint['id'], tier_name])
|
1306
|
+
while ::Morpheus::Cli::OptionTypes::confirm("Add another instance now?", options.merge({default: false})) do
|
1307
|
+
add_instance([blueprint['id'], tier_name])
|
1308
|
+
end
|
1309
|
+
# if !add_instance_result
|
1310
|
+
# end
|
1311
|
+
end
|
1312
|
+
end
|
1313
|
+
# print details
|
1314
|
+
get([blueprint['name']])
|
1315
|
+
end
|
1316
|
+
return 0
|
1317
|
+
rescue RestClient::Exception => e
|
1318
|
+
print_rest_exception(e, options)
|
1319
|
+
exit 1
|
1320
|
+
end
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
def update_tier(args)
|
1324
|
+
options = {}
|
1325
|
+
new_tier_name = nil
|
1326
|
+
boot_order = nil
|
1327
|
+
linked_tiers = nil
|
1328
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1329
|
+
opts.banner = subcommand_usage("[id] [tier]")
|
1330
|
+
opts.on('--name VALUE', String, "Tier Name") do |val|
|
1331
|
+
new_tier_name = val
|
1332
|
+
end
|
1333
|
+
opts.on('--bootOrder NUMBER', String, "Boot Order" ) do |val|
|
1334
|
+
boot_order = val
|
1335
|
+
end
|
1336
|
+
opts.on('--linkedTiers x,y,z', Array, "Connected Tiers") do |val|
|
1337
|
+
linked_tiers = val
|
1338
|
+
end
|
1339
|
+
build_common_options(opts, options, [:options, :json, :dry_run, :remote])
|
1340
|
+
end
|
1341
|
+
optparse.parse!(args)
|
1342
|
+
|
1343
|
+
if args.count != 2
|
1344
|
+
print_error Morpheus::Terminal.angry_prompt
|
1345
|
+
puts_error "#{command_name} update-tier expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
1346
|
+
return 1
|
1347
|
+
end
|
1348
|
+
blueprint_name = args[0]
|
1349
|
+
tier_name = args[1]
|
1350
|
+
|
1351
|
+
connect(options)
|
1352
|
+
|
1353
|
+
begin
|
1354
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1355
|
+
return 1 if blueprint.nil?
|
1356
|
+
|
1357
|
+
blueprint["config"] ||= {}
|
1358
|
+
blueprint["config"]["tiers"] ||= {}
|
1359
|
+
tiers = blueprint["config"]["tiers"]
|
1360
|
+
|
1361
|
+
if !tiers[tier_name]
|
1362
|
+
print_red_alert "Tier not found by name #{tier_name}"
|
1363
|
+
return 1
|
1364
|
+
end
|
1365
|
+
tier = tiers[tier_name]
|
1366
|
+
|
1367
|
+
|
1368
|
+
if options[:no_prompt]
|
1369
|
+
if !(new_tier_name || boot_order || linked_tiers)
|
1370
|
+
print_error Morpheus::Terminal.angry_prompt
|
1371
|
+
puts_error "#{command_name} update-tier requires an option to update.\n#{optparse}"
|
1372
|
+
return 1
|
1373
|
+
end
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
# prompt update tier
|
1377
|
+
# Name
|
1378
|
+
if !new_tier_name
|
1379
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Tier Name', 'type' => 'text', 'required' => true, 'description' => 'Rename the tier', 'defaultValue' => tier_name}], options[:options])
|
1380
|
+
new_tier_name = v_prompt['name']
|
1381
|
+
end
|
1382
|
+
if new_tier_name && new_tier_name != tier_name
|
1383
|
+
old_tier_name = tier_name
|
1384
|
+
if tiers[new_tier_name]
|
1385
|
+
print_red_alert "A tier named #{tier_name} already exists."
|
1386
|
+
return 1
|
1387
|
+
end
|
1388
|
+
tier = tiers.delete(tier_name)
|
1389
|
+
tiers[new_tier_name] = tier
|
1390
|
+
# Need to fix all the linkedTiers
|
1391
|
+
tiers.each do |k, v|
|
1392
|
+
if v['linkedTiers'] && v['linkedTiers'].include?(tier_name)
|
1393
|
+
v['linkedTiers'] = v['linkedTiers'].map {|it| it == tier_name ? new_tier_name : it }
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
# old_tier_name = tier_name
|
1397
|
+
tier_name = new_tier_name
|
1398
|
+
end
|
1399
|
+
|
1400
|
+
# Boot Order
|
1401
|
+
if !boot_order
|
1402
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootOrder', 'fieldLabel' => 'Boot Order', 'type' => 'text', 'required' => false, 'description' => 'Sequence order for starting app instances by tier. 0-N', 'defaultValue' => tier['bootOrder']}], options[:options])
|
1403
|
+
boot_order = v_prompt['bootOrder']
|
1404
|
+
end
|
1405
|
+
if boot_order.to_s == 'null'
|
1406
|
+
tier.delete('bootOrder')
|
1407
|
+
elsif boot_order.to_s != ''
|
1408
|
+
tier['bootOrder'] = boot_order.to_i
|
1409
|
+
end
|
1410
|
+
|
1411
|
+
# Connected Tiers
|
1412
|
+
if !linked_tiers
|
1413
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'linkedTiers', 'fieldLabel' => 'Connected Tiers', 'type' => 'text', 'required' => false, 'description' => 'Names of connected tiers, comma separated', 'defaultValue' => (tier['linkedTiers'] ? tier['linkedTiers'].join(',') : nil)}], options[:options])
|
1414
|
+
linked_tiers = v_prompt['linkedTiers'].to_s.split(',').collect {|it| it.strip }.select {|it| it != ''}
|
1415
|
+
end
|
1416
|
+
current_linked_tiers = tier['linkedTiers'] || []
|
1417
|
+
if linked_tiers && linked_tiers != current_linked_tiers
|
1418
|
+
remove_tiers = current_linked_tiers - linked_tiers
|
1419
|
+
remove_tiers.each do |other_tier_name|
|
1420
|
+
unlink_result = unlink_tiers(tiers, [tier_name, other_tier_name])
|
1421
|
+
# could just re-prompt unless options[:no_prompt]
|
1422
|
+
return 1 if !unlink_result
|
1423
|
+
end
|
1424
|
+
add_tiers = linked_tiers - current_linked_tiers
|
1425
|
+
add_tiers.each do |other_tier_name|
|
1426
|
+
link_result = link_tiers(tiers, [tier_name, other_tier_name])
|
1427
|
+
# could just re-prompt unless options[:no_prompt]
|
1428
|
+
return 1 if !link_result
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
# ok, make api request
|
1433
|
+
blueprint["config"]["tiers"] = tiers
|
1434
|
+
request_payload = blueprint["config"]
|
1435
|
+
# request_payload = {blueprint: blueprint}
|
1436
|
+
|
1437
|
+
if options[:dry_run]
|
1438
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1439
|
+
return
|
1440
|
+
end
|
1441
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1442
|
+
|
1443
|
+
if options[:json]
|
1444
|
+
puts JSON.pretty_generate(json_response)
|
1445
|
+
elsif !options[:quiet]
|
1446
|
+
print_green_success "Updated tier #{tier_name}"
|
1447
|
+
get([blueprint['id']])
|
1448
|
+
end
|
1449
|
+
return 0
|
1450
|
+
rescue RestClient::Exception => e
|
1451
|
+
print_rest_exception(e, options)
|
1452
|
+
exit 1
|
1453
|
+
end
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
def remove_tier(args)
|
1457
|
+
options = {}
|
1458
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1459
|
+
opts.banner = subcommand_usage("[id] [tier]")
|
1460
|
+
build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
|
1461
|
+
end
|
1462
|
+
optparse.parse!(args)
|
1463
|
+
|
1464
|
+
if args.count < 2
|
1465
|
+
print_error Morpheus::Terminal.angry_prompt
|
1466
|
+
puts_error "#{command_name} remove-tier expects 2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
1467
|
+
return 1
|
1468
|
+
end
|
1469
|
+
blueprint_name = args[0]
|
1470
|
+
tier_name = args[1]
|
1471
|
+
|
1472
|
+
connect(options)
|
1473
|
+
|
1474
|
+
begin
|
1475
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1476
|
+
return 1 if blueprint.nil?
|
1477
|
+
|
1478
|
+
blueprint["config"] ||= {}
|
1479
|
+
blueprint["config"]["tiers"] ||= {}
|
1480
|
+
tiers = blueprint["config"]["tiers"]
|
1481
|
+
|
1482
|
+
if !tiers[tier_name]
|
1483
|
+
# print_red_alert "Tier not found by name #{tier_name}"
|
1484
|
+
# return 1
|
1485
|
+
print cyan,"Tier #{tier_name} does not exist.",reset,"\n"
|
1486
|
+
return 0
|
1487
|
+
end
|
1488
|
+
|
1489
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the tier #{tier_name}?")
|
1490
|
+
exit
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
# remove it
|
1494
|
+
tiers.delete(tier_name)
|
1495
|
+
|
1496
|
+
# ok, make api request
|
1497
|
+
blueprint["config"]["tiers"] = tiers
|
1498
|
+
request_payload = blueprint["config"]
|
1499
|
+
# request_payload = {blueprint: blueprint}
|
1500
|
+
|
1501
|
+
if options[:dry_run]
|
1502
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1503
|
+
return
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1507
|
+
|
1508
|
+
|
1509
|
+
if options[:json]
|
1510
|
+
print JSON.pretty_generate(json_response)
|
1511
|
+
print "\n"
|
1512
|
+
else
|
1513
|
+
print_green_success "Removed tier #{tier_name}"
|
1514
|
+
get([blueprint['name']])
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
rescue RestClient::Exception => e
|
1518
|
+
print_rest_exception(e, options)
|
1519
|
+
exit 1
|
1520
|
+
end
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
def connect_tiers(args)
|
1524
|
+
options = {}
|
1525
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1526
|
+
opts.banner = subcommand_usage("[id] [Tier1] [Tier2]")
|
1527
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
1528
|
+
end
|
1529
|
+
optparse.parse!(args)
|
1530
|
+
|
1531
|
+
if args.count < 3
|
1532
|
+
print_error Morpheus::Terminal.angry_prompt
|
1533
|
+
puts_error "#{command_name} connect-tiers expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
1534
|
+
# puts optparse
|
1535
|
+
return 1
|
1536
|
+
end
|
1537
|
+
blueprint_name = args[0]
|
1538
|
+
tier1_name = args[1]
|
1539
|
+
tier2_name = args[2]
|
1540
|
+
|
1541
|
+
connect(options)
|
1542
|
+
|
1543
|
+
begin
|
1544
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1545
|
+
return 1 if blueprint.nil?
|
1546
|
+
|
1547
|
+
blueprint["config"] ||= {}
|
1548
|
+
tiers = blueprint["config"]["tiers"]
|
1549
|
+
|
1550
|
+
if !tiers || tiers.keys.size == 0
|
1551
|
+
error_msg = "Blueprint #{blueprint['name']} has no tiers."
|
1552
|
+
# print_red_alert "Blueprint #{blueprint['name']} has no tiers."
|
1553
|
+
# raise_command_error "Blueprint #{blueprint['name']} has no tiers."
|
1554
|
+
print_error Morpheus::Terminal.angry_prompt
|
1555
|
+
puts_error "Blueprint #{blueprint['name']} has no tiers."
|
1556
|
+
return 1
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
connect_tiers = []
|
1560
|
+
tier1 = tiers[tier1_name]
|
1561
|
+
tier2 = tiers[tier2_name]
|
1562
|
+
# uhh support N args
|
1563
|
+
|
1564
|
+
if tier1.nil?
|
1565
|
+
print_red_alert "Tier not found by name #{tier1_name}!"
|
1566
|
+
return 1
|
1567
|
+
end
|
1568
|
+
|
1569
|
+
if tier2.nil?
|
1570
|
+
print_red_alert "Tier not found by name #{tier2_name}!"
|
1571
|
+
return 1
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
tier1["linkedTiers"] = tier1["linkedTiers"] || []
|
1575
|
+
tier2["linkedTiers"] = tier2["linkedTiers"] || []
|
1576
|
+
|
1577
|
+
found_edge = tier1["linkedTiers"].include?(tier2_name) || tier2["linkedTiers"].include?(tier1_name)
|
1578
|
+
|
1579
|
+
if found_edge
|
1580
|
+
puts cyan,"Tiers #{tier1_name} and #{tier2_name} are already connected.",reset
|
1581
|
+
return 0
|
1582
|
+
end
|
1583
|
+
|
1584
|
+
# ok to be connect the tiers
|
1585
|
+
# note: the ui doesn't hook up both sides eh?
|
1586
|
+
|
1587
|
+
if !tier1["linkedTiers"].include?(tier2_name)
|
1588
|
+
tier1["linkedTiers"].push(tier2_name)
|
1589
|
+
end
|
1590
|
+
|
1591
|
+
if !tier2["linkedTiers"].include?(tier1_name)
|
1592
|
+
tier2["linkedTiers"].push(tier1_name)
|
1593
|
+
end
|
1594
|
+
|
1595
|
+
# ok, make api request
|
1596
|
+
blueprint["config"]["tiers"] = tiers
|
1597
|
+
request_payload = blueprint["config"]
|
1598
|
+
# request_payload = {blueprint: blueprint}
|
1599
|
+
|
1600
|
+
if options[:dry_run]
|
1601
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1602
|
+
return
|
1603
|
+
end
|
1604
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1605
|
+
|
1606
|
+
|
1607
|
+
if options[:json]
|
1608
|
+
print JSON.pretty_generate(json_response)
|
1609
|
+
print "\n"
|
1610
|
+
else
|
1611
|
+
print_green_success "Connected 2 tiers for blueprint #{blueprint['name']}"
|
1612
|
+
get([blueprint['name']])
|
1613
|
+
end
|
1614
|
+
|
1615
|
+
rescue RestClient::Exception => e
|
1616
|
+
print_rest_exception(e, options)
|
1617
|
+
exit 1
|
1618
|
+
end
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
def disconnect_tiers(args)
|
1622
|
+
options = {}
|
1623
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1624
|
+
opts.banner = subcommand_usage("[id] [Tier1] [Tier2]")
|
1625
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
1626
|
+
end
|
1627
|
+
optparse.parse!(args)
|
1628
|
+
|
1629
|
+
if args.count < 3
|
1630
|
+
print_error Morpheus::Terminal.angry_prompt
|
1631
|
+
puts_error "#{command_name} disconnect-tiers expects 3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
|
1632
|
+
# puts optparse
|
1633
|
+
return 1
|
1634
|
+
end
|
1635
|
+
blueprint_name = args[0]
|
1636
|
+
tier1_name = args[1]
|
1637
|
+
tier2_name = args[2]
|
1638
|
+
|
1639
|
+
connect(options)
|
1640
|
+
|
1641
|
+
begin
|
1642
|
+
blueprint = find_blueprint_by_name_or_id(blueprint_name)
|
1643
|
+
return 1 if blueprint.nil?
|
1644
|
+
|
1645
|
+
blueprint["config"] ||= {}
|
1646
|
+
tiers = blueprint["config"]["tiers"]
|
1647
|
+
|
1648
|
+
if !tiers || tiers.keys.size == 0
|
1649
|
+
# print_red_alert "Blueprint #{blueprint['name']} has no tiers."
|
1650
|
+
# raise_command_error "Blueprint #{blueprint['name']} has no tiers."
|
1651
|
+
print_error Morpheus::Terminal.angry_prompt
|
1652
|
+
puts_error "Blueprint #{blueprint['name']} has no tiers."
|
1653
|
+
return 1
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
connect_tiers = []
|
1657
|
+
tier1 = tiers[tier1_name]
|
1658
|
+
tier2 = tiers[tier2_name]
|
1659
|
+
# uhh support N args
|
1660
|
+
|
1661
|
+
if tier1.nil?
|
1662
|
+
print_red_alert "Tier not found by name #{tier1_name}!"
|
1663
|
+
return 1
|
1664
|
+
end
|
1665
|
+
|
1666
|
+
if tier2.nil?
|
1667
|
+
print_red_alert "Tier not found by name #{tier2_name}!"
|
1668
|
+
return 1
|
1669
|
+
end
|
1670
|
+
|
1671
|
+
tier1["linkedTiers"] = tier1["linkedTiers"] || []
|
1672
|
+
tier2["linkedTiers"] = tier2["linkedTiers"] || []
|
1673
|
+
|
1674
|
+
found_edge = tier1["linkedTiers"].include?(tier2_name) || tier2["linkedTiers"].include?(tier1_name)
|
1675
|
+
|
1676
|
+
if found_edge
|
1677
|
+
puts cyan,"Tiers #{tier1_name} and #{tier2_name} are not connected.",reset
|
1678
|
+
return 0
|
1679
|
+
end
|
1680
|
+
|
1681
|
+
# remove links
|
1682
|
+
tier1["linkedTiers"] = tier1["linkedTiers"].reject {|it| it == tier2_name }
|
1683
|
+
tier2["linkedTiers"] = tier2["linkedTiers"].reject {|it| it == tier1_name }
|
1684
|
+
|
1685
|
+
# ok, make api request
|
1686
|
+
blueprint["config"]["tiers"] = tiers
|
1687
|
+
request_payload = blueprint["config"]
|
1688
|
+
# request_payload = {blueprint: blueprint}
|
1689
|
+
|
1690
|
+
if options[:dry_run]
|
1691
|
+
print_dry_run @blueprints_interface.dry.update(blueprint['id'], request_payload)
|
1692
|
+
return
|
1693
|
+
end
|
1694
|
+
json_response = @blueprints_interface.update(blueprint['id'], request_payload)
|
1695
|
+
|
1696
|
+
|
1697
|
+
if options[:json]
|
1698
|
+
print JSON.pretty_generate(json_response)
|
1699
|
+
print "\n"
|
1700
|
+
else
|
1701
|
+
print_green_success "Connected 2 tiers for blueprint #{blueprint['name']}"
|
1702
|
+
get([blueprint['name']])
|
1703
|
+
end
|
1704
|
+
|
1705
|
+
rescue RestClient::Exception => e
|
1706
|
+
print_rest_exception(e, options)
|
1707
|
+
exit 1
|
1708
|
+
end
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
def available_tiers(args)
|
1712
|
+
options = {}
|
1713
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1714
|
+
opts.banner = subcommand_usage()
|
1715
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
1716
|
+
end
|
1717
|
+
optparse.parse!(args)
|
1718
|
+
connect(options)
|
1719
|
+
params = {}
|
1720
|
+
|
1721
|
+
begin
|
1722
|
+
if options[:dry_run]
|
1723
|
+
print_dry_run @blueprints_interface.dry.list_tiers(params)
|
1724
|
+
return
|
1725
|
+
end
|
1726
|
+
json_response = @blueprints_interface.list_tiers(params)
|
1727
|
+
tiers = json_response["tiers"] # just a list of names
|
1728
|
+
if options[:json]
|
1729
|
+
puts JSON.pretty_generate(json_response)
|
1730
|
+
else
|
1731
|
+
print_h1 "Available Tiers"
|
1732
|
+
if tiers.empty?
|
1733
|
+
print yellow,"No tiers found.",reset,"\n"
|
1734
|
+
else
|
1735
|
+
# rows = tiers.collect do |tier|
|
1736
|
+
# {
|
1737
|
+
# id: tier['id'],
|
1738
|
+
# name: tier['name'],
|
1739
|
+
# }
|
1740
|
+
# end
|
1741
|
+
# print cyan
|
1742
|
+
# tp rows, [:name]
|
1743
|
+
print cyan
|
1744
|
+
tiers.each do |tier_name|
|
1745
|
+
puts tier_name
|
1746
|
+
end
|
1747
|
+
end
|
1748
|
+
print reset,"\n"
|
1749
|
+
end
|
1750
|
+
return 0
|
1751
|
+
rescue RestClient::Exception => e
|
1752
|
+
print_rest_exception(e, options)
|
1753
|
+
exit 1
|
1754
|
+
end
|
1755
|
+
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
private
|
1759
|
+
|
1760
|
+
|
1761
|
+
def add_blueprint_option_types(connected=true)
|
1762
|
+
[
|
1763
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
|
1764
|
+
{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
|
1765
|
+
{'fieldName' => 'category', 'fieldLabel' => 'Category', 'type' => 'text', 'required' => false, 'displayOrder' => 3},
|
1766
|
+
#{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => true}
|
1767
|
+
]
|
1768
|
+
end
|
1769
|
+
|
1770
|
+
def update_blueprint_option_types(connected=true)
|
1771
|
+
list = add_blueprint_option_types(connected)
|
1772
|
+
list = list.reject {|it| ["group"].include? it['fieldName'] }
|
1773
|
+
list.each {|it| it['required'] = false }
|
1774
|
+
list
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
def find_blueprint_by_name_or_id(val)
|
1778
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
1779
|
+
return find_blueprint_by_id(val)
|
1780
|
+
else
|
1781
|
+
return find_blueprint_by_name(val)
|
1782
|
+
end
|
1783
|
+
end
|
1784
|
+
|
1785
|
+
def find_blueprint_by_id(id)
|
1786
|
+
begin
|
1787
|
+
json_response = @blueprints_interface.get(id.to_i)
|
1788
|
+
return json_response['blueprint']
|
1789
|
+
rescue RestClient::Exception => e
|
1790
|
+
if e.response && e.response.code == 404
|
1791
|
+
print_red_alert "Blueprint not found by id #{id}"
|
1792
|
+
else
|
1793
|
+
raise e
|
1794
|
+
end
|
1795
|
+
end
|
1796
|
+
end
|
1797
|
+
|
1798
|
+
def find_blueprint_by_name(name)
|
1799
|
+
blueprints = @blueprints_interface.list({name: name.to_s})['blueprints']
|
1800
|
+
if blueprints.empty?
|
1801
|
+
print_red_alert "Blueprint not found by name #{name}"
|
1802
|
+
return nil
|
1803
|
+
elsif blueprints.size > 1
|
1804
|
+
print_red_alert "#{blueprints.size} blueprints by name #{name}"
|
1805
|
+
print_blueprints_table(blueprints, {color: red})
|
1806
|
+
print reset,"\n"
|
1807
|
+
return nil
|
1808
|
+
else
|
1809
|
+
return blueprints[0]
|
1810
|
+
end
|
1811
|
+
end
|
1812
|
+
|
1813
|
+
def find_group_by_name(name)
|
1814
|
+
group_results = @groups_interface.get(name)
|
1815
|
+
if group_results['groups'].empty?
|
1816
|
+
print_red_alert "Group not found by name #{name}"
|
1817
|
+
return nil
|
1818
|
+
end
|
1819
|
+
return group_results['groups'][0]
|
1820
|
+
end
|
1821
|
+
|
1822
|
+
def find_cloud_by_name(group_id, name)
|
1823
|
+
option_results = @options_interface.options_for_source('clouds',{groupId: group_id})
|
1824
|
+
match = option_results['data'].find { |grp| grp['value'].to_s == name.to_s || grp['name'].downcase == name.downcase}
|
1825
|
+
if match.nil?
|
1826
|
+
print_red_alert "Cloud not found by name #{name}"
|
1827
|
+
return nil
|
1828
|
+
else
|
1829
|
+
return match['value']
|
1830
|
+
end
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
def print_blueprints_table(blueprints, opts={})
|
1834
|
+
table_color = opts[:color] || cyan
|
1835
|
+
rows = blueprints.collect do |blueprint|
|
1836
|
+
#instance_type_names = (blueprint['instanceTypes'] || []).collect {|it| it['name'] }.join(', ')
|
1837
|
+
instance_type_names = []
|
1838
|
+
# if blueprint['config'] && blueprint['config']["tiers"]
|
1839
|
+
# blueprint['config']["tiers"]
|
1840
|
+
# end
|
1841
|
+
{
|
1842
|
+
id: blueprint['id'],
|
1843
|
+
name: blueprint['name'],
|
1844
|
+
description: blueprint['description'],
|
1845
|
+
category: blueprint['category'],
|
1846
|
+
tiers_summary: format_blueprint_tiers_summary(blueprint)
|
1847
|
+
}
|
1848
|
+
end
|
1849
|
+
|
1850
|
+
term_width = current_terminal_width()
|
1851
|
+
tiers_col_width = 60
|
1852
|
+
if term_width > 190
|
1853
|
+
tiers_col_width += 130
|
1854
|
+
end
|
1855
|
+
columns = [
|
1856
|
+
:id,
|
1857
|
+
:name,
|
1858
|
+
:description,
|
1859
|
+
:category,
|
1860
|
+
{:tiers_summary => {:display_name => "TIERS", :max_width => tiers_col_width} }
|
1861
|
+
]
|
1862
|
+
if opts[:include_fields]
|
1863
|
+
columns = opts[:include_fields]
|
1864
|
+
end
|
1865
|
+
print table_color
|
1866
|
+
print as_pretty_table(rows, columns, opts)
|
1867
|
+
print reset
|
1868
|
+
end
|
1869
|
+
|
1870
|
+
def generate_id(len=16)
|
1871
|
+
id = ""
|
1872
|
+
len.times { id << (1 + rand(9)).to_s }
|
1873
|
+
id
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
def format_blueprint_tiers_summary(blueprint)
|
1877
|
+
# don't use colors here, or cell truncation will not work
|
1878
|
+
str = ""
|
1879
|
+
if blueprint["config"] && blueprint["config"]["tiers"]
|
1880
|
+
tier_descriptions = blueprint["config"]["tiers"].collect do |tier_name, tier_config|
|
1881
|
+
# maybe do Tier Name (instance, instance2)
|
1882
|
+
instance_blurbs = []
|
1883
|
+
if tier_config["instances"]
|
1884
|
+
tier_config["instances"].each do |instance_config|
|
1885
|
+
if instance_config["instance"] && instance_config["instance"]["type"]
|
1886
|
+
# only have type: code in the config, rather not name fetch remotely right now..
|
1887
|
+
# instance_blurbs << instance_config["instance"]["type"]
|
1888
|
+
instance_name = instance_config["instance"]["name"] || ""
|
1889
|
+
instance_type_code = instance_config["instance"]["type"]
|
1890
|
+
instances_str = "#{instance_type_code}"
|
1891
|
+
if instance_name.to_s != ""
|
1892
|
+
instances_str << " - #{instance_name}"
|
1893
|
+
end
|
1894
|
+
begin
|
1895
|
+
config_list = parse_scoped_instance_configs(instance_config)
|
1896
|
+
if config_list.size == 0
|
1897
|
+
instances_str << " (No configs)"
|
1898
|
+
elsif config_list.size == 1
|
1899
|
+
# configs_str = config_list.collect {|it|
|
1900
|
+
# str = ""
|
1901
|
+
# it[:scope].to_s.inspect
|
1902
|
+
# }.join(", ")
|
1903
|
+
the_config = config_list[0]
|
1904
|
+
scope_str = the_config[:scope].collect {|k,v| v.to_s }.join("/")
|
1905
|
+
instances_str << " (#{scope_str})"
|
1906
|
+
else
|
1907
|
+
instances_str << " (#{config_list.size} configs)"
|
1908
|
+
end
|
1909
|
+
rescue => err
|
1910
|
+
puts_error "Failed to parse instance scoped instance configs: #{err.class} #{err.message}"
|
1911
|
+
raise err
|
1912
|
+
end
|
1913
|
+
instance_blurbs << instances_str
|
1914
|
+
end
|
1915
|
+
end
|
1916
|
+
end
|
1917
|
+
if instance_blurbs.size > 0
|
1918
|
+
tier_name + ": #{instance_blurbs.join(', ')}"
|
1919
|
+
else
|
1920
|
+
tier_name + ": (empty)"
|
1921
|
+
end
|
1922
|
+
end
|
1923
|
+
str += tier_descriptions.compact.join(", ")
|
1924
|
+
end
|
1925
|
+
str
|
1926
|
+
end
|
1927
|
+
|
1928
|
+
def print_blueprint_details(blueprint)
|
1929
|
+
print cyan
|
1930
|
+
description_cols = {
|
1931
|
+
"ID" => 'id',
|
1932
|
+
"Name" => 'name',
|
1933
|
+
"Description" => 'description',
|
1934
|
+
"Category" => 'category',
|
1935
|
+
"Image" => lambda {|it| it['config'] ? (it['config']['image'] == '/assets/apps/template.png' ? '(default)' : it['config']['image']) : '' },
|
1936
|
+
"Visibility" => 'visibility'
|
1937
|
+
}
|
1938
|
+
print_description_list(description_cols, blueprint)
|
1939
|
+
# print_h2 "Tiers"
|
1940
|
+
if blueprint["config"] && blueprint["config"]["tiers"] && blueprint["config"]["tiers"].keys.size != 0
|
1941
|
+
print cyan
|
1942
|
+
#puts as_yaml(blueprint["config"]["tiers"])
|
1943
|
+
blueprint["config"]["tiers"].each do |tier_name, tier_config|
|
1944
|
+
# print_h2 "Tier: #{tier_name}"
|
1945
|
+
print_h2 tier_name
|
1946
|
+
# puts " Instances:"
|
1947
|
+
if tier_config['instances'] && tier_config['instances'].size != 0
|
1948
|
+
# puts as_yaml(tier)
|
1949
|
+
tier_config['instances'].each_with_index do |instance_config, instance_index|
|
1950
|
+
instance_name = instance_config["instance"]["name"] || ""
|
1951
|
+
instance_type_code = ""
|
1952
|
+
if instance_config["instance"]["type"]
|
1953
|
+
instance_type_code = instance_config["instance"]["type"]
|
1954
|
+
end
|
1955
|
+
instance_bullet = ""
|
1956
|
+
# instance_bullet += "#{green} - #{bold}#{instance_type_code}#{reset}"
|
1957
|
+
instance_bullet += "#{green}#{bold}#{instance_type_code}#{reset}"
|
1958
|
+
if instance_name.to_s != ""
|
1959
|
+
instance_bullet += "#{green} - #{instance_name}#{reset}"
|
1960
|
+
end
|
1961
|
+
puts instance_bullet
|
1962
|
+
# print "\n"
|
1963
|
+
begin
|
1964
|
+
config_list = parse_scoped_instance_configs(instance_config)
|
1965
|
+
print cyan
|
1966
|
+
if config_list.size > 0
|
1967
|
+
print "\n"
|
1968
|
+
if config_list.size == 1
|
1969
|
+
puts " Config:"
|
1970
|
+
else
|
1971
|
+
puts " Configs (#{config_list.size}):"
|
1972
|
+
end
|
1973
|
+
config_list.each do |config_obj|
|
1974
|
+
# puts " = #{config_obj[:scope].inspect}"
|
1975
|
+
config_scope = config_obj[:scope]
|
1976
|
+
scoped_instance_config = config_obj[:config]
|
1977
|
+
config_description = ""
|
1978
|
+
config_items = []
|
1979
|
+
if config_scope[:environment]
|
1980
|
+
config_items << {label: "Environment", value: config_scope[:environment]}
|
1981
|
+
end
|
1982
|
+
if config_scope[:group]
|
1983
|
+
config_items << {label: "Group", value: config_scope[:group]}
|
1984
|
+
end
|
1985
|
+
if config_scope[:cloud]
|
1986
|
+
config_items << {label: "Cloud", value: config_scope[:cloud]}
|
1987
|
+
end
|
1988
|
+
# if scoped_instance_config['plan'] && scoped_instance_config['plan']['code']
|
1989
|
+
# config_items << {label: "Plan", value: scoped_instance_config['plan']['code']}
|
1990
|
+
# end
|
1991
|
+
config_description = config_items.collect {|item| "#{item[:label]}: #{item[:value]}"}.join(", ")
|
1992
|
+
puts " * #{config_description}"
|
1993
|
+
end
|
1994
|
+
else
|
1995
|
+
print white," Instance has no configs, see `app-templates add-instance-config \"#{blueprint['name']}\" \"#{tier_name}\" \"#{instance_type_code}\"`",reset,"\n"
|
1996
|
+
end
|
1997
|
+
rescue => err
|
1998
|
+
#puts_error "Failed to parse instance scoped instance configs for blueprint #{blueprint['id']} #{blueprint['name']} Exception: #{err.class} #{err.message}"
|
1999
|
+
end
|
2000
|
+
print "\n"
|
2001
|
+
#puts as_yaml(instance_config)
|
2002
|
+
# todo: iterate over
|
2003
|
+
# instance_config["groups"][group_name]["clouds"][cloud_name]
|
2004
|
+
end
|
2005
|
+
|
2006
|
+
print cyan
|
2007
|
+
if tier_config['bootOrder']
|
2008
|
+
puts "Boot Order: #{tier_config['bootOrder']}"
|
2009
|
+
end
|
2010
|
+
if tier_config['linkedTiers'] && !tier_config['linkedTiers'].empty?
|
2011
|
+
puts "Connected Tiers: #{tier_config['linkedTiers'].join(', ')}"
|
2012
|
+
end
|
2013
|
+
|
2014
|
+
else
|
2015
|
+
print white," Tier is empty, see `app-templates add-instance \"#{blueprint['name']}\" \"#{tier_name}\"`",reset,"\n"
|
2016
|
+
end
|
2017
|
+
# print "\n"
|
2018
|
+
|
2019
|
+
end
|
2020
|
+
# print "\n"
|
2021
|
+
|
2022
|
+
else
|
2023
|
+
print white,"\nTemplate is empty, see `app-templates add-tier \"#{blueprint['name']}\"`",reset,"\n"
|
2024
|
+
end
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
# this parses the environments => groups => clouds tree structure
|
2028
|
+
# and returns a list of objects like {scope: {group:'thegroup'}, config: Map}
|
2029
|
+
# this would be be better as a recursive function, brute forced for now.
|
2030
|
+
def parse_scoped_instance_configs(instance_config)
|
2031
|
+
config_list = []
|
2032
|
+
if instance_config['environments'] && instance_config['environments'].keys.size > 0
|
2033
|
+
instance_config['environments'].each do |env_name, env_config|
|
2034
|
+
if env_config['groups']
|
2035
|
+
env_config['groups'].each do |group_name, group_config|
|
2036
|
+
if group_config['clouds'] && !group_config['clouds'].empty?
|
2037
|
+
group_config['clouds'].each do |cloud_name, cloud_config|
|
2038
|
+
config_list << {config: cloud_config, scope: {environment: env_name, group: group_name, cloud: cloud_name}}
|
2039
|
+
end
|
2040
|
+
end
|
2041
|
+
if (!group_config['clouds'] || group_config['clouds'].empty?)
|
2042
|
+
config_list << {config: group_config, scope: {environment: env_name, group: group_name}}
|
2043
|
+
end
|
2044
|
+
end
|
2045
|
+
end
|
2046
|
+
if env_config['clouds'] && !env_config['clouds'].empty?
|
2047
|
+
env_config['clouds'].each do |cloud_name, cloud_config|
|
2048
|
+
config_list << {config: cloud_config, scope: {environment: env_name, cloud: cloud_name}}
|
2049
|
+
end
|
2050
|
+
end
|
2051
|
+
if (!env_config['groups'] || env_config['groups'].empty?) && (!env_config['clouds'] || env_config['clouds'].empty?)
|
2052
|
+
config_list << {config: env_config, scope: {environment: env_name}}
|
2053
|
+
end
|
2054
|
+
end
|
2055
|
+
end
|
2056
|
+
if instance_config['groups']
|
2057
|
+
instance_config['groups'].each do |group_name, group_config|
|
2058
|
+
if group_config['clouds'] && !group_config['clouds'].empty?
|
2059
|
+
group_config['clouds'].each do |cloud_name, cloud_config|
|
2060
|
+
config_list << {config: cloud_config, scope: {group: group_name, cloud: cloud_name}}
|
2061
|
+
end
|
2062
|
+
end
|
2063
|
+
if (!group_config['clouds'] || group_config['clouds'].empty?)
|
2064
|
+
config_list << {config: group_config, scope: {group: group_name}}
|
2065
|
+
end
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
if instance_config['clouds']
|
2069
|
+
instance_config['clouds'].each do |cloud_name, cloud_config|
|
2070
|
+
config_list << {config: cloud_config, scope: {cloud: cloud_name}}
|
2071
|
+
end
|
2072
|
+
end
|
2073
|
+
return config_list
|
2074
|
+
end
|
2075
|
+
|
2076
|
+
def link_tiers(tiers, tier_names)
|
2077
|
+
# tiers = blueprint["config"]["tiers"]
|
2078
|
+
tier_names = [tier_names].flatten.collect {|it| it }.compact.uniq
|
2079
|
+
if !tiers
|
2080
|
+
print_red_alert "No tiers found for template"
|
2081
|
+
return false
|
2082
|
+
end
|
2083
|
+
|
2084
|
+
existing_tier_names = tiers.keys
|
2085
|
+
matching_tier_names = tier_names.map {|tier_name|
|
2086
|
+
existing_tier_names.find {|k| k.downcase == tier_name.downcase }
|
2087
|
+
}.compact
|
2088
|
+
if matching_tier_names.size != tier_names.size
|
2089
|
+
print_red_alert "Template does not contain tiers: '#{tier_names}'"
|
2090
|
+
return false
|
2091
|
+
end
|
2092
|
+
matching_tier_names.each do |tier_name|
|
2093
|
+
tier = tiers[tier_name]
|
2094
|
+
tier['linkedTiers'] ||= []
|
2095
|
+
other_tier_names = matching_tier_names.select {|it| tier_name != it}
|
2096
|
+
other_tier_names.each do |other_tier_name|
|
2097
|
+
if !tier['linkedTiers'].include?(other_tier_name)
|
2098
|
+
tier['linkedTiers'].push(other_tier_name)
|
2099
|
+
end
|
2100
|
+
end
|
2101
|
+
end
|
2102
|
+
return true
|
2103
|
+
end
|
2104
|
+
|
2105
|
+
def unlink_tiers(tiers, tier_names)
|
2106
|
+
# tiers = blueprint["config"]["tiers"]
|
2107
|
+
tier_names = [tier_names].flatten.collect {|it| it }.compact.uniq
|
2108
|
+
if !tiers
|
2109
|
+
print_red_alert "No tiers found for template"
|
2110
|
+
return false
|
2111
|
+
end
|
2112
|
+
|
2113
|
+
existing_tier_names = tiers.keys
|
2114
|
+
matching_tier_names = tier_names.map {|tier_name|
|
2115
|
+
existing_tier_names.find {|k| k.downcase == tier_name.downcase }
|
2116
|
+
}.compact
|
2117
|
+
if matching_tier_names.size != tier_names.size
|
2118
|
+
print_red_alert "Template does not contain tiers: '#{tier_names}'"
|
2119
|
+
return false
|
2120
|
+
end
|
2121
|
+
matching_tier_names.each do |tier_name|
|
2122
|
+
tier = tiers[tier_name]
|
2123
|
+
tier['linkedTiers'] ||= []
|
2124
|
+
other_tier_names = matching_tier_names.select {|it| tier_name != it}
|
2125
|
+
other_tier_names.each do |other_tier_name|
|
2126
|
+
if tier['linkedTiers'].include?(other_tier_name)
|
2127
|
+
tier['linkedTiers'] = tier['linkedTiers'].reject {|it| it == other_tier_name }
|
2128
|
+
end
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
return true
|
2132
|
+
end
|
2133
|
+
|
2134
|
+
end
|