morpheus-cli 8.0.2 → 8.0.4

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: 06ea65156f0a57e7af971494347aae4844608a6cb877087dc9394a127841a75e
4
- data.tar.gz: a93ddec1dc9e647e23a111a168126dbfc3b683d04844731f5bc53a461305b140
3
+ metadata.gz: 7cbc73275f4621016909ca9b3d12fcdf879a27d948c5d0e2b5e168d849850954
4
+ data.tar.gz: c30e3333325812730d68d16fa340ed8f858926b22aaf8d3ffd95fc9d4f9d2ef8
5
5
  SHA512:
6
- metadata.gz: 9ca9b136bd006211eed46cd89103c2e7af9157d8111595f4e90b09a7a1e014570405256d64401c78311503e3d4b0aaecf5690526b2014985d79c515e0093eba3
7
- data.tar.gz: f1355c6ec99b7a05e068e215ba4581a75a712bef545b661a04e7a31ad0b1711071d7f65c316e938171da60d66d0a297898068f5efb70e750a7dc987b2f12014e
6
+ metadata.gz: e9b1b734e792e44addc36e281765be1598d7e9d94e312f4e8c7cdd5a0da8ade497d8989421e56793dc86897a37d1fdafca26da81bda67f0591481ddec642099f
7
+ data.tar.gz: e6ff86116594db8837b84a0baa8b15e0f6bb777dd3df415fad23f6918a9f097b210026b2479391decaac93c1a21177f082fd4449914d9bcb910f899228b60a70
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 8.0.2
3
+ RUN gem install morpheus-cli -v 8.0.4
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -8,18 +8,16 @@ class Morpheus::LicenseInterface < Morpheus::APIClient
8
8
  execute(method: :get, url: url, headers: headers)
9
9
  end
10
10
 
11
- def install(key)
11
+ def install(payload)
12
12
  url = "#{@base_url}/api/license"
13
13
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
14
- payload = {license: key}
15
14
  execute(method: :post, url: url, headers: headers, payload: payload.to_json)
16
15
  end
17
16
 
18
- def test(key)
17
+ def test(payload)
19
18
  # use /test instead, since 4.1.1
20
19
  url = "#{@base_url}/api/license/decode"
21
20
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
22
- payload = {license: key}
23
21
  execute(method: :post, url: url, headers: headers, payload: payload.to_json)
24
22
  end
25
23
 
@@ -422,7 +422,7 @@ class Morpheus::Cli::Clusters
422
422
  options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
423
423
  end
424
424
  opts.on('--workflow ID', String, "Workflow") do |val|
425
- options['taskSetId'] = val.to_i
425
+ options[:workflow] = val.to_i
426
426
  end
427
427
  add_server_options(opts, options)
428
428
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
@@ -608,13 +608,12 @@ class Morpheus::Cli::Clusters
608
608
  end
609
609
 
610
610
  # Controller type
