morpheus-cli 8.0.13 → 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.
@@ -0,0 +1,599 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::Systems
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::RestCommand
6
+
7
+ set_command_name :systems
8
+ set_command_description "View and manage systems."
9
+ register_subcommands :list, :get, :add, :update, :remove, :'add-uninitialized', {:'initialize' => 'exec_initialize'}, {:'validate' => 'exec_validate'}
10
+
11
+ protected
12
+
13
+ # Systems API uses lowercase keys in payloads.
14
+ def system_object_key
15
+ 'system'
16
+ end
17
+
18
+ def system_list_key
19
+ 'systems'
20
+ end
21
+
22
+ def system_list_column_definitions(options)
23
+ {
24
+ "ID" => 'id',
25
+ "Name" => 'name',
26
+ "Type" => lambda {|it| it['type'] ? it['type']['name'] : '' },
27
+ "Layout" => lambda {|it| it['layout'] ? it['layout']['name'] : '' },
28
+ "Status" => 'status',
29
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
30
+ "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) }
31
+ }
32
+ end
33
+
34
+ def system_column_definitions(options)
35
+ {
36
+ "ID" => 'id',
37
+ "Name" => 'name',
38
+ "Description" => 'description',
39
+ "Status" => 'status',
40
+ "Status Message" => 'statusMessage',
41
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
42
+ "External ID" => 'externalId',
43
+ "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
44
+ "Last Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
45
+ }
46
+ end
47
+
48
+ def render_response_for_get(json_response, options)
49
+ render_response(json_response, options, rest_object_key) do
50
+ record = json_response[rest_object_key] || json_response
51
+ print_h1 rest_label, [], options
52
+ print cyan
53
+ print_description_list(rest_column_definitions(options), record, options)
54
+ print reset,"\n"
55
+ end
56
+ end
57
+
58
+ def add(args)
59
+ options = {}
60
+ params = {}
61
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
62
+ opts.banner = subcommand_usage("[name]")
63
+ opts.on('--name NAME', String, "System Name") do |val|
64
+ params['name'] = val.to_s
65
+ end
66
+ opts.on('--description [TEXT]', String, "Description") do |val|
67
+ params['description'] = val.to_s
68
+ end
69
+ opts.on('--type TYPE', String, "System Type ID or name") do |val|
70
+ params['type'] = val
71
+ end
72
+ opts.on('--layout LAYOUT', String, "System Layout ID or name") do |val|
73
+ params['layout'] = val
74
+ end
75
+ build_standard_add_options(opts, options)
76
+ opts.footer = "Create a new system.\n[name] is optional and can be passed as the first argument."
77
+ end
78
+ optparse.parse!(args)
79
+ connect(options)
80
+
81
+ payload = nil
82
+ if options[:payload]
83
+ payload = options[:payload]
84
+ payload[rest_object_key] ||= {}
85
+ payload[rest_object_key].deep_merge!(params) unless params.empty?
86
+ payload[rest_object_key]['name'] ||= args[0] if args[0]
87
+ else
88
+ system_payload = {}
89
+
90
+ # Name
91
+ system_payload['name'] = params['name'] || args[0]
92
+ if !system_payload['name'] && !options[:no_prompt]
93
+ system_payload['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true}], options[:options], @api_client, {})['name']
94
+ end
95
+ raise_command_error "Name is required.\n#{optparse}" if system_payload['name'].to_s.empty?
96
+
97
+ # Description
98
+ if !params['description'] && !options[:no_prompt]
99
+ system_payload['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false}], options[:options], @api_client, {})['description']
100
+ else
101
+ system_payload['description'] = params['description']
102
+ end
103
+
104
+ # Type
105
+ available_types = system_types_for_dropdown
106
+ type_val = params['type']
107
+ if type_val
108
+ 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')
109
+ raise_command_error "System type not found: #{type_val}" unless type_id
110
+ system_payload['type'] = {'id' => type_id}
111
+ elsif !options[:no_prompt]
112
+ if available_types.empty?
113
+ raise_command_error "No system types found."
114
+ else
115
+ print cyan, "Available System Types\n", reset
116
+ available_types.each do |t|
117
+ print " #{t['id']}) #{t['name']}#{t['code'] ? " (#{t['code']})" : ''}\n"
118
+ end
119
+ selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'text', 'fieldLabel' => 'System Type ID', 'required' => true}], options[:options], @api_client, {})['type']
120
+ type_id = available_types.find { |t| t['id'].to_s == selected.to_s }&.dig('id')
121
+ raise_command_error "Invalid system type id: #{selected}" unless type_id
122
+ system_payload['type'] = {'id' => type_id}
123
+ end
124
+ end
125
+
126
+ # Layout
127
+ available_layouts = system_layouts_for_dropdown(system_payload.dig('type', 'id'))
128
+ layout_val = params['layout']
129
+ if layout_val
130
+ 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')
131
+ raise_command_error "System layout not found: #{layout_val}" unless layout_id
132
+ system_payload['layout'] = {'id' => layout_id}
133
+ elsif !options[:no_prompt]
134
+ if available_layouts.empty?
135
+ raise_command_error "No system layouts found for selected type."
136
+ else
137
+ print cyan, "Available System Layouts\n", reset
138
+ available_layouts.each do |l|
139
+ print " #{l['id']}) #{l['name']}#{l['code'] ? " (#{l['code']})" : ''}\n"
140
+ end
141
+ selected = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'text', 'fieldLabel' => 'System Layout ID', 'required' => true}], options[:options], @api_client, {})['layout']
142
+ layout_id = available_layouts.find { |l| l['id'].to_s == selected.to_s }&.dig('id')
143
+ raise_command_error "Invalid system layout id: #{selected}" unless layout_id
144
+ system_payload['layout'] = {'id' => layout_id}
145
+ end
146
+ end
147
+
148
+ payload = {rest_object_key => system_payload}
149
+ end
150
+
151
+ if options[:dry_run]
152
+ print_dry_run rest_interface.dry.create(payload)
153
+ return
154
+ end
155
+
156
+ rest_interface.setopts(options)
157
+ json_response = rest_interface.create(payload)
158
+ render_response(json_response, options, rest_object_key) do
159
+ system_id = json_response['id'] || json_response.dig(rest_object_key, 'id')
160
+ print_green_success "System created"
161
+ get([system_id.to_s] + (options[:remote] ? ['-r', options[:remote]] : [])) if system_id
162
+ end
163
+ end
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
+
496
+ def remove(args)
497
+ options = {}
498
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
499
+ opts.banner = subcommand_usage("[system]")
500
+ build_standard_remove_options(opts, options)
501
+ opts.footer = <<-EOT
502
+ Delete an existing system.
503
+ [system] is required. This is the name or id of a system.
504
+ EOT
505
+ end
506
+ optparse.parse!(args)
507
+ verify_args!(args: args, optparse: optparse, count: 1)
508
+ connect(options)
509
+
510
+ system = nil
511
+ if args[0].to_s =~ /\A\d{1,}\Z/
512
+ json_response = rest_interface.get(args[0].to_i)
513
+ system = json_response[rest_object_key] || json_response
514
+ else
515
+ system = find_by_name(rest_key, args[0])
516
+ end
517
+ return 1, "System not found for '#{args[0]}'" if system.nil?
518
+
519
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the system #{system['name']}?")
520
+ return 9, "aborted"
521
+ end
522
+
523
+ if options[:dry_run]
524
+ print_dry_run rest_interface.dry.destroy(system['id'])
525
+ return
526
+ end
527
+
528
+ rest_interface.setopts(options)
529
+ json_response = rest_interface.destroy(system['id'])
530
+ render_response(json_response, options) do
531
+ print_green_success "System #{system['name']} removed"
532
+ end
533
+ end
534
+
535
+ def update(args)
536
+ options = {}
537
+ params = {}
538
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
539
+ opts.banner = subcommand_usage("[system] --name --description")
540
+ opts.on("--name NAME", String, "Updates System Name") do |val|
541
+ params['name'] = val.to_s
542
+ end
543
+ opts.on("--description [TEXT]", String, "Updates System Description") do |val|
544
+ params['description'] = val.to_s
545
+ end
546
+ build_standard_update_options(opts, options, [:find_by_name])
547
+ opts.footer = <<-EOT
548
+ Update an existing system.
549
+ [system] is required. This is the name or id of a system.
550
+ EOT
551
+ end
552
+ optparse.parse!(args)
553
+ verify_args!(args: args, optparse: optparse, count: 1)
554
+ connect(options)
555
+
556
+ system = nil
557
+ if args[0].to_s =~ /\A\d{1,}\Z/
558
+ json_response = rest_interface.get(args[0].to_i)
559
+ system = json_response[rest_object_key] || json_response
560
+ else
561
+ system = find_by_name(rest_key, args[0])
562
+ end
563
+ return 1, "System not found for '#{args[0]}'" if system.nil?
564
+
565
+ passed_options = parse_passed_options(options)
566
+ params.deep_merge!(passed_options) unless passed_options.empty?
567
+ params.booleanize!
568
+
569
+ payload = parse_payload(options) || {rest_object_key => params}
570
+ if payload[rest_object_key].nil? || payload[rest_object_key].empty?
571
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
572
+ end
573
+
574
+ if options[:dry_run]
575
+ print_dry_run rest_interface.dry.update(system['id'], payload)
576
+ return
577
+ end
578
+
579
+ rest_interface.setopts(options)
580
+ json_response = rest_interface.update(system['id'], payload)
581
+ render_response(json_response, options, rest_object_key) do
582
+ print_green_success "Updated system #{system['id']}"
583
+ get([system['id']] + (options[:remote] ? ['-r', options[:remote]] : []))
584
+ end
585
+ end
586
+
587
+ def system_types_for_dropdown
588
+ result = @api_client.system_types.list({'max' => 100})
589
+ items = result ? (result['systemTypes'] || result[:systemTypes] || result['types'] || result[:types] || []) : []
590
+ items.map { |t| {'id' => t['id'] || t[:id], 'name' => t['name'] || t[:name], 'value' => (t['id'] || t[:id]).to_s, 'code' => t['code'] || t[:code]} }
591
+ end
592
+
593
+ def system_layouts_for_dropdown(type_id = nil)
594
+ return [] if type_id.nil?
595
+ result = @api_client.system_types.list_layouts(type_id, {'max' => 100})
596
+ items = result ? (result['systemTypeLayouts'] || result[:systemTypeLayouts] || result['layouts'] || result[:layouts] || []) : []
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] || []} }
598
+ end
599
+ end
@@ -3,6 +3,7 @@ require 'morpheus/cli/cli_command'
3
3
  class Morpheus::Cli::TenantsCommand
