morpheus-cli 4.1.8 → 4.1.9

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +24 -0
  4. data/lib/morpheus/api/{old_cypher_interface.rb → budgets_interface.rb} +10 -11
  5. data/lib/morpheus/api/cloud_datastores_interface.rb +7 -0
  6. data/lib/morpheus/api/cloud_resource_pools_interface.rb +2 -2
  7. data/lib/morpheus/api/cypher_interface.rb +18 -12
  8. data/lib/morpheus/api/health_interface.rb +72 -0
  9. data/lib/morpheus/api/instances_interface.rb +1 -1
  10. data/lib/morpheus/api/library_instance_types_interface.rb +7 -0
  11. data/lib/morpheus/api/log_settings_interface.rb +6 -0
  12. data/lib/morpheus/api/network_security_servers_interface.rb +30 -0
  13. data/lib/morpheus/api/price_sets_interface.rb +42 -0
  14. data/lib/morpheus/api/prices_interface.rb +68 -0
  15. data/lib/morpheus/api/provisioning_settings_interface.rb +29 -0
  16. data/lib/morpheus/api/servers_interface.rb +1 -1
  17. data/lib/morpheus/api/service_plans_interface.rb +34 -11
  18. data/lib/morpheus/api/task_sets_interface.rb +8 -0
  19. data/lib/morpheus/api/tasks_interface.rb +8 -0
  20. data/lib/morpheus/cli.rb +6 -3
  21. data/lib/morpheus/cli/appliance_settings_command.rb +13 -5
  22. data/lib/morpheus/cli/approvals_command.rb +1 -1
  23. data/lib/morpheus/cli/apps.rb +88 -28
  24. data/lib/morpheus/cli/backup_settings_command.rb +1 -1
  25. data/lib/morpheus/cli/blueprints_command.rb +2 -0
  26. data/lib/morpheus/cli/budgets_command.rb +672 -0
  27. data/lib/morpheus/cli/cli_command.rb +13 -2
  28. data/lib/morpheus/cli/cli_registry.rb +1 -0
  29. data/lib/morpheus/cli/clusters.rb +40 -274
  30. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +114 -66
  31. data/lib/morpheus/cli/commands/standard/coloring_command.rb +12 -0
  32. data/lib/morpheus/cli/commands/standard/curl_command.rb +31 -6
  33. data/lib/morpheus/cli/commands/standard/echo_command.rb +8 -3
  34. data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +1 -1
  35. data/lib/morpheus/cli/containers_command.rb +37 -24
  36. data/lib/morpheus/cli/cypher_command.rb +191 -150
  37. data/lib/morpheus/cli/health_command.rb +903 -0
  38. data/lib/morpheus/cli/hosts.rb +43 -32
  39. data/lib/morpheus/cli/instances.rb +119 -68
  40. data/lib/morpheus/cli/jobs_command.rb +1 -1
  41. data/lib/morpheus/cli/library_instance_types_command.rb +61 -11
  42. data/lib/morpheus/cli/library_option_types_command.rb +2 -2
  43. data/lib/morpheus/cli/log_settings_command.rb +46 -3
  44. data/lib/morpheus/cli/logs_command.rb +24 -17
  45. data/lib/morpheus/cli/mixins/accounts_helper.rb +2 -0
  46. data/lib/morpheus/cli/mixins/logs_helper.rb +73 -19
  47. data/lib/morpheus/cli/mixins/print_helper.rb +29 -1
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +554 -96
  49. data/lib/morpheus/cli/mixins/whoami_helper.rb +13 -1
  50. data/lib/morpheus/cli/networks_command.rb +3 -0
  51. data/lib/morpheus/cli/option_types.rb +83 -53
  52. data/lib/morpheus/cli/price_sets_command.rb +543 -0
  53. data/lib/morpheus/cli/prices_command.rb +669 -0
  54. data/lib/morpheus/cli/processes_command.rb +0 -2
  55. data/lib/morpheus/cli/provisioning_settings_command.rb +237 -0
  56. data/lib/morpheus/cli/remote.rb +9 -4
  57. data/lib/morpheus/cli/reports_command.rb +10 -4
  58. data/lib/morpheus/cli/roles.rb +93 -38
  59. data/lib/morpheus/cli/security_groups.rb +10 -0
  60. data/lib/morpheus/cli/service_plans_command.rb +736 -0
  61. data/lib/morpheus/cli/tasks.rb +220 -8
  62. data/lib/morpheus/cli/tenants_command.rb +3 -16
  63. data/lib/morpheus/cli/users.rb +2 -25
  64. data/lib/morpheus/cli/version.rb +1 -1
  65. data/lib/morpheus/cli/whitelabel_settings_command.rb +18 -18
  66. data/lib/morpheus/cli/whoami.rb +28 -10
  67. data/lib/morpheus/cli/workflows.rb +488 -36
  68. data/lib/morpheus/formatters.rb +22 -0
  69. data/morpheus-cli.gemspec +1 -0
  70. metadata +28 -5
  71. data/lib/morpheus/cli/accounts.rb +0 -335
  72. data/lib/morpheus/cli/old_cypher_command.rb +0 -412
@@ -120,8 +120,8 @@ class Morpheus::Cli::LibraryOptionTypesCommand
120
120
  return
121
121
  end
122
122
  option_type = find_option_type_by_name_or_id(id)
123
- exit 1 if option_type.nil?
124
-
123
+ return 1 if option_type.nil?
124
+ json_response = {'optionType' => option_type}
125
125
  if options[:json]
126
126
  puts as_json(json_response, options, "optionType")
127
127
  return 0
@@ -5,9 +5,9 @@ class Morpheus::Cli::LogSettingsCommand
5
5
  include Morpheus::Cli::AccountsHelper
6
6
 
7
7
  set_command_name :'log-settings'
8
- set_command_hidden
8
+
9
9
  register_subcommands :get, :update
10
- register_subcommands :enable_integration, :disable_integration
10
+ register_subcommands :enable_integration, :disable_integration, :remove_integration
11
11
  register_subcommands :add_syslog_rule, :remove_syslog_rule
12
12
 
13
13
  set_default_subcommand :get
@@ -92,7 +92,7 @@ class Morpheus::Cli::LogSettingsCommand
92
92
  optparse = Morpheus::Cli::OptionParser.new do |opts|
93
93
  opts.banner = opts.banner = subcommand_usage()
94
94
  opts.on("--enabled [on|off]", ['on','off'], "Logs enabled") do |val|