611
- server_types = @server_types_interface.list({computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes'].reject {|it| it['provisionType']['code'] == 'manual'} unless ['kubernetes-cluster', 'mvm-cluster'].include?(cluster_type_code)
611
+ server_types = @server_types_interface.list({computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes'].reject {|it| it['provisionType']['code'] == 'manual'} unless ['mvm-cluster'].include?(cluster_type_code)
612
612
  controller_provision_type = nil
613
613
  resource_pool = nil
614
614
 
615
615
  if !server_types.empty?
616
616
  controller_type = server_types.first
617
-
618
617
  if controller_type['provisionType']
619
618
  if provision_type && provision_type['id'] == controller_type['provisionType']['id']
620
619
  controller_provision_type = provision_type
@@ -747,10 +746,11 @@ class Morpheus::Cli::Clusters
747
746
 
748
747
  # Workflow / Automation
749
748
  if provision_type['code'] != 'manual' && controller_type && controller_type['hasAutomation']
750
- task_set_id = options[:taskSetId] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSet', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, api_params.merge({'phase' => 'postProvision'}))['taskSet']
749
+ task_set_id = options[:workflow] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSetId', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, api_params.merge({'phase' => 'postProvision'}))['taskSetId']
751
750
 
752
751
  if !task_set_id.nil?
753
- server_payload['taskSet'] = {'id' => task_set_id}
752
+ # server_payload['taskSet'] = {'id' => task_set_id}
753
+ cluster_payload['taskSetId'] = task_set_id
754
754
  end
755
755
  end
756
756
 
@@ -3784,7 +3784,7 @@ class Morpheus::Cli::Clusters
3784
3784
  subtitles << " Process ID: #{process_id}"
3785
3785
  subtitles += parse_list_subtitles(options)
3786
3786
  print_h1 title, subtitles, options
3787
- print_process_details(process)
3787
+ print_process_details(process, options)
3788
3788
 
3789
3789
  print_h2 "Process Events", options
3790
3790
  process_events = process['events'] || process['processEvents'] || []
@@ -4003,7 +4003,7 @@ class Morpheus::Cli::Clusters
4003
4003
  subtitles = []
4004
4004
  subtitles += parse_list_subtitles(options)
4005
4005
  print_h1 title, subtitles, options
4006
- print_process_event_details(process_event)
4006
+ print_process_event_details(process_event, options)
4007
4007
  print reset, "\n"
4008
4008
  return 0
4009
4009
  end
@@ -4015,37 +4015,6 @@ class Morpheus::Cli::Clusters
4015
4015
 
4016
4016
  private
4017
4017
 
4018
- def print_process_event_details(process_event, options={})
4019
- # process_event =~ process
4020
- description_cols = {
4021
- "Process ID" => lambda {|it| it['processId'] },
4022
- "Event ID" => lambda {|it| it['id'] },
4023
- "Name" => lambda {|it| it['displayName'] },
4024
- "Description" => lambda {|it| it['description'] },
4025
- "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
4026
- "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
4027
- "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
4028
- "End Date" => lambda {|it| format_local_dt(it['endDate']) },
4029
- "Duration" => lambda {|it| format_process_duration(it) },
4030
- "Status" => lambda {|it| format_process_status(it) },
4031
- }
4032
- print_description_list(description_cols, process_event)
4033
-
4034
- if process_event['error']
4035
- print_h2 "Error", options
4036
- print reset
4037
- #puts format_process_error(process_event)
4038
- puts process_event['error'].to_s.strip
4039
- end
4040
-
4041
- if process_event['output']
4042
- print_h2 "Output", options
4043
- print reset
4044
- #puts format_process_error(process_event)
4045
- puts process_event['output'].to_s.strip
4046
- end
4047
- end
4048
-
4049
4018
  def print_clusters_table(clusters, opts={})
4050
4019
  table_color = opts[:color] || cyan
4051
4020
  rows = clusters.collect do |cluster|
@@ -2229,6 +2229,18 @@ EOT
2229
2229
  options = {}
2230
2230
  optparse = Morpheus::Cli::OptionParser.new do |opts|
2231
2231
  opts.banner = subcommand_usage("[host]")
2232
+ opts.on('--ignoreDaemonsets [on|off]', String, "Ignore Daemonsets") do |val|
2233
+ options[:ignoreDaemonsets] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
2234
+ end
2235
+ opts.on('--force [on|off]', String, "Force") do |val|
2236
+ options[:force] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
2237
+ end
2238
+ opts.on('--deleteEmptyDir [on|off]', String, "Delete Empty Directories") do |val|
2239
+ options[:deleteEmptyDir] = (val.to_s.empty? || val.to_s == 'on' || val.to_s == 'true')
2240
+ end
2241
+ opts.on('--deleteLocalData [on|off]', String, "Delete Local Data") do |val|
2242
+ options[:deleteLocalData] = (val.to_s == 'on' || val.to_s == 'true')
2243
+ end
2232
2244
  build_standard_update_options(opts, options, [:auto_confirm])
2233
2245
  opts.footer = <<-EOT
2234
2246
  Enable maintenance mode for a host.
@@ -2252,6 +2264,27 @@ EOT
2252
2264
  # end
2253
2265
 
2254
2266
  payload = {}
2267
+
2268
+ if server.dig('config', 'kubernetesRole')
2269
+ payload[:server] = {}
2270
+ payload[:server][:ignoreDaemonsets] = options.fetch(:ignoreDaemonsets) do
2271
+ prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ignoreDaemonsets', 'fieldLabel' => 'Ignore Daemonsets', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
2272
+ prompt['ignoreDaemonsets'] == 'on'
2273
+ end
2274
+ payload[:server][:force] = options.fetch(:force) do
2275
+ prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'force', 'fieldLabel' => 'Force', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
2276
+ prompt['force'] == 'on'
2277
+ end
2278
+ payload[:server][:deleteEmptyDir] = options.fetch(:deleteEmptyDir) do
2279
+ prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deleteEmptyDir', 'fieldLabel' => 'Delete Empty Directories', 'type' => 'checkbox', 'defaultValue' => true, 'required' => false}], options, @api_client, {})
2280
+ prompt['deleteEmptyDir'] == 'on'
2281
+ end
2282
+ payload[:server][:deleteLocalData] = options.fetch(:deleteLocalData) do
2283
+ prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'deleteLocalData', 'fieldLabel' => 'Delete Local Data', 'type' => 'checkbox', 'defaultValue' => false, 'required' => false}], options, @api_client, {})
2284
+ prompt['force'] == 'on'
2285
+ end
2286
+ end
2287
+
2255
2288
  if options[:payload]
2256
2289
  payload = options[:payload]
2257
2290
  payload.deep_merge!(parse_passed_options(options))
@@ -2921,6 +2921,12 @@ class Morpheus::Cli::Instances
2921
2921
  opts.on( '--description VALUE', String, "Snapshot Description." ) do |val|
2922
2922
  options[:options]['description'] = val
2923
2923
  end
2924
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
2925
+ options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
2926
+ end
2927
+ opts.on(nil, '--no-refresh', "Do not refresh" ) do
2928
+ options[:no_refresh] = true
2929
+ end
2924
2930
  build_standard_add_options(opts, options, [:auto_confirm])
2925
2931
  opts.footer = <<-EOT
2926
2932
  Create a snapshot for an instance.
@@ -2949,6 +2955,18 @@ EOT
2949
2955
  json_response = @instances_interface.snapshot(instance['id'], payload)
2950
2956
  render_response(json_response, options, 'snapshots') do
2951
2957
  print_green_success "Snapshot initiated."
2958
+ process_id = json_response['processIds'][0] rescue nil
2959
+ if process_id
2960
+ unless options[:no_refresh]
2961
+ process = wait_for_process_execution(process_id, options)
2962
+ snapshot_id = process['resultId']
2963
+ if snapshot_id
2964
+ Morpheus::Cli::Snapshots.new.handle(["get", snapshot_id] + (options[:remote] ? ["-r",options[:remote]] : []))
2965
+ end
2966
+ end
2967
+ else
2968
+ # puts "No process returned"
2969
+ end
2952
2970
  end
2953
2971
  return 0, nil
2954
2972
  end
@@ -3707,7 +3725,7 @@ EOT
3707
3725
  if options[:json]
3708
3726
  puts as_json(json_response, options)
3709
3727
  else
3710
- print_green_success "Snapshots attaced to instance #{instance['name']} queued for deletion."
3728
+ print_green_success "Snapshots attached to instance #{instance['name']} queued for deletion."
3711
3729
  end
3712
3730
  return 0
3713
3731
 
@@ -4496,7 +4514,7 @@ EOT
4496
4514
  subtitles << " Process ID: #{process_id}"
4497
4515
  subtitles += parse_list_subtitles(options)
4498
4516
  print_h1 title, subtitles, options
4499
- print_process_details(process)
4517
+ print_process_details(process, options)
4500
4518
 
4501
4519
  print_h2 "Process Events", options
4502
4520
  process_events = process['events'] || process['processEvents'] || []
@@ -5502,67 +5520,6 @@ private
5502
5520
  {'fieldName' => 'maxDisk', 'fieldLabel' => 'Max Disk', 'type' => 'number', 'description' => 'Maximum storage percent (0-100)'},
5503
5521
  ]