4
4
  include Morpheus::Cli::CliCommand
5
5
  include Morpheus::Cli::AccountsHelper
6
+ include Morpheus::Cli::WhoamiHelper
6
7
  set_command_name :tenants
7
8
  set_command_description "View and manage tenants (accounts)."
8
9
  register_subcommands :list, :count, :get, :add, :update, :remove
@@ -21,6 +22,7 @@ class Morpheus::Cli::TenantsCommand
21
22
  @account_users_interface = @api_client.account_users
22
23
  @accounts_interface = @api_client.accounts
23
24
  @roles_interface = @api_client.roles
25
+ @whoami_interface = @api_client.whoami
24
26
  end
25
27
 
26
28
  def handle(args)
@@ -286,18 +288,25 @@ EOT
286
288
  [
287
289
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
288
290
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
289
- {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
290
- @roles_interface.list(nil, {roleType:'account'})['roles'].collect {|it|
291
+ {'fieldContext' => 'parentAccount', 'fieldName' => 'id', 'fieldLabel' => 'Parent Tenant', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
292
+ @accounts_interface.list({max:10000})['accounts'].collect {|it|
291
293
  {"name" => (it["authority"] || it["name"]), "value" => it["id"]}
292
294
  }
293
295
  }, 'displayOrder' => 3},
296
+ {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
297
+ tenant_id = (api_params['parentAccount']['id'] rescue nil)
298
+ params = {max: 10000, roleType:'account', tenantId: tenant_id}
299
+ @roles_interface.list(nil, params)['roles'].collect {|it|
300
+ {"name" => (it["authority"] || it["name"]), "value" => it["id"]}
301
+ }
302
+ }, 'displayOrder' => 4},
294
303
  {'fieldName' => 'currency', 'fieldLabel' => 'Currency', 'type' => 'text', 'defaultValue' => 'USD', 'displayOrder' => 4}
295
304
  ]
296
305
  end
297
306
 
298
307
  def update_account_option_types
299
308
  list = add_account_option_types()
300
- # list = list.reject {|it| ["interval"].include? it['fieldName'] }
309
+ list = list.reject {|it| it['fieldContext'] == 'parentAccount' }
301
310
  list.each {|it| it.delete('required') }
302
311
  list.each {|it| it.delete('defaultValue') }
303
312
  list
@@ -45,6 +45,7 @@ module Morpheus::Cli::AccountsHelper
45
45
  "# Instances" => 'stats.instanceCount',
46
46
  "# Users" => 'stats.userCount',
47
47
  "Role" => lambda {|it| it['role']['authority'] rescue nil },
48
+ "Parent Tenant" => lambda {|it| it['parent']['name'] rescue nil },
48
49
  "Master" => lambda {|it| format_boolean(it['master']) },
49
50
  "Currency" => 'currency',
50
51
  "Status" => lambda {|it|