95
- params['enabled'] = ['true','on'].include?(val.to_s.strip)
95
+ params['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
96
96
  end
97
97
  opts.on("-R", "--retention NUMBER", Integer, "Availability time frame in days") do |val|
98
98
  params['retentionDays'] = val.to_i
@@ -284,6 +284,49 @@ class Morpheus::Cli::LogSettingsCommand
284
284
  end
285
285
  end
286
286
 
287
+ def remove_integration(args)
288
+ options = {}
289
+
290
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
291
+ opts.banner = opts.banner = subcommand_usage("[name]")
292
+ build_common_options(opts, options, [:json, :dry_run, :quiet, :remote])
293
+ opts.footer = "Deletes specifed integration.\n" +
294
+ "[name] is required. Currently supports splunk and logrhythm integrations."
295
+ end
296
+
297
+ optparse.parse!(args)
298
+ connect(options)
299
+ if args.count != 1
300
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
301
+ return 1
302
+ end
303
+
304
+ begin
305
+ @log_settings_interface.setopts(options)
306
+ if options[:dry_run]
307
+ print_dry_run @log_settings_interface.dry.destroy_integration(args[0])
308
+ return
309
+ end
310
+ json_response = @log_settings_interface.destroy_integration(args[0])
311
+
312
+ if options[:json]
313
+ puts as_json(json_response, options)
314
+ elsif !options[:quiet]
315
+ if json_response['success']
316
+ print_green_success "Integration removed"
317
+ get([] + (options[:remote] ? ["-r",options[:remote]] : []))
318
+ else
319
+ print_red_alert "Error removing integration: #{json_response['msg'] || json_response['errors']}"
320
+ end
321
+ end
322
+ return 0
323
+
324
+ rescue RestClient::Exception => e
325
+ print_rest_exception(e, options)
326
+ exit 1
327
+ end
328
+ end
329
+
287
330
  def add_syslog_rule(args)
288
331
  options = {}
289
332
 
@@ -62,6 +62,15 @@ class Morpheus::Cli::LogsCommand
62
62
  # opts.on('--interval TIME','--interval TIME', "Interval of time to include, in seconds. Default is 30 days ago.") do |val|
63
63
  # options[:interval] = parse_time(val).utc.iso8601
64
64
  # end
65
+ opts.on('--level VALUE', String, "Log Level. DEBUG,INFO,WARN,ERROR") do |val|
66
+ params['level'] = params['level'] ? [params['level'], val].flatten : val
67
+ end
68
+ opts.on('--table', '--table', "Format ouput as a table.") do
69
+ options[:table] = true
70
+ end
71
+ opts.on('-a', '--all', "Display all details: entire message." ) do
72
+ options[:details] = true
73
+ end
65
74
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
66
75
  opts.footer = "List logs for a container.\n" +
67
76
  "[id] is required. This is the id of a container."
@@ -73,7 +82,7 @@ class Morpheus::Cli::LogsCommand
73
82
  connect(options)
74
83
  begin
75
84
  params.merge!(parse_list_options(options))
76
- params[:query] = params.delete(:phrase) unless params[:phrase].nil?
85
+ params['query'] = params.delete('phrase') if params['phrase']
77
86
  params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
78
87
  params['startMs'] = (options[:start].to_i * 1000) if options[:start]
79
88
  params['endMs'] = (options[:end].to_i * 1000) if options[:end]
@@ -85,22 +94,19 @@ class Morpheus::Cli::LogsCommand
85
94
  return
86
95
  end
87
96
  json_response = @logs_interface.list(params)
88
- render_result = render_with_format(json_response, options, 'data')
97
+ render_result = json_response['logs'] ? render_with_format(json_response, options, 'logs') : render_with_format(json_response, options, 'data')
89
98
  return 0 if render_result
90
99
 
91
100
  title = "Morpheus Logs"
92
101
  subtitles = parse_list_subtitles(options)
93
- if params[:start]
94
- subtitles << "Start: #{params[:start]}".strip
95
- end
96
- if params[:end]
97
- subtitles << "End: #{params[:end]}".strip
102
+ if options[:start]
103
+ subtitles << "Start: #{options[:start]}".strip
98
104
  end
99
- if params[:interval]
100
- subtitles << "Interval: #{params[:interval]}".strip
105
+ if options[:end]
106
+ subtitles << "End: #{options[:end]}".strip
101
107
  end
102
- if params[:query]
103
- subtitles << "Search: #{params[:query]}".strip
108
+ if params['query']
109
+ subtitles << "Search: #{params['query']}".strip
104
110
  end
105
111
  if params['servers']
106
112
  subtitles << "Servers: #{params['servers']}".strip
@@ -111,15 +117,16 @@ class Morpheus::Cli::LogsCommand
111
117
  if params['clusters']
112
118
  subtitles << "Clusters: #{params['clusters']}".strip
113
119
  end
114
- if params[:query]
115
- subtitles << "Search: #{params[:query]}".strip
120
+ if params['level']
121
+ subtitles << "Level: #{params['level']}"
116
122
  end
117
123
  print_h1 title, subtitles, options
118
- if json_response['data'].empty?
119
- puts "#{cyan}No logs found.#{reset}"
124
+ logs = json_response['data'] || json_response['logs']
125
+ if logs.empty?
126
+ print "#{cyan}No logs found.#{reset}\n"
120
127
  else
121
- puts format_log_records(json_response['data'], options)
122
- print_results_pagination({'meta'=>{'total'=>json_response['total'],'size'=>json_response['data'].size,'max'=>(json_response['max'] || options[:max]),'offset'=>(json_response['offset'] || options[:offset] || 0)}})
128
+ print format_log_records(logs, options)
129
+ print_results_pagination({'meta'=>{'total'=>(json_response['total']['value'] rescue json_response['total']),'size'=>logs.size,'max'=>(json_response['max'] || options[:max]),'offset'=>(json_response['offset'] || options[:offset] || 0)}})
123
130
  end
124
131
  print reset,"\n"
125
132
  return 0
@@ -331,6 +331,8 @@ module Morpheus::Cli::AccountsHelper
331
331
  val ||= 'none'
332
332
  if val == 'none'
333
333
  "#{white}#{val.to_s.capitalize}#{cyan}"
334
+ # elsif val == 'read'
335
+ # "#{cyan}#{val.to_s.capitalize}#{cyan}"
334
336
  else
335
337
  "#{green}#{val.to_s.capitalize}#{cyan}"
336
338
  end
@@ -15,6 +15,12 @@ module Morpheus::Cli::LogsHelper
15
15
  @logs_interface
16
16
  end
17
17
 
18
+ def instances_interface
19
+ # @api_client.instances
20
+ raise "#{self.class} has not defined @instances_interface" if @instances_interface.nil?
21
+ @instances_interface
22
+ end
23
+
18
24
  def containers_interface
19
25
  ## @api_client.containers
20
26
  raise "#{self.class} has not defined @logs_interface" if @containers_interface.nil?
@@ -33,33 +39,81 @@ module Morpheus::Cli::LogsHelper
33
39
  @clusters_interface
34
40
  end
35
41
 
36
- def print_log_records(log_records, options={})
37
- print format_log_records(log_records, options)
38
- end
39
-
40
- def format_log_records(log_records, options={})
42
+ def format_log_records(log_records, options={}, show_object=true)
43
+ if options[:table]
44
+ return format_log_table(log_records, options, show_object)
45
+ end
41
46
  out = ""
42
47
  table_color = options.key?(:color) ? options[:color] : cyan
43
48
  log_records.each do |log_entry|
44
- log_level = ''
45
- case log_entry['level']
46
- when 'INFO'
47
- log_level = "#{blue}#{bold}INFO#{reset}"
48
- when 'DEBUG'
49
- log_level = "#{white}#{bold}DEBUG#{reset}"
50
- when 'WARN'
51
- log_level = "#{yellow}#{bold}WARN#{reset}"
52
- when 'ERROR'
53
- log_level = "#{red}#{bold}ERROR#{reset}"
54
- when 'FATAL'
55
- log_level = "#{red}#{bold}FATAL#{reset}"
56
- end
49
+ log_level = format_log_level(log_entry['level'], table_color, 6)
57
50
  out << table_color if table_color
58
- out << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message'].to_s.strip}"
51
+ # out << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message'].to_s.strip}"
52
+ out << "#{log_level} "
53
+ out << "[#{log_entry['ts']}] "
54
+ if show_object
55
+ out << "(#{log_entry['typeCode']} #{log_entry['objectId']}) "
56
+ end
57
+ log_msg = ""
58
+ if options[:details] || options[:all]
59
+ log_msg = log_entry['message'].to_s.strip
60
+ else
61
+ # truncate_string(log_entry['message'].to_s.split.select {|it| it.to_s.strip != "" }.first, 100)
62
+ log_msg = truncate_string(log_entry['message'].to_s.gsub("\r\n", " ").gsub("\n", " "), 100)
63
+ end
64
+ out << "- #{log_msg}"
59
65
  out << table_color if table_color
60
66
  out << "\n"
61
67
  end
62
68
  return out
63
69
  end
64
70
 
71
+ def format_log_table(logs, options={}, show_object=true)
72
+ out = ""
73
+ table_color = options.key?(:color) ? options[:color] : cyan
74
+ log_columns = [
75
+ {"LEVEL" => lambda {|log_entry| format_log_level(log_entry['level'], table_color) } },
76
+ {"DATE" => lambda {|log_entry| log_entry['ts'] } },
77
+ {"SOURCE" => lambda {|log_entry| "#{log_entry['typeCode']} #{log_entry['objectId']}" } },
78
+ {"MESSAGE" => lambda {|log_entry|
79
+ if options[:details] || options[:all]
80
+ log_entry['message'].to_s
81
+ else
82
+ truncate_string(log_entry['message'].to_s.split.first, 100)
83
+ end
84
+ } }
85
+ ]
86
+ if show_object != true
87
+ log_columns = log_columns.reject {|it| it.key?("SOURCE") }
88
+ end
89
+ # if options[:include_fields]
90
+ # columns = options[:include_fields]
91
+ # end
92
+ out << as_pretty_table(logs, log_columns, options.merge(responsive_table:false))
93
+ # out << "\n"
94
+ return out
95
+ end
96
+
97
+ def format_log_level(val, return_color=cyan, label_width=nil)
98
+ log_level = ''
99
+ display_value = val.to_s
100
+ if label_width
101
+ display_value = display_value.ljust(label_width, ' ')
102
+ end
103
+ case val
104
+ when 'INFO'
105
+ log_level = "#{blue}#{bold}#{display_value}#{reset}#{return_color}"
106
+ when 'DEBUG'
107
+ log_level = "#{white}#{bold}#{display_value}#{reset}#{return_color}"
108
+ when 'WARN'
109
+ log_level = "#{yellow}#{bold}#{display_value}#{reset}#{return_color}"
110
+ when 'ERROR'
111
+ log_level = "#{red}#{bold}#{display_value}#{reset}#{return_color}"
112
+ when 'FATAL'
113
+ log_level = "#{red}#{bold}#{display_value}#{reset}#{return_color}"
114
+ else
115
+ log_level = val
116
+ end
117
+ return log_level
118
+ end
65
119
  end
@@ -1026,7 +1026,8 @@ module Morpheus::Cli::PrintHelper
1026
1026
 
1027
1027
  if options[:include_fields]
1028
1028
  if object_key
1029
- data[object_key] = filter_data(data[object_key], options[:include_fields])
1029
+ # data[object_key] = filter_data(data[object_key], options[:include_fields])
1030
+ data = {(object_key) => filter_data(data[object_key], options[:include_fields]) }
1030
1031
  else
1031
1032
  data = filter_data(data, options[:include_fields])
1032
1033
  end
@@ -1115,4 +1116,31 @@ module Morpheus::Cli::PrintHelper
1115
1116
  end
1116
1117
  end
1117
1118
 
1119
+ def format_percent(val, sig_dig=2)
1120
+ if val.nil?
1121
+ return ""
1122
+ end
1123
+ percent_value = val.to_f
1124
+ if percent_value == 0
1125
+ return "0%"
1126
+ else
1127
+ return percent_value.round(sig_dig).to_s + "%"
1128
+ end
1129
+ end
1130
+
1131
+ # returns 0.50 / s ie {{value}} / {{unit}}
1132
+ def format_rate(amount, unit='s', sig_dig=2)
1133
+ if amount.to_f == 0
1134
+ return "0.00" + " / " + unit.to_s
1135
+ else
1136
+ rtn = amount.to_f.round(2).to_s
1137
+ parts = rtn.split('.')
1138
+ # number_str = format_number(parts[0])
1139
+ number_str = parts[0].to_s
1140
+ decimal_str = "#{parts[1]}".ljust(sig_dig, "0")
1141
+ number_str + "." + decimal_str
1142
+ return number_str + "." + decimal_str + " / " + unit.to_s
1143
+ end
1144
+ end
1145
+
1118
1146
  end
@@ -31,6 +31,28 @@ module Morpheus::Cli::ProvisioningHelper
31
31
  @instance_types_interface
32
32
  end
33
33
 
34
+ def instance_type_layouts_interface
35
+ # @api_client.instance_types
36
+ raise "#{self.class} has not defined @library_layouts" if @library_layouts_interface.nil?
37
+ @library_layouts_interface
38
+ end
39
+
40
+ def clouds_interface
41
+ # @api_client.instance_types
42
+ raise "#{self.class} has not defined @clouds_interface" if @clouds_interface.nil?
43
+ @clouds_interface
44
+ end
45
+
46
+ def cloud_datastores_interface
47
+ raise "#{self.class} has not defined @clouds_datastores_interface" if @clouds_datastores_interface.nil?
48
+ @clouds_datastores_interface
49
+ end
50
+
51
+ def accounts_interface
52
+ raise "#{self.class} has not defined @accounts_interface" if @accounts_interface.nil?
53
+ @accounts_interface
54
+ end
55
+
34
56
  def get_available_groups(refresh=false)
35
57
  if !@available_groups || refresh
36
58
  option_results = options_interface.options_for_source('groups',{})
@@ -62,6 +84,22 @@ module Morpheus::Cli::ProvisioningHelper
62
84
  return group["clouds"]
63
85
  end
64
86
 
87
+ def get_available_accounts(refresh=false)
88
+ if !@available_accounts || refresh
89
+ @available_accounts = accounts_interface.list()['accounts']
90
+ end
91
+ @available_accounts
92
+ end
93
+
94
+ def get_available_plans(refresh=false)
95
+ if !@available_plans || refresh
96
+ @available_plans = instances_interface.search_plans['searchPlans'].collect {|it|
97
+ {"name" => it["name"], "value" => it["id"]}
98
+ }
99
+ end
100
+ @available_plans
101
+ end
102
+
65
103
  def find_group_by_id_for_provisioning(val)
66
104
  groups = get_available_groups()
67
105
  group = groups.find {|it| it["id"].to_s == val.to_s }
@@ -208,6 +246,15 @@ module Morpheus::Cli::ProvisioningHelper
208
246
  return instance
209
247
  end
210
248
 
249
+ def find_instance_type_layout_by_id(layout_id, id)
250
+ json_results = instance_type_layouts_interface.get(layout_id, id)
251
+ if json_results['instanceTypeLayout'].empty?
252
+ print_red_alert "Instance type layout not found by id #{id}"
253
+ exit 1
254
+ end
255
+ json_results['instanceTypeLayout']
256
+ end
257
+
211
258
  def find_workflow_by_name_or_id(val)
212
259
  if val.to_s =~ /\A\d{1,}\Z/
213
260
  return find_workflow_by_id(val)
@@ -244,8 +291,12 @@ module Morpheus::Cli::ProvisioningHelper
244
291
  end
245
292
  end
246
293
 
294
+ def find_cloud_resource_pool_by_name_or_id(cloud_id, val)
295
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_cloud_resource_pool_by_id(cloud_id, val) : find_cloud_resource_pool_by_name(cloud_id, val)
296
+ end
297
+
247
298
  def get_provision_type_for_zone_type(zone_type_id)
248
- @clouds_interface.cloud_type(zone_type_id)['zoneType']['provisionTypes'].first rescue nil
299
+ clouds_interface.cloud_type(zone_type_id)['zoneType']['provisionTypes'].first rescue nil
249
300
  end
250
301
 
251
302
  # prompts user for all the configuartion options for a particular instance
@@ -254,8 +305,10 @@ module Morpheus::Cli::ProvisioningHelper
254
305
  #puts "prompt_new_instance() #{options}"
255
306
  print reset # clear colors
256
307
  options[:options] ||= {}
308
+
257
309
  # Group
258
- default_group = @active_group_id ? find_group_by_name_or_id_for_provisioning(@active_group_id) : nil
310
+ default_group = find_group_by_name_or_id_for_provisioning(options[:default_group] || @active_group_id) if options[:default_group] || @active_group_id
311
+
259
312
  group_id = nil
260
313
  group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
261
314
  if group
@@ -278,8 +331,11 @@ module Morpheus::Cli::ProvisioningHelper
278
331
  available_clouds = get_available_clouds(group_id)
279
332
  cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => get_available_clouds(group_id), 'required' => true, 'description' => 'Select Cloud.', 'defaultValue' => options[:default_cloud] ? options[:default_cloud] : nil}],options[:options],api_client,{groupId: group_id})