5504
5522
  end
5505
-
5506
- def print_process_details(process)
5507
- description_cols = {
5508
- "Process ID" => lambda {|it| it['id'] },
5509
- "Name" => lambda {|it| it['displayName'] },
5510
- "Description" => lambda {|it| it['description'] },
5511
- "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
5512
- "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
5513
- "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
5514
- "End Date" => lambda {|it| format_local_dt(it['endDate']) },
5515
- "Duration" => lambda {|it| format_process_duration(it) },
5516
- "Status" => lambda {|it| format_process_status(it) },
5517
- # "# Events" => lambda {|it| (it['events'] || []).size() },
5518
- }
5519
- print_description_list(description_cols, process)
5520
-
5521
- if process['error']
5522
- print_h2 "Error", options
5523
- print reset
5524
- #puts format_process_error(process_event)
5525
- puts process['error'].to_s.strip
5526
- end
5527
-
5528
- if process['output']
5529
- print_h2 "Output", options
5530
- print reset
5531
- #puts format_process_error(process_event)
5532
- puts process['output'].to_s.strip
5533
- end
5534
- end
5535
-
5536
- def print_process_event_details(process_event, options={})
5537
- # process_event =~ process
5538
- description_cols = {
5539
- "Process ID" => lambda {|it| it['processId'] },
5540
- "Event ID" => lambda {|it| it['id'] },
5541
- "Name" => lambda {|it| it['displayName'] },
5542
- "Description" => lambda {|it| it['description'] },
5543
- "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
5544
- "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
5545
- "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
5546
- "End Date" => lambda {|it| format_local_dt(it['endDate']) },
5547
- "Duration" => lambda {|it| format_process_duration(it) },
5548
- "Status" => lambda {|it| format_process_status(it) },
5549
- }
5550
- print_description_list(description_cols, process_event)
5551
-
5552
- if process_event['error']
5553
- print_h2 "Error", options
5554
- print reset
5555
- #puts format_process_error(process_event)
5556
- puts process_event['error'].to_s.strip
5557
- end
5558
-
5559
- if process_event['output']
5560
- print_h2 "Output", options
5561
- print reset
5562
- #puts format_process_error(process_event)
5563
- puts process_event['output'].to_s.strip
5564
- end
5565
- end
5566
5523
 
