morpheus-cli 6.0.2 → 6.1.1

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: eb39926e3f31b77cdca9175335ca0260b720641e469a240618fc98816b2b98ee
4
- data.tar.gz: ef40d39ffa3f4a6d51139524fee83dd990b082c326b528ad66a6a8e6629c43e0
3
+ metadata.gz: 3cf8719e8fbab69a6e0de10b9d2920b0394bb55d4928ba4266b82c9430f53658
4
+ data.tar.gz: a518e58f7b2adc050e786ca6f29e0f7832787e5c294c495d5599960f7232aacc
5
5
  SHA512:
6
- metadata.gz: 32d58f65d940360fdfb48ff00f88d7f2221dbe4ad1d50adbd441ee68ae79cf7fb5e1314b7d1644808a8795fa362b0e0698e0d89ef8f57187442880fc1a0fec40
7
- data.tar.gz: 19f7a173551371d306edbbbde91ad910f96c5c77fe8c70ef09200ba85e50885e4533cfb36c1f6c26b3cacacb629d7459aabb569f03e71d07e0a5720e3134fc9c
6
+ metadata.gz: 30263a5621c68dc42d8bb1093c67c21a27eab9c440bd12848a345ddee7200d45d8744531418dd9b936529405623a7c464d44b957503903ac0fc1e2bb462b431a
7
+ data.tar.gz: 2bc5e8dc185195a1dc470394d487b1c970c1c8626701803636441ae84a6f138352dcafd07481d39a151c24911a05526dfa8748882ee7b1b48f57c02bba584e54
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.2
3
+ RUN gem install morpheus-cli -v 6.1.1
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -492,6 +492,10 @@ class Morpheus::APIClient
492
492
  Morpheus::LoadBalancerPoolsInterface.new(common_interface_options).setopts(@options)
493
493
  end
494
494
 
495
+ def load_balancer_pools_secondary
496
+ Morpheus::LoadBalancerPoolsSecondaryInterface.new(common_interface_options).setopts(@options)
497
+ end
498
+
495
499
  def load_balancer_profiles
496
500
  Morpheus::LoadBalancerProfilesInterface.new(common_interface_options).setopts(@options)
497
501
  end
@@ -500,6 +504,10 @@ class Morpheus::APIClient
500
504
  Morpheus::LoadBalancerMonitorsInterface.new(common_interface_options).setopts(@options)
501
505
  end
502
506
 
507
+ def load_balancer_pool_nodes
508
+ Morpheus::LoadBalancerPoolNodesInterface.new(common_interface_options).setopts(@options)
509
+ end
510
+
503
511
  def tasks
504
512
  Morpheus::TasksInterface.new(common_interface_options).setopts(@options)
505
513
  end
@@ -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
@@ -9,7 +9,7 @@ class Morpheus::CypherInterface < Morpheus::APIClient
9
9
 
10
10
  def get(item_key, params={})
11
11
  raise "#{self.class}.get() passed a blank item_key!" if item_key.to_s == ''
12
- url = "#{@base_url}#{base_path}/#{item_key}"
12
+ url = "#{@base_url}#{base_path}/#{escape_item_key(item_key)}"
13
13
  headers = { :params => params, :authorization => "Bearer #{@access_token}" }
14
14
  execute({method: :get, url: url, headers: headers})
15
15
  end
@@ -17,29 +17,35 @@ class Morpheus::CypherInterface < Morpheus::APIClient
17
17
  # list url is the same as get but uses $itemKey/?list=true
18
18
  # method: 'LIST' would be neat though
19
19
  def list(item_key=nil, params={})
20
- url = item_key ? "#{@base_url}#{base_path}/#{item_key}" : "#{@base_url}#{base_path}"
20
+ url = item_key ? "#{@base_url}#{base_path}/#{escape_item_key(item_key)}" : "#{@base_url}#{base_path}"
21
21
  params.merge!({list:'true'}) # ditch this probably
22
22
  headers = { :params => params, :authorization => "Bearer #{@access_token}" }
23
23
  execute({method: :get, url: url, headers: headers})
24
24
  end
25
25
 
26
26
  def create(item_key, params={}, payload={})