280
333
  cloud_id = cloud_prompt['cloud']
281
- cloud = available_clouds.find {|it| it['id'] == cloud_id.to_i || it['name'] == cloud_id.to_s }
282
334
  end
335
+
336
+ cloud = clouds_interface.get(cloud_id)['zone']
337
+ cloud_type = cloud['zoneType'] || {}
338
+
283
339
  # Instance Type
284
340
  instance_type_code = nil
285
341
  if options[:instance_type_code]
@@ -302,9 +358,38 @@ module Morpheus::Cli::ProvisioningHelper
302
358
  elsif options[:options]['instance'] && options[:options]['instance']['name']
303
359
  options[:options]['name'] = options[:options]['instance']['name']
304
360
  end
305
- name_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Instance Name', 'type' => 'text', 'required' => options[:name_required]}], options[:options])
306
- instance_name = name_prompt['name']
307
-
361
+
362
+ while instance_name.nil? do
363
+ name_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Instance Name', 'type' => 'text', 'required' => options[:name_required], 'defaultValue' => options[:default_name]}], options[:options])
364
+
365
+ if name_prompt['name'].nil? && !options[:name_required]
366
+ break
367
+ else
368
+ if instances_interface.get({name: name_prompt['name']})['instances'].empty?
369
+ instance_name = name_prompt['name']
370
+ else
371
+ print_red_alert "Name must be unique"
372
+
373
+ if options[:default_name] == name_prompt['name']
374
+ options[:default_name] += '-2'
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ # config
381
+ config = {}
382
+ if cloud_type['code'] == 'amazon' && (cloud['config'] || {})['isVpc'] == 'false' && (cloud['config'] || {})['vpc'] == ''
383
+ config['isEC2'] = true
384
+ else
385
+ config['isEC2'] = false
386
+ if cloud_type['code'] == 'amazon' && (cloud['config'] || {})['isVpc'] == 'true' && (cloud['config'] || {})['vpc'] != ''
387
+ config['isVpcSelectable'] = false
388
+ config['resourcePoolId'] = cloud['config']['vpc']
389
+ else
390
+ config['isVpcSelectable'] = true
391
+ end
392
+ end
308
393
 