5567
5524
  def update_wiki_page_option_types
5568
5525
  [
@@ -55,6 +55,10 @@ class Morpheus::Cli::License
55
55
  print "#{yellow}No license currently installed#{reset}\n\n"
56
56
  else
57
57
  print_license_details(json_response)
58
+ licenses = json_response['installedLicenses']
59
+ if licenses
60
+ print_installed_licenses(licenses, options)
61
+ end
58
62
  print_h2 "Current Usage", [], options
59
63
  print_license_usage(license, current_usage)
60
64
  print reset,"\n"
@@ -72,40 +76,55 @@ class Morpheus::Cli::License
72
76
  account_name = nil
73
77
  optparse = Morpheus::Cli::OptionParser.new do |opts|
74
78
  opts.banner = subcommand_usage("[key]")
75
- build_common_options(opts, options, [:json, :dry_run, :remote])
79
+ opts.on('-a', '--add', "Stack this license on top of existing licenses. Without this option all existing licenses will be replaced." ) do
80
+ options[:options]['installAction'] = 'add'
81
+ end
82
+ opts.on('--stack', '--stack', "Alias for --add" ) do
83
+ options[:options]['installAction'] = 'add'
84
+ end
85
+ opts.on('--replace', '--replace', "Replace existing licenses with this new license. This is the default behavior." ) do
86
+ options[:options]['installAction'] = 'replace'
87
+ end
88
+ build_standard_add_options(opts, options)
76
89
  opts.footer = "Install a new license key.\n" +
77
90
  "This will potentially change the enabled features and capabilities of your appliance."
78
91
  end
79
92
  optparse.parse!(args)
80
- if args.count > 1
81
- raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
82
- end
93
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
83
94
  connect(options)
84
- begin
95
+ payload = {}
96
+ if options[:payload]
97
+ payload = options[:payload]
98
+ payload.deep_merge!(parse_passed_options(options))
99
+ else
100
+ payload.deep_merge!(parse_passed_options(options))
85
101
  if args[0]
86
- key = args[0]
102
+ key = args[0].strip
87
103
  else
88
104
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true}], options[:options])
89
- key = v_prompt['licenseKey'] || ''
105
+ key = v_prompt['licenseKey'].to_s.strip
90
106
  end
91
- @license_interface.setopts(options)
92
- if options[:dry_run]
93
- print_dry_run @license_interface.dry.install(key)
94
- return 0
95
- end
96
- json_response = @license_interface.install(key)
97
- license = json_response['license']
98
- if options[:json]
99
- puts JSON.pretty_generate(json_response)
100
- else
101
- print_green_success "License installed!"
102
- get([] + (options[:remote] ? ["-r",options[:remote]] : []))
107
+ payload['license'] = key
108
+ # Stack Licenses?
109
+ # load current licenses to see if this prompt is required
110
+ json_response = @license_interface.get()
111
+ licenses = json_response['installedLicenses']
112
+ if licenses && !(licenses.size == 1 && key.index(licenses[0]['keyId']) == 0)
113
+ install_actions = [{'name' => 'Add', 'value' => 'add'},{'name' => 'Replace', 'value' => 'replace'}]
114
+ payload['installAction'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'installAction', 'fieldLabel' => 'Install Action', 'type' => 'select', 'selectOptions' => install_actions, 'required' => true, 'defaultValue' => 'replace', 'description' => "Install action to perform. Use add to stack this license on top of existing licenses. Use replace (default) to remove any existing license(s)."}], options[:options])['installAction']
103
115
  end
116
+ end
117
+ @license_interface.setopts(options)
118
+ if options[:dry_run]
119
+ print_dry_run @license_interface.dry.install(payload)
104
120
  return 0
105
- rescue RestClient::Exception => e
106
- print_rest_exception(e, options)
107
- return false
108
121
  end
122
+ json_response = @license_interface.install(payload)
123
+ render_response(json_response, options) do
124
+ print_green_success "License installed!"
125
+ get([] + (options[:remote] ? ["-r",options[:remote]] : []))
126
+ end
127
+ return 0, nil
109
128
  end
110
129
 
111
130
  def decode(args)
@@ -117,7 +136,16 @@ class Morpheus::Cli::License
117
136
  options = {}
118
137
  optparse = Morpheus::Cli::OptionParser.new do |opts|
119
138
  opts.banner = subcommand_usage("[key]")
120
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
139
+ opts.on('-a', '--add', "Stack this license on top of existing licenses. Without this option all existing licenses will be replaced." ) do
140
+ options[:options]['installAction'] = 'add'
141
+ end
142
+ opts.on('--stack', '--stack', "Alias for --add" ) do
143
+ options[:options]['installAction'] = 'add'
144
+ end
145
+ opts.on('--replace', '--replace', "Replace existing licenses with this new license. This is the default behavior." ) do
146
+ options[:options]['installAction'] = 'replace'
147
+ end
148
+ build_standard_add_options(opts, options, [:fields])
121
149
  opts.footer = "Test a license key.\n" +
122
150
  "This is a way to decode and view a license key before installing it."
123
151
  end
@@ -126,50 +154,48 @@ class Morpheus::Cli::License
126
154
  raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
127
155
  end
128
156
  connect(options)
129
- key = nil
130
- if args[0]
131
- key = args[0]
157
+ payload = {}
158
+ if options[:payload]
159
+ payload = options[:payload]
160
+ payload.deep_merge!(parse_passed_options(options))
132
161
  else
133
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true}], options[:options])
134
- key = v_prompt['licenseKey'] || ''
135
- end
136
- begin
137
- @license_interface.setopts(options)
138
- if options[:dry_run]
139
- print_dry_run @license_interface.dry.test(key)
140
- return
141
- end
142
- json_response = @license_interface.test(key)
143
- license = json_response['license']
144
- current_usage = json_response['currentUsage'] || {}
145
- exit_code, err = 0, nil
146
- if license.nil?
147
- err = "Unable to decode license."
148
- exit_code = 1
149
- #return err, exit_code
150
- end
151
- render_result = render_with_format(json_response, options)
152
- if render_result
153
- return exit_code, err
154
- end
155
- if options[:quiet]
156
- return exit_code, err
162
+ payload.deep_merge!(parse_passed_options(options))
163
+ key = nil
164
+ if args[0]
165
+ key = args[0].strip
166
+ else
167
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'licenseKey', 'fieldLabel' => 'License Key', 'type' => 'text', 'required' => true}], options[:options])
168
+ key = v_prompt['licenseKey'] || ''
157
169
  end
158
- if exit_code != 0
159
- print_error red, err.to_s, reset, "\n"
160
- return exit_code, err
170
+ payload['license'] = key
171
+ # Stack Licenses?
172
+ # load current licenses to see if this prompt is required
173
+ json_response = @license_interface.get()
174
+ licenses = json_response['installedLicenses']
175
+ if licenses && !(licenses.size == 1 && key.index(licenses[0]['keyId']) == 0)
176
+ install_actions = [{'name' => 'Add', 'value' => 'add'},{'name' => 'Replace', 'value' => 'replace'}]
177
+ payload['installAction'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'installAction', 'fieldLabel' => 'Install Action', 'type' => 'select', 'selectOptions' => install_actions, 'required' => true, 'defaultValue' => 'replace', 'description' => "Install action to perform. Use add to stack this license on top of existing licenses. Use replace (default) to remove any existing license(s)."}], options[:options])['installAction']
161
178
  end
162
-
179
+ end
180
+ @license_interface.setopts(options)
181
+ if options[:dry_run]
182
+ print_dry_run @license_interface.dry.test(payload)
183
+ return
184
+ end
185
+ json_response = @license_interface.test(payload)
186
+ render_response(json_response, options) do
187
+ license = json_response['license']
188
+ current_usage = json_response['currentUsage'] || {}
163
189
  # all good
164
190
  print_h1 "License"
165
191
  print_license_details(json_response)
192
+ licenses = json_response['installedLicenses']
193
+ if licenses
194
+ print_installed_licenses(licenses, options)
195
+ end
166
196
  print_h2 "License Usage", [], options
167
197
  print_license_usage(license, current_usage)
168
198
  print reset,"\n"
169
- return exit_code, err
170
- rescue RestClient::Exception => e
171
- print_rest_exception(e, options)
172
- return 1
173
199
  end
174
200
  end
175
201
 
@@ -177,44 +203,58 @@ class Morpheus::Cli::License
177
203
  options = {}
178
204
  params = {}
179
205
  optparse = Morpheus::Cli::OptionParser.new do |opts|
180
- opts.banner = subcommand_usage("[key]")
206
+ opts.banner = subcommand_usage("[key ID]")
207
+ opts.on('--key KEY', String, "License Key ID to uninstall (only the first 8 characters are required to identify license to uninstall). By default all licenses are uninstalled.") do |val|
208
+ options[:key] = val.to_s
209
+ end
181
210
  build_common_options(opts, options, [:auto_confirm, :json, :yaml, :dry_run, :remote])
182
- opts.footer = "Uninstall the current license key.\n" +
183
- "This clears out the current license key from the appliance.\n" +
211
+ opts.footer = "Uninstall license key(s).\n" +
212
+ "Use [key] or --key to uninstall only the specified license key.\n" +
213
+ "By default all currently installed license keys are uninstalled.\n" +
184
214
  "The function of the remote appliance will be restricted without a license installed.\n" +
185
215
  "Be careful using this."
186
216
  end
187
217
  optparse.parse!(args)
188
- if args.count > 0
189
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
190
- end
218
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
191
219
  connect(options)
192
- begin
193
- @license_interface.setopts(options)
194
- if options[:dry_run]
195
- print_dry_run @license_interface.dry.uninstall(params)
196
- return
197
- end
198
-
199
- unless options[:quiet]
200
- print cyan,"#{bold}WARNING!#{reset}#{cyan} You are about to uninstall your license key.",reset,"\n"
201
- print yellow, "Be careful using this. Make sure you have a copy of your key somewhere if you intend to use it again.",reset, "\n"
202
- print "\n"
220
+ key_id = nil
221
+ if args[0] || options[:key]
222
+ key_id = (args[0] || options[:key])[0..7]
223
+ end
224
+ # load current licenses to prompt user which to uninstall
225
+ json_response = @license_interface.get()
226
+ licenses = json_response['installedLicenses'] # || [json_response['licenses']]
227
+ if key_id
228
+ # appliance version < 8.0.4 does not return installedLicenses list
229
+ if licenses.nil?
230
+ raise_command_error "Appliance version does not support uninstalling specific keys"
203
231
  end
204
-
205
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you want to uninstall the license key for remote #{@appliance_name} - #{@appliance_url}?")
206
- return 9, "command aborted"
232
+ matching_license = licenses.find {|it| it['keyId'] == key_id }
233
+ if matching_license.nil?
234
+ raise_command_error "Unable to find installed license key '#{key_id}'"
207
235
  end
236
+ params['keyId'] = key_id
237
+ end
238
+
239
+ unless options[:quiet]
240
+ print cyan,"#{bold}WARNING!#{reset}#{cyan} You are about to uninstall your license key (#{key_id ? key_id : 'ALL'})",reset,"\n"
241
+ print yellow, "Be careful using this. Make sure you have a copy of your key somewhere if you intend to use it again.",reset, "\n"
242
+ print "\n"
243
+ end
244
+
245
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you want to uninstall the license key (#{key_id ? key_id : 'ALL'}) for remote #{@appliance_name} - #{@appliance_url}?")
246
+ return 9, "command aborted"
247
+ end
208
248
 
209
- json_response = @license_interface.uninstall(params)
210
- render_result = render_with_format(json_response, options)
211
- return 0 if render_result
212
- return 0 if options[:quiet]
249
+ @license_interface.setopts(options)
250
+ if options[:dry_run]
251
+ print_dry_run @license_interface.dry.uninstall(params)
252
+ return
253
+ end
254
+ json_response = @license_interface.uninstall(params)
255
+
256
+ render_response(json_response, options) do
213
257
  print_green_success "License uninstalled!"
214
- return 0
215
- rescue RestClient::Exception => e
216
- print_rest_exception(e, options)
217
- return 1
218
258
  end
219
259
  end
220
260
 
@@ -267,8 +307,10 @@ class Morpheus::Cli::License
267
307
 
268
308
  def print_license_details(json_response)
269
309
  license = json_response['license']
310
+ licenses = json_response['installedLicenses']
270
311
  current_usage = json_response['currentUsage'] || {}
271
312
  description_cols = {
313
+ "Key ID" => lambda {|it| licenses ? licenses.collect {|li| li['keyId'] }.join(", ") : it['keyId'] },
272
314
  "Account" => 'accountName',
273
315
  "Product Tier" => lambda {|it| format_product_tier(it) },
274
316
  "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
@@ -285,7 +327,7 @@ class Morpheus::Cli::License
285
327
  "Hard Limit" => lambda {|it| format_boolean it["hardLimit"] },
286
328
  "Limit Type" => lambda {|it| format_limit_type(it) },
287
329
  }
288
-
330
+ description_cols.delete("Key ID") if !license['keyId']
289
331
  description_cols.delete("Multi-Tenant") if !license['multiTenant']
290
332
  description_cols.delete("White Label") if !license['whitelabel']
291
333
 
@@ -373,5 +415,46 @@ class Morpheus::Cli::License
373
415
  cyan + label.rjust(label_width, ' ') + ": " + generate_usage_bar(used, max, chart_opts) + cyan + used.to_s.rjust(label_width, ' ') + " / " + (max.to_i > 0 ? max.to_s : unlimited_label).to_s.ljust(label_width, ' ') + "\n"
374
416
  end
375
417
 
418
+ def print_installed_licenses(licenses, options)
419
+ print_h2 "Installed Licenses", [], options
420
+ license_columns = {
421
+ "Key ID" => 'keyId',
422
+ "Account" => 'accountName',
423
+ "Product Tier" => lambda {|it| format_product_tier(it) },
424
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
425
+ "End Date" => lambda {|it|
426
+ if it['endDate']
427
+ format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
428
+ else
429
+ 'Never'
430
+ end
431
+ },
432
+ "Multi-Tenant" => lambda {|it| format_boolean it["multiTenant"] },
433
+ "White Label" => lambda {|it| format_boolean it["whitelabel"] },
434
+ "Stats Reporting" => lambda {|it| format_boolean it["reportStatus"] },
435
+ "Hard Limit" => lambda {|it| format_boolean it["hardLimit"] },
436
+ "Limit Type" => lambda {|it| format_limit_type(it) },
437
+ "Limit Type" => lambda {|it| format_limit_type(it) },
438
+ }
439
+ if licenses[0] && licenses[0]['limitType'] == 'workload'
440
+ license_columns.merge!({
441
+ "Workloads" => 'maxInstances'
442
+ })
443
+ else
444
+ license_columns.merge!({
445
+ "Managed Servers" => 'maxManagedServers',
446
+ "Discovered Servers" => 'maxDiscoveredServers',
447
+ "Hosts" => 'maxHosts',
448
+ "HPE VM Hosts" => 'maxMvm',
449
+ "HPE VM Sockets" => 'maxMvmSockets',
450
+ "Iac Deployments" => 'maxIac',
451
+ "Xaas Instances" => 'maxXaas',
452
+ "Executions" => 'maxExecutions',
453
+ "Distributed Workers" => 'maxDistributedWorkers',
454
+ # "Discovered Objects" => 'maxDiscoveredObjects'
455
+ })
456
+ end
457
+ print as_pretty_table(licenses, license_columns, options)
458
+ end
376
459
 
377
460
  end
@@ -493,6 +493,9 @@ class Morpheus::Cli::NetworksCommand
493
493
  # allow arbitrary -O options
494
494
  payload['network'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
495
495
 
496
+ # all of these prompts pass in options instead of options[:options] so merge them here
497
+ options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
498
+
496
499
  # Name
497
500
  if options['name']
498
501
  payload['network']['name'] = options['name']
@@ -813,16 +816,14 @@ class Morpheus::Cli::NetworksCommand
813
816
  ## Advanced Options
814
817
 
815
818
  # Network Domain
816
- if network_type['networkDomainEditable'] && payload['network']['networkDomain'].nil?
817
- if options.key?('domain')
818
- if options['domain'].to_s.empty? # clear it?
819
+ if payload['network']['networkDomain'].nil?
820
+ if options.key?('domain') && options['domain'].to_s.empty? # clear it?
819
821
  payload['network']['networkDomain'] = {'id' => nil}
820
822
  else
821
823
  # always prompt to handle value as name instead of id...
822
824
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'domain', 'fieldLabel' => 'Network Domain', 'type' => 'select', 'optionSource' => 'networkDomains', 'required' => false, 'description' => ''}], options, @api_client)
823
825
  payload['network']['networkDomain'] = {'id' => v_prompt['domain'].to_i} unless v_prompt['domain'].to_s.empty?
824
826
  end
825
- end
826
827
  end
827
828
 
828
829
  # Search Domains
@@ -295,7 +295,7 @@ class Morpheus::Cli::Processes
295
295
  subtitles << " Process ID: #{process_id}"
296
296
  subtitles += parse_list_subtitles(options)
297
297
  print_h1 title, subtitles
298
- print_process_details(process)
298
+ print_process_details(process, options)
299
299
 
300
300
  print_h2 "Process Events"
301
301
  process_events = process['events'] || process['processEvents'] || []
@@ -456,7 +456,7 @@ EOT
456
456
  subtitles = []
457
457
  subtitles += parse_list_subtitles(options)
458
458
  print_h1 title, subtitles
459
- print_process_event_details(process_event)
459
+ print_process_event_details(process_event, options)
460
460
  print reset, "\n"
461
461
  return 0
462
462
  end
@@ -73,15 +73,15 @@ class Morpheus::Cli::Snapshots
73
73
  "Name" => 'name',
74
74
  "Description" => 'description',
75
75
  "External Id" => 'externalId',
76
- "Status" => 'status',
77
- "State" => 'state',
76
+ # "State" => 'state',
78
77
  "Snapshot Type" => 'snapshotType',
79
- "Snapshot Created" => 'snapshotCreated',
80
- "Cloud" => 'zone.name',
81
- "Datastore" => 'datastore',
82
- "Parent Snapshot" => 'parentSnapshot',
83
- "Active" => 'currentlyActive',
84
- "Date Created" => 'dateCreated'
78
+ "Date Created" => lambda {|it| format_local_dt(it['snapshotCreated']) },
79
+ "Cloud" => lambda {|it| format_name_and_id(it['zone']) },
80
+ "Datastore" => lambda {|it| format_name_and_id(it['datastore']) },
81
+ "Parent Snapshot" => lambda {|it| format_name_and_id(it['parentSnapshot']) },
82
+ "Active" => lambda {|it| format_boolean(it['currentlyActive']) },
83
+ "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
84
+ "Status" => lambda {|it| format_snapshot_status(it) }
85
85
  }
86
86
  print_description_list(description_cols, snapshot)
87
87
 
@@ -134,4 +134,20 @@ class Morpheus::Cli::Snapshots
134
134
  exit 1
135
135
  end
136
136
  end
137
+
138
+ protected
139
+
140
+ def format_snapshot_status(snapshot, return_color=cyan)
141
+ out = ""
142
+ status_string = snapshot['status'].to_s
143
+ if status_string == 'complete'
144
+ out << "#{green}#{status_string.upcase}#{return_color}"
145
+ elsif status_string == 'failed'
146
+ out << "#{red}#{status_string.upcase}#{return_color}"
147
+ else
148
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
149
+ end
150
+ out
151
+ end
152
+
137
153
  end
@@ -8,8 +8,17 @@ module Morpheus::Cli::ProcessesHelper
8
8
  klass.send :include, Morpheus::Cli::PrintHelper
9
9
  end
10
10
 
11
+ def api_client
12
+ raise "#{self.class} has not defined @api_client" if @api_client.nil?
13
+ @api_client
14
+ end
15
+
16
+ def processes_interface
17
+ # get_interface('processes')
18
+ api_client.processes
19
+ end
11
20
 
12
- def print_process_details(process)
21
+ def print_process_details(process, options={})
13
22
  description_cols = {
14
23
  "Process ID" => lambda {|it| it['id'] },
15
24
  "Name" => lambda {|it| it['displayName'] },
@@ -22,7 +31,7 @@ module Morpheus::Cli::ProcessesHelper
22
31
  "Status" => lambda {|it| format_process_status(it) },
23
32
  # "# Events" => lambda {|it| (it['events'] || []).size() },
24
33
  }
25
- print_description_list(description_cols, process)
34
+ print_description_list(description_cols, process, options)
26
35
 
27
36
  if process['error']
28
37
  print_h2 "Error"
@@ -39,7 +48,7 @@ module Morpheus::Cli::ProcessesHelper
39
48
  end
40
49
  end
41
50
 
42
- def print_process_event_details(process_event)
51
+ def print_process_event_details(process_event, options={})
43
52
  # process_event =~ process
44
53
  description_cols = {
45
54
  "Process ID" => lambda {|it| it['processId'] },
@@ -53,7 +62,7 @@ module Morpheus::Cli::ProcessesHelper
53
62
  "Duration" => lambda {|it| format_process_duration(it) },
54
63
  "Status" => lambda {|it| format_process_status(it) },
55
64
  }
56
- print_description_list(description_cols, process_event)
65
+ print_description_list(description_cols, process_event, options)
57
66
 
58
67
  if process_event['error']
59
68
  print_h2 "Error"
@@ -111,4 +120,25 @@ module Morpheus::Cli::ProcessesHelper
111
120
  out
112
121
  end
113
122
 
123
+ def wait_for_process_execution(process_id, options={}, print_output = true)
124
+ refresh_interval = 10
125
+ if options[:refresh_interval].to_i > 0
126
+ refresh_interval = options[:refresh_interval]
127
+ end
128
+ refresh_display_seconds = refresh_interval % 1.0 == 0 ? refresh_interval.to_i : refresh_interval
129
+ unless options[:quiet]
130
+ print cyan, "Refreshing every #{refresh_display_seconds} seconds until process is complete...", "\n", reset
131
+ end
132
+ process = processes_interface.get(process_id)['process']
133
+ while ['new','queued','pending','running'].include?(process['status']) do
134
+ sleep(refresh_interval)
135
+ process = processes_interface.get(process_id)['process']
136
+ end
137
+ if print_output && options[:quiet] != true
138
+ print_h1 "Process Details", [], options
139
+ print_process_details(process, options)
140
+ end
141
+ return process
142
+ end
143
+
114
144
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "8.0.2"
4
+ VERSION = "8.0.4"
5
5
  end
6
6
  end
@@ -486,7 +486,7 @@ def format_name_and_id(obj)
486
486
  elsif(obj.is_a?(Hash))
487
487
  "#{obj['name']} [#{obj['id']}]" rescue ""
488
488
  else
489
- object.to_s
489
+ obj.to_s
490
490
  end
491
491
  end
492
492
 
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: 8.0.2
4
+ version: 8.0.4
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: 2025-01-16 00:00:00.000000000 Z
14
+ date: 2025-03-14 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: public_suffix