morpheus-cli 4.1.9 → 4.1.10

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,418 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::LibraryResourceSpecsCommand
4
+ include Morpheus::Cli::CliCommand
5
+ set_command_hidden # hidden until complete
6
+ set_command_name :'library-spec-templates'
7
+
8
+ register_subcommands :list, :get, :add, :update, :remove
9
+
10
+ def connect(opts)
11
+ @api_client = establish_remote_appliance_connection(opts)
12
+ @resource_specs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).library_resource_specs
13
+ end
14
+
15
+ def handle(args)
16
+ handle_subcommand(args)
17
+ end
18
+
19
+ def list(args)
20
+ options = {}
21
+ params = {}
22
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
23
+ opts.banner = subcommand_usage()
24
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
25
+ opts.footer = "List resource spec templates."
26
+ end
27
+ optparse.parse!(args)
28
+ connect(options)
29
+ if args.count > 0
30
+ print_error Morpheus::Terminal.angry_prompt
31
+ puts_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.inspect}\n#{optparse}"
32
+ return 1
33
+ end
34
+ begin
35
+ # construct payload
36
+ params.merge!(parse_list_options(options))
37
+ @resource_specs_interface.setopts(options)
38
+ if options[:dry_run]
39
+ print_dry_run @resource_specs_interface.dry.list(params)
40
+ return
41
+ end
42
+ # do it
43
+ json_response = @resource_specs_interface.list(params)
44
+ # print result and return output
45
+ if options[:json]
46
+ puts as_json(json_response, options, "specs")
47
+ return 0
48
+ elsif options[:csv]
49
+ puts records_as_csv(json_response['specTemplates'], options)
50
+ return 0
51
+ elsif options[:yaml]
52
+ puts as_yaml(json_response, options, "specs")
53
+ return 0
54
+ end
55
+ resource_specs = json_response['specTemplates']
56
+ title = "Morpheus Library - Resource Spec Templates"
57
+ subtitles = []
58
+ subtitles += parse_list_subtitles(options)
59
+ print_h1 title, subtitles
60
+ if resource_specs.empty?
61
+ print cyan,"No resource specs found.",reset,"\n"
62
+ else
63
+ print_resource_specs_table(resource_specs, options)
64
+ print_results_pagination(json_response)
65
+ end
66
+ print reset,"\n"
67
+ rescue RestClient::Exception => e
68
+ print_rest_exception(e, options)
69
+ return 1
70
+ end
71
+ end
72
+
73
+ def get(args)
74
+ options = {}
75
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
76
+ opts.banner = subcommand_usage("[name]")
77
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
78
+ end
79
+ optparse.parse!(args)
80
+ if args.count < 1
81
+ puts optparse
82
+ return 1
83
+ end
84
+ connect(options)
85
+ id_list = parse_id_list(args)
86
+ return run_command_for_each_arg(id_list) do |arg|
87
+ _get(arg, options)
88
+ end
89
+ end
90
+
91
+ def _get(id, options)
92
+
93
+ begin
94
+ resource_spec = find_resource_spec_by_name_or_id(id)
95
+ if resource_spec.nil?
96
+ return 1
97
+ end
98
+ @resource_specs_interface.setopts(options)
99
+ if options[:dry_run]
100
+ print_dry_run @resource_specs_interface.dry.get(resource_spec['id'])
101
+ return
102
+ end
103
+ json_response = @resource_specs_interface.get(resource_spec['id'])
104
+ resource_spec = json_response['specTemplate']
105
+ instances = json_response['instances'] || []
106
+ servers = json_response['servers'] || []
107
+ if options[:json]
108
+ puts as_json(json_response, options, 'specTemplate')
109
+ return 0
110
+ elsif options[:yaml]
111
+ puts as_yaml(json_response, options, 'specTemplate')
112
+ return 0
113
+ elsif options[:csv]
114
+ puts records_as_csv([json_response['specTemplate']], options)
115
+ return 0
116
+ end
117
+
118
+ print_h1 "Resource Spec Details"
119
+ print cyan
120
+ detemplateion_cols = {
121
+ "ID" => lambda {|it| it['id'] },
122
+ "Name" => lambda {|it| it['name'] },
123
+ "Type" => lambda {|it| format_resource_spec_type(it['templateType']) },
124
+ "Source" => lambda {|it| it['source'] },
125
+ #"Owner" => lambda {|it| it['account'] ? it['account']['name'] : '' },
126
+ "Created By" => lambda {|it| it['createdBy'] },
127
+ "Updated By" => lambda {|it| it['updatedBy'] },
128
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
129
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
130
+ }
131
+ print_detemplateion_list(detemplateion_cols, resource_spec)
132
+
133
+ print_h2 "Content"
134
+
135
+ puts resource_spec['content']
136
+
137
+ print reset,"\n"
138
+ return 0
139
+ rescue RestClient::Exception => e
140
+ print_rest_exception(e, options)
141
+ return 1
142
+ end
143
+ end
144
+
145
+ def add(args)
146
+ options = {}
147
+ params = {'templateType' => 'bash', 'templatePhase' => 'provision'}
148
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
149
+ opts.banner = subcommand_usage("[name]")
150
+ opts.on('--name VALUE', String, "Name") do |val|
151
+ params['name'] = val
152
+ end
153
+ opts.on('--type [kubernetes|helm|terraform]', String, "Spec Template Type.") do |val|
154
+ params['templateType'] = val
155
+ end
156
+ # opts.on('--phase [provision|start|stop]', String, "Template Phase. Default is 'provision'") do |val|
157
+ # params['templatePhase'] = val
158
+ # end
159
+ opts.on('--category VALUE', String, "Category") do |val|
160
+ params['category'] = val
161
+ end
162
+ opts.on('--template TEXT', String, "Contents of the template.") do |val|
163
+ params['template'] = val
164
+ end
165
+ opts.on('--file FILE', "File containing the template. This can be used instead of --template" ) do |filename|
166
+ full_filename = File.expand_path(filename)
167
+ if File.exists?(full_filename)
168
+ params['template'] = File.read(full_filename)
169
+ else
170
+ print_red_alert "File not found: #{full_filename}"
171
+ exit 1
172
+ end
173
+ # use the filename as the name by default.
174
+ if !params['name']
175
+ params['name'] = File.basename(full_filename)
176
+ end
177
+ end
178
+ # opts.on('--enabled [on|off]', String, "Can be used to disable it") do |val|
179
+ # options['enabled'] = !(val.to_s == 'off' || val.to_s == 'false')
180
+ # end
181
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
182
+ opts.footer = "Create a new spec template." + "\n" +
183
+ "[name] is required and can be passed as --name instead."
184
+ end
185
+ optparse.parse!(args)
186
+ # support [name] as first argument
187
+ if args[0]
188
+ params['name'] = args[0]
189
+ end
190
+ if !params['name']
191
+ print_error Morpheus::Terminal.angry_prompt
192
+ puts_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
193
+ return 1
194
+ end
195
+ connect(options)
196
+ begin
197
+ # construct payload
198
+ payload = nil
199
+ if options[:payload]
200
+ payload = options[:payload]
201
+ else
202
+ # merge -O options into normally parsed options
203
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
204
+ # todo: prompt?
205
+ payload = {'specTemplate' => params}
206
+ end
207
+ @resource_specs_interface.setopts(options)
208
+ if options[:dry_run]
209
+ print_dry_run @resource_specs_interface.dry.create(payload)
210
+ return
211
+ end
212
+ json_response = @resource_specs_interface.create(payload)
213
+ if options[:json]
214
+ puts as_json(json_response, options)
215
+ elsif !options[:quiet]
216
+ resource_spec = json_response['specTemplate']
217
+ print_green_success "Added spec template #{resource_spec['name']}"
218
+ _get(resource_spec['id'], {})
219
+ end
220
+ return 0
221
+ rescue RestClient::Exception => e
222
+ print_rest_exception(e, options)
223
+ return 1
224
+ end
225
+ end
226
+
227
+
228
+ def update(args)
229
+ options = {}
230
+ params = {}
231
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
232
+ opts.banner = subcommand_usage("[name]")
233
+ opts.on('--name VALUE', String, "Name") do |val|
234
+ params['name'] = val
235
+ end
236
+ # opts.on('--code VALUE', String, "Code") do |val|
237
+ # params['code'] = val
238
+ # end
239
+ # opts.on('--detemplateion VALUE', String, "Detemplateion") do |val|
240
+ # params['detemplateion'] = val
241
+ # end
242
+ opts.on('--type [bash|powershell]', String, "Template Type") do |val|
243
+ params['templateType'] = val
244
+ end
245
+ opts.on('--phase [start|stop]', String, "Template Phase") do |val|
246
+ params['templatePhase'] = val
247
+ end
248
+ opts.on('--category VALUE', String, "Category") do |val|
249
+ params['category'] = val
250
+ end
251
+ opts.on('--template TEXT', String, "Contents of the template.") do |val|
252
+ params['template'] = val
253
+ end
254
+ opts.on('--file FILE', "File containing the template. This can be used instead of --template" ) do |filename|
255
+ full_filename = File.expand_path(filename)
256
+ if File.exists?(full_filename)
257
+ params['template'] = File.read(full_filename)
258
+ else
259
+ print_red_alert "File not found: #{full_filename}"
260
+ exit 1
261
+ end
262
+ end
263
+ # opts.on('--enabled [on|off]', String, "Can be used to disable it") do |val|
264
+ # options['enabled'] = !(val.to_s == 'off' || val.to_s == 'false')
265
+ # end
266
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
267
+ opts.footer = "Update a spec template." + "\n" +
268
+ "[name] is required. This is the name or id of a spec template."
269
+ end
270
+ optparse.parse!(args)
271
+ if args.count != 1
272
+ print_error Morpheus::Terminal.angry_prompt
273
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
274
+ return 1
275
+ end
276
+ connect(options)
277
+ begin
278
+ resource_spec = find_resource_spec_by_name_or_id(args[0])
279
+ if resource_spec.nil?
280
+ return 1
281
+ end
282
+ # construct payload
283
+ payload = nil
284
+ if options[:payload]
285
+ payload = options[:payload]
286
+ else
287
+ # merge -O options into normally parsed options
288
+ params.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
289
+ payload = {'specTemplate' => params}
290
+ end
291
+ @resource_specs_interface.setopts(options)
292
+ if options[:dry_run]
293
+ print_dry_run @resource_specs_interface.dry.update(resource_spec["id"], payload)
294
+ return
295
+ end
296
+ json_response = @resource_specs_interface.update(resource_spec["id"], payload)
297
+ if options[:json]
298
+ puts as_json(json_response, options)
299
+ elsif !options[:quiet]
300
+ print_green_success "Updated spec template #{resource_spec['name']}"
301
+ _get(resource_spec['id'], {})
302
+ end
303
+ return 0
304
+ rescue RestClient::Exception => e
305
+ print_rest_exception(e, options)
306
+ return 1
307
+ end
308
+ end
309
+
310
+ def remove(args)
311
+ options = {}
312
+ params = {}
313
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
314
+ opts.banner = subcommand_usage("[name]")
315
+ build_common_options(opts, options, [:json, :dry_run, :quiet, :auto_confirm])
316
+ end
317
+ optparse.parse!(args)
318
+ if args.count < 1
319
+ puts optparse
320
+ return 127
321
+ end
322
+ connect(options)
323
+
324
+ begin
325
+ resource_spec = find_resource_spec_by_name_or_id(args[0])
326
+ if resource_spec.nil?
327
+ return 1
328
+ end
329
+
330
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to delete spec template '#{resource_spec['name']}'?", options)
331
+ return false
332
+ end
333
+
334
+ # payload = {
335
+ # 'specTemplate' => {id: resource_spec["id"]}
336
+ # }
337
+ # payload['specTemplate'].merge!(resource_spec)
338
+ payload = params
339
+ @resource_specs_interface.setopts(options)
340
+ if options[:dry_run]
341
+ print_dry_run @resource_specs_interface.dry.destroy(resource_spec["id"])
342
+ return
343
+ end
344
+
345
+ json_response = @resource_specs_interface.destroy(resource_spec["id"])
346
+ if options[:json]
347
+ puts as_json(json_response, options)
348
+ elsif !options[:quiet]
349
+ print_green_success "Deleted spec template #{resource_spec['name']}"
350
+ end
351
+ return 0, nil
352
+ rescue RestClient::Exception => e
353
+ print_rest_exception(e, options)
354
+ return 1
355
+ end
356
+ end
357
+
358
+
359
+ private
360
+
361
+ def find_resource_spec_by_name_or_id(val)
362
+ if val.to_s =~ /\A\d{1,}\Z/
363
+ return find_resource_spec_by_id(val)
364
+ else
365
+ return find_resource_spec_by_name(val)
366
+ end
367
+ end
368
+
369
+ def find_resource_spec_by_id(id)
370
+ begin
371
+ json_response = @resource_specs_interface.get(id.to_i)
372
+ return json_response['specTemplate']
373
+ rescue RestClient::Exception => e
374
+ if e.response && e.response.code == 404
375
+ print_red_alert "Spec Template not found by id #{id}"
376
+ else
377
+ raise e
378
+ end
379
+ end
380
+ end
381
+
382
+ def find_resource_spec_by_name(name)
383
+ resource_specs = @resource_specs_interface.list({name: name.to_s})['specTemplates']
384
+ if resource_specs.empty?
385
+ print_red_alert "Spec Template not found by name #{name}"
386
+ return nil
387
+ elsif resource_specs.size > 1
388
+ print_red_alert "#{resource_specs.size} spec templates found by name #{name}"
389
+ print_resource_specs_table(resource_specs, {color: red})
390
+ print_red_alert "Try using ID instead"
391
+ print reset,"\n"
392
+ return nil
393
+ else
394
+ return resource_specs[0]
395
+ end
396
+ end
397
+
398
+ def print_resource_specs_table(resource_specs, opts={})
399
+ columns = [
400
+ {"ID" => lambda {|resource_spec| resource_spec['id'] } },
401
+ {"NAME" => lambda {|resource_spec| resource_spec['name'] } },
402
+ #{"OWNER" => lambda {|resource_spec| resource_spec['account'] ? resource_spec['account']['name'] : '' } },
403
+ ]
404
+ if opts[:include_fields]
405
+ columns = opts[:include_fields]
406
+ end
407
+ print as_pretty_table(resource_specs, columns, opts)
408
+ end
409
+
410
+ def format_resource_spec_type(val)
411
+ val.to_s # .capitalize
412
+ end
413
+
414
+ def format_resource_spec_phase(val)
415
+ val.to_s # .capitalize
416
+ end
417
+
418
+ end
@@ -37,6 +37,12 @@ module Morpheus::Cli::ProvisioningHelper
37
37
  @library_layouts_interface