309
394
  payload = {
310
395
  'zoneId' => cloud_id,
@@ -319,7 +404,8 @@ module Morpheus::Cli::ProvisioningHelper
319
404
  'instanceType' => {
320
405
  'code' => instance_type_code
321
406
  }
322
- }
407
+ },
408
+ 'config' => config
323
409
  }
324
410
 
325
411
  # allow arbitrary -O values passed by the user
@@ -361,45 +447,53 @@ module Morpheus::Cli::ProvisioningHelper
361
447
  payload['instance']['tags'] = v_prompt['tags'].split(',').collect {|it| it.to_s.strip }.compact.uniq if !v_prompt['tags'].empty?
362
448
 
363
449
  # Version and Layout
364
-
365
- available_versions = options_interface.options_for_source('instanceVersions',{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})['data']
366
- default_version_value = payload['instance']['version'] ? payload['instance']['version'] : payload['version']
367
- default_layout_value = payload['instance']['layout'] ? payload['instance']['layout'] : payload['layout']
368
- if default_layout_value && default_layout_value.is_a?(Hash)
369
- default_layout_value = default_layout_value['id']
370
- end
371
- # JD: version is always nil because it is not stored in the blueprint or config !!
372
- # so for now, infer the version from the layout
373
- # requires api 3.6.2 to get "layouts" from /options/versions
374
- if default_layout_value && default_version_value.to_s.empty?
375
- available_versions.each do |available_version|
376
- if available_version["layouts"]
377
- selected_layout = available_version["layouts"].find {|it| it["value"].to_s == default_layout_value.to_s || it["id"].to_s == default_layout_value.to_s || it["code"].to_s == default_layout_value.to_s }
378
- if selected_layout
379
- default_version_value = available_version["value"]
380
- break
450
+ if options[:version]
451
+ version_value = options[:version]
452
+ else
453
+ available_versions = options_interface.options_for_source('instanceVersions',{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})['data']
454
+ default_version_value = payload['instance']['version'] ? payload['instance']['version'] : payload['version']
455
+ default_layout_value = payload['instance']['layout'] ? payload['instance']['layout'] : payload['layout']
456
+ if default_layout_value && default_layout_value.is_a?(Hash)
457
+ default_layout_value = default_layout_value['id']
458
+ end
459
+ # JD: version is always nil because it is not stored in the blueprint or config !!
460
+ # so for now, infer the version from the layout
461
+ # requires api 3.6.2 to get "layouts" from /options/versions
462
+ if default_layout_value && default_version_value.to_s.empty?
463
+ available_versions.each do |available_version|
464
+ if available_version["layouts"]
465
+ selected_layout = available_version["layouts"].find {|it| it["value"].to_s == default_layout_value.to_s || it["id"].to_s == default_layout_value.to_s || it["code"].to_s == default_layout_value.to_s }
466
+ if selected_layout
467
+ default_version_value = available_version["value"]
468
+ break
469
+ end
381
470
  end
382
471
  end
383
472
  end
384
- end
385
473
 
386
- # do not require version if a layout is passed
387
- version_value = default_version_value
388
- version_is_required = default_layout_value.nil?
389
- if default_layout_value.nil? && options[:options]["layout"].nil? && options[:always_prompt] != true
390
- #version_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'type' => 'select', 'fieldLabel' => 'Version', 'optionSource' => 'instanceVersions', 'required' => true, 'skipSingleOption' => true, 'autoPickOption' => true, 'description' => 'Select which version of the instance type to be provisioned.', 'defaultValue' => default_version_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})
391
- version_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'type' => 'select', 'fieldLabel' => 'Version', 'selectOptions' => available_versions, 'required' => version_is_required, 'skipSingleOption' => true, 'autoPickOption' => true, 'description' => 'Select which version of the instance type to be provisioned.', 'defaultValue' => default_version_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})
392
- version_value = version_prompt['version']
474
+ # do not require version if a layout is passed
475
+ version_value = default_version_value
476
+ version_is_required = default_layout_value.nil?
477
+ if default_layout_value.nil? && options[:options]["layout"].nil? && options[:always_prompt] != true
478
+ #version_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'type' => 'select', 'fieldLabel' => 'Version', 'optionSource' => 'instanceVersions', 'required' => true, 'skipSingleOption' => true, 'autoPickOption' => true, 'description' => 'Select which version of the instance type to be provisioned.', 'defaultValue' => default_version_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})
479
+ version_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'type' => 'select', 'fieldLabel' => 'Version', 'selectOptions' => available_versions, 'required' => version_is_required, 'skipSingleOption' => true, 'autoPickOption' => true, 'description' => 'Select which version of the instance type to be provisioned.', 'defaultValue' => default_version_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})
480
+ version_value = version_prompt['version']
481
+ end
393
482
  end
483
+
394
484
  # JD: there is a bug here, the version needs to be passed perhaps? or the optionSource methods need updating...
395
485
  # could just allow for now ...
396
486
  # if options[:options]["layout"]
397
487
  # layout_id = options[:options]["layout"]
398
488
  # ...
399
489
  # end
400
- layout_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.', 'defaultValue' => default_layout_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], version: version_value})
401
- layout_id = layout_prompt['layout']
402
- layout = instance_type['instanceTypeLayouts'].find{ |lt| lt['id'] == layout_id.to_i}
490
+ layout_id = options[:layout].to_i if options[:layout]
491
+
492
+ if !layout_id
493
+ layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.', 'defaultValue' => default_layout_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], version: version_value, creatable: true})['layout']
494
+ end
495
+
496
+ layout = find_instance_type_layout_by_id(instance_type['id'], layout_id.to_i)
403
497
  if !layout
404
498
  print_red_alert "Layout not found by id #{layout_id}"
405
499
  exit 1
@@ -407,22 +501,31 @@ module Morpheus::Cli::ProvisioningHelper
407
501
  payload['instance']['layout'] = {'id' => layout['id']}
408
502
 
409
503
  # prompt for service plan
410
- service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, layoutId: layout_id, siteId: group_id})
504
+ service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, layoutId: layout['id'], siteId: group_id})
411
505
  service_plans = service_plans_json["plans"]
412
- service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"], 'code' => sp['code']} } # already sorted
413
- default_plan = nil
414
- if payload['plan']
415
- default_plan = payload['plan']
416
- elsif payload['instance'] && payload['instance']['plan']
417
- default_plan = payload['instance']['plan']
418
- end
419
- default_plan_value = default_plan.is_a?(Hash) ? default_plan['id'] : default_plan
420
- plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this instance', 'defaultValue' => default_plan_value}],options[:options])
421
- plan_id = plan_prompt['servicePlan']
422
- service_plan = service_plans.find {|sp| sp["id"] == plan_id.to_i }
506
+ service_plan = service_plans.find {|sp| sp['id'] == options[:service_plan].to_i} if options[:service_plan]
507
+
423
508
  if !service_plan
