morpheus-cli 6.0.2 → 6.1.1

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: 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