38
38
  end
39
39
 
40
+ def provision_types_interface
41
+ # api_client.provision_types
42
+ raise "#{self.class} has not defined @provision_types_interface" if @provision_types_interface.nil?
43
+ @provision_types_interface
44
+ end
45
+
40
46
  def clouds_interface
41
47
  # @api_client.instance_types
42
48
  raise "#{self.class} has not defined @clouds_interface" if @clouds_interface.nil?
@@ -489,6 +495,10 @@ module Morpheus::Cli::ProvisioningHelper
489
495
  # end
490
496
  layout_id = options[:layout].to_i if options[:layout]
491
497
 
498
+ # determine layout and provision_type
499
+ # provision_type = (layout && provision_type ? provision_type : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
500
+ # need to GET layout and provision type by ID in order to get optionTypes, and other settings...
501
+
492
502
  if !layout_id
493
503
  layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.', 'defaultValue' => default_layout_value}],options[:options],api_client,{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id'], version: version_value, creatable: true})['layout']
494
504
  end
@@ -499,6 +509,22 @@ module Morpheus::Cli::ProvisioningHelper
499
509
  exit 1
500
510
  end
501
511
  payload['instance']['layout'] = {'id' => layout['id']}
512
+
513
+ # need to GET provision type for optionTypes, and other settings...
514
+ #provision_type = (layout && provision_type ? provision_type : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
515
+ provision_type = nil
516
+ if layout && layout['provisionTypeCode']
517
+ provision_type = provision_types_interface.list({code:layout['provisionTypeCode']})['provisionTypes'][0]
518
+ if provision_type.nil?
519
+ print_red_alert "Provision Type not found by code #{layout['provisionTypeCode']}"
520
+ exit 1
521
+ end
522
+ elsif layout && layout['provisionType']
523
+ # api used to return entire record under layout.provisionType
524
+ provision_type = layout['provisionType']
525
+ else
526
+ provision_type = get_provision_type_for_zone_type(cloud['zoneType']['id'])
527
+ end
502
528
 