424
- print_red_alert "Plan not found by id #{plan_id}"
425
- exit 1
509
+ service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"], 'code' => sp['code']} } # already sorted
510
+ default_plan = nil
511
+ if payload['plan']
512
+ default_plan = payload['plan']
513
+ elsif payload['instance'] && payload['instance']['plan']
514
+ default_plan = payload['instance']['plan']
515
+ end
516
+
517
+ if options[:default_plan] && service_plans_dropdown.find {|sp| [sp["name"], sp["value"].to_s, sp["code"]].include?(options[:default_plan].to_s)}
518
+ default_plan_value = options[:default_plan]
519
+ else
520
+ default_plan_value = options[:default_plan] || (default_plan.is_a?(Hash) ? default_plan['id'] : default_plan)
521
+ end
522
+ plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this instance', 'defaultValue' => default_plan_value}],options[:options])
523
+ plan_id = plan_prompt['servicePlan']
524
+ service_plan = service_plans.find {|sp| sp["id"] == plan_id.to_i }
525
+ if !service_plan
526
+ print_red_alert "Plan not found by id #{plan_id}"
527
+ exit 1
528
+ end
426
529
  end
427
530
  #todo: consolidate these, instances api looks for instance.plan.id and apps looks for plan.id
428
531
  payload['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
@@ -458,50 +561,87 @@ module Morpheus::Cli::ProvisioningHelper
458
561
  # pluck out the resourcePoolId option type to prompt for
459
562
  resource_pool_option_type = option_type_list.find {|opt| ['resourcePool','resourcePoolId','azureResourceGroupId'].include?(opt['fieldName']) }
460
563
  option_type_list = option_type_list.reject {|opt| ['resourcePool','resourcePoolId','azureResourceGroupId'].include?(opt['fieldName']) }
461
- resource_pool_option_type ||= {'fieldContext' => 'config', 'fieldName' => 'resourcePoolId', 'type' => 'select', 'fieldLabel' => 'Resource Pool', 'optionSource' => 'zonePools', 'required' => true, 'skipSingleOption' => true, 'description' => 'Select resource pool.'}
462
- resource_pool_prompt = Morpheus::Cli::OptionTypes.prompt([resource_pool_option_type],options[:options],api_client,{groupId: group_id, siteId: group_id, zoneId: cloud_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], planId: service_plan["id"], layoutId: layout["id"]})
463
- resource_pool_prompt.deep_compact!
464
- payload.deep_merge!(resource_pool_prompt)
465
- resource_pool = Morpheus::Cli::OptionTypes.get_last_select()
466
- if resource_pool_option_type['fieldContext'] && resource_pool_prompt[resource_pool_option_type['fieldContext']]
467
- pool_id = resource_pool_prompt[resource_pool_option_type['fieldContext']][resource_pool_option_type['fieldName']]
468
- elsif resource_pool_prompt[resource_pool_option_type['fieldName']]
469
- pool_id = resource_pool_prompt[resource_pool_option_type['fieldName']]
564
+ resource_pool_options = @options_interface.options_for_source('zonePools', {groupId: group_id, siteId: group_id, zoneId: cloud_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], planId: service_plan["id"], layoutId: layout["id"]})['data']
565
+ resource_pool = resource_pool_options.find {|opt| opt['id'] == options[:resource_pool].to_i} if options[:resource_pool]
566
+
567
+ if resource_pool
568
+ pool_id = resource_pool['id']
569
+ else
570
+ if options[:default_resource_pool]
571
+ default_resource_pool = resource_pool_options.find {|rp| rp['id'] == options[:default_resource_pool]}
572
+ end
573
+ resource_pool_option_type ||= {'fieldContext' => 'config', 'fieldName' => 'resourcePoolId', 'type' => 'select', 'fieldLabel' => 'Resource Pool', 'selectOptions' => resource_pool_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Select resource pool.', 'defaultValue' => default_resource_pool ? default_resource_pool['name'] : nil}
574
+ resource_pool_prompt = Morpheus::Cli::OptionTypes.prompt([resource_pool_option_type],options[:options],api_client,{})
575
+ resource_pool_prompt.deep_compact!
576
+ payload.deep_merge!(resource_pool_prompt)
577
+ resource_pool = Morpheus::Cli::OptionTypes.get_last_select()
578
+ if resource_pool_option_type['fieldContext'] && resource_pool_prompt[resource_pool_option_type['fieldContext']]
579
+ pool_id = resource_pool_prompt[resource_pool_option_type['fieldContext']][resource_pool_option_type['fieldName']]
580
+ elsif resource_pool_prompt[resource_pool_option_type['fieldName']]
581
+ pool_id = resource_pool_prompt[resource_pool_option_type['fieldName']]
582
+ end
470
583
  end
471
584
  end
472
585
 
473
586
  # remove host selection for kubernetes
474
- if resource_pool && resource_pool['providerType'] == 'kubernetes'
475
- option_type_list = option_type_list.reject {|opt|
476
- ['provisionServerId'].include?(opt['fieldName'])
477
- }
587
+ if resource_pool
588
+ payload['config']['poolProviderType'] = resource_pool['providerType'] if resource_pool['providerType']
589
+ if resource_pool['providerType'] == 'kubernetes'
590
+ option_type_list = option_type_list.reject {|opt| ['provisionServerId'].include?(opt['fieldName'])}
591
+ end
592
+
593
+ # add selectable datastores for resource pool
594
+ if options[:select_datastore]
595
+ service_plan['datastores'] = {'cluster' => [], 'store' => []}
596
+ selectable_datastores = cloud_datastores_interface.selectable(cloud_id, {'siteId' => group_id, 'resourcePoolId' => resource_pool['id']})
597
+ ['cluster', 'store'].each do |type|
598
+ service_plan['datastores'][type] ||= []
599
+ selectable_datastores[type].reject { |ds| service_plan['datastores'][type].find {|it| it['id'] == ds['id']} }.each { |ds|
600
+ service_plan['datastores'][type] << ds
601
+ }
602
+ end
603
+
604
+ if layout['provisionType'] && layout['provisionType']['supportsAutoDatastore']
605
+ service_plan['autoOptions'] ||= []
606
+ if service_plan['datastores']['cluster'].count > 0 && !service_plan['autoOptions'].find {|it| it['id'] == 'autoCluster'}
607
+ service_plan['autoOptions'] << {'id' => 'autoCluster', 'name' => 'Auto - Cluster'}
608
+ end
609
+ if service_plan['datastores']['store'].count > 0 && !service_plan['autoOptions'].find {|it| it['id'] == 'auto'}
610
+ service_plan['autoOptions'] << {'id' => 'auto', 'name' => 'Auto - Datastore'}
611
+ end
612
+ end
613
+ end
478
614
  end
479
615
 
480
616
  # plan_info has this property already..
481
617
  # has_datastore = layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasDatastore"]
482
618
  # service_plan['hasDatastore'] = has_datastore
483
619
 
620
+ # set root volume name if has mounts
621
+ mounts = (layout['mounts'] || []).reject {|it| !it['canPersist']}
622
+ if mounts.count > 0
623
+ options[:root_volume_name] = mounts[0]['shortName']
624
+ end
625
+
484
626
  # prompt for volumes
485
- # if payload['volumes'].nil?
486
- volumes = prompt_volumes(service_plan, options, api_client, {zoneId: cloud_id, layoutId: layout_id, siteId: group_id})
487
- if !volumes.empty?
488
- payload['volumes'] = volumes
489
- end
490
- # end
627
+ volumes = prompt_volumes(service_plan, options, api_client, {zoneId: cloud_id, layoutId: layout['id'], siteId: group_id})
628
+ if !volumes.empty?
629
+ payload['volumes'] = volumes
630
+ end
491
631
 
492
- # if payload['networkInterfaces'].nil?
493
- if layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasNetworks"]
494
- # prompt for network interfaces (if supported)
495
- begin
496
- network_interfaces = prompt_network_interfaces(cloud_id, layout["provisionType"]["id"], pool_id, options)
497
- if !network_interfaces.empty?
498
- payload['networkInterfaces'] = network_interfaces
499
- end
500
- rescue RestClient::Exception => e
501
- print yellow,"Unable to load network options. Proceeding...",reset,"\n"
502
- print_rest_exception(e, options) if Morpheus::Logging.debug?
632
+ # prompt networks
633
+ if layout["provisionType"] && layout["provisionType"]["id"] && layout["provisionType"]["hasNetworks"] # && layout["provisionType"]["supportsNetworkSelection"]
634
+ # prompt for network interfaces (if supported)
635
+ begin
636
+ network_interfaces = prompt_network_interfaces(cloud_id, layout["provisionType"]["id"], pool_id, options)
637
+ if !network_interfaces.empty?
638
+ payload['networkInterfaces'] = network_interfaces
503
639
  end
640
+ rescue RestClient::Exception => e
641
+ print yellow,"Unable to load network options. Proceeding...",reset,"\n"
642
+ print_rest_exception(e, options) if Morpheus::Logging.debug?
504
643
  end
644
+ end
505
645
  # end
506
646
 
507
647
  # Security Groups
@@ -511,27 +651,34 @@ module Morpheus::Cli::ProvisioningHelper
511
651
  # ok.. seed data has changed and serverTypes do not have this optionType anymore...
512
652
  if sg_option_type.nil?
513
653
  if layout["provisionType"] && (layout["provisionType"]["code"] == 'amazon')
514
- sg_option_type = {'fieldContext' => 'config', 'fieldName' => 'securityId', 'type' => 'select', 'fieldLabel' => 'Security Group', 'optionSource' => 'amazonSecurityGroup', 'required' => true, 'description' => 'Select security group.'}
654
+ sg_option_type = {'fieldContext' => 'config', 'fieldName' => 'securityId', 'type' => 'select', 'fieldLabel' => 'Security Group', 'optionSource' => 'amazonSecurityGroup', 'required' => true, 'description' => 'Select security group.', 'defaultValue' => options[:default_security_group]}
515
655
  end
516
656
  end
517
657
  has_security_groups = !!sg_option_type
518
658
  if options[:security_groups]
519
659
  payload['securityGroups'] = options[:security_groups].collect {|sg_id| {'id' => sg_id} }
520
- else
521
- if has_security_groups
522
- security_groups_array = prompt_security_groups(sg_option_type, {zoneId: cloud_id, poolId: pool_id}, options)
523
- if !security_groups_array.empty?
524
- payload['securityGroups'] = security_groups_array.collect {|sg_id| {'id' => sg_id} }
525
- end
660
+ elsif has_security_groups
661
+ security_groups_array = prompt_security_groups(sg_option_type, {zoneId: cloud_id, poolId: pool_id}, options)
662
+ if !security_groups_array.empty?
663
+ payload['securityGroups'] = security_groups_array.collect {|sg_id| {'id' => sg_id} }
526
664
  end
527
665
  end
528
666
 
529
-
530
667
  # prompt for option types
531
668
  api_params = {groupId: group_id, cloudId: cloud_id, zoneId: cloud_id, instanceTypeId: instance_type['id'], version: version_value}
532
669
  api_params['config'] = payload['config'] if payload['config']
533
670
  api_params['poolId'] = payload['config']['resourcePoolId'] if payload['config'] && payload['config']['resourcePoolId']
534
671
 
672
+ # set option type defaults from config
673
+ if options[:default_config]
674
+ options[:default_config].each do |k,v|
675
+ if v && !(v.kind_of?(Array) && v.empty?)
676
+ option_type = option_type_list.find {|ot| ot['fieldContext'] == 'config' && ot['fieldName'] == k}
677
+ option_type['defaultValue'] = v if option_type
678
+ end
679
+ end
680
+ end
681
+
535
682
  instance_config_payload = Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, api_params)
