morpheus-cli 5.5.2.1 → 5.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.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Dockerfile +1 -1
  4. data/README.md +57 -4
  5. data/Rakefile +9 -0
  6. data/bin/morpheus +4 -4
  7. data/lib/morpheus/api/api_client.rb +8 -2
  8. data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
  9. data/lib/morpheus/api/archive_files_interface.rb +3 -3
  10. data/lib/morpheus/api/clients_interface.rb +2 -2
  11. data/lib/morpheus/api/clusters_interface.rb +8 -1
  12. data/lib/morpheus/api/containers_interface.rb +29 -16
  13. data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
  14. data/lib/morpheus/api/doc_interface.rb +8 -6
  15. data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
  16. data/lib/morpheus/api/health_interface.rb +1 -1
  17. data/lib/morpheus/api/image_builder_interface.rb +3 -3
  18. data/lib/morpheus/api/instances_interface.rb +25 -0
  19. data/lib/morpheus/api/logs_interface.rb +2 -4
  20. data/lib/morpheus/api/monitoring_interface.rb +6 -6
  21. data/lib/morpheus/api/packages_interface.rb +1 -1
  22. data/lib/morpheus/api/reports_interface.rb +1 -1
  23. data/lib/morpheus/api/servers_interface.rb +9 -1
  24. data/lib/morpheus/api/storage_providers_interface.rb +2 -2
  25. data/lib/morpheus/api/virtual_images_interface.rb +1 -1
  26. data/lib/morpheus/api.rb +2 -0
  27. data/lib/morpheus/benchmarking.rb +1 -1
  28. data/lib/morpheus/cli/cli_command.rb +69 -36
  29. data/lib/morpheus/cli/cli_registry.rb +19 -10
  30. data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
  31. data/lib/morpheus/cli/commands/apps.rb +1 -1
  32. data/lib/morpheus/cli/commands/archives_command.rb +25 -33
  33. data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
  34. data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
  35. data/lib/morpheus/cli/commands/cat_command.rb +1 -1
  36. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +12 -12
  37. data/lib/morpheus/cli/commands/clouds.rb +3 -3
  38. data/lib/morpheus/cli/commands/clusters.rb +154 -3
  39. data/lib/morpheus/cli/commands/containers_command.rb +398 -253
  40. data/lib/morpheus/cli/commands/deployments.rb +1 -1
  41. data/lib/morpheus/cli/commands/deploys.rb +9 -9
  42. data/lib/morpheus/cli/commands/doc.rb +15 -16
  43. data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
  44. data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
  45. data/lib/morpheus/cli/commands/groups.rb +2 -2
  46. data/lib/morpheus/cli/commands/health_command.rb +4 -4
  47. data/lib/morpheus/cli/commands/hosts.rb +43 -5
  48. data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
  49. data/lib/morpheus/cli/commands/instances.rb +419 -148
  50. data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
  51. data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
  52. data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
  53. data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
  54. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
  55. data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
  56. data/lib/morpheus/cli/commands/login.rb +1 -1
  57. data/lib/morpheus/cli/commands/man_command.rb +32 -18
  58. data/lib/morpheus/cli/commands/packages_command.rb +11 -11
  59. data/lib/morpheus/cli/commands/plugins.rb +1 -1
  60. data/lib/morpheus/cli/commands/policies_command.rb +4 -4
  61. data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
  62. data/lib/morpheus/cli/commands/remote.rb +1 -1
  63. data/lib/morpheus/cli/commands/reports_command.rb +3 -3
  64. data/lib/morpheus/cli/commands/roles.rb +6 -3
  65. data/lib/morpheus/cli/commands/security_groups.rb +1 -1
  66. data/lib/morpheus/cli/commands/shell.rb +40 -62
  67. data/lib/morpheus/cli/commands/snapshots.rb +3 -5
  68. data/lib/morpheus/cli/commands/source_command.rb +8 -16
  69. data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
  70. data/lib/morpheus/cli/commands/tasks.rb +2 -2
  71. data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
  72. data/lib/morpheus/cli/commands/view.rb +5 -1
  73. data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +4 -4
  74. data/lib/morpheus/cli/commands/whoami.rb +2 -2
  75. data/lib/morpheus/cli/credentials.rb +30 -8
  76. data/lib/morpheus/cli/dot_file.rb +8 -15
  77. data/lib/morpheus/cli/error_handler.rb +16 -0
  78. data/lib/morpheus/cli/errors.rb +8 -1
  79. data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
  80. data/lib/morpheus/cli/mixins/rest_command.rb +18 -18
  81. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +12 -12
  82. data/lib/morpheus/cli/option_parser.rb +5 -1
  83. data/lib/morpheus/cli/option_types.rb +59 -12
  84. data/lib/morpheus/cli/version.rb +1 -1
  85. data/lib/morpheus/cli.rb +26 -16
  86. data/lib/morpheus/ext/rest_client.rb +3 -2
  87. data/lib/morpheus/formatters.rb +1 -1
  88. data/lib/morpheus/logging.rb +4 -4
  89. data/lib/morpheus/morpkg.rb +4 -4
  90. data/lib/morpheus/rest_client.rb +2 -2
  91. data/lib/morpheus/routes.rb +2 -2
  92. data/lib/morpheus/terminal.rb +65 -16
  93. data/lib/morpheus.rb +1 -1
  94. data/morpheus-cli.gemspec +1 -0
  95. data/test/api/containers_interface_test.rb +68 -0
  96. data/test/api/doc_interface_test.rb +35 -0
  97. data/test/api/instances_interface_test.rb +22 -0
  98. data/test/api/whoami_interface_test.rb +14 -0
  99. data/test/cli/access_token_test.rb +36 -0
  100. data/test/cli/auth_test.rb +82 -0
  101. data/test/cli/cli_test.rb +48 -0
  102. data/test/cli/containers_test.rb +92 -0
  103. data/test/cli/doc_test.rb +35 -0
  104. data/test/cli/help_test.rb +25 -0
  105. data/test/cli/instances_test.rb +36 -0
  106. data/test/cli/man_test.rb +14 -0
  107. data/test/cli/remote_test.rb +89 -0
  108. data/test/cli/roles_test.rb +34 -0
  109. data/test/cli/shell_test.rb +81 -0
  110. data/test/cli/version_test.rb +23 -0
  111. data/test/cli/view_test.rb +55 -0
  112. data/test/cli/whoami_test.rb +17 -0
  113. data/test/morpheus_test.rb +16 -0
  114. data/test/test_case.rb +338 -0
  115. data/test/test_config.rb +137 -0
  116. data/test/test_data_helper.rb +97 -0
  117. metadata +61 -3
@@ -260,7 +260,7 @@ class Morpheus::Cli::BlueprintsCommand
260
260
  end
261
261
 
262
262
  def update(args)
263
- params, payload, options = {}, {}, {}
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
- params, payload, options = {}, {}, {}
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
- error_msg = "Blueprint #{blueprint['name']} has no tiers."
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 => e
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
- #puts_error "Failed to parse instance scoped instance configs for blueprint #{blueprint['id']} #{blueprint['name']} Exception: #{err.class} #{err.message}"
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.exists?(script_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.exists?(script_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.exists?(arg_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.exists?(filename)
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.exists?(filename)
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.exists?(full_filename)
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[:err]}"
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[:err]}"
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.exists?(filename)
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.exists?(filename)
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.exists?(full_filename)
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[:err]}"
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[:err]}"
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.exists?(filename)
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.exists?(filename)
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.exists?(full_filename)
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.exists?(filename)
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.exists?(filename)
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.exists?(full_filename)
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.exists?(config_file) || !File.file?(config_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.exists?(config_file) || !File.file?(config_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