503
529
  # prompt for service plan
504
530
  service_plans_json = @instances_interface.service_plans({zoneId: cloud_id, layoutId: layout['id'], siteId: group_id})
@@ -531,9 +557,6 @@ module Morpheus::Cli::ProvisioningHelper
531
557
  payload['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
532
558
  payload['instance']['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
533
559
 
534
- # determine provision_type
535
- # provision_type = (layout && layout['provisionType'] ? layout['provisionType'] : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
536
-
537
560
  # build option types
538
561
  option_type_list = []
539
562
  if !layout['optionTypes'].nil? && !layout['optionTypes'].empty?
@@ -542,8 +565,8 @@ module Morpheus::Cli::ProvisioningHelper
542
565
  if !instance_type['optionTypes'].nil? && !instance_type['optionTypes'].empty?
543
566
  option_type_list += instance_type['optionTypes']
544
567
  end
545
- if !layout['provisionType'].nil? && !layout['provisionType']['optionTypes'].nil? && !layout['provisionType']['optionTypes'].empty?
546
- option_type_list += layout['provisionType']['optionTypes']
568
+ if !provision_type.nil? && !provision_type['optionTypes'].nil? && !provision_type['optionTypes'].empty?
569
+ option_type_list += provision_type['optionTypes']
547
570
  end
548
571
  if !payload['volumes'].empty?
549
572
  option_type_list = reject_volume_option_types(option_type_list)
@@ -601,7 +624,7 @@ module Morpheus::Cli::ProvisioningHelper
601
624
  }
602
625
  end
603
626
 
604
- if layout['provisionType'] && layout['provisionType']['supportsAutoDatastore']
627
+ if provision_type && provision_type['supportsAutoDatastore']
605
628
  service_plan['autoOptions'] ||= []
606
629
  if service_plan['datastores']['cluster'].count > 0 && !service_plan['autoOptions'].find {|it| it['id'] == 'autoCluster'}
607
630
  service_plan['autoOptions'] << {'id' => 'autoCluster', 'name' => 'Auto - Cluster'}