536
683
  payload.deep_merge!(instance_config_payload)
537
684
 
@@ -558,10 +705,10 @@ module Morpheus::Cli::ProvisioningHelper
558
705
  # returns array of volumes based on service plan options (plan_info)
559
706
  def prompt_volumes(plan_info, options={}, api_client=nil, api_params={})
560
707
  #puts "Configure Volumes:"
561
- no_prompt = (options[:no_prompt] || (options[:options] && options[:options][:no_prompt]))
708
+ # return [] if plan_info['noDisks']
562
709
 
710
+ no_prompt = (options[:no_prompt] || (options[:options] && options[:options][:no_prompt]))
563
711
  volumes = []
564
-
565
712
  plan_size = nil
566
713
  if plan_info['maxStorage']
567
714
  plan_size = plan_info['maxStorage'].to_i / (1024 * 1024 * 1024)
@@ -615,7 +762,7 @@ module Morpheus::Cli::ProvisioningHelper
615
762
 
616
763
  field_context = "rootVolume"
617
764
 
618
- volume_label = 'root'
765
+ volume_label = options[:root_volume_name] || 'root'
619
766
  volume = {
620
767
  'id' => -1,
621
768
  'rootVolume' => true,
@@ -634,7 +781,8 @@ module Morpheus::Cli::ProvisioningHelper
634
781
  storage_type_id = nil
635
782
  storage_type = nil
636
783
  else
637
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'storageType', 'type' => 'select', 'fieldLabel' => 'Root Storage Type', 'selectOptions' => root_storage_types, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a storage type.', 'defaultValue' => volume['storageType']}], options[:options])
784
+ default_storage_type = root_storage_types.find {|t| t['value'].to_s == volume['storageType'].to_s}
785
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'storageType', 'type' => 'select', 'fieldLabel' => 'Root Storage Type', 'selectOptions' => root_storage_types, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a storage type.', 'defaultValue' => default_storage_type ? default_storage_type['name'] : volume['storageType']}], options[:options])
638
786
  storage_type_id = v_prompt[field_context]['storageType']
639
787
  storage_type = plan_info['storageTypes'].find {|i| i['id'] == storage_type_id.to_i }
640
788
  volume['storageType'] = storage_type_id
@@ -670,8 +818,10 @@ module Morpheus::Cli::ProvisioningHelper
670
818
  #volume['sizeId'] = nil #volume.delete('sizeId')
671
819
  end
672
820
  if !datastore_options.empty?
673
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => 'Root Datastore', 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => volume['datastoreId']}], options[:options])
821
+ default_datastore = datastore_options.find {|ds| ds['value'].to_s == volume['datastoreId'].to_s}
822
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => 'Root Datastore', 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => default_datastore ? default_datastore['name'] : volume['datastoreId']}], options[:options])
674
823
  volume['datastoreId'] = v_prompt[field_context]['datastoreId']
824
+ volume['hasDatastore'] = true
675
825
  end
676
826
 
677
827
  volumes << volume
@@ -736,6 +886,7 @@ module Morpheus::Cli::ProvisioningHelper
736
886
  if !datastore_options.empty?
737
887
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => "Disk #{volume_index} Datastore", 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => volume['datastoreId']}], options[:options])
738
888
  volume['datastoreId'] = v_prompt[field_context]['datastoreId']
889
+ volume['hasDatastore'] = true
739
890
  end
740
891
 
741
892
  volumes << volume
@@ -1056,6 +1207,10 @@ module Morpheus::Cli::ProvisioningHelper
1056
1207
  if network_groups
1057
1208
  networks = network_groups + networks
1058
1209
  end
1210
+ network_subnets = zone_network_data['networkSubnets']
1211
+ if network_subnets
1212
+ networks += network_subnets
1213
+ end
1059
1214
  network_interface_types = (zone_network_data['networkTypes'] || []).sort { |x,y| x['displayOrder'] <=> y['displayOrder'] }
1060
1215
  enable_network_type_selection = (zone_network_data['enableNetworkTypeSelection'] == 'on' || zone_network_data['enableNetworkTypeSelection'] == true)
1061
1216
  has_networks = zone_network_data["hasNetworks"] == true
@@ -1102,11 +1257,26 @@ module Morpheus::Cli::ProvisioningHelper
1102
1257
  network_interface = options[:options]['networkInterfaces'][interface_index]
1103
1258
  end
1104
1259
 
