morpheus-cli 4.1.9 → 4.1.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'}