morpheus-cli 6.0.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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