1260
+ default_network_id = network_interface['networkId'] || network_interface['id']
1261
+
1262
+ if network_interface['network'] && network_interface['network']['id']
1263
+ if network_interface['network']['subnet']
1264
+ default_network_id = "subnet-#{network_interface['network']['subnet']}"
1265
+ elsif network_interface['network']['group']
1266
+ default_network_id = "networkGroup-#{network_interface['network']['group']}"
1267
+ else
1268
+ default_network_id = "network-#{network_interface['network']['id']}"
1269
+ end
1270
+ end
1271
+
1272
+ default_network_value = (network_options.find {|n| n['value'] == default_network_id} || {})['name']
1273
+
1105
1274
  # choose network
1106
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'networkId', 'type' => 'select', 'fieldLabel' => "Network", 'selectOptions' => network_options, 'required' => true, 'skipSingleOption' => false, 'description' => 'Choose a network for this interface.', 'defaultValue' => network_interface['networkId']}], options[:options])
1275
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'networkId', 'type' => 'select', 'fieldLabel' => "Network", 'selectOptions' => network_options, 'required' => true, 'skipSingleOption' => false, 'description' => 'Choose a network for this interface.', 'defaultValue' => default_network_value}], options[:options])
1107
1276
  network_interface['network'] = {}
1108
1277
  network_interface['network']['id'] = v_prompt[field_context]['networkId'].to_s
1109
1278
  selected_network = networks.find {|it| it["id"].to_s == network_interface['network']['id'] }
1279
+ network_options.reject! {|it| it['value'] == v_prompt[field_context]['networkId']}
1110
1280
 
1111
1281
  if !selected_network
1112
1282
  print_red_alert "Network not found by id #{network_interface['network']['id']}!"
@@ -1115,7 +1285,8 @@ module Morpheus::Cli::ProvisioningHelper
1115
1285
 
1116
1286
  # choose network interface type
1117
1287
  if enable_network_type_selection && !network_interface_type_options.empty?
1118
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'networkInterfaceTypeId', 'type' => 'select', 'fieldLabel' => "Network Interface Type", 'selectOptions' => network_interface_type_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a network interface type.', 'defaultValue' => network_interface['networkInterfaceTypeId']}], options[:options])
1288
+ default_interface_type_value = (network_interface_type_options.find {|t| t['value'].to_s == network_interface['networkInterfaceTypeId'].to_s} || {})['name']
1289
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'networkInterfaceTypeId', 'type' => 'select', 'fieldLabel' => "Network Interface Type", 'selectOptions' => network_interface_type_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a network interface type.', 'defaultValue' => default_interface_type_value}], options[:options])
1119
1290
  network_interface['networkInterfaceTypeId'] = v_prompt[field_context]['networkInterfaceTypeId'].to_i
1120
1291
  end
1121
1292
 
@@ -1140,7 +1311,7 @@ module Morpheus::Cli::ProvisioningHelper
1140
1311
  interface_index += 1
1141
1312
  if options[:options] && options[:options]['networkInterfaces'] && options[:options]['networkInterfaces'][interface_index]
1142
1313
  add_another_interface = true
1143
- elsif max_networks && network_interfaces.size >= max_networks
1314
+ elsif (max_networks && network_interfaces.size >= max_networks) || network_options.count == 0
1144
1315
  add_another_interface = false
1145
1316
  else
1146
1317
  has_another_interface = options[:options] && options[:options]["networkInterface#{interface_index+1}"]
@@ -1211,12 +1382,14 @@ module Morpheus::Cli::ProvisioningHelper
1211
1382
  def prompt_security_groups(sg_option_type, api_params, options)
1212
1383
  no_prompt = (options[:no_prompt] || (options[:options] && options[:options][:no_prompt]))
1213
1384
  security_groups_array = []
1214
-
1215
1385
  sg_required = sg_option_type['required']
1216
1386
  sg_index = 0
1217
1387
  add_another_sg = sg_required || (!no_prompt && Morpheus::Cli::OptionTypes.confirm("Add a security group?", {default: false}))
1218
1388
  while add_another_sg do
1219
1389
  cur_sg_option_type = sg_option_type.merge({'required' => (sg_index == 0 ? sg_required : false)})
1390
+ if sg_index == 0 && !options[:default_security_group].nil?
1391
+ cur_sg_option_type['defaultValue'] = options[:default_security_group]
1392
+ end
1220
1393
  field_context = cur_sg_option_type['fieldContext']
1221
1394
  field_name = cur_sg_option_type['fieldName']
1222
1395
  v_prompt = Morpheus::Cli::OptionTypes.prompt([cur_sg_option_type], options[:options], api_client, api_params)
@@ -1331,4 +1504,289 @@ module Morpheus::Cli::ProvisioningHelper
1331
1504
  [{'name' => 'Dev', 'value' => 'dev'}, {'name' => 'Test', 'value' => 'qa'}, {'name' => 'Staging', 'value' => 'staging'}, {'name' => 'Production', 'value' => 'production'}]
1332
1505
  end
1333
1506
 