27
- url = "#{@base_url}#{base_path}/#{item_key}"
27
+ url = "#{@base_url}#{base_path}/#{escape_item_key(item_key)}"
28
28
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
29
29
  execute({method: :post, url: url, headers: headers, payload: payload.to_json})
30
30
  end
31
31
 
32
32
  # update is not even needed I don't think, same as POST
33
33
  def update(item_key, params={}, payload={})
34
- url = "#{@base_url}#{base_path}/#{item_key}"
34
+ url = "#{@base_url}#{base_path}/#{escape_item_key(item_key)}"
35
35
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
36
36
  execute({method: :put, url: url, headers: headers, payload: payload.to_json})
37
37
  end
38
38
 
39
39
  def destroy(item_key, params={})
40
- url = "#{@base_url}#{base_path}/#{item_key}"
40
+ url = "#{@base_url}#{base_path}/#{escape_item_key(item_key)}"
41
41
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
42
42
  execute({method: :delete, url: url, headers: headers})
43
43
  end
44
44
 
45
+ protected
46
+
47
+ def escape_item_key(item_key)
48
+ item_key.to_s.split("/").collect {|i| CGI::escape(i.to_s) }.join("/")
49
+ end
50
+
45
51
  end
@@ -0,0 +1,8 @@
1
+ require 'morpheus/api/secondary_rest_interface'
2
+
3
+ class Morpheus::LoadBalancerPoolNodesInterface < Morpheus::SecondaryRestInterface
4
+
5
+ def base_path(pool_id)
6
+ "/api/load-balancer-pools/#{pool_id}/nodes"
7
+ end
8
+ end
@@ -1,9 +1,9 @@
1
- require 'morpheus/api/secondary_rest_interface'
1
+ require 'morpheus/api/rest_interface'
2
2
 
3
- class Morpheus::LoadBalancerPoolsInterface < Morpheus::SecondaryRestInterface
3
+ class Morpheus::LoadBalancerPoolsInterface < Morpheus::RestInterface
4
4
 
5
- def base_path(load_balancer_id)
6
- "/api/load-balancers/#{load_balancer_id}/pools"
5
+ def base_path
6
+ "/api/load-balancer-pools"
7
7
  end
8
8
 
9
9
  end
@@ -0,0 +1,9 @@
1
+ require 'morpheus/api/secondary_rest_interface'
2
+
3
+ class Morpheus::LoadBalancerPoolsSecondaryInterface < Morpheus::SecondaryRestInterface
4
+
5
+ def base_path(load_balancer_id)
6
+ "/api/load-balancers/#{load_balancer_id}/pools"
7
+ end
8
+
9
+ end
@@ -2,10 +2,10 @@ require 'morpheus/api/api_client'
2
2
 
3
3
  class Morpheus::RolesInterface < Morpheus::APIClient
4
4
 
5
- def get(account_id, id)
5
+ def get(account_id, id, params={})
6
6
  raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
7
7
  url = build_url(account_id, id)
8
- headers = { params: {}, authorization: "Bearer #{@access_token}" }
8
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
9
9
  execute(method: :get, url: url, headers: headers)
10
10
  end
11
11
 
@@ -21,24 +21,24 @@ class Morpheus::RolesInterface < Morpheus::APIClient
21
21
  execute(method: :get, url: url, headers: headers)
22
22
  end
23
23
 
24
- def create(account_id, options)
24
+ def create(account_id, options, params={})
25
25
  url = build_url(account_id)
26
26
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
27
27
  payload = options
28
- execute(method: :post, url: url, headers: headers, payload: payload.to_json)
28
+ execute(method: :post, url: url, headers: headers, payload: payload.to_json, params: params)
29
29
  end
30
30
 
31
- def update(account_id, id, options)
31
+ def update(account_id, id, options, params={})
32
32
  url = build_url(account_id, id)
33
33
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
34
34
  payload = options
35
- execute(method: :put, url: url, headers: headers, payload: payload.to_json)
35
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json, params: params)
36
36
  end
37
37
 
38
- def destroy(account_id, id)
38
+ def destroy(account_id, id, params={})
39
39
  url = build_url(account_id, id)
40
40
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
41
- execute(method: :delete, url: url, headers: headers)
41
+ execute(method: :delete, url: url, headers: headers, params: params)
42
42
  end
43
43
 
44
44
  def update_permission(account_id, id, options)
@@ -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
 
@@ -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