morpheus-cli 5.5.2.2 → 5.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/Dockerfile +1 -1
- data/README.md +57 -4
- data/Rakefile +9 -0
- data/bin/morpheus +4 -4
- data/lib/morpheus/api/api_client.rb +8 -2
- data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
- data/lib/morpheus/api/archive_files_interface.rb +3 -3
- data/lib/morpheus/api/clients_interface.rb +2 -2
- data/lib/morpheus/api/clusters_interface.rb +8 -1
- data/lib/morpheus/api/containers_interface.rb +29 -16
- data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
- data/lib/morpheus/api/doc_interface.rb +8 -6
- data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
- data/lib/morpheus/api/health_interface.rb +1 -1
- data/lib/morpheus/api/image_builder_interface.rb +3 -3
- data/lib/morpheus/api/instances_interface.rb +25 -0
- data/lib/morpheus/api/logs_interface.rb +2 -4
- data/lib/morpheus/api/monitoring_interface.rb +6 -6
- data/lib/morpheus/api/packages_interface.rb +1 -1
- data/lib/morpheus/api/reports_interface.rb +1 -1
- data/lib/morpheus/api/servers_interface.rb +9 -1
- data/lib/morpheus/api/storage_providers_interface.rb +2 -2
- data/lib/morpheus/api/virtual_images_interface.rb +1 -1
- data/lib/morpheus/api.rb +2 -0
- data/lib/morpheus/benchmarking.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +69 -36
- data/lib/morpheus/cli/cli_registry.rb +19 -10
- data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
- data/lib/morpheus/cli/commands/apps.rb +1 -1
- data/lib/morpheus/cli/commands/archives_command.rb +25 -33
- data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
- data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/cat_command.rb +1 -1
- data/lib/morpheus/cli/commands/catalog_item_types_command.rb +12 -12
- data/lib/morpheus/cli/commands/clouds.rb +3 -3
- data/lib/morpheus/cli/commands/clusters.rb +154 -3
- data/lib/morpheus/cli/commands/containers_command.rb +398 -253
- data/lib/morpheus/cli/commands/deployments.rb +1 -1
- data/lib/morpheus/cli/commands/deploys.rb +9 -9
- data/lib/morpheus/cli/commands/doc.rb +15 -16
- data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
- data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
- data/lib/morpheus/cli/commands/groups.rb +2 -2
- data/lib/morpheus/cli/commands/health_command.rb +4 -4
- data/lib/morpheus/cli/commands/hosts.rb +43 -5
- data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
- data/lib/morpheus/cli/commands/instances.rb +419 -148
- data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
- data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
- data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
- data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
- data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
- data/lib/morpheus/cli/commands/login.rb +1 -1
- data/lib/morpheus/cli/commands/man_command.rb +32 -18
- data/lib/morpheus/cli/commands/packages_command.rb +11 -11
- data/lib/morpheus/cli/commands/plugins.rb +1 -1
- data/lib/morpheus/cli/commands/policies_command.rb +4 -4
- data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
- data/lib/morpheus/cli/commands/remote.rb +1 -1
- data/lib/morpheus/cli/commands/reports_command.rb +3 -3
- data/lib/morpheus/cli/commands/security_groups.rb +1 -1
- data/lib/morpheus/cli/commands/shell.rb +40 -62
- data/lib/morpheus/cli/commands/snapshots.rb +3 -5
- data/lib/morpheus/cli/commands/source_command.rb +8 -16
- data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
- data/lib/morpheus/cli/commands/tasks.rb +2 -2
- data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
- data/lib/morpheus/cli/commands/view.rb +5 -1
- data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +4 -4
- data/lib/morpheus/cli/commands/whoami.rb +2 -2
- data/lib/morpheus/cli/credentials.rb +30 -8
- data/lib/morpheus/cli/dot_file.rb +8 -15
- data/lib/morpheus/cli/error_handler.rb +16 -0
- data/lib/morpheus/cli/errors.rb +8 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
- data/lib/morpheus/cli/mixins/rest_command.rb +18 -18
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +12 -12
- data/lib/morpheus/cli/option_parser.rb +5 -1
- data/lib/morpheus/cli/option_types.rb +59 -12
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli.rb +26 -16
- data/lib/morpheus/ext/rest_client.rb +3 -2
- data/lib/morpheus/formatters.rb +1 -1
- data/lib/morpheus/logging.rb +4 -4
- data/lib/morpheus/morpkg.rb +4 -4
- data/lib/morpheus/rest_client.rb +2 -2
- data/lib/morpheus/routes.rb +2 -2
- data/lib/morpheus/terminal.rb +65 -16
- data/lib/morpheus.rb +1 -1
- data/morpheus-cli.gemspec +1 -0
- data/test/api/containers_interface_test.rb +68 -0
- data/test/api/doc_interface_test.rb +35 -0
- data/test/api/instances_interface_test.rb +22 -0
- data/test/api/whoami_interface_test.rb +14 -0
- data/test/cli/access_token_test.rb +36 -0
- data/test/cli/auth_test.rb +82 -0
- data/test/cli/cli_test.rb +48 -0
- data/test/cli/containers_test.rb +92 -0
- data/test/cli/doc_test.rb +35 -0
- data/test/cli/help_test.rb +25 -0
- data/test/cli/instances_test.rb +36 -0
- data/test/cli/man_test.rb +14 -0
- data/test/cli/remote_test.rb +89 -0
- data/test/cli/roles_test.rb +34 -0
- data/test/cli/shell_test.rb +81 -0
- data/test/cli/version_test.rb +23 -0
- data/test/cli/view_test.rb +55 -0
- data/test/cli/whoami_test.rb +17 -0
- data/test/morpheus_test.rb +16 -0
- data/test/test_case.rb +338 -0
- data/test/test_config.rb +137 -0
- data/test/test_data_helper.rb +97 -0
- metadata +61 -3
@@ -260,7 +260,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
260
260
|
end
|
261
261
|
|
262
262
|
def update(args)
|
263
|
-
|
263
|
+
payload, options = {}, {}
|
264
264
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
265
265
|
opts.banner = subcommand_usage("[blueprint] [options]")
|
266
266
|
build_option_type_options(opts, options, update_blueprint_option_types(false))
|
@@ -283,7 +283,6 @@ class Morpheus::Cli::BlueprintsCommand
|
|
283
283
|
blueprint = find_blueprint_by_name_or_id(args[0])
|
284
284
|
return 1 if blueprint.nil?
|
285
285
|
payload = {}
|
286
|
-
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
|
287
286
|
if options[:payload]
|
288
287
|
payload = options[:payload]
|
289
288
|
payload.deep_merge!(parse_passed_options(options))
|
@@ -341,7 +340,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
341
340
|
end
|
342
341
|
|
343
342
|
def update_permissions(args)
|
344
|
-
|
343
|
+
payload, options = {}, {}
|
345
344
|
group_access_all = nil
|
346
345
|
group_access_list = nil
|
347
346
|
group_defaults_list = nil
|
@@ -453,7 +452,6 @@ class Morpheus::Cli::BlueprintsCommand
|
|
453
452
|
|
454
453
|
|
455
454
|
def upload_image(args)
|
456
|
-
image_type_name = nil
|
457
455
|
options = {}
|
458
456
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
459
457
|
opts.banner = subcommand_usage("[blueprint] [file]")
|
@@ -889,7 +887,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
889
887
|
end
|
890
888
|
|
891
889
|
def remove_instance_config(args)
|
892
|
-
instance_index = nil
|
890
|
+
#instance_index = nil
|
893
891
|
options = {}
|
894
892
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
895
893
|
opts.banner = subcommand_usage("[blueprint] [tier] [instance] -g GROUP -c CLOUD")
|
@@ -1081,7 +1079,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1081
1079
|
end
|
1082
1080
|
|
1083
1081
|
def remove_instance(args)
|
1084
|
-
instance_index = nil
|
1082
|
+
#instance_index = nil
|
1085
1083
|
options = {}
|
1086
1084
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1087
1085
|
opts.banner = subcommand_usage("[blueprint] [tier] [instance]")
|
@@ -1402,7 +1400,6 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1402
1400
|
new_tier_name = v_prompt['name']
|
1403
1401
|
end
|
1404
1402
|
if new_tier_name && new_tier_name != tier_name
|
1405
|
-
old_tier_name = tier_name
|
1406
1403
|
if tiers[new_tier_name]
|
1407
1404
|
print_red_alert "A tier named #{tier_name} already exists."
|
1408
1405
|
return 1
|
@@ -1415,7 +1412,6 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1415
1412
|
v['linkedTiers'] = v['linkedTiers'].map {|it| it == tier_name ? new_tier_name : it }
|
1416
1413
|
end
|
1417
1414
|
end
|
1418
|
-
# old_tier_name = tier_name
|
1419
1415
|
tier_name = new_tier_name
|
1420
1416
|
end
|
1421
1417
|
|
@@ -1577,15 +1573,9 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1577
1573
|
tiers = blueprint["config"]["tiers"]
|
1578
1574
|
|
1579
1575
|
if !tiers || tiers.keys.size == 0
|
1580
|
-
|
1581
|
-
# print_red_alert "Blueprint #{blueprint['name']} has no tiers."
|
1582
|
-
# raise_command_error "Blueprint #{blueprint['name']} has no tiers."
|
1583
|
-
print_error Morpheus::Terminal.angry_prompt
|
1584
|
-
puts_error "Blueprint #{blueprint['name']} has no tiers."
|
1585
|
-
return 1
|
1576
|
+
raise_command_error "Blueprint #{blueprint['name']} has no tiers."
|
1586
1577
|
end
|
1587
1578
|
|
1588
|
-
connect_tiers = []
|
1589
1579
|
tier1 = tiers[tier1_name]
|
1590
1580
|
tier2 = tiers[tier2_name]
|
1591
1581
|
# uhh support N args
|
@@ -1682,7 +1672,6 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1682
1672
|
return 1
|
1683
1673
|
end
|
1684
1674
|
|
1685
|
-
connect_tiers = []
|
1686
1675
|
tier1 = tiers[tier1_name]
|
1687
1676
|
tier2 = tiers[tier2_name]
|
1688
1677
|
# uhh support N args
|
@@ -1859,7 +1848,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1859
1848
|
@available_blueprint_types = results['types'].collect {|it|
|
1860
1849
|
{"name" => (it["name"] || it["code"]), "value" => (it["code"] || it["value"])}
|
1861
1850
|
}
|
1862
|
-
rescue RestClient::Exception
|
1851
|
+
rescue RestClient::Exception
|
1863
1852
|
# older version
|
1864
1853
|
@available_blueprint_types = [{"name" => "Morpheus", "value" => "morpheus"}, {"name" => "Terraform", "value" => "terraform"}, {"name" => "CloudFormation", "value" => "cloudFormation"}, {"name" => "ARM template", "value" => "arm"}]
|
1865
1854
|
end
|
@@ -1935,7 +1924,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
1935
1924
|
table_color = opts[:color] || cyan
|
1936
1925
|
rows = blueprints.collect do |blueprint|
|
1937
1926
|
#instance_type_names = (blueprint['instanceTypes'] || []).collect {|it| it['name'] }.join(', ')
|
1938
|
-
instance_type_names = []
|
1927
|
+
#instance_type_names = []
|
1939
1928
|
# if blueprint['config'] && blueprint['config']["tiers"]
|
1940
1929
|
# blueprint['config']["tiers"]
|
1941
1930
|
# end
|
@@ -2086,7 +2075,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
2086
2075
|
end
|
2087
2076
|
sorted_tiers = tiers.collect {|k,v| [k,v] }.sort {|a,b| a[1]['tierIndex'] <=> b[1]['tierIndex'] }
|
2088
2077
|
sorted_tiers.each do |tier_obj|
|
2089
|
-
tier_name = tier_obj[0]
|
2078
|
+
#tier_name = tier_obj[0]
|
2090
2079
|
tier_config = tier_obj[1]
|
2091
2080
|
if tier_config && tier_config['instances']
|
2092
2081
|
tier_config['instances'].each_with_index do |instance_config, instance_index|
|
@@ -2145,7 +2134,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
2145
2134
|
config_list.each do |config_obj|
|
2146
2135
|
# puts " = #{config_obj[:scope].inspect}"
|
2147
2136
|
config_scope = config_obj[:scope]
|
2148
|
-
scoped_instance_config = config_obj[:config]
|
2137
|
+
#scoped_instance_config = config_obj[:config]
|
2149
2138
|
config_description = ""
|
2150
2139
|
config_items = []
|
2151
2140
|
if config_scope[:environment]
|
@@ -2167,7 +2156,7 @@ class Morpheus::Cli::BlueprintsCommand
|
|
2167
2156
|
print white," Instance has no configs, use `blueprints add-instance-config \"#{blueprint['name']}\" \"#{tier_name}\" \"#{instance_name.to_s.empty? ? instance_type_code : instance_name}\"`",reset,"\n"
|
2168
2157
|
end
|
2169
2158
|
rescue => err
|
2170
|
-
|
2159
|
+
Morpheus::Logging::DarkPrinter.puts "Failed to parse instance config at index #{instance_index}. Exception: #{err.class} #{err.message}" if Morpheus::Logging.debug?
|
2171
2160
|
end
|
2172
2161
|
print "\n"
|
2173
2162
|
#puts as_yaml(instance_config)
|
@@ -173,7 +173,7 @@ class Morpheus::Cli::BootScriptsCommand
|
|
173
173
|
params = Morpheus::Cli::OptionTypes.prompt(my_options, options[:options], @api_client, options[:params])
|
174
174
|
script_file = params.delete('file')
|
175
175
|
if script_file
|
176
|
-
if !File.
|
176
|
+
if !File.exist?(script_file)
|
177
177
|
print_red_alert "File not found: #{script_file}"
|
178
178
|
return 1
|
179
179
|
end
|
@@ -244,7 +244,7 @@ class Morpheus::Cli::BootScriptsCommand
|
|
244
244
|
# params = Morpheus::Cli::OptionTypes.prompt(my_options, options[:options], @api_client, options[:params])
|
245
245
|
script_file = params.delete('file')
|
246
246
|
if script_file
|
247
|
-
if !File.
|
247
|
+
if !File.exist?(script_file)
|
248
248
|
print_red_alert "File not found: #{script_file}"
|
249
249
|
return 1
|
250
250
|
end
|
@@ -25,7 +25,7 @@ class Morpheus::Cli::CatCommand
|
|
25
25
|
arg_files = args
|
26
26
|
arg_files.each do |arg_file|
|
27
27
|
arg_file = File.expand_path(arg_file)
|
28
|
-
if !File.
|
28
|
+
if !File.exist?(arg_file)
|
29
29
|
print_error Morpheus::Terminal.angry_prompt
|
30
30
|
puts_error "#{command_name}: file not found: '#{arg_file}'"
|
31
31
|
#print_red_alert "morpheus cat: file not found: '#{arg_file}'"
|
@@ -248,7 +248,7 @@ EOT
|
|
248
248
|
logo_file = 'null' # clear it
|
249
249
|
else
|
250
250
|
filename = File.expand_path(filename)
|
251
|
-
if !File.
|
251
|
+
if !File.exist?(filename)
|
252
252
|
raise_command_error "File not found: #{filename}"
|
253
253
|
end
|
254
254
|
logo_file = File.new(filename, 'rb')
|
@@ -261,7 +261,7 @@ EOT
|
|
261
261
|
dark_logo_file = 'null' # clear it
|
262
262
|
else
|
263
263
|
filename = File.expand_path(filename)
|
264
|
-
if !File.
|
264
|
+
if !File.exist?(filename)
|
265
265
|
raise_command_error "File not found: #{filename}"
|
266
266
|
end
|
267
267
|
dark_logo_file = File.new(filename, 'rb')
|
@@ -271,7 +271,7 @@ EOT
|
|
271
271
|
options[:config_file] = val.to_s
|
272
272
|
file_content = nil
|
273
273
|
full_filename = File.expand_path(options[:config_file])
|
274
|
-
if File.
|
274
|
+
if File.exist?(full_filename)
|
275
275
|
file_content = File.read(full_filename)
|
276
276
|
else
|
277
277
|
print_red_alert "File not found: #{full_filename}"
|
@@ -281,7 +281,7 @@ EOT
|
|
281
281
|
config_map = parse_result[:data]
|
282
282
|
if config_map.nil?
|
283
283
|
# todo: bubble up JSON.parse error message
|
284
|
-
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:
|
284
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
|
285
285
|
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
286
286
|
else
|
287
287
|
params['config'] = config_map
|
@@ -345,7 +345,7 @@ EOT
|
|
345
345
|
config_map = parse_result[:data]
|
346
346
|
if config_map.nil?
|
347
347
|
# todo: bubble up JSON.parse error message
|
348
|
-
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:
|
348
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
|
349
349
|
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
350
350
|
else
|
351
351
|
params['config'] = config_map
|
@@ -405,7 +405,7 @@ EOT
|
|
405
405
|
logo_file = 'null' # clear it
|
406
406
|
else
|
407
407
|
filename = File.expand_path(filename)
|
408
|
-
if !File.
|
408
|
+
if !File.exist?(filename)
|
409
409
|
raise_command_error "File not found: #{filename}"
|
410
410
|
end
|
411
411
|
logo_file = File.new(filename, 'rb')
|
@@ -418,7 +418,7 @@ EOT
|
|
418
418
|
dark_logo_file = 'null' # clear it
|
419
419
|
else
|
420
420
|
filename = File.expand_path(filename)
|
421
|
-
if !File.
|
421
|
+
if !File.exist?(filename)
|
422
422
|
raise_command_error "File not found: #{filename}"
|
423
423
|
end
|
424
424
|
dark_logo_file = File.new(filename, 'rb')
|
@@ -428,7 +428,7 @@ EOT
|
|
428
428
|
options[:config_file] = val.to_s
|
429
429
|
file_content = nil
|
430
430
|
full_filename = File.expand_path(options[:config_file])
|
431
|
-
if File.
|
431
|
+
if File.exist?(full_filename)
|
432
432
|
file_content = File.read(full_filename)
|
433
433
|
else
|
434
434
|
print_red_alert "File not found: #{full_filename}"
|
@@ -438,7 +438,7 @@ EOT
|
|
438
438
|
config_map = parse_result[:data]
|
439
439
|
if config_map.nil?
|
440
440
|
# todo: bubble up JSON.parse error message
|
441
|
-
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:
|
441
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
|
442
442
|
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
443
443
|
else
|
444
444
|
params['config'] = config_map
|
@@ -495,7 +495,7 @@ EOT
|
|
495
495
|
config_map = parse_result[:data]
|
496
496
|
if config_map.nil?
|
497
497
|
# todo: bubble up JSON.parse error message
|
498
|
-
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:
|
498
|
+
raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:error]}"
|
499
499
|
#raise_command_error "Failed to parse config as valid YAML or JSON."
|
500
500
|
else
|
501
501
|
params['config'] = config_map
|
@@ -564,7 +564,7 @@ EOT
|
|
564
564
|
logo_file = 'null' # clear it
|
565
565
|
else
|
566
566
|
filename = File.expand_path(filename)
|
567
|
-
if !File.
|
567
|
+
if !File.exist?(filename)
|
568
568
|
raise_command_error "File not found: #{filename}"
|
569
569
|
end
|
570
570
|
logo_file = File.new(filename, 'rb')
|
@@ -603,7 +603,7 @@ EOT
|
|
603
603
|
dark_logo_file = 'null' # clear it
|
604
604
|
else
|
605
605
|
filename = File.expand_path(filename)
|
606
|
-
if !File.
|
606
|
+
if !File.exist?(filename)
|
607
607
|
raise_command_error "File not found: #{filename}"
|
608
608
|
end
|
609
609
|
dark_logo_file = File.new(filename, 'rb')
|
@@ -891,7 +891,7 @@ class Morpheus::Cli::Clouds
|
|
891
891
|
build_option_type_options(opts, options, update_wiki_page_option_types)
|
892
892
|
opts.on('--file FILE', "File containing the wiki content. This can be used instead of --content") do |filename|
|
893
893
|
full_filename = File.expand_path(filename)
|
894
|
-
if File.
|
894
|
+
if File.exist?(full_filename)
|
895
895
|
params['content'] = File.read(full_filename)
|
896
896
|
else
|
897
897
|
print_red_alert "File not found: #{full_filename}"
|
@@ -987,7 +987,7 @@ EOT
|
|
987
987
|
logo_file = 'null' # clear it
|
988
988
|
else
|
989
989
|
filename = File.expand_path(filename)
|
990
|
-
if !File.
|
990
|
+
if !File.exist?(filename)
|
991
991
|
print_red_alert "File not found: #{filename}"
|
992
992
|
exit 1
|
993
993
|
end
|
@@ -1037,7 +1037,7 @@ EOT
|
|
1037
1037
|
dark_logo_file = 'null' # clear it
|
1038
1038
|
else
|
1039
1039
|
filename = File.expand_path(filename)
|
1040
|
-
if !File.
|
1040
|
+
if !File.exist?(filename)
|
1041
1041
|
print_red_alert "File not found: #{filename}"
|
1042
1042
|
exit 1
|
1043
1043
|
end
|
@@ -23,6 +23,7 @@ class Morpheus::Cli::Clusters
|
|
23
23
|
register_subcommands :update_permissions
|
24
24
|
register_subcommands :api_config, :view_api_token, :view_kube_config
|
25
25
|
register_subcommands :wiki, :update_wiki
|
26
|
+
register_subcommands :apply_template
|
26
27
|
|
27
28
|
def connect(opts)
|
28
29
|
@api_client = establish_remote_appliance_connection(opts)
|
@@ -42,6 +43,7 @@ class Morpheus::Cli::Clusters
|
|
42
43
|
@user_groups_interface = @api_client.user_groups
|
43
44
|
@accounts_interface = @api_client.accounts
|
44
45
|
@logs_interface = @api_client.logs
|
46
|
+
@execution_request_interface = @api_client.execution_request
|
45
47
|
#@active_security_group = ::Morpheus::Cli::SecurityGroups.load_security_group_file
|
46
48
|
end
|
47
49
|
|
@@ -3242,7 +3244,7 @@ class Morpheus::Cli::Clusters
|
|
3242
3244
|
build_option_type_options(opts, options, update_wiki_page_option_types)
|
3243
3245
|
opts.on('--file FILE', "File containing the wiki content. This can be used instead of --content") do |filename|
|
3244
3246
|
full_filename = File.expand_path(filename)
|
3245
|
-
if File.
|
3247
|
+
if File.exist?(full_filename)
|
3246
3248
|
params['content'] = File.read(full_filename)
|
3247
3249
|
else
|
3248
3250
|
print_red_alert "File not found: #{full_filename}"
|
@@ -3562,6 +3564,125 @@ class Morpheus::Cli::Clusters
|
|
3562
3564
|
end
|
3563
3565
|
end
|
3564
3566
|
|
3567
|
+
def apply_template(args)
|
3568
|
+
options = {}
|
3569
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
3570
|
+
opts.banner = subcommand_usage( "[cluster] --specTemplate --serviceUrl")
|
3571
|
+
opts.on("--specTemplate [TEXT]", String, "Name or ID of desired Spec Template to apply to cluster") do |val|
|
3572
|
+
options[:specTemplate] = val.to_s
|
3573
|
+
end
|
3574
|
+
opts.on("--serviceUrl [TEXT]", String, "Url of template to apply to Cluster") do |val|
|
3575
|
+
options[:serviceUrl] = val.to_s
|
3576
|
+
end
|
3577
|
+
opts.on("--specYaml [TEXT]", String, "Yaml to apply to Cluster") do |val|
|
3578
|
+
options[:specYaml] = val.to_s
|
3579
|
+
end
|
3580
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
3581
|
+
opts.footer = "Apply a Template to a Cluster.\n" +
|
3582
|
+
"[cluster] is required. This is the name or id of an existing cluster."
|
3583
|
+
end
|
3584
|
+
|
3585
|
+
optparse.parse!(args)
|
3586
|
+
if args.count != 1
|
3587
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
3588
|
+
end
|
3589
|
+
connect(options)
|
3590
|
+
|
3591
|
+
begin
|
3592
|
+
payload = nil
|
3593
|
+
cluster = nil
|
3594
|
+
|
3595
|
+
if options[:payload]
|
3596
|
+
payload = options[:payload]
|
3597
|
+
# support -O OPTION switch on top of --payload
|
3598
|
+
if options[:options]
|
3599
|
+
payload['cluster'] ||= {}
|
3600
|
+
payload['cluster'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
|
3601
|
+
end
|
3602
|
+
|
3603
|
+
if !payload['cluster'].empty?
|
3604
|
+
cluster = find_cluster_by_name_or_id(payload['cluster']['id'] || payload['cluster']['name'])
|
3605
|
+
end
|
3606
|
+
else
|
3607
|
+
cluster = find_cluster_by_name_or_id(args[0])
|
3608
|
+
cluster_payload = {}
|
3609
|
+
cluster_payload['specTemplate'] = options[:specTemplate] if !options[:specTemplate].empty?
|
3610
|
+
cluster_payload['serviceUrl'] = options[:serviceUrl] if !options[:serviceUrl].empty?
|
3611
|
+
cluster_payload['specYaml'] = options[:specYaml] if !options[:specYaml].empty?
|
3612
|
+
payload = cluster_payload
|
3613
|
+
end
|
3614
|
+
|
3615
|
+
if !cluster
|
3616
|
+
print_red_alert "No clusters available for update"
|
3617
|
+
exit 1
|
3618
|
+
end
|
3619
|
+
|
3620
|
+
if cluster_payload.empty?
|
3621
|
+
type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => "Type", 'selectOptions' => apply_temp_options, 'required' => true, 'description' => 'Choose type of template being used.'}])['type']
|
3622
|
+
if type == 'specTemplate'
|
3623
|
+
cluster_payload['specTemplate'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'specTemplate', 'type' => 'select', 'fieldLabel' => "Spec Template", 'selectOptions' => available_kube_templates, 'required' => true, 'description' => 'Choose a template.'}], options[:options])['specTemplate']
|
3624
|
+
elsif type == 'yaml'
|
3625
|
+
file_params = Morpheus::Cli::OptionTypes.file_content_prompt({'fieldName' => 'source', 'fieldLabel' => 'File Content', 'type' => 'file-content', 'required' => true}, {'source' => {'source' => 'local'}}, nil, {})
|
3626
|
+
cluster_payload['specYaml'] = file_params['content']
|
3627
|
+
else
|
3628
|
+
cluster_payload['specUrl'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'specUrl', 'type' => 'text', 'fieldLabel' => 'Spec Url', 'required' => true, 'description' => 'Url of template.'}])['specUrl']
|
3629
|
+
end
|
3630
|
+
end
|
3631
|
+
|
3632
|
+
if options[:dry_run]
|
3633
|
+
print_dry_run @clusters_interface.dry.apply_template(cluster['id'], cluster_payload)
|
3634
|
+
return
|
3635
|
+
end
|
3636
|
+
|
3637
|
+
json_response = @clusters_interface.apply_template(cluster['id'], cluster_payload)
|
3638
|
+
if options[:json]
|
3639
|
+
print JSON.pretty_generate(json_response)
|
3640
|
+
print "\n"
|
3641
|
+
elsif json_response['msg'] != nil
|
3642
|
+
print_red_alert "There was an error #{json_response['msg']}"
|
3643
|
+
else
|
3644
|
+
print_green_success 'Template applied to Cluster. Check Execution Request for results'
|
3645
|
+
json_response = @execution_request_interface.get(json_response['executionId'], {})
|
3646
|
+
|
3647
|
+
if json_response['executionRequest'] && json_response['executionRequest']['errorMessage']
|
3648
|
+
print_red_alert "There was an error: #{json_response['executionRequest']['errorMessage']}"
|
3649
|
+
print_red_alert "execution request id: #{json_response['executionRequest']['uniqueId']}"
|
3650
|
+
else
|
3651
|
+
execution_request = json_response['executionRequest']
|
3652
|
+
print_h1 "Execution Request Details"
|
3653
|
+
print cyan
|
3654
|
+
description_cols = {
|
3655
|
+
#"ID" => lambda {|it| it['id'] },
|
3656
|
+
"Unique ID" => lambda {|it| it['uniqueId'] },
|
3657
|
+
"Server ID" => lambda {|it| it['serverId'] },
|
3658
|
+
"Instance ID" => lambda {|it| it['instanceId'] },
|
3659
|
+
"Container ID" => lambda {|it| it['containerId'] },
|
3660
|
+
"Expires At" => lambda {|it| format_local_dt it['expiresAt'] },
|
3661
|
+
"Exit Code" => lambda {|it| it['exitCode'] },
|
3662
|
+
"Status" => lambda {|it| format_execution_request_status(it) },
|
3663
|
+
#"Created By" => lambda {|it| it['createdById'] },
|
3664
|
+
#"Subdomain" => lambda {|it| it['subdomain'] },
|
3665
|
+
}
|
3666
|
+
description_cols.delete("Server ID") if execution_request['serverId'].nil?
|
3667
|
+
description_cols.delete("Instance ID") if execution_request['instanceId'].nil?
|
3668
|
+
description_cols.delete("Container ID") if execution_request['containerId'].nil?
|
3669
|
+
description_cols.delete("Exit Code") if execution_request['exitCode'].nil?
|
3670
|
+
print_description_list(description_cols, execution_request)
|
3671
|
+
|
3672
|
+
if execution_request['stdErr'].to_s.strip != '' && execution_request['stdErr'] != "stdin: is not a tty\n"
|
3673
|
+
print_h2 "Error"
|
3674
|
+
puts execution_request['stdErr'].to_s.strip
|
3675
|
+
end
|
3676
|
+
if execution_request['stdOut']
|
3677
|
+
print_h2 "Output"
|
3678
|
+
puts execution_request['stdOut'].to_s.strip
|
3679
|
+
end
|
3680
|
+
print reset, "\n"
|
3681
|
+
end
|
3682
|
+
end
|
3683
|
+
end
|
3684
|
+
end
|
3685
|
+
|
3565
3686
|
def history_event_details(args)
|
3566
3687
|
options = {}
|
3567
3688
|
process_event_id = nil
|
@@ -4057,7 +4178,7 @@ class Morpheus::Cli::Clusters
|
|
4057
4178
|
end
|
4058
4179
|
opts.on('--volumes-file FILE', String, "Volumes Config from a local JSON or YAML file") do |val|
|
4059
4180
|
config_file = File.expand_path(val)
|
4060
|
-
if !File.
|
4181
|
+
if !File.exist?(config_file) || !File.file?(config_file)
|
4061
4182
|
print_red_alert "Specified volumes file not found: #{config_file}"
|
4062
4183
|
exit 1
|
4063
4184
|
end
|
@@ -4093,7 +4214,7 @@ class Morpheus::Cli::Clusters
|
|
4093
4214
|
end
|
4094
4215
|
opts.on('--network-interfaces-file FILE', String, "Network Interfaces Config from a local JSON or YAML file") do |val|
|
4095
4216
|
config_file = File.expand_path(val)
|
4096
|
-
if !File.
|
4217
|
+
if !File.exist?(config_file) || !File.file?(config_file)
|
4097
4218
|
print_red_alert "Specified network interfaces file not found: #{config_file}"
|
4098
4219
|
exit 1
|
4099
4220
|
end
|
@@ -4235,4 +4356,34 @@ class Morpheus::Cli::Clusters
|
|
4235
4356
|
it
|
4236
4357
|
end
|
4237
4358
|
end
|
4359
|
+
|
4360
|
+
def available_kube_templates
|
4361
|
+
option_results = options_interface.options_for_source('availableKubeTemplates')
|
4362
|
+
available_templates = option_results['data'].collect {|it|
|
4363
|
+
{"id" => it["value"], "name" => it["name"], "value" => it["value"]}
|
4364
|
+
}
|
4365
|
+
|
4366
|
+
return available_templates
|
4367
|
+
end
|
4368
|
+
|
4369
|
+
def apply_temp_options
|
4370
|
+
[
|
4371
|
+
{"id" => "specYaml", "name" => "YAML", "value" => "yaml"},
|
4372
|
+
{"id" => 'specTemplate', "name" => "Spec Template", "value" => 'specTemplate'},
|
4373
|
+
{"id" => 'url', "name" => 'Url of Template', "value" => 'url'}
|
4374
|
+
]
|
4375
|
+
end
|
4376
|
+
|
4377
|
+
def format_execution_request_status(execution_request, return_color=cyan)
|
4378
|
+
out = ""
|
4379
|
+
status_str = execution_request['status']
|
4380
|
+
if status_str == 'complete'
|
4381
|
+
out << "#{green}#{status_str.upcase}#{return_color}"
|
4382
|
+
elsif status_str == 'failed' || status_str == 'expired'
|
4383
|
+
out << "#{red}#{status_str.upcase}#{return_color}"
|
4384
|
+
else
|
4385
|
+
out << "#{cyan}#{status_str.upcase}#{return_color}"
|
4386
|
+
end
|
4387
|
+
out
|
4388
|
+
end
|
4238
4389
|
end
|