1507
+ def add_perms_options(opts, options, excludes = [])
1508
+ if !excludes.include?('groups')
1509
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
1510
+ options[:groupAccessAll] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1511
+ end
1512
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
1513
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
1514
+ options[:groupAccessList] = []
1515
+ else
1516
+ options[:groupAccessList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
1517
+ end
1518
+ end
1519
+ if !excludes.include?('groupDefaults')
1520
+ opts.on('--group-defaults LIST', Array, "Group Default Selection, comma separated list of group IDs") do |list|
1521
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
1522
+ options[:groupDefaultsList] = []
1523
+ else
1524
+ options[:groupDefaultsList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
1525
+ end
1526
+ end
1527
+ end
1528
+ end
1529
+
1530
+ if !excludes.include?('plans')
1531
+ opts.on('--plan-access-all [on|off]', String, "Toggle Access for all service plans.") do |val|
1532
+ options[:planAccessAll] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1533
+ end
1534
+ opts.on('--plan-access LIST', Array, "Service Plan Access, comma separated list of plan IDs.") do |list|
1535
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
1536
+ options[:planAccessList] = []
1537
+ else
1538
+ options[:planAccessList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
1539
+ end
1540
+ end
1541
+ opts.on('--plan-defaults LIST', Array, "Plan Default Selection, comma separated list of plan IDs") do |list|
1542
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
1543
+ options[:planDefaultsList] = []
1544
+ else
1545
+ options[:planDefaultsList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
1546
+ end
1547
+ end
1548
+ end
1549
+
1550
+ if !excludes.include?('visibility')
1551
+ opts.on('--visibility VISIBILITY', String, "Visibility [private|public]") do |val|
1552
+ options[:visibility] = val
1553
+ end
1554
+ end
1555
+
1556
+ if !excludes.include?('tenants')
1557
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
1558
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
1559
+ options[:tenants] = []
1560
+ else
1561
+ options[:tenants] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
1562
+ end
1563
+ end
1564
+ end
1565
+ end
1566
+
1567
+ def prompt_permissions(options, excludes = [])
1568
+ all_groups = nil
1569
+ group_access = nil
1570
+ all_plans = nil
1571
+ plan_access = nil
1572
+
1573
+ # Group Access
1574
+ if !excludes.include?('groups')
1575
+ if !options[:groupAccessAll].nil?
1576
+ all_groups = options[:groupAccessAll]
1577
+ end
1578
+
1579
+ if !options[:groupAccessList].empty?
1580
+ group_access = options[:groupAccessList].collect {|site_id| {'id' => site_id.to_i}} || []
1581
+ elsif !options[:no_prompt]
1582
+ available_groups = options[:available_groups] || get_available_groups
1583
+
1584
+ if available_groups.empty?
1585
+ #print_red_alert "No available groups"
1586
+ #exit 1
1587
+ elsif available_groups.count > 1
1588
+ available_groups.unshift({"id" => '0', "name" => "All", "value" => "all"})
1589
+
1590
+ # default to all
1591
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group Access', 'selectOptions' => available_groups, 'required' => false, 'description' => 'Add Group Access.'}], options[:options], @api_client, {})['group']
1592
+
1593
+ if !group_id.nil?
1594
+ if group_id == 'all'
1595
+ all_groups = true
1596
+ else
1597
+ group_access = (excludes.include?('groupDefaults') ? [{'id' => group_id}] : [{'id' => group_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_groups.find{|it| it['value'] == group_id}['name']}' as default?", {:default => false})}])
1598
+ end
1599
+ available_groups = available_groups.reject {|it| it['value'] == group_id}
1600
+
1601
+ while !group_id.nil? && !available_groups.empty? && Morpheus::Cli::OptionTypes.confirm("Add another group access?", {:default => false})
1602
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group Access', 'selectOptions' => available_groups, 'required' => false, 'description' => 'Add Group Access.'}], options[:options], @api_client, {})['group']
1603
+
1604
+ if !group_id.nil?
1605
+ if group_id == 'all'
1606
+ all_groups = true
1607
+ else
1608
+ group_access ||= []
1609
+ group_access << (excludes.include?('groupDefaults') ? {'id' => group_id} : {'id' => group_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_groups.find{|it| it['value'] == group_id}['name']}' as default?", {:default => false})})
1610
+ end
1611
+ available_groups = available_groups.reject {|it| it['value'] == group_id}
1612
+ end
1613
+ end
1614
+ end
1615
+ end
1616
+ end
1617
+ end
1618
+
1619
+ # Plan Access
1620
+ if !excludes.include?('plans')
1621
+ if !options[:planAccessAll].nil?
1622
+ all_plans = options[:planAccessAll]
1623
+ end
1624
+
1625
+ if !options[:planAccessList].empty?
1626
+ plan_access = options[:planAccessList].collect {|plan_id| {'id' => plan_id.to_i}}
1627
+ elsif !options[:no_prompt]
1628
+ available_plans = options[:available_plans] || get_available_plans
1629
+
1630
+ if available_plans.empty?
1631
+ #print_red_alert "No available plans"
1632
+ #exit 1
1633
+ elsif !available_plans.empty? && !options[:no_prompt]
1634
+ available_plans.unshift({"id" => '0', "name" => "All", "value" => "all"})
1635
+
1636
+ # default to all
1637
+ plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Service Plan Access', 'selectOptions' => available_plans, 'required' => false, 'description' => 'Add Service Plan Access.'}], options[:options], @api_client, {})['plan']
1638
+
1639
+ if !plan_id.nil?
1640
+ if plan_id == 'all'
1641
+ all_plans = true
1642
+ else
1643
+ plan_access = [{'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['id'] == plan_id}['name']}' as default?", {:default => false})}]
1644
+ end
1645
+
1646
+ available_plans = available_plans.reject {|it| it['value'] == plan_id}
1647
+
1648
+ while !plan_id.nil? && !available_plans.empty? && Morpheus::Cli::OptionTypes.confirm("Add another service plan access?", {:default => false})
1649
+ plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Service Plan Access', 'selectOptions' => available_plans, 'required' => false, 'description' => 'Add Service Plan Access.'}], options[:options], @api_client, {})['plan']
1650
+
1651
+ if !plan_id.nil?
1652
+ if plan_id == 'all'
1653
+ all_plans = true
1654
+ else
1655
+ plan_access ||= []
1656
+ plan_access << {'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['value'] == plan_id}['name']}' as default?", {:default => false})}
1657
+ end
1658
+ available_plans = available_plans.reject {|it| it['value'] == plan_id}
1659
+ end
1660
+ end
1661
+ end
1662
+ end
1663
+ end
1664
+ end
1665
+
1666
+ resource_perms = {}
1667
+ resource_perms['all'] = all_groups if !all_groups.nil?
1668
+ resource_perms['sites'] = group_access if !group_access.nil?
1669
+ resource_perms['allPlans'] = all_plans if !all_plans.nil?
1670
+ resource_perms['plans'] = plan_access if !plan_access.nil?
1671
+
1672
+ permissions = {'resourcePermissions' => resource_perms}
1673
+
1674
+ available_accounts = get_available_accounts.collect {|it| {'name' => it['name'], 'value' => it['id']}}
1675
+ accounts = []
1676
+
1677
+ # Prompts for multi tenant
1678
+ if available_accounts.count > 1
1679
+ visibility = options[:visibility]
1680
+
1681
+ if !visibility && !options[:no_prompt]
1682
+ visibility = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Tenant Permissions Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility']
1683
+ end
1684
+
1685
+ permissions['resourcePool'] = {'visibility' => visibility} if visibility
1686
+
1687
+ # Tenants
1688
+ if !excludes.include?('tenants')
1689
+ if !options[:tenants].nil?
1690
+ accounts = options[:tenants].collect {|id| id.to_i}
1691
+ elsif !options[:no_prompt]
1692
+ account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id']
1693
+
1694
+ if !account_id.nil?
1695
+ accounts << account_id
1696
+ available_accounts = available_accounts.reject {|it| it['value'] == account_id}
1697
+
1698
+ while !available_accounts.empty? && (account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Another Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id'])
1699
+ if !account_id.nil?
1700
+ accounts << account_id
1701
+ available_accounts = available_accounts.reject {|it| it['value'] == account_id}
1702
+ end
1703
+ end
1704
+ end
1705
+ end
1706
+ permissions['tenantPermissions'] = {'accounts' => accounts}
1707
+ end
1708
+ end
1709
+ permissions
1710
+ end
1711
+
1712
+ def print_permissions(permissions, excludes = [])
1713
+ if permissions.nil?
1714
+ print_h2 "Permissions"
1715
+ print yellow,"No permissions found.",reset,"\n"
1716
+ else
1717
+ if !permissions['resourcePermissions'].nil?
1718
+ if !excludes.include?('groups')
1719
+ print_h2 "Group Access"
1720
+
1721
+ if excludes.include?('groupDefaults')
1722
+ groups = []
1723
+ groups << 'All' if permissions['resourcePermissions']['all']
1724
+ groups += permissions['resourcePermissions']['sites'].collect {|it| it['name']} if permissions['resourcePermissions']['sites']
1725
+
1726
+ if groups.count > 0
1727
+ print cyan,"#{groups.join(', ')}".center(20)
1728
+ else
1729
+ print yellow,"No group access",reset,"\n"
1730
+ end
1731
+ print "\n"
1732
+ else
1733
+ rows = []
1734
+ if permissions['resourcePermissions']['all']
1735
+ rows.push({group: 'All'})
1736
+ end
1737
+ if permissions['resourcePermissions']['sites']
1738
+ permissions['resourcePermissions']['sites'].each do |site|
1739
+ rows.push({group: site['name'], default: site['default'] ? 'Yes' : ''})
1740
+ end
1741
+ end
1742
+ if rows.empty?
1743
+ print yellow,"No group access",reset,"\n"
1744
+ else
1745
+ columns = [:group, :default]
1746
+ print cyan
1747
+ print as_pretty_table(rows, columns)
1748
+ end
1749
+ end
1750
+ end
1751
+
1752
+ if !excludes.include?('plans')
1753
+ print_h2 "Plan Access"
1754
+ rows = []
1755
+ if permissions['resourcePermissions']['allPlans']
1756
+ rows.push({plan: 'All'})
1757
+ end
1758
+ if permissions['resourcePermissions']['plans']
1759
+ permissions['resourcePermissions']['plans'].each do |plan|
1760
+ rows.push({plan: plan['name'], default: plan['default'] ? 'Yes' : ''})
1761
+ end
1762
+ end
1763
+ if rows.empty?
1764
+ print yellow,"No plan access",reset,"\n"
1765
+ else
1766
+ columns = [:plan, :default]
1767
+ print cyan
1768
+ print as_pretty_table(rows, columns)
1769
+ end
1770
+ end
1771
+
1772
+ if !excludes.include?('tenants')
1773
+ if !permissions['tenantPermissions'].nil?
1774
+ print_h2 "Tenant Permissions"
1775
+ if !permissions['resourcePool'].nil?
1776
+ print cyan
1777
+ print "Visibility: #{permissions['resourcePool']['visibility'].to_s.capitalize}".center(20)
1778
+ print "\n"
1779
+ end
1780
+ if !permissions['tenantPermissions'].nil?
1781
+ print cyan
1782
+ print "Accounts: #{permissions['tenantPermissions']['accounts'].join(', ')}".center(20)
1783
+ print "\n"
1784
+ end
1785
+ end
1786
+ end
1787
+ print "\n"
1788
+ end
1789
+ end
1790
+ end
1791
+
1334
1792
  end