morpheus-cli 8.1.0 → 8.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: f058297ee2bc6b9cc1dac3f97bb2aad27785687af9283e89b5275b101a126145
4
- data.tar.gz: 21f00f181e173a464d579d2f6368d12fbe9880530d01fd59cebc40775eb9af78
3
+ metadata.gz: 1c5ef57fd8e0005ce881d8a2e8de0d8100e965447dd5e66dfded62ab50229c8a
4
+ data.tar.gz: 64705b005697dbab2f6b0c15fa58400182a8a2a063c5a99d93cb0364b101227c
5
5
  SHA512:
6
- metadata.gz: fea8e30a0ff487c060873ca4ba6a3d4af91c52f819e6956675d653dc57c330d495b2f27b9d821e7314da332573acb8bf6bc17380a283fe1404a80789eebda5b0
7
- data.tar.gz: 8d5fb8b37a542e13f5e9656e2d355b9224ed51ede6a4e749620b3dbdf059a3449b2b09e64c4d97a0b2458469d99508dbf3029a158c1a234a72736ccdc7b6437f
6
+ metadata.gz: feedeaf9d8c6241a0076af156b22182dc76ffb91feafbfd5fff9bae98016624f93400351d7fd37d4cb0fa16d42eb50444f5fd895d554bbb4214e6f8eb30c30e6
7
+ data.tar.gz: bbb9fb396e8b36bf35bf75a1afbcd08d3c0d000716486035c24d64a9540615a0de01e084ebce2dec11828d3345a5e9643b41c71b339bedbc92d17f8f79b10aa2
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 8.1.0
3
+ RUN gem install morpheus-cli -v 8.1.1
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -2,58 +2,69 @@ require 'morpheus/api/api_client'
2
2
  # this may change to just /api/image-builds
3
3
  class Morpheus::ImageBuilderImageBuildsInterface < Morpheus::APIClient
4
4
 
5
+ def base_path
6
+ "#{@base_url}/api/image-builds"
7
+ end
8
+
5
9
  def get(id, params={})
6
10
  raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
7
- url = "#{@base_url}/api/image-builds/#{id}"
11
+ url = "#{base_path}/#{id}"
8
12
  headers = { params: params, authorization: "Bearer #{@access_token}" }
9
13
  opts = {method: :get, url: url, headers: headers}
10
14
  execute(opts)
11
15
  end
12
16
 
13
17
  def list(params={})
14
- url = "#{@base_url}/api/image-builds"
18
+ url = "#{base_path}"
15
19
  headers = { params: params, authorization: "Bearer #{@access_token}" }
16
20
  opts = {method: :get, url: url, headers: headers}
17
21
  execute(opts)
18
22
  end
19
23
 
20
24
  def create(payload)
21
- url = "#{@base_url}/api/image-builds"
25
+ url = "#{base_path}"
22
26
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
23
27
  opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
24
28
  execute(opts)
25
29
  end
26
30
 
27
31
  # def validate_save(payload)
28
- # url = "#{@base_url}/api/image-builds/validate-save"
32
+ # url = "#{base_path}/validate-save"
29
33
  # headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
30
34
  # opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
31
35
  # execute(opts)
32
36
  # end
33
37
 
34
38
  def update(id, payload)
35
- url = "#{@base_url}/api/image-builds/#{id}"
39
+ url = "#{base_path}/#{id}"
36
40
  headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
37
41
  opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
38
42
  execute(opts)
39
43
  end
40
44
 
41
45
  def destroy(id, params={})
42
- url = "#{@base_url}/api/image-builds/#{id}"
46
+ url = "#{base_path}/#{id}"
43
47
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
44
48
  opts = {method: :delete, url: url, headers: headers}
45
49
  execute(opts)
46
50
  end
47
51
 
48
52
  def run(id, params={})
49
- url = "#{@base_url}/api/image-builds/#{id}/run"
53
+ url = "#{base_path}/#{id}/run"
50
54
  headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
51
55
  opts = {method: :post, url: url, headers: headers}
52
56
  execute(opts)
53
57
  end
54
58
 
55
59
  def list_executions(id, params={})
56
- url = "#{@base_url}/api/image-builds/#{id}/list-executions"
60
+ url = "#{base_path}/#{id}/list-executions"
61
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
62
+ opts = {method: :get, url: url, headers: headers}
63
+ execute(opts)
64
+ end
65
+
66
+ def create_options(params={})
67
+ url = "#{base_path}/create-options"
57
68
  headers = { params: params, authorization: "Bearer #{@access_token}" }
58
69
  opts = {method: :get, url: url, headers: headers}
59
70
  execute(opts)
@@ -6,4 +6,18 @@ class Morpheus::SystemsInterface < Morpheus::RestInterface
6
6
  "/api/infrastructure/systems"
7
7
  end
8
8
 
9
+ def save_uninitialized(payload, params={}, headers={})
10
+ execute(method: :post, url: "#{base_path}/uninitialized", params: params, payload: payload, headers: headers)
11
+ end
12
+
13
+ def initialize_system(id, payload={}, params={}, headers={})
14
+ validate_id!(id)
15
+ execute(method: :put, url: "#{base_path}/#{CGI::escape(id.to_s)}/initialize", params: params, payload: payload, headers: headers)
16
+ end
17
+
18
+ def validate_system(id, params={}, headers={})
19
+ validate_id!(id)
20
+ execute(method: :get, url: "#{base_path}/#{CGI::escape(id.to_s)}/validate", params: params, headers: headers)
21
+ end
22
+
9
23
  end
@@ -651,9 +651,9 @@ class Morpheus::Cli::ImageBuilderCommand
651
651
  private
652
652
 
653
653
  def get_available_image_build_types()
654
- # todo: api call
655
654
  [
656
- {'name' => 'VMware', 'code' => 'vmware', 'instanceType' => {'code' => 'vmware'}}
655
+ {'name' => 'VMware', 'code' => 'vmware', 'instanceType' => {'code' => 'vmware'}},
656
+ {'name' => 'KVM', 'code' => 'kvm', 'instanceType' => {'code' => 'kvm'}}
657
657
  ]
658
658
  end
659
659
 
@@ -663,17 +663,6 @@ class Morpheus::Cli::ImageBuilderCommand
663
663
  }
664
664
  end
665
665
 
666
- def find_image_build_type(val)
667
- if val.nil? || val.to_s.empty?
668
- return nil
669
- else
670
- return get_available_image_build_types().find { |it|
671
- (it['code'].to_s.downcase == val.to_s.downcase) ||
672
- (it['name'].to_s.downcase == val.to_s.downcase)
673
- }
674
- end
675
- end
676
-
677
666
  def add_image_build_option_types(connected=true)
678
667
  [
679
668
  {'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => get_available_image_build_types_dropdown(), 'required' => true, 'description' => 'Choose the type of image build.'},
@@ -901,15 +890,28 @@ class Morpheus::Cli::ImageBuilderCommand
901
890
  def prompt_new_image_build(options={}, default_values={}, do_require=true)
902
891
  payload = {}
903
892
 
893
+ # load to create-options data for help with prompting
894
+ create_options = nil
895
+ begin
896
+ create_options = @image_builds_interface.create_options({})
897
+ rescue => ex
898
+ Morpheus::Logging::DarkPrinter.puts "Unable to load image build create options data" if Morpheus::Logging.debug?
899
+ end
904
900
  # Summary / Settings Tab
905
901
 
906
902
  # Image Build Type
903
+ image_build_types = nil
904
+ if create_options && create_options['imageBuildTypes']
905
+ image_build_types = create_options['imageBuildTypes'].collect {|it| it.merge({'value' => it['code']}) }
906
+ else
907
+ image_build_types = get_available_image_build_types_dropdown
908
+ end
907
909
  image_build_type = nil
908
910
  if options['type']
909
- image_build_type = find_image_build_type(options['type'])
911
+ image_build_type = image_build_types.find { |it| (it['code'].to_s.downcase == options['type'].to_s.downcase) || (it['name'].to_s.downcase == options['type'].to_s.downcase) }
910
912
  else
911
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => get_available_image_build_types_dropdown(), 'required' => do_require, 'description' => 'Choose the type of image build.', 'defaultValue' => default_values['type'], :fmt=>:natural}], options, @api_client)
912
- image_build_type = find_image_build_type(v_prompt['type'])
913
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => image_build_types, 'required' => do_require, 'description' => 'Choose the type of image build.', 'defaultValue' => default_values['type'], :fmt=>:natural}], options, @api_client)
914
+ image_build_type = Morpheus::Cli::OptionTypes.get_last_select()
913
915
  end
914
916
  if !image_build_type
915
917
  print_red_alert "Image Build Type not found!"
@@ -6,10 +6,8 @@ class Morpheus::Cli::Systems
6
6
 
7
7
  set_command_name :systems
8
8
  set_command_description "View and manage systems."
9
- register_subcommands :list, :get, :add, :update, :remove
10
-
11
- set_command_hidden
12
-
9
+ register_subcommands :list, :get, :add, :update, :remove, :'add-uninitialized', {:'initialize' => 'exec_initialize'}, {:'validate' => 'exec_validate'}
10
+
13
11
  protected
14
12
 
15
13
  # Systems API uses lowercase keys in payloads.
@@ -164,6 +162,337 @@ class Morpheus::Cli::Systems
164
162
  end
165
163
  end
166
164
 
165
+ def add_uninitialized(args)
166
+ options = {}
167
+ params = {}
168
+ components = []
169
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
170
+ opts.banner = subcommand_usage("[name]")
171
+ opts.on('--name NAME', String, "System Name") do |val|
172
+ params['name'] = val.to_s
173
+ end
174
+ opts.on('--description [TEXT]', String, "Description") do |val|
175
+ params['description'] = val.to_s
176
+ end
177
+ opts.on('--type TYPE', String, "System Type ID or name") do |val|
178
+ params['type'] = val
179
+ end
180
+ opts.on('--layout LAYOUT', String, "System Layout ID or name") do |val|
181
+ params['layout'] = val
182
+ end
183
+ opts.on('--config JSON', String, "System config JSON") do |val|
184
+ params['config'] = JSON.parse(val)
185
+ end
186
+ opts.on('--externalId ID', String, "External ID") do |val|
187
+ params['externalId'] = val.to_s
188
+ end
189
+ opts.on('--component JSON', String, "Component JSON (can be repeated). e.g. '{\"typeCode\":\"compute-node\",\"name\":\"CN-1\",\"config\":{\"ip\":\"10.0.0.1\"}}'") do |val|
190
+ components << JSON.parse(val)
191
+ end
192
+ opts.on('--components JSON', String, "Components JSON array") do |val|
193
+ components.concat(JSON.parse(val))
194
+ end
195
+ build_standard_add_options(opts, options)
196
+ opts.footer = <<-EOT
197
+ Create a new system in an uninitialized state.
198
+ This creates a skeleton system with components but does not invoke provider initialization.
199
+ [name] is optional and can be passed as the first argument.
200
+ EOT
201
+ end
202
+ optparse.parse!(args)
203
+ connect(options)
204
+
205
+ payload = nil
206
+ if options[:payload]
207
+ payload = options[:payload]
208
+ payload[rest_object_key] ||= {}
209
+ payload[rest_object_key].deep_merge!(params) unless params.empty?
210
+ payload[rest_object_key]['name'] ||= args[0] if args[0]
211
+ payload[rest_object_key]['components'] = components unless components.empty?
212
+ else
213
+ system_payload = {}
214
+
215
+ # Name
216
+ system_payload['name'] = params['name'] || args[0]
217
+ if !system_payload['name'] && !options[:no_prompt]
218
+ system_payload['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true}], options[:options], @api_client, {})['name']
219
+ end
220
+ raise_command_error "Name is required.\n#{optparse}" if system_payload['name'].to_s.empty?
221
+
222
+ # Description
223
+ if !params['description'] && !options[:no_prompt]
224
+ system_payload['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false}], options[:options], @api_client, {})['description']
225
+ else
226
+ system_payload['description'] = params['description']
227
+ end
228
+
229
+ # Type
230
+ available_types = system_types_for_dropdown
231
+ type_val = params['type']
232
+ if type_val
233
+ type_id = type_val =~ /\A\d+\Z/ ? type_val.to_i : available_types.find { |t| t['name'] == type_val || t['code'] == type_val }&.dig('id')
234
+ raise_command_error "System type not found: #{type_val}" unless type_id
235
+ system_payload['type'] = {'id' => type_id}
236
+ elsif !options[:no_prompt]
237
+ if available_types.empty?
238
+ raise_command_error "No system types found."
239
+ else
240
+ print cyan, "Available System Types\n", reset
241
+ available_types.each do |t|
242
+ print " #{t['id']}) #{t['name']}#{t['code'] ? " (#{t['code']})" : ''}\n"
243
+ end
244
+ selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'text', 'fieldLabel' => 'System Type ID', 'required' => true}], options[:options], @api_client, {})['type']
245
+ type_id = available_types.find { |t| t['id'].to_s == selected.to_s }&.dig('id')
246
+ raise_command_error "Invalid system type id: #{selected}" unless type_id
247
+ system_payload['type'] = {'id' => type_id}
248
+ end
249
+ end
250
+
251
+ # Layout
252
+ available_layouts = system_layouts_for_dropdown(system_payload.dig('type', 'id'))
253
+ selected_layout = nil
254
+ layout_val = params['layout']
255
+ if layout_val
256
+ layout_id = layout_val =~ /\A\d+\Z/ ? layout_val.to_i : available_layouts.find { |l| l['name'] == layout_val || l['code'] == layout_val }&.dig('id')
257
+ raise_command_error "System layout not found: #{layout_val}" unless layout_id
258
+ system_payload['layout'] = {'id' => layout_id}
259
+ selected_layout = available_layouts.find { |l| l['id'].to_i == layout_id.to_i }
260
+ elsif !options[:no_prompt]
261
+ if available_layouts.empty?
262
+ raise_command_error "No system layouts found for selected type."
263
+ else
264
+ print cyan, "Available System Layouts\n", reset
265
+ available_layouts.each do |l|
266
+ print " #{l['id']}) #{l['name']}#{l['code'] ? " (#{l['code']})" : ''}\n"
267
+ end
268
+ selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'text', 'fieldLabel' => 'System Layout ID', 'required' => true}], options[:options], @api_client, {})['layout']
269
+ selected_layout = available_layouts.find { |l| l['id'].to_s == selected.to_s }
270
+ raise_command_error "Invalid system layout id: #{selected}" unless selected_layout
271
+ system_payload['layout'] = {'id' => selected_layout['id']}
272
+ end
273
+ end
274
+
275
+ # Config
276
+ system_payload['config'] = params['config'] if params['config']
277
+
278
+ # External ID
279
+ system_payload['externalId'] = params['externalId'] if params['externalId']
280
+
281
+ # Components — prompt interactively if none provided via flags
282
+ if components.empty? && !options[:no_prompt] && selected_layout
283
+ available_component_types = selected_layout['componentTypes'] || []
284
+ if available_component_types.any?
285
+ print cyan, "\nAvailable Component Types for layout '#{selected_layout['name']}':\n", reset
286
+ available_component_types.each do |ct|
287
+ print " #{ct['code']}#{ct['category'] ? " [#{ct['category']}]" : ''} - #{ct['name']}\n"
288
+ end
289
+ print "\n"
290
+ add_more = true
291
+ while add_more
292
+ add_component = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'addComponent', 'type' => 'text', 'fieldLabel' => 'Add a component? (yes/no)', 'required' => true, 'defaultValue' => 'yes'}], options[:options], @api_client, {})['addComponent']
293
+ if add_component.to_s.downcase =~ /^(y|yes)$/
294
+ # Component type selection
295
+ print cyan, "Component Types:\n", reset
296
+ available_component_types.each_with_index do |ct, idx|
297
+ print " #{idx + 1}) #{ct['name']} (#{ct['code']})\n"
298
+ end
299
+ selected_ct = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'componentType', 'type' => 'text', 'fieldLabel' => 'Component Type (number or code)', 'required' => true}], options[:options], @api_client, {})['componentType']
300
+ component_type = nil
301
+ if selected_ct =~ /\A\d+\Z/
302
+ idx = selected_ct.to_i - 1
303
+ component_type = available_component_types[idx] if idx >= 0 && idx < available_component_types.size
304
+ else
305
+ component_type = available_component_types.find { |ct| ct['code'] == selected_ct }
306
+ end
307
+ if component_type.nil?
308
+ print_red_alert "Invalid component type: #{selected_ct}"
309
+ next
310
+ end
311
+ # Component name
312
+ default_name = component_type['name']
313
+ comp_name = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'componentName', 'type' => 'text', 'fieldLabel' => 'Component Name', 'required' => false, 'defaultValue' => default_name}], options[:options], @api_client, {})['componentName']
314
+ comp = {'typeCode' => component_type['code'], 'name' => comp_name || default_name}
315
+ # Component config
316
+ comp_config = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'componentConfig', 'type' => 'text', 'fieldLabel' => 'Component Config JSON (optional)', 'required' => false}], options[:options], @api_client, {})['componentConfig']
317
+ if comp_config && !comp_config.to_s.empty?
318
+ begin
319
+ comp['config'] = JSON.parse(comp_config)
320
+ rescue JSON::ParserError => e
321
+ print_red_alert "Invalid JSON: #{e.message}"
322
+ next
323
+ end
324
+ end
325
+ components << comp
326
+ print_green_success "Added component: #{comp['name']} (#{comp['typeCode']})"
327
+ else
328
+ add_more = false
329
+ end
330
+ end
331
+ end
332
+ end
333
+ system_payload['components'] = components unless components.empty?
334
+
335
+ payload = {rest_object_key => system_payload}
336
+ end
337
+
338
+ if options[:dry_run]
339
+ print_dry_run rest_interface.dry.save_uninitialized(payload)
340
+ return
341
+ end
342
+
343
+ rest_interface.setopts(options)
344
+ json_response = rest_interface.save_uninitialized(payload)
345
+ render_response(json_response, options, rest_object_key) do
346
+ system_id = json_response['id'] || json_response.dig(rest_object_key, 'id')
347
+ print_green_success "Uninitialized system created"
348
+ get([system_id.to_s] + (options[:remote] ? ['-r', options[:remote]] : [])) if system_id
349
+ end
350
+ end
351
+
352
+ def exec_initialize(args)
353
+ options = {}
354
+ params = {}
355
+ components = []
356
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
357
+ opts.banner = subcommand_usage("[system]")
358
+ opts.on('--name NAME', String, "Update the system name before initializing") do |val|
359
+ params['name'] = val.to_s
360
+ end
361
+ opts.on('--description [TEXT]', String, "Update the description before initializing") do |val|
362
+ params['description'] = val.to_s
363
+ end
364
+ opts.on('--externalId ID', String, "Set the external ID before initializing") do |val|
365
+ params['externalId'] = val.to_s
366
+ end
367
+ opts.on('--config JSON', String, "Set config JSON before initializing") do |val|
368
+ params['config'] = JSON.parse(val)
369
+ end
370
+ opts.on('--component JSON', String, "Component update JSON (can be repeated). e.g. '{\"typeCode\":\"compute-node\",\"externalId\":\"ext-001\",\"config\":{\"ip\":\"10.0.0.1\"}}'") do |val|
371
+ components << JSON.parse(val)
372
+ end
373
+ opts.on('--components JSON', String, "Component updates JSON array") do |val|
374
+ components.concat(JSON.parse(val))
375
+ end
376
+ build_standard_update_options(opts, options)
377
+ opts.footer = <<-EOT
378
+ Initialize an existing system that is in an uninitialized state.
379
+ This invokes the provider's prepare and initialize lifecycle methods.
380
+ [system] is required. This is the name or id of a system.
381
+ EOT
382
+ end
383
+ optparse.parse!(args)
384
+ verify_args!(args: args, optparse: optparse, count: 1)
385
+ connect(options)
386
+
387
+ system_record = nil
388
+ if args[0].to_s =~ /\A\d{1,}\Z/
389
+ json_response = rest_interface.get(args[0].to_i)
390
+ system_record = json_response[rest_object_key] || json_response
391
+ else
392
+ system_record = find_by_name(rest_key, args[0])
393
+ end
394
+ return 1, "System not found for '#{args[0]}'" if system_record.nil?
395
+
396
+ # Prompt for component updates if none provided via flags and not in no-prompt mode
397
+ if components.empty? && !options[:no_prompt] && !options[:payload]
398
+ existing_components = system_record['components'] || []
399
+ if existing_components.any?
400
+ print cyan, "\nExisting Components:\n", reset
401
+ existing_components.each_with_index do |c, idx|
402
+ type_code = c.dig('type', 'code') || 'unknown'
403
+ print " #{idx + 1}) #{c['name']} (#{type_code})#{c['externalId'] ? " externalId=#{c['externalId']}" : ''}\n"
404
+ end
405
+ print "\n"
406
+ update_components = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'updateComponents', 'type' => 'text', 'fieldLabel' => 'Update any components? (yes/no)', 'required' => true, 'defaultValue' => 'no'}], options[:options], @api_client, {})['updateComponents']
407
+ if update_components.to_s.downcase =~ /^(y|yes)$/
408
+ existing_components.each do |c|
409
+ type_code = c.dig('type', 'code') || 'unknown'
410
+ print cyan, "\nComponent: #{c['name']} (#{type_code})\n", reset
411
+ comp_update = {'typeCode' => type_code}
412
+
413
+ ext_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'externalId', 'type' => 'text', 'fieldLabel' => " External ID (current: #{c['externalId'] || 'none'})", 'required' => false}], options[:options], @api_client, {})['externalId']
414
+ comp_update['externalId'] = ext_id unless ext_id.to_s.empty?
415
+
416
+ comp_config = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'config', 'type' => 'text', 'fieldLabel' => ' Config JSON (optional)', 'required' => false}], options[:options], @api_client, {})['config']
417
+ if comp_config && !comp_config.to_s.empty?
418
+ begin
419
+ comp_update['config'] = JSON.parse(comp_config)
420
+ rescue JSON::ParserError => e
421
+ print_red_alert "Invalid JSON for component config: #{e.message}"
422
+ end
423
+ end
424
+
425
+ components << comp_update if comp_update.keys.length > 1
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ payload = {}
432
+ if options[:payload]
433
+ payload = options[:payload]
434
+ payload[rest_object_key] ||= {}
435
+ payload[rest_object_key].deep_merge!(params) unless params.empty?
436
+ payload[rest_object_key]['components'] = components unless components.empty?
437
+ else
438
+ params['components'] = components unless components.empty?
439
+ payload = {rest_object_key => params}
440
+ end
441
+
442
+ if options[:dry_run]
443
+ print_dry_run rest_interface.dry.initialize_system(system_record['id'], payload)
444
+ return
445
+ end
446
+
447
+ rest_interface.setopts(options)
448
+ json_response = rest_interface.initialize_system(system_record['id'], payload)
449
+ render_response(json_response, options, rest_object_key) do
450
+ print_green_success "System #{system_record['name']} initialized"
451
+ get([system_record['id'].to_s] + (options[:remote] ? ['-r', options[:remote]] : []))
452
+ end
453
+ end
454
+
455
+ def exec_validate(args)
456
+ options = {}
457
+ params = {}
458
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
459
+ opts.banner = subcommand_usage("[system]")
460
+ build_standard_get_options(opts, options)
461
+ opts.footer = <<-EOT
462
+ Validate an existing system by running the provider's pre-check phase.
463
+ No state changes are made. Returns success if the provider's prepare check passes.
464
+ [system] is required. This is the name or id of a system.
465
+ EOT
466
+ end
467
+ optparse.parse!(args)
468
+ verify_args!(args: args, optparse: optparse, count: 1)
469
+ connect(options)
470
+
471
+ system = nil
472
+ if args[0].to_s =~ /\A\d{1,}\Z/
473
+ json_response = rest_interface.get(args[0].to_i)
474
+ system = json_response[rest_object_key] || json_response
475
+ else
476
+ system = find_by_name(rest_key, args[0])
477
+ end
478
+ return 1, "System not found for '#{args[0]}'" if system.nil?
479
+
480
+ if options[:dry_run]
481
+ print_dry_run rest_interface.dry.validate_system(system['id'])
482
+ return
483
+ end
484
+
485
+ rest_interface.setopts(options)
486
+ json_response = rest_interface.validate_system(system['id'])
487
+ render_response(json_response, options) do
488
+ if json_response['success']
489
+ print_green_success "System #{system['name']} validated successfully"
490
+ else
491
+ print_red_alert "System #{system['name']} validation failed: #{json_response['msg']}"
492
+ end
493
+ end
494
+ end
495
+
167
496
  def remove(args)
168
497
  options = {}
169
498
  optparse = Morpheus::Cli::OptionParser.new do |opts|
@@ -265,6 +594,6 @@ EOT
265
594
  return [] if type_id.nil?
266
595
  result = @api_client.system_types.list_layouts(type_id, {'max' => 100})
267
596
  items = result ? (result['systemTypeLayouts'] || result[:systemTypeLayouts] || result['layouts'] || result[:layouts] || []) : []
268
- items.map { |l| {'id' => l['id'] || l[:id], 'name' => l['name'] || l[:name], 'value' => (l['id'] || l[:id]).to_s, 'code' => l['code'] || l[:code]} }
597
+ items.map { |l| {'id' => l['id'] || l[:id], 'name' => l['name'] || l[:name], 'value' => (l['id'] || l[:id]).to_s, 'code' => l['code'] || l[:code], 'componentTypes' => l['componentTypes'] || l[:componentTypes] || []} }
269
598
  end
270
599
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "8.1.0"
4
+ VERSION = "8.1.1"
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.0
4
+ version: 8.1.1
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: 2026-03-11 00:00:00.000000000 Z
14
+ date: 2026-04-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: tins