morpheus-cli 6.0.1 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4160be97bfb96acf0fe1091e9e8d6ecde617bd2aba547ef1cfab8a11d6134ecf
4
- data.tar.gz: 5e0675e4e1061595e560e2395625e7a7546813e68e09a8237ccd33d92aeb948d
3
+ metadata.gz: 57fffe54fd7ee43a1bd35bb15703bb85edca71ec59758374d9556d1743338f90
4
+ data.tar.gz: 3472c73b3bc3bf91b84e437eb593b367d86cf8aa32439d370eb6c15de40674fb
5
5
  SHA512:
6
- metadata.gz: a2992a310163d78082f7d748ca4ae50c673c32e7ae782497bb89f721135ed66509549c124290e9b4bd644cad2e3838c878331427ddd1090325f8946c7d30e731
7
- data.tar.gz: 48ec07412e0cdcad359ea2dc050cc5017a9a39e368fb662e50b880628213fc4681b85da9cd5d8503ea7426be0fbac77b5e61d2dd8bc397ab13f0ce21695d2758
6
+ metadata.gz: e87cbd833e3633ddf03129d7eabfdb11b12a2956f6c6f66b43a5d42fd41cbb7a209085b6f1008e89ffbcf98251b8d6fc8e0e0d1b845a952b863c0f6add10386b
7
+ data.tar.gz: f51460150a7ef907e7c679d75c54830405ef18323c26aa0bcf1bb158c03ff7e05c53114b0e9df2edf4bba337ec6c843871f0fd3bd0ab9ad15ce9ceed836f2bfe
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 6.0.1
3
+ RUN gem install morpheus-cli -v 6.1.0
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -6,6 +6,10 @@ class Morpheus::BackupsInterface < Morpheus::RestInterface
6
6
  "/api/backups"
7
7
  end
8
8
 
9
+ def create_options(payload, params={}, headers={})
10
+ execute(method: :post, url: "#{base_path}/create", params: params, payload: payload, headers: headers)
11
+ end
12
+
9
13
  def summary(params={})
10
14
  execute(method: :get, url: "#{base_path}/summary", params: params)
11
15
  end
@@ -23,6 +23,22 @@ class Morpheus::NetworkServicesInterface < Morpheus::APIClient
23
23
  execute(method: :get, url: url, headers: headers)
24
24
  end
25
25
 
26
+ def get_server(server_id)
27
+ execute(method: :get, url: "#{@base_url}/api/networks/servers/#{server_id}", params: {}, headers: {})
28
+ end
29
+
30
+ def list_servers()
31
+ execute(method: :get, url: "#{@base_url}/api/networks/servers", params: {}, headers: {})
32
+ end
33
+
34
+ def refresh(server_id)
35
+ url = "#{@base_url}/api/networks/servers/#{server_id}/refresh"
36
+
37
+ headers = { :params => {}, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
38
+ opts = {method: :post, url: url, headers: headers}
39
+ execute(opts)
40
+ end
41
+
26
42
  # def create(payload)
27
43
  # url = "#{@base_url}/api/networks/services"
28
44
  # headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -293,6 +293,7 @@ module Morpheus
293
293
  #opts.separator ""
294
294
  # opts.separator "Common options:"
295
295
  option_keys = includes.clone
296
+ all_option_keys = option_keys.dup
296
297
  # todo: support --quiet everywhere
297
298
  # turn on some options all the time..
298
299
  # unless command_name == "shell"
@@ -445,7 +446,8 @@ module Morpheus
445
446
  raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
446
447
  end
447
448
  end
448
- opts.on('--payload-dir DIRECTORY', String, "Payload from a local directory containing 1-N JSON or YAML files, skip all prompting.") do |val|
449
+ opts.on('--payload-dir DIRECTORY', String, "Payload from a local directory containing 1-N JSON or YAML files, skip all prompting. This makes one request, merging all the files into a single payload.") do |val|
450
+ print_error yellow,"[DEPRECATED] The option `--payload-dir` is deprecated and will be removed. Use `--payloads` to make requests for each file in a directory.",reset,"\n"
449
451
  options[:payload_dir] = val.to_s
450
452
  payload_dir = File.expand_path(options[:payload_dir])
451
453
  if !Dir.exist?(payload_dir) || !File.directory?(payload_dir)
@@ -476,6 +478,7 @@ module Morpheus
476
478
  raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
477
479
  end
478
480
  end
481
+ opts.add_hidden_option('--payload-dir')
479
482
  opts.on('--payload-json JSON', String, "Payload JSON, skip all prompting") do |val|
480
483
  begin
481
484
  options[:payload] = JSON.parse(val.to_s)
@@ -490,7 +493,69 @@ module Morpheus
490
493
  raise ::OptionParser::InvalidOption.new("Failed to parse payload as YAML. Error: #{ex.message}")
491
494
  end
492
495
  end
493
-
496
+ # --payloads test-data/item*.json
497
+ opts.on('--payloads PATH', String, "Payload(s) from one or more local JSON or YAML files, skip all prompting and execute the request 1-N times, once for each file. PATH can be a directory or a file pattern.") do |val|
498
+ # maybe use parse_array(val) to support csv..
499
+ # find files matching PATH
500
+ # todo: probably support recursive... can be done with '**/*.json' now though.
501
+ if val.to_s.strip.empty?
502
+ raise ::OptionParser::InvalidOption.new("PATH must be provided as directory, file or pattern to find JSON or YAML files.")
503
+ end
504
+ filepath = File.expand_path(val.to_s.strip)
505
+ files = []
506
+ if File.directory?(filepath)
507
+ # passed the name of a directory, include all the JSON and YAML files directly under it
508
+ Dir.glob(File.join(filepath, "*")).each do |file|
509
+ if File.file?(file) && ['.json','.yaml','.yml'].include?(File.extname(file))
510
+ files << file
511
+ end
512
+ end
513
+ if files.empty?
514
+ raise ::OptionParser::InvalidOption.new("Failed to find any .json or .yaml files under the directory: #{filepath}")
515
+ end
516
+ elsif File.file?(filepath)
517
+ # passed the name of a file
518
+ files << filepath
519
+ else
520
+ # assume it is a pattern to find files with
521
+ files = Dir.glob(filepath)
522
+ if files.empty?
523
+ raise ::OptionParser::InvalidOption.new("Failed to find any files matching path: #{filepath}")
524
+ end
525
+ end
526
+ # parse files as JSON or YAML
527
+ options[:payload_files] ||= []
528
+ options[:payloads] ||= []
529
+ files.each do |file|
530
+ if options[:payload_files].include?(file)
531
+ next
532
+ else
533
+ options[:payload_files] << file
534
+ end
535
+ payload = nil
536
+ begin
537
+ payload_file = File.expand_path(file)
538
+ if !File.exist?(payload_file) || !File.file?(payload_file)
539
+ raise ::OptionParser::InvalidOption.new("File not found: #{payload_file}")
540
+ end
541
+ # todo: could use parse_json_or_yaml()
542
+ payload = nil
543
+ if payload_file =~ /\.ya?ml\Z/
544
+ payload = YAML.load_file(payload_file)
545
+ else
546
+ payload = JSON.parse(File.read(payload_file))
547
+ end
548
+ options[:payloads] << payload
549
+ rescue => ex
550
+ raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
551
+ end
552
+ end
553
+ end if all_option_keys.include?(:payloads)
554
+ opts.on('--payloads-ignore-error', "Continue processing payloads if an error occurs. The default behavior is to stop processing when an error occurs.") do
555
+ options[:payloads_ignore_error] = true
556
+ end if all_option_keys.include?(:payloads)
557
+ when :payloads
558
+ # added under when :payloads... just need it here to avoid unknown key error
494
559
  when :list
495
560
  opts.on( '-m', '--max MAX', "Max Results" ) do |val|
496
561
  # api supports max=-1 for all at the moment..
@@ -1435,6 +1500,54 @@ module Morpheus
1435
1500
  payload
1436
1501
  end
1437
1502
 
1503
+ def parse_payloads(options={}, object_key=nil, &block)
1504
+ payloads = []
1505
+ if options[:payload]
1506
+ # --payload option was used
1507
+ payload = options[:payload]
1508
+ # support -O OPTION switch on top of --payload
1509
+ apply_options(payload, options, object_key)
1510
+ payloads << payload
1511
+ elsif options[:payloads]
1512
+ # --payloads option was used
1513
+ payloads = options[:payloads]
1514
+ # payloads.each { |it| apply_options(it, options, object_key) }
1515
+ else
1516
+ # default is to construct one using the block
1517
+ payload = {}
1518
+ apply_options(payload, options, object_key)
1519
+ if block_given?
1520
+ result = yield payload
1521
+ #payload = result if result
1522
+ end
1523
+ payloads << payload
1524
+ end
1525
+ return payloads
1526
+ end
1527
+
1528
+ def process_payloads(payloads, options, &block)
1529
+ if !payloads.is_a?(Array) || payloads.compact.empty?
1530
+ raise "process_payloads() requires an Array of at least one payload and instead got: (#{payloads.class}) #{payloads.inspect}"
1531
+ end
1532
+ results = []
1533
+ payloads.each do |payload|
1534
+ begin
1535
+ result = yield payload
1536
+ results << [0, nil]
1537
+ rescue => e
1538
+ if options[:payloads_ignore_error]
1539
+ # results << [1, e.message]
1540
+ result = Morpheus::Cli::ErrorHandler.new(my_terminal.stderr).handle_error(e) # lol
1541
+ results << result
1542
+ # continue
1543
+ else
1544
+ raise e
1545
+ end
1546
+ end
1547
+ end
1548
+ return results.last
1549
+ end
1550
+
1438
1551
  def build_payload(options, object_key=nil)
1439
1552
  payload = {}
1440
1553
  if options[:payload]
@@ -10,7 +10,7 @@ class Morpheus::Cli::BackupJobsCommand
10
10
 
11
11
  set_command_name :'backup-jobs'
12
12
 
13
- register_subcommands :list, :get #, :add, :update, :remove, :run
13
+ register_subcommands :list, :get, :add, :update, :remove, :run
14
14
 
15
15
  def connect(opts)
16
16
  @api_client = establish_remote_appliance_connection(opts)
@@ -55,11 +55,7 @@ class Morpheus::Cli::BackupJobsCommand
55
55
  end
56
56
  print reset,"\n"
57
57
  end
58
- if backup_jobs.empty?
59
- return 1, "no backup jobs found"
60
- else
61
- return 0, nil
62
- end
58
+ return 0, nil
63
59
  end
64
60
 
65
61
  def get(args)
@@ -250,7 +246,7 @@ EOT
250
246
  def add_backup_job_option_types
251
247
  [
252
248
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
253
- {'fieldName' => 'code', 'fieldLabel' => 'Code', 'type' => 'text', 'required' => true, 'displayOrder' => 2},
249
+ #{'fieldName' => 'code', 'fieldLabel' => 'Code', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
254
250
  {'fieldName' => 'retentionCount', 'fieldLabel' => 'Retention Count', 'type' => 'number', 'displayOrder' => 3},
255
251
  {'fieldName' => 'scheduleId', 'fieldLabel' => 'Schedule', 'type' => 'select', 'optionSource' => 'executeSchedules', 'displayOrder' => 4}, # should use jobSchedules instead maybe? do we support manual schedules for backups?
256
252
  ]
@@ -10,12 +10,14 @@ class Morpheus::Cli::BackupsCommand
10
10
 
11
11
  set_command_name :'backups'
12
12
 
13
- register_subcommands :list, :get #, :add, :update, :remove, :run, :restore
13
+ register_subcommands :list, :get, :add, :update, :remove, :run, :restore
14
14
 
15
15
  def connect(opts)
16
16
  @api_client = establish_remote_appliance_connection(opts)
17
17
  @backups_interface = @api_client.backups
18
18
  @backup_jobs_interface = @api_client.backup_jobs
19
+ @instances_interface = @api_client.instances
20
+ @servers_interface = @api_client.servers
19
21
  end
20
22
 
21
23
  def handle(args)
@@ -50,16 +52,12 @@ class Morpheus::Cli::BackupsCommand
50
52
  if backups.empty?
51
53
  print cyan,"No backups found.",reset,"\n"
52
54
  else
53
- print as_pretty_table(backups, backup_column_definitions.upcase_keys!, options)
55
+ print as_pretty_table(backups, backup_list_column_definitions.upcase_keys!, options)
54
56
  print_results_pagination(json_response)
55
57
  end
56
58
  print reset,"\n"
57
59
  end
58
- if backups.empty?
59
- return 1, "no backups found"
60
- else
61
- return 0, nil
62
- end
60
+ return 0, nil
63
61
  end
64
62
 
65
63
  def get(args)
@@ -83,6 +81,13 @@ EOT
83
81
  end
84
82
 
85
83
  def _get(id, params, options)
84
+ if id.to_s !~ /\A\d{1,}\Z/
85
+ record = find_by_name(:backup, id)
86
+ if record.nil?
87
+ return 1, "Backup not found for '#{id}'"
88
+ end
89
+ id = record['id']
90
+ end
86
91
  @backups_interface.setopts(options)
87
92
  if options[:dry_run]
88
93
  print_dry_run @backups_interface.dry.get(id, params)
@@ -93,7 +98,11 @@ EOT
93
98
  render_response(json_response, options, 'backup') do
94
99
  print_h1 "Backup Details", [], options
95
100
  print cyan
96
- print_description_list(backup_column_definitions, backup)
101
+ columns = backup_column_definitions
102
+ columns.delete("Instance") if backup['instance'].nil?
103
+ columns.delete("Container ID") if backup['containerId'].nil?
104
+ columns.delete("Host") if backup['server'].nil?
105
+ print_description_list(columns, backup, options)
97
106
  print reset,"\n"
98
107
  end
99
108
  return 0, nil
@@ -104,8 +113,26 @@ EOT
104
113
  params = {}
105
114
  optparse = Morpheus::Cli::OptionParser.new do |opts|
106
115
  opts.banner = subcommand_usage("[name] [options]")
107
- build_option_type_options(opts, options, add_backup_option_types)
108
- build_option_type_options(opts, options, add_backup_advanced_option_types)
116
+ # build_option_type_options(opts, options, add_backup_option_types)
117
+ opts.on('--source VALUE', String, "Backup Source: instance, host or provider") do |val|
118
+ options[:options]['source'] = val
119
+ end
120
+ opts.on('--instance VALUE', String, "Instance Name or ID") do |val|
121
+ options[:options]['source'] = 'instance'
122
+ options[:options]['instanceId'] = val
123
+ end
124
+ opts.on('--host VALUE', String, "Host Name or ID") do |val|
125
+ options[:options]['source'] = 'server'
126
+ options[:options]['serverId'] = val
127
+ end
128
+ opts.on('--server VALUE', String, "alias for --host") do |val|
129
+ options[:options]['source'] = 'server'
130
+ options[:options]['serverId'] = val
131
+ end
132
+ opts.add_hidden_option('--server')
133
+ opts.on('--name VALUE', String, "Name") do |val|
134
+ options[:options]['name'] = val
135
+ end
109
136
  build_standard_add_options(opts, options)
110
137
  opts.footer = <<-EOT
111
138
  Create a new backup.
@@ -121,11 +148,74 @@ EOT
121
148
  payload.deep_merge!({'backup' => parse_passed_options(options)})
122
149
  else
123
150
  payload.deep_merge!({'backup' => parse_passed_options(options)})
124
- v_prompt = Morpheus::Cli::OptionTypes.prompt(add_backup_option_types(), options[:options], @api_client, options[:params])
125
- params.deep_merge!(v_prompt)
126
- advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_backup_advanced_option_types, options[:options], @api_client, options[:params])
127
- advanced_config.deep_compact!
128
- params.deep_merge!(advanced_config)
151
+
152
+ location_type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'source', 'fieldLabel' => 'Source', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Host', 'value' => 'server'}, {'name' => 'Provider', 'value' => 'provider'}], 'defaultValue' => 'instance', 'required' => true, 'description' => 'Where is the backup located?'}], options[:options], @api_client)['source']
153
+ params['locationType'] = location_type
154
+ if location_type == 'instance'
155
+ # Instance
156
+ avail_instances = @instances_interface.list({max:10000})['instances'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
157
+ params['instanceId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'instanceId', 'fieldLabel' => 'Instance', 'type' => 'select', 'selectOptions' => avail_instances, 'required' => true}], options[:options], @api_client)['instanceId']
158
+ # Name
159
+ params['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Backup Name'}], options[:options], @api_client)['name']
160
+ elsif location_type == 'server'
161
+ # Server
162
+ avail_servers = @servers_interface.list({max:10000, 'vmHypervisor' => nil, 'containerHypervisor' => nil})['servers'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
163
+ params['serverId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'serverId', 'fieldLabel' => 'Host', 'type' => 'select', 'selectOptions' => avail_servers, 'required' => true}], options[:options], @api_client)['serverId']
164
+ # Name
165
+ params['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Backup Name'}], options[:options], @api_client)['name']
166
+ elsif location_type == 'provider'
167
+ # todo: prompt for provider inputs
168
+ # sourceProviderId
169
+ # storageProvider
170
+ end
171
+ # POST to /create to get available option info for containers, backupTypes, backupProviderTypes, etc.
172
+ payload['backup'].deep_merge!(params)
173
+ create_results = @backups_interface.create_options(payload)
174
+
175
+ if location_type == 'instance' || location_type == 'server'
176
+ if location_type == 'instance'
177
+ # Container
178
+ avail_containers = (create_results['containers'] || []).collect {|it| {'name' => it['name'], 'value' => it['id']} }
179
+ if avail_containers.empty?
180
+ raise_command_error "No available containers found for selected instance"
181
+ else
182
+ params['containerId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'containerId', 'fieldLabel' => 'Container', 'type' => 'select', 'selectOptions' => avail_containers, 'defaultValue' => avail_containers[0] ? avail_containers[0]['name'] : nil, 'required' => true}], options[:options], @api_client)['containerId']
183
+ end
184
+ elsif location_type == 'server'
185
+
186
+
187
+ end
188
+ # Backup Type
189
+ avail_backup_types = (create_results['backupTypes'] || []).collect {|it| {'name' => it['name'], 'value' => it['code']} }
190
+ if avail_backup_types.empty?
191
+ raise_command_error "No available backup types found"
192
+ else
193
+ params['backupType'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'backupType', 'fieldLabel' => 'Backup Type', 'type' => 'select', 'selectOptions' => avail_backup_types, 'defaultValue' => avail_backup_types[0] ? avail_backup_types[0]['name'] : nil, 'required' => true}], options[:options], @api_client)['backupType']
194
+ end
195
+
196
+ # Job / Schedule
197
+ params['jobAction'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobAction', 'fieldLabel' => 'Backup Job Type', 'type' => 'select', 'optionSource' => 'backupJobActions', 'required' => true, 'defaultValue' => 'new'}], options[:options], @api_client)['jobAction']
198
+ if params['jobAction'] == 'new'
199
+ params['jobName'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobName', 'fieldLabel' => 'Job Name', 'type' => 'text', 'required' => false, 'defaultValue' => nil}], options[:options], @api_client)['jobName']
200
+ default_retention_count = create_results['backup'] ? create_results['backup']['retentionCount'] : nil
201
+ params['retentionCount'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'retentionCount', 'fieldLabel' => 'Retention Count', 'type' => 'number', 'required' => false, 'defaultValue' => default_retention_count}], options[:options], @api_client)['retentionCount']
202
+ params['jobSchedule'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobSchedule', 'fieldLabel' => 'Backup Schedule', 'type' => 'select', 'optionSource' => 'executeSchedules', 'required' => true}], options[:options], @api_client)['jobSchedule']
203
+ elsif params['jobAction'] == 'clone'
204
+ params['jobId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobId', 'fieldLabel' => 'Backup Job', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
205
+ @backup_jobs_interface.list({max:10000})['jobs'].collect {|backup_job|
206
+ {'name' => backup_job['name'], 'value' => backup_job['id'], 'id' => backup_job['id']}
207
+ }
208
+ }, 'required' => true}], options[:options], @api_client)['jobId']
209
+ params['jobName'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobName', 'fieldLabel' => 'Job Name', 'type' => 'text', 'required' => false, 'defaultValue' => nil}], options[:options], @api_client)['jobName']
210
+ elsif params['jobAction'] == 'addTo'
211
+ params['jobId'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'jobId', 'fieldLabel' => 'Backup Job', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
212
+ @backup_jobs_interface.list({max:10000})['jobs'].collect {|backup_job|
213
+ {'name' => backup_job['name'], 'value' => backup_job['id'], 'id' => backup_job['id']}
214
+ }
215
+ }, 'required' => true}], options[:options], @api_client)['jobId']
216
+ end
217
+ end
218
+
129
219
  payload['backup'].deep_merge!(params)
130
220
  end
131
221
  @backups_interface.setopts(options)
@@ -226,10 +316,33 @@ EOT
226
316
 
227
317
  private
228
318
 
319
+ def backup_list_column_definitions()
320
+ {
321
+ "ID" => 'id',
322
+ "Name" => 'name',
323
+ "Schedule" => lambda {|it| it['schedule']['name'] rescue '' },
324
+ "Backup Job" => lambda {|it| it['job']['name'] rescue '' },
325
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
326
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
327
+ }
328
+ end
329
+
229
330
  def backup_column_definitions()
230
331
  {
231
332
  "ID" => 'id',
232
333
  "Name" => 'name',
334
+ "Location Type" => lambda {|it|
335
+ if it['locationType'] == "instance"
336
+ "Instance"
337
+ elsif it['locationType'] == "server"
338
+ "Host"
339
+ elsif it['locationType'] == "storage"
340
+ "Provider"
341
+ end
342
+ },
343
+ "Instance" => lambda {|it| it['instance']['name'] rescue '' },
344
+ "Container ID" => lambda {|it| it['containerId'] rescue '' },
345
+ "Host" => lambda {|it| it['server']['name'] rescue '' },
233
346
  "Schedule" => lambda {|it| it['schedule']['name'] rescue '' },
234
347
  "Backup Job" => lambda {|it| it['job']['name'] rescue '' },
235
348
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
@@ -240,13 +353,14 @@ EOT
240
353
  # this is not so simple, need to first choose select instance, host or provider
241
354
  def add_backup_option_types
242
355
  [
243
- {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
356
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
357
+ {'fieldName' => 'backupType', 'fieldLabel' => 'Backup Type', 'type' => 'select', 'optionSource' => 'backupTypes', 'required' => true},
358
+ {'fieldName' => 'jobAction', 'fieldLabel' => 'Backup Job Type', 'type' => 'select', 'optionSource' => 'backupJobActions', 'required' => true},
244
359
  {'fieldName' => 'jobId', 'fieldLabel' => 'Backup Job', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
245
- # @options_interface.options_for_source("licenseTypes", {})['data']
246
- @backup_jobs_interface.list({max:10000})['backupJobs'].collect {|backup_job|
360
+ @backup_jobs_interface.list({max:10000})['jobs'].collect {|backup_job|
247
361
  {'name' => backup_job['name'], 'value' => backup_job['id'], 'id' => backup_job['id']}
248
362
  }
249
- }, 'required' => true, 'displayOrder' => 3},
363
+ }, 'required' => true},
250
364
  ]
251
365
  end
252
366
 
@@ -27,7 +27,11 @@ class Morpheus::Cli::CurlCommand
27
27
  curl_method = "DELETE"
28
28
  end
29
29
  opts.on( '--data DATA', String, "HTTP request body for use with POST and PUT, typically JSON." ) do |val|
30
- curl_data = val
30
+ begin
31
+ options[:payload] = JSON.parse(val.to_s)
32
+ rescue => ex
33
+ raise ::OptionParser::InvalidOption.new("Failed to parse payload as JSON. Error: #{ex.message}")
34
+ end
31
35
  end
32
36
  opts.on('--absolute', "Absolute path, value can be used to prevent automatic using the automatic /api/ path prefix to the path by default.") do
33
37
  options[:absolute_path] = true
@@ -350,7 +350,7 @@ class Morpheus::Cli::InvoicesCommand
350
350
  if cost_rows.sum { |it| it[:extra].to_f } == 0
351
351
  cost_columns.delete("Extra".upcase)
352
352
  end
353
- print as_pretty_table(cost_rows, cost_columns, options)
353
+ print as_pretty_table(cost_rows, cost_columns, options.merge(include_fields: nil))
354
354
  else
355
355
  print "\n"
356
356
  print yellow, "No invoice totals data", reset, "\n"
@@ -641,7 +641,7 @@ Update an invoice.
641
641
  payload[:date] = val.to_s
642
642
  end
643
643
  opts.on( '--rebuild', "Rebuild invoices for period. Only applies to mode=costing." ) do |val|
644
- query_params[:rebuild] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
644
+ payload[:rebuild] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
645
645
  end
646
646
  build_standard_update_options(opts, options, [:query, :auto_confirm])
647
647
  opts.footer = <<-EOT
@@ -24,6 +24,9 @@ class Morpheus::Cli::LoadBalancers
24
24
  render_response(json_response, options, rest_object_key) do
25
25
  record = json_response[rest_object_key]
26
26
  options[:exclude_username] = record['username'].nil?
27
+ options[:exclude_owner] = record['owner'].nil?
28
+ options[:exclude_tenants] = record['tenants'].nil? || record['tenants'].empty?
29
+ options[:exclude_permissions] = record['resourcePermission'].nil?
27
30
  print_h1 rest_label, [], options
28
31
  print cyan
29
32
  print_description_list(rest_column_definitions(options), record, options)
@@ -125,6 +128,8 @@ EOT
125
128
  "Provider ID" => 'externalId'
126
129
  }
127
130
  columns.merge!({"Username" => 'username'}) if !options[:exclude_username]
131
+ columns.merge!({"Owner" => lambda { |it| it['owner']['name'] rescue '' }}) if !options[:exclude_owner]
132
+ columns.merge!({"Tenants" => lambda { |it| it['tenants'].collect {|tenant| tenant['name']}.join(', ') rescue '' }}) if !options[:exclude_tenants]
128
133
  columns.merge({
129
134
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
130
135
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
@@ -7,7 +7,7 @@ class Morpheus::Cli::NetworkServicesCommand
7
7
  set_command_name :'network-services'
8
8
 
9
9
  # register_subcommands :list, :get, :add, :update, :remove
10
- register_subcommands :list
10
+ register_subcommands :list, :refresh
11
11
 
12
12
  # set_default_subcommand :list
13
13
 
@@ -90,6 +90,43 @@ class Morpheus::Cli::NetworkServicesCommand
90
90
  exit 1
91
91
  end
92
92
  end
93
+
94
+ def refresh(args)
95
+ options = {}
96
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
97
+ opts.banner = subcommand_usage("[name]")
98
+ build_common_options(opts, options, [:auto_confirm, :quiet, :json, :dry_run, :remote])
99
+ opts.footer = "Refresh a network integration/server.\n" +
100
+ "[name] is required. This is the name or id of a network server/integration."
101
+ end
102
+ optparse.parse!(args)
103
+ if args.count < 1
104
+ puts optparse
105
+ exit 1
106
+ end
107
+ connect(options)
108
+ begin
109
+ server = find_network_server_by_name_or_id(args[0])
110
+ if !server
111
+ exit 1
112
+ end
113
+ @network_services_interface.setopts(options)
114
+ if options[:dry_run]
115
+ print_dry_run @network_services_interface.dry.refresh(server['id'])
116
+ return
117
+ end
118
+ json_response = @network_services_interface.refresh(server['id'])
119
+ if options[:json]
120
+ print JSON.pretty_generate(json_response)
121
+ print "\n"
122
+ elsif !options[:quiet]
123
+ print_green_success "Refreshing #{server["name"]}"
124
+ end
125
+ rescue RestClient::Exception => e
126
+ print_rest_exception(e, options)
127
+ exit 1
128
+ end
129
+ end
93
130
 
94
131
 
95
132
  private
@@ -136,4 +173,26 @@ class Morpheus::Cli::NetworkServicesCommand
136
173
  end
137
174
  end
138
175
 
176
+ def find_network_server_by_name_or_id(val)
177
+ if val.to_s =~ /\A\d{1,}\Z/
178
+ return find_network_server_by_id(val)
179
+ else
180
+ return find_network_service_by_name(val)
181
+ end
182
+ end
183
+
184
+ def find_network_server_by_id(id)
185
+ begin
186
+ json_response = @network_services_interface.get_server(id.to_i)
187
+ return json_response['networkServer']
188
+ rescue RestClient::Exception => e
189
+ if e.response && e.response.code == 404
190
+ print_red_alert "Network Service not found by id #{id}"
191
+ return nil
192
+ else
193
+ raise e
194
+ end
195
+ end
196
+ end
197
+
139
198
  end
@@ -222,8 +222,10 @@ class Morpheus::Cli::NetworksCommand
222
222
  "Domain" => lambda {|it| it['networkDomain'] ? it['networkDomain']['name'] : '' },
223
223
  "Search Domains" => lambda {|it| it['searchDomains'] },
224
224
  "Pool" => lambda {|it| it['pool'] ? it['pool']['name'] : '' },
225
+ "IPv6 Pool" => lambda {|it| it['poolIPv6'] ? it['poolIPv6']['name'] : '' },
225
226
  "VPC" => lambda {|it| it['zonePool'] ? it['zonePool']['name'] : '' },
226
227
  "DHCP" => lambda {|it| it['dhcpServer'] ? 'Yes' : 'No' },
228
+ "IPv6 DHCP" => lambda {|it| it['dhcpServerIPv6'] ? 'Yes' : 'No' },
227
229
  "Active" => lambda {|it| format_boolean(it['active']) },
228
230
  "Allow IP Override" => lambda {|it| it['allowStaticOverride'] ? 'Yes' : 'No' },
229
231
  "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
@@ -381,6 +383,12 @@ class Morpheus::Cli::NetworksCommand
381
383
  opts.on('--dhcp-server [on|off]', String, "DHCP Server") do |val|
382
384
  options['dhcpServer'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
383
385
  end
386
+ opts.on('--dhcp-serverIPv6 [on|off]', String, "IPv6 DHCP Server") do |val|
387
+ options['dhcpServerIPv6'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
388
+ end
389
+ opts.on('--dhcp-server-ipv6 [on|off]', String, "IPv6 DHCP Server") do |val|
390
+ options['dhcpServerIPv6'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
391
+ end
384
392
  opts.on('--allow-ip-override [on|off]', String, "Allow IP Override") do |val|
385
393
  options['allowStaticOverride'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
386
394
  end
@@ -723,6 +731,15 @@ class Morpheus::Cli::NetworksCommand
723
731
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dnsSecondaryIPv6', 'fieldLabel' => 'IPv6 DNS Secondary', 'type' => 'text', 'required' => false, 'description' => ''}], options)
724
732
  payload['network']['dnsSecondaryIPv6'] = v_prompt['dnsSecondaryIPv6']
725
733
  end
734
+ end
735
+ # DHCP Server
736
+ if network_type['dhcpServerEditable'] && payload['network']['dhcpServerIPv6'].nil?
737
+ if options['dhcpServerIPv6'] != nil
738
+ payload['network']['dhcpServerIPv6'] = options['dhcpServerIPv6']
739
+ else
740
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dhcpServerIPv6', 'fieldLabel' => 'IPv6 DHCP Server', 'type' => 'checkbox', 'required' => false, 'description' => ''}], options)
741
+ payload['network']['dhcpServerIPv6'] = v_prompt['dhcpServerIPv6']
742
+ end
726
743
  end
727
744
  end
728
745
 
@@ -1003,6 +1020,12 @@ class Morpheus::Cli::NetworksCommand
1003
1020
  opts.on('--dhcp-server [on|off]', String, "DHCP Server") do |val|
1004
1021
  options['dhcpServer'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1005
1022
  end
1023
+ opts.on('--dhcp-serverIPv6 [on|off]', String, "IPv6 DHCP Server") do |val|
1024
+ options['dhcpServerIPv6'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1025
+ end
1026
+ opts.on('--dhcp-server-ipv6 [on|off]', String, "IPv6 DHCP Server") do |val|
1027
+ options['dhcpServerIPv6'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1028
+ end
1006
1029
  opts.on('--allow-ip-override [on|off]', String, "Allow IP Override") do |val|
1007
1030
  options['allowStaticOverride'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
1008
1031
  end
@@ -1244,6 +1267,14 @@ class Morpheus::Cli::NetworksCommand
1244
1267
  # payload['network']['dhcpServer'] = v_prompt['dhcpServer']
1245
1268
  end
1246
1269
 
1270
+ # IPv6 DHCP Server
1271
+ if options['dhcpServerIPv6'] != nil
1272
+ payload['network']['dhcpServerIPv6'] = options['dhcpServerIPv6']
1273
+ else
1274
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'dhcpServerIPv6', 'fieldLabel' => 'IPv6 DHCP Server', 'type' => 'checkbox', 'required' => false, 'description' => ''}], options)
1275
+ # payload['network']['dhcpServerIPv6'] = v_prompt['dhcpServerIPv6']
1276
+ end
1277
+
1247
1278
  # Allow IP Override
1248
1279
  if options['allowStaticOverride'] != nil
1249
1280
  payload['network']['allowStaticOverride'] = options['allowStaticOverride']
@@ -544,13 +544,12 @@ EOT
544
544
  def update_cart(args)
545
545
  options = {}
546
546
  params = {}
547
- payload = {}
548
547
  optparse = Morpheus::Cli::OptionParser.new do |opts|
549
548
  opts.banner = subcommand_usage("--name [name]")
550
549
  opts.on('--name [NAME]', String, "Set an optional name for your catalog order") do |val|
551
550
  options[:options]['name'] = val.to_s
552
551
  end
553
- build_standard_update_options(opts, options, [:sigdig])
552
+ build_standard_update_options(opts, options, [:payloads, :sigdig])
554
553
  opts.footer = <<-EOT
555
554
  Update your cart settings, such as name.
556
555
  EOT
@@ -560,29 +559,26 @@ EOT
560
559
  connect(options)
561
560
  # fetch current cart
562
561
  # cart = @service_catalog_interface.get_cart()['cart']
563
- payload = {}
564
562
  update_cart_object_key = 'order'
565
- if options[:payload]
566
- payload = options[:payload]
563
+ payloads = parse_payloads(options, update_cart_object_key) do |payload|
567
564
  payload.deep_merge!({update_cart_object_key => parse_passed_options(options)})
568
- else
569
- payload.deep_merge!({update_cart_object_key => parse_passed_options(options)})
570
- payload.deep_merge!({update_cart_object_key => params})
571
565
  if payload[update_cart_object_key].empty? # || options[:no_prompt]
572
566
  raise_command_error "Specify at least one option to update.\n#{optparse}"
573
567
  end
574
568
  end
575
- @service_catalog_interface.setopts(options)
576
- if options[:dry_run]
577
- print_dry_run @service_catalog_interface.dry.update_cart(payload)
578
- return
579
- end
580
- json_response = @service_catalog_interface.update_cart(payload)
581
- #cart = json_response['cart']
582
- #cart = @service_catalog_interface.get_cart()['cart']
583
- render_response(json_response, options, 'cart') do
584
- print_green_success "Updated cart"
585
- get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
569
+ process_payloads(payloads, options) do |payload|
570
+ @service_catalog_interface.setopts(options)
571
+ if options[:dry_run]
572
+ print_dry_run @service_catalog_interface.dry.update_cart(payload)
573
+ next
574
+ end
575
+ json_response = @service_catalog_interface.update_cart(payload)
576
+ #cart = json_response['cart']
577
+ #cart = @service_catalog_interface.get_cart()['cart']
578
+ render_response(json_response, options, 'cart') do
579
+ print_green_success "Updated cart"
580
+ get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
581
+ end
586
582
  end
587
583
  return 0, nil
588
584
  end
@@ -590,7 +586,6 @@ EOT
590
586
  def add(args)
591
587
  options = {}
592
588
  params = {}
593
- payload = {}
594
589
  type_id = nil
595
590
  workflow_context = nil
596
591
  workflow_target = nil
@@ -613,7 +608,7 @@ EOT
613
608
  workflow_target = val.to_s
614
609
  end
615
610
  opts.add_hidden_option('--sigdig')
616
- build_standard_update_options(opts, options, [:sigdig])
611
+ build_standard_update_options(opts, options, [:payloads, :sigdig])
617
612
  opts.footer = <<-EOT
618
613
  Add an item to your cart
619
614
  [type] is required, this is name or id of a catalog item type.
@@ -626,13 +621,8 @@ EOT
626
621
  if args.count > 0
627
622
  type_id = args.join(" ")
628
623
  end
629
- payload = {}
630
624
  add_item_object_key = 'item'
631
- payload = {add_item_object_key => {} }
632
- if options[:payload]
633
- payload = options[:payload]
634
- payload.deep_merge!({add_item_object_key => parse_passed_options(options)})
635
- else
625
+ payloads = parse_payloads(options, add_item_object_key) do |payload|
636
626
  payload.deep_merge!({add_item_object_key => parse_passed_options(options)})
637
627
  # prompt for Type
638
628
  if type_id
@@ -725,49 +715,45 @@ EOT
725
715
  if options[:validate_only]
726
716
  params['validate'] = true
727
717
  end
728
- @service_catalog_interface.setopts(options)
729
- if options[:dry_run]
730
- print_dry_run @service_catalog_interface.dry.create_cart_item(payload, params)
731
- return
732
- end
733
- json_response = @service_catalog_interface.create_cart_item(payload, params)
734
- cart_item = json_response['item']
735
- render_response(json_response, options) do
736
- if options[:validate_only]
737
- if json_response['success']
738
- print_h2 "Validated Cart Item", [], options
739
- cart_item_columns = {
740
- "Type" => lambda {|it| it['type']['name'] rescue '' },
741
- #"Qty" => lambda {|it| it['quantity'] },
742
- "Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" },
743
- "Status" => lambda {|it|
744
- status_string = format_catalog_item_status(it)
745
- if it['errorMessage'].to_s != ""
746
- status_string << " - #{it['errorMessage']}"
747
- end
748
- status_string
749
- },
750
- #"Config" => lambda {|it| truncate_string(format_name_values(it['config']), 50) }
751
- }
752
- print as_pretty_table([cart_item], cart_item_columns.upcase_keys!)
753
- print reset, "\n"
754
- print_green_success(json_response['msg'] || "Item is valid")
755
- print reset, "\n"
718
+ process_payloads(payloads, options) do |payload|
719
+ @service_catalog_interface.setopts(options)
720
+ if options[:dry_run]
721
+ print_dry_run @service_catalog_interface.dry.create_cart_item(payload, params)
722
+ next
723
+ end
724
+ json_response = @service_catalog_interface.create_cart_item(payload, params)
725
+ cart_item = json_response['item']
726
+ render_response(json_response, options) do
727
+ if options[:validate_only]
728
+ if json_response['success']
729
+ print_h2 "Validated Cart Item", [], options
730
+ cart_item_columns = {
731
+ "Type" => lambda {|it| it['type']['name'] rescue '' },
732
+ #"Qty" => lambda {|it| it['quantity'] },
733
+ "Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" },
734
+ "Status" => lambda {|it|
735
+ status_string = format_catalog_item_status(it)
736
+ if it['errorMessage'].to_s != ""
737
+ status_string << " - #{it['errorMessage']}"
738
+ end
739
+ status_string
740
+ },
741
+ #"Config" => lambda {|it| truncate_string(format_name_values(it['config']), 50) }
742
+ }
743
+ print as_pretty_table([cart_item], cart_item_columns.upcase_keys!)
744
+ print reset, "\n"
745
+ print_green_success(json_response['msg'] || "Item is valid")
746
+ print reset, "\n"
747
+ else
748
+ # not needed because it will be http 400
749
+ print_rest_errors(json_response, options)
750
+ end
756
751
  else
757
- # not needed because it will be http 400
758
- print_rest_errors(json_response, options)
752
+ print_green_success "Added item to cart"
753
+ get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
759
754
  end
760
- else
761
- print_green_success "Added item to cart"
762
- get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
763
755
  end
764
756
  end
765
- if json_response['success']
766
- return 0, nil
767
- else
768
- # not needed because it will be http 400
769
- return 1, json_response['msg'] || 'request failed'
770
- end
771
757
  end
772
758
 
773
759
  def update_cart_item(args)
@@ -882,7 +868,6 @@ EOT
882
868
  def checkout(args)
883
869
  options = {}
884
870
  params = {}
885
- payload = {}
886
871
  optparse = Morpheus::Cli::OptionParser.new do |opts|
887
872
  opts.banner = subcommand_usage()
888
873
  build_standard_add_options(opts, options, [:auto_confirm, :sigdig])
@@ -936,7 +921,6 @@ EOT
936
921
  def add_order(args)
937
922
  options = {}
938
923
  params = {}
939
- payload = {}
940
924
  type_id = nil
941
925
  workflow_context = nil
942
926
  workflow_target = nil
@@ -961,7 +945,7 @@ EOT
961
945
  opts.on('--target ID', String, "Target Resource (Instance or Server) for operational workflow types") do |val|
962
946
  workflow_target = val.to_s
963
947
  end
964
- build_standard_add_options(opts, options, [:sigdig])
948
+ build_standard_add_options(opts, options, [:payloads, :sigdig])
965
949
  opts.footer = <<-EOT
966
950
  Place an order for new inventory.
967
951
  This allows creating a new order without using the cart.
@@ -978,14 +962,8 @@ EOT
978
962
  end
979
963
  payload = {}
980
964
  order_object_key = 'order'
981
- payload = {order_object_key => {} }
982
- passed_options = parse_passed_options(options)
983
- if options[:payload]
984
- payload = options[:payload]
985
- payload.deep_merge!({order_object_key => passed_options}) unless passed_options.empty?
986
- else
987
- payload.deep_merge!({order_object_key => passed_options}) unless passed_options.empty?
988
-
965
+ payloads = parse_payloads(options, order_object_key) do |payload|
966
+ payload.deep_merge!({order_object_key => {}})
989
967
  # Prompt for 1-N Types
990
968
  # still_prompting = options[:no_prompt] != true
991
969
  still_prompting = true
@@ -1106,36 +1084,32 @@ EOT
1106
1084
  params['validate'] = true
1107
1085
  #payload['validate'] = true
1108
1086
  end
1109
- @service_catalog_interface.setopts(options)
1110
- if options[:dry_run]
1111
- print_dry_run @service_catalog_interface.dry.create_order(payload, params)
1112
- return
1113
- end
1114
- json_response = @service_catalog_interface.create_order(payload, params)
1115
- order = json_response['order'] || json_response['cart']
1116
- render_response(json_response, options) do
1117
- if options[:validate_only]
1118
- if json_response['success']
1119
- print_h2 "Review Order", [], options
1120
- print_order_details(order, options)
1121
- print_green_success(json_response['msg'] || "Order is valid")
1122
- print reset, "\n"
1087
+ process_payloads(payloads, options) do |payload|
1088
+ @service_catalog_interface.setopts(options)
1089
+ if options[:dry_run]
1090
+ print_dry_run @service_catalog_interface.dry.create_order(payload, params)
1091
+ next
1092
+ end
1093
+ json_response = @service_catalog_interface.create_order(payload, params)
1094
+ order = json_response['order'] || json_response['cart']
1095
+ render_response(json_response, options) do
1096
+ if options[:validate_only]
1097
+ if json_response['success']
1098
+ print_h2 "Review Order", [], options
1099
+ print_order_details(order, options)
1100
+ print_green_success(json_response['msg'] || "Order is valid")
1101
+ print reset, "\n"
1102
+ else
1103
+ # not needed because it will be http 400
1104
+ print_rest_errors(json_response, options)
1105
+ end
1123
1106
  else
1124
- # not needed because it will be http 400
1125
- print_rest_errors(json_response, options)
1107
+ print_green_success "Order placed"
1108
+ print_h2 "Order Details", [], options
1109
+ print_order_details(order, options)
1126
1110
  end
1127
- else
1128
- print_green_success "Order placed"
1129
- print_h2 "Order Details", [], options
1130
- print_order_details(order, options)
1131
1111
  end
1132
1112
  end
1133
- if json_response['success']
1134
- return 0, nil
1135
- else
1136
- # not needed because it will be http 400
1137
- return 1, json_response['msg'] || 'request failed'
1138
- end
1139
1113
  end
1140
1114
 
1141
1115
  def remove(args)
@@ -326,6 +326,18 @@ class Morpheus::Cli::ServicePlanCommand
326
326
  opts.on('--max-cores NUMBER', String, "Max cores") do |val|
327
327
  ((params['config'] ||= {})['ranges'] ||= {})['maxCores'] = val.to_i
328
328
  end
329
+ opts.on('--min-sockets NUMBER', String, "Min sockets") do |val|
330
+ ((params['config'] ||= {})['ranges'] ||= {})['minSockets'] = val.to_i
331
+ end
332
+ opts.on('--max-sockets NUMBER', String, "Max sockets") do |val|
333
+ ((params['config'] ||= {})['ranges'] ||= {})['maxSockets'] = val.to_i
334
+ end
335
+ opts.on('--min-cores-per-socket NUMBER', String, "Min cores per socket") do |val|
336
+ ((params['config'] ||= {})['ranges'] ||= {})['minCoresPerSocket'] = val.to_i
337
+ end
338
+ opts.on('--max-cores-per-socket NUMBER', String, "Max cores per socket") do |val|
339
+ ((params['config'] ||= {})['ranges'] ||= {})['maxCoresPerSocket'] = val.to_i
340
+ end
329
341
  add_perms_options(opts, options, ['plans', 'groupDefaults'])
330
342
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
331
343
  opts.footer = "Create service plan"
@@ -429,6 +441,16 @@ class Morpheus::Cli::ServicePlanCommand
429
441
  {'fieldContext' => 'config.ranges', 'fieldGroup' => 'Custom Ranges', 'fieldName' => 'maxCores', 'fieldLabel' => 'Max Cores', 'type' => 'number', 'displayOrder' => 8}
430
442
  ]
431
443
 
444
+ if provision_type['hasSocketRange']
445
+ addn_options.push({'fieldContext' => 'config.ranges', 'fieldGroup' => 'Custom Ranges', 'fieldName' => 'minSockets', 'fieldLabel' => 'Min Sockets', 'type' => 'number', 'displayOrder' => 9})
446
+ addn_options.push({'fieldContext' => 'config.ranges', 'fieldGroup' => 'Custom Ranges', 'fieldName' => 'maxSockets', 'fieldLabel' => 'Max Sockets', 'type' => 'number', 'displayOrder' => 10})
447
+ end
448
+
449
+ if provision_type['hasCoresPerSocketRange']
450
+ addn_options.push({'fieldContext' => 'config.ranges', 'fieldGroup' => 'Custom Ranges', 'fieldName' => 'minCoresPerSocket', 'fieldLabel' => 'Min Cores Per Socket', 'type' => 'number', 'displayOrder' => 11})
451
+ addn_options.push({'fieldContext' => 'config.ranges', 'fieldGroup' => 'Custom Ranges', 'fieldName' => 'maxCoresPerSocket', 'fieldLabel' => 'Max Cores Per Socket', 'type' => 'number', 'displayOrder' => 12})
452
+ end
453
+
432
454
  v_prompt = Morpheus::Cli::OptionTypes.prompt(addn_options, options[:options], @api_client, params)
433
455
  params.deep_merge!(v_prompt)
434
456
 
@@ -575,6 +597,18 @@ class Morpheus::Cli::ServicePlanCommand
575
597
  end
576
598
  opts.on('--max-cores NUMBER', String, "Max cores") do |val|
577
599
  ((params['config'] ||= {})['ranges'] ||= {})['maxCores'] = val.to_i
600
+ end
601
+ opts.on('--min-sockets NUMBER', String, "Min sockets") do |val|
602
+ ((params['config'] ||= {})['ranges'] ||= {})['minSockets'] = val.to_i
603
+ end
604
+ opts.on('--max-sockets NUMBER', String, "Max sockets") do |val|
605
+ ((params['config'] ||= {})['ranges'] ||= {})['maxSockets'] = val.to_i
606
+ end
607
+ opts.on('--min-cores-per-socket NUMBER', String, "Min cores per socket") do |val|
608
+ ((params['config'] ||= {})['ranges'] ||= {})['minCoresPerSocket'] = val.to_i
609
+ end
610
+ opts.on('--max-cores-per-socket NUMBER', String, "Max cores per socket") do |val|
611
+ ((params['config'] ||= {})['ranges'] ||= {})['maxCoresPerSocket'] = val.to_i
578
612
  end
579
613
  add_perms_options(opts, options, ['plans', 'groupDefaults'])
580
614
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
@@ -12,7 +12,7 @@ module Morpheus::Cli::BackupsHelper
12
12
  @backups_interface
13
13
  end
14
14
 
15
- def backup_jobs_interfaces
15
+ def backup_jobs_interface
16
16
  raise "#{self.class} has not defined @backup_jobs_interface" if @backup_jobs_interface.nil?
17
17
  @backup_jobs_interface
18
18
  end
@@ -777,15 +777,21 @@ module Morpheus::Cli::ProvisioningHelper
777
777
  option_type_list = option_type_list.reject {|opt| ['resourcePool','resourcePoolId','azureResourceGroupId'].include?(opt['fieldName']) }
778
778
 
779
779
  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'], layoutId: layout["id"]}.merge(service_plan.nil? ? {} : {planId: service_plan["id"]}))['data']
780
- resource_pool = resource_pool_options.find {|opt| opt['id'] == options[:resource_pool].to_i} if options[:resource_pool]
780
+ if options[:resource_pool]
781
+ resource_pool = resource_pool_options.find {|opt| opt['value'] == options[:resource_pool].to_s || opt['value'] == "pool-#{options[:resource_pool]}"}
782
+ end
781
783
  pool_required = provision_type["zonePoolRequired"]
782
-
784
+ # Should pool_id have the pool-,poolGroup- prefix or not?
785
+ use_pool_prefix = resource_pool_options.find {|opt| opt['value'].to_s.include?("pool") }
783
786
  if resource_pool
784
- pool_id = resource_pool['id']
787
+ pool_id = resource_pool['id'] # id or value?
785
788
  else
786
789
  if options[:default_resource_pool]
787
790
  default_resource_pool = resource_pool_options.find {|rp| rp['id'] == options[:default_resource_pool]}
788
791
  end
792
+ if use_pool_prefix && options[:options]['config'] && options[:options]['config']['resourcePoolId'] && !options[:options]['config']['resourcePoolId'].to_s.include?("pool")
793
+ options[:options]['config']['resourcePoolId'] = "pool-" + options[:options]['config']['resourcePoolId'].to_s
794
+ end
789
795
  resource_pool_option_type ||= {'fieldContext' => 'config', 'fieldName' => 'resourcePoolId', 'type' => 'select', 'fieldLabel' => 'Resource Pool', 'selectOptions' => resource_pool_options, 'required' => pool_required, 'skipSingleOption' => true, 'description' => 'Select resource pool.', 'defaultValue' => default_resource_pool ? default_resource_pool['name'] : nil}
790
796
  resource_pool_prompt = Morpheus::Cli::OptionTypes.prompt([resource_pool_option_type],options[:options],api_client,{}, no_prompt, true)
791
797
  resource_pool_prompt.deep_compact!
@@ -796,7 +802,7 @@ module Morpheus::Cli::ProvisioningHelper
796
802
  elsif resource_pool_prompt[resource_pool_option_type['fieldName']]
797
803
  pool_id = resource_pool_prompt[resource_pool_option_type['fieldName']]
798
804
  end
799
- resource_pool ||= resource_pool_options.find {|it| it['id'] == pool_id}
805
+ resource_pool ||= resource_pool_options.find {|it| it['value'].to_s == pool_id.to_s}
800
806
  end
801
807
  end
802
808
  end
@@ -582,6 +582,9 @@ module Morpheus
582
582
 
583
583
  if no_prompt
584
584
  if !value_found
585
+ if default_value == ""
586
+ default_value = nil
587
+ end
585
588
  if !default_value.nil? && !select_options.nil?
586
589
  matched_option = select_options.find {|it| it[value_field].to_s == default_value.to_s }
587
590
  if matched_option.nil?
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "6.0.1"
4
+ VERSION = "6.1.0"
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.1
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Estes
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2023-03-14 00:00:00.000000000 Z
14
+ date: 2023-04-20 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler