morpheus-cli 2.12.5 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -0
  3. data/lib/morpheus/api/api_client.rb +15 -30
  4. data/lib/morpheus/api/app_templates_interface.rb +34 -7
  5. data/lib/morpheus/api/apps_interface.rb +20 -1
  6. data/lib/morpheus/api/archive_buckets_interface.rb +124 -0
  7. data/lib/morpheus/api/archive_files_interface.rb +182 -0
  8. data/lib/morpheus/api/{network_pools_interface.rb → image_builder_boot_scripts_interface.rb} +6 -6
  9. data/lib/morpheus/api/{policies_interface.rb → image_builder_image_builds_interface.rb} +20 -15
  10. data/lib/morpheus/api/image_builder_interface.rb +26 -0
  11. data/lib/morpheus/api/{network_proxies_interface.rb → image_builder_preseed_scripts_interface.rb} +6 -6
  12. data/lib/morpheus/cli.rb +10 -9
  13. data/lib/morpheus/cli/alias_command.rb +10 -9
  14. data/lib/morpheus/cli/app_templates.rb +1566 -457
  15. data/lib/morpheus/cli/apps.rb +284 -108
  16. data/lib/morpheus/cli/archives_command.rb +2184 -0
  17. data/lib/morpheus/cli/boot_scripts_command.rb +382 -0
  18. data/lib/morpheus/cli/cli_command.rb +9 -35
  19. data/lib/morpheus/cli/error_handler.rb +2 -0
  20. data/lib/morpheus/cli/hosts.rb +15 -3
  21. data/lib/morpheus/cli/image_builder_command.rb +1208 -0
  22. data/lib/morpheus/cli/instances.rb +118 -47
  23. data/lib/morpheus/cli/man_command.rb +27 -24
  24. data/lib/morpheus/cli/mixins/print_helper.rb +19 -5
  25. data/lib/morpheus/cli/mixins/provisioning_helper.rb +20 -20
  26. data/lib/morpheus/cli/option_types.rb +45 -14
  27. data/lib/morpheus/cli/preseed_scripts_command.rb +381 -0
  28. data/lib/morpheus/cli/remote.rb +1 -0
  29. data/lib/morpheus/cli/roles.rb +2 -2
  30. data/lib/morpheus/cli/shell.rb +3 -2
  31. data/lib/morpheus/cli/version.rb +1 -1
  32. data/lib/morpheus/ext/hash.rb +22 -0
  33. data/lib/morpheus/formatters.rb +33 -0
  34. data/lib/morpheus/terminal.rb +1 -1
  35. metadata +13 -21
  36. data/lib/morpheus/api/cloud_policies_interface.rb +0 -47
  37. data/lib/morpheus/api/group_policies_interface.rb +0 -47
  38. data/lib/morpheus/api/network_domains_interface.rb +0 -47
  39. data/lib/morpheus/api/network_groups_interface.rb +0 -47
  40. data/lib/morpheus/api/network_pool_servers_interface.rb +0 -47
  41. data/lib/morpheus/api/network_services_interface.rb +0 -47
  42. data/lib/morpheus/api/networks_interface.rb +0 -54
  43. data/lib/morpheus/cli/network_domains_command.rb +0 -571
  44. data/lib/morpheus/cli/network_groups_command.rb +0 -602
  45. data/lib/morpheus/cli/network_pool_servers_command.rb +0 -430
  46. data/lib/morpheus/cli/network_pools_command.rb +0 -495
  47. data/lib/morpheus/cli/network_proxies_command.rb +0 -594
  48. data/lib/morpheus/cli/network_services_command.rb +0 -148
  49. data/lib/morpheus/cli/networks_command.rb +0 -855
  50. data/lib/morpheus/cli/policies_command.rb +0 -847
  51. data/scripts/generate_morpheus_commands_help.morpheus +0 -1313
@@ -55,6 +55,8 @@ class Morpheus::Cli::ErrorHandler
55
55
  # if !options[:debug]
56
56
  # return exit_code
57
57
  # end
58
+ when ArgumentError
59
+ @stderr.puts "#{red}Argument Error: #{err.message}#{reset}"
58
60
  else
59
61
  @stderr.puts "#{red}Unexpected Error#{reset}"
60
62
  end
@@ -605,13 +605,25 @@ class Morpheus::Cli::Hosts
605
605
  options = {}
606
606
  query_params = {removeResources: 'on', force: 'off'}
607
607
  optparse = OptionParser.new do|opts|
608
- opts.banner = subcommand_usage("[name] [-f] [-S]")
609
- opts.on( '-f', '--force', "Force Remove" ) do
608
+ opts.banner = subcommand_usage("[name] [-fS]")
609
+ opts.on( '-f', '--force', "Force Delete" ) do
610
610
  query_params[:force] = 'on'
611
611
  end
612
- opts.on( '-S', '--skip-remove-infrastructure', "Skip removal of underlying cloud infrastructure" ) do
612
+ opts.on( '-S', '--skip-remove-infrastructure', "Skip removal of underlying cloud infrastructure. Same as --remove-resources off" ) do
613
613
  query_params[:removeResources] = 'off'
614
614
  end
615
+ opts.on('--remove-resources [on|off]', ['on','off'], "Remove Infrastructure. Default is on if server is managed.") do |val|
616
+ query_params[:removeResources] = val
617
+ end
618
+ opts.on('--remove-volumes [on|off]', ['on','off'], "Remove Volumes. Default is on.") do |val|
619
+ query_params[:removeVolumes] = val
620
+ end
621
+ opts.on('--remove-instances [on|off]', ['on','off'], "Remove Associated Instances.") do |val|
622
+ query_params[:removeInstances] = val
623
+ end
624
+ opts.on('--release-eips [on|off]', ['on','off'], "Release EIPs, default is true. Amazon only.") do |val|
625
+ params[:releaseEIPs] = val
626
+ end
615
627
  build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
616
628
  end
617
629
  optparse.parse!(args)
@@ -0,0 +1,1208 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require 'rest_client'
4
+ require 'optparse'
5
+ require 'filesize'
6
+ require 'table_print'
7
+ require 'morpheus/cli/cli_command'
8
+ require 'morpheus/cli/mixins/provisioning_helper'
9
+ require 'morpheus/cli/boot_scripts_command'
10
+ require 'morpheus/cli/preseed_scripts_command'
11
+
12
+ class Morpheus::Cli::ImageBuilderCommand
13
+ include Morpheus::Cli::CliCommand
14
+ include Morpheus::Cli::ProvisioningHelper
15
+
16
+ set_command_name :'image-builder' # :'image-builds'
17
+
18
+ register_subcommands :list, :get, :add, :update, :remove
19
+ register_subcommands :run
20
+ register_subcommands :'list-runs' => :list_executions
21
+
22
+ # err, these are kept under this namespace
23
+ register_subcommands :'boot-scripts' => :boot_scripts
24
+ register_subcommands :'preseed-scripts' => :preseed_scripts
25
+
26
+ # set_default_subcommand :list
27
+
28
+ def initialize()
29
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
30
+ end
31
+
32
+ def connect(opts)
33
+ @api_client = establish_remote_appliance_connection(opts)
34
+ @image_builder_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).image_builder
35
+ @image_builds_interface = @image_builder_interface.image_builds
36
+ @boot_scripts_interface = @image_builder_interface.boot_scripts
37
+ @preseed_scripts_interface = @image_builder_interface.preseed_scripts
38
+ @groups_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).groups
39
+ @clouds_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).clouds
40
+ @instances_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instances
41
+ @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
42
+ @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
43
+ @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
44
+ end
45
+
46
+ def handle(args)
47
+ handle_subcommand(args)
48
+ end
49
+
50
+ def boot_scripts(args)
51
+ Morpheus::Cli::BootScriptsCommand.new.handle(args)
52
+ end
53
+
54
+ def preseed_scripts(args)
55
+ Morpheus::Cli::PreseedScriptsCommand.new.handle(args)
56
+ end
57
+
58
+ def list(args)
59
+ options = {}
60
+ optparse = OptionParser.new do|opts|
61
+ opts.banner = subcommand_usage()
62
+ build_common_options(opts, options, [:list, :json, :dry_run])
63
+ end
64
+ optparse.parse!(args)
65
+ connect(options)
66
+ begin
67
+ params = {}
68
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
69
+ params[k] = options[k] unless options[k].nil?
70
+ end
71
+
72
+ if options[:dry_run]
73
+ print_dry_run @image_builds_interface.dry.list(params)
74
+ return
75
+ end
76
+
77
+ json_response = @image_builds_interface.list(params)
78
+ if options[:json]
79
+ print JSON.pretty_generate(json_response)
80
+ print "\n"
81
+ return
82
+ end
83
+ image_builds = json_response['imageBuilds']
84
+ title = "Morpheus Image Builds"
85
+ subtitles = []
86
+ # if group
87
+ # subtitles << "Group: #{group['name']}".strip
88
+ # end
89
+ # if cloud
90
+ # subtitles << "Cloud: #{cloud['name']}".strip
91
+ # end
92
+ if params[:phrase]
93
+ subtitles << "Search: #{params[:phrase]}".strip
94
+ end
95
+ print_h1 title, subtitles
96
+ if image_builds.empty?
97
+ print cyan,"No image builds found.",reset,"\n"
98
+ else
99
+ rows = image_builds.collect {|image_build|
100
+ last_result = image_build['lastResult']
101
+ status_str = format_image_build_status(image_build, cyan)
102
+ result_str = format_image_build_execution_result(last_result, cyan)
103
+ row = {
104
+ id: image_build['id'],
105
+ name: image_build['name'],
106
+ # description: image_build['description'],
107
+ type: image_build['type'] ? image_build['type']['name'] : 'N/A',
108
+ group: image_build['site'] ? image_build['site']['name'] : '',
109
+ cloud: image_build['zone'] ? image_build['zone']['name'] : '',
110
+ executionCount: image_build['executionCount'] ? image_build['executionCount'].to_i : '',
111
+ lastRunDate: last_result ? format_local_dt(last_result['startDate']) : '',
112
+ status: status_str,
113
+ result: result_str
114
+ }
115
+ row
116
+ }
117
+ columns = [:id, :name, :type, {:lastRunDate => {label: 'Last Run Date'.upcase}},
118
+ :status, {:result => {max_width: 60}}]
119
+ term_width = current_terminal_width()
120
+ # if term_width > 170
121
+ # columns += [:cpu, :memory, :storage]
122
+ # end
123
+ # custom pretty table columns ...
124
+ if options[:include_fields]
125
+ columns = options[:include_fields]
126
+ end
127
+ print cyan
128
+ print as_pretty_table(rows, columns, options)
129
+ print reset
130
+ print_results_pagination(json_response)
131
+ end
132
+ print reset,"\n"
133
+ rescue RestClient::Exception => e
134
+ print_rest_exception(e, options)
135
+ exit 1
136
+ end
137
+ end
138
+
139
+ def get(args)
140
+ options = {}
141
+ optparse = OptionParser.new do|opts|
142
+ opts.banner = subcommand_usage("[image-build]")
143
+ build_common_options(opts, options, [:json, :dry_run])
144
+ end
145
+ optparse.parse!(args)
146
+ if args.count < 1
147
+ print_error Morpheus::Terminal.angry_prompt
148
+ puts_error "#{command_name} missing argument: [image-build]\n#{optparse}"
149
+ return 1
150
+ end
151
+ connect(options)
152
+ begin
153
+ if options[:dry_run]
154
+ if args[0].to_s =~ /\A\d{1,}\Z/
155
+ print_dry_run @image_builds_interface.dry.get(args[0].to_i)
156
+ else
157
+ print_dry_run @image_builds_interface.dry.list({name:args[0]})
158
+ end
159
+ return
160
+ end
161
+ image_build = find_image_build_by_name_or_id(args[0])
162
+ return 1 if image_build.nil?
163
+ # json_response = {'imageBuild' => image_build} # skip redundant request
164
+ json_response = @image_builds_interface.get(image_build['id'])
165
+ image_build = json_response['imageBuild']
166
+ if options[:json]
167
+ print JSON.pretty_generate(json_response)
168
+ return
169
+ end
170
+ print_h1 "Image Build Details"
171
+ print cyan
172
+ description_cols = {
173
+ "ID" => 'id',
174
+ # "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
175
+ "Name" => 'name',
176
+ "Description" => 'description',
177
+ "Group" => lambda {|it| it['site'] ? it['site']['name'] : '' },
178
+ "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
179
+ "Plan" => lambda {|it|
180
+ if it['config'] && it['config']['plan']
181
+ it['config']['plan']['code'] # name needed!
182
+ else
183
+ ""
184
+ end
185
+ },
186
+ "Image" => lambda {|it|
187
+ if it['config'] && it['config']['template']
188
+ it['config']['template']
189
+ elsif it['config'] && it['config']['image']
190
+ it['config']['image']
191
+ else
192
+ ""
193
+ end
194
+ },
195
+ "Boot Script" => lambda {|it|
196
+ if it['bootScript']
197
+ if it['bootScript'].kind_of?(Hash)
198
+ it['bootScript']['fileName'] || it['bootScript']['name'] || it['bootScript']['id']
199
+ else
200
+ it['bootScript']
201
+ end
202
+ else
203
+ ""
204
+ end
205
+ },
206
+ "Preseed Script" => lambda {|it|
207
+ if it['preseedScript']
208
+ if it['preseedScript'].kind_of?(Hash)
209
+ it['preseedScript']['fileName'] || it['preseedScript']['name'] || it['preseedScript']['id']
210
+ else
211
+ it['preseedScript']
212
+ end
213
+ else
214
+ ""
215
+ end
216
+ },
217
+ # Additional Scripts
218
+ "Scripts" => lambda {|it|
219
+ if it['scripts']
220
+ script_names = it['scripts'].collect do |script|
221
+ if script.kind_of?(Hash)
222
+ script['name']
223
+ else
224
+ script
225
+ end
226
+ end
227
+ script_names.join(", ")
228
+ else
229
+ ""
230
+ end
231
+ },
232
+ "SSH Username" => lambda {|it| it['sshUsername'] },
233
+ "SSH Password" => lambda {|it| it['sshPassword'].to_s.empty? ? '' : '(hidden)' }, # api returns masked
234
+ "Storage Provider" => lambda {|it|
235
+ if it['storageProvider']
236
+ if it['storageProvider'].kind_of?(Hash)
237
+ it['storageProvider']['name'] || it['storageProvider']['id']
238
+ else
239
+ it['storageProvider']
240
+ end
241
+ else
242
+ ""
243
+ end
244
+ },
245
+ "Build Output Name" => lambda {|it| it['buildOutputName'] },
246
+ "Conversion Formats" => lambda {|it| it['conversionFormats'] },
247
+ "Cloud Init?" => lambda {|it| it['isCloudInit'] ? 'Yes' : 'No' },
248
+ "Keep Results" => lambda {|it| it['keepResults'].to_i == 0 ? 'All' : it['keepResults'] },
249
+ "Last Run Date" => lambda {|it|
250
+ last_result = it['lastResult']
251
+ last_result ? format_local_dt(last_result['startDate']) : ''
252
+ },
253
+ "Status" => lambda {|it| format_image_build_status(it) },
254
+ }
255
+ print_description_list(description_cols, image_build)
256
+
257
+ #json_response = @image_builds_interface.list_executions(image_build['id'], params)
258
+ image_build_executions = json_response['imageBuildExecutions'] # yep, show() returns the last 100 run
259
+ image_build_executions = image_build_executions.first(10) # limit to just 10
260
+ if image_build_executions && image_build_executions.size > 0
261
+ print_h2 "Recent Executions"
262
+ print_image_build_executions_table(image_build_executions, opts={})
263
+ print_results_pagination({size:image_build_executions.size,total:image_build['executionCount'].to_i}, {:label => "execution", :n_label => "executions"})
264
+ else
265
+ puts "\nNo executions found.\n"
266
+ end
267
+
268
+ print reset,"\n"
269
+
270
+ rescue RestClient::Exception => e
271
+ print_rest_exception(e, options)
272
+ return 1
273
+ end
274
+ end
275
+
276
+ def add(args)
277
+ options = {}
278
+ optparse = OptionParser.new do|opts|
279
+ opts.banner = subcommand_usage("[options]")
280
+ # build_option_type_options(opts, options, add_image_build_option_types(false))
281
+ opts.on( '-t', '--type TYPE', "Image Build Type" ) do |val|
282
+ options['type'] = val
283
+ end
284
+ opts.on('--name VALUE', String, "Name") do |val|
285
+ options['name'] = val
286
+ end
287
+ opts.on('--description VALUE', String, "Description") do |val|
288
+ options['description'] = val
289
+ end
290
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
291
+ options['group'] = val
292
+ end
293
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
294
+ options['cloud'] = val
295
+ end
296
+ # opts.on( '-e', '--env ENVIRONMENT', "Environment" ) do |val|
297
+ # options[:cloud] = val
298
+ # end
299
+ opts.on('--config JSON', String, "Instance Config JSON") do |val|
300
+ options['config'] = JSON.parse(val.to_s)
301
+ end
302
+ opts.on('--config-yaml YAML', String, "Instance Config YAML") do |val|
303
+ options['config'] = YAML.load(val.to_s)
304
+ end
305
+ opts.on('--config-file FILE', String, "Instance Config from a local JSON or YAML file") do |val|
306
+ options['configFile'] = val.to_s
307
+ end
308
+ # opts.on('--configFile FILE', String, "Instance Config from a local file") do |val|
309
+ # options['configFile'] = val.to_s
310
+ # end
311
+ opts.on('--bootScript VALUE', String, "Boot Script ID") do |val|
312
+ options['bootScript'] = val.to_s
313
+ end
314
+ opts.on('--bootCommand VALUE', String, "Boot Command. This can be used in place of a bootScript") do |val|
315
+ options['bootCommand'] = val.to_s
316
+ end
317
+ opts.on('--preseedScript VALUE', String, "Preseed Script ID") do |val|
318
+ options['preseedScript'] = val.to_s
319
+ end
320
+ opts.on('--scripts LIST', String, "Additional Scripts (comma separated names or ids)") do |val|
321
+ # uh don't put commas or leading/trailing spaces in script names pl
322
+ options['scripts'] = val.to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
323
+ end
324
+ opts.on('--sshUsername VALUE', String, "SSH Username") do |val|
325
+ options['sshUsername'] = val.to_s
326
+ end
327
+ opts.on('--sshPassword VALUE', String, "SSH Password") do |val|
328
+ options['sshPassword'] = val.to_s
329
+ end
330
+ opts.on('--storageProvider VALUE', String, "Storage Provider ID") do |val|
331
+ options['storageProvider'] = val.to_s
332
+ end
333
+ opts.on('--isCloudInit [on|off]', String, "Cloud Init?") do |val|
334
+ options['isCloudInit'] = (val.to_s == 'on' || val.to_s == 'true')
335
+ end
336
+ opts.on('--buildOutputName VALUE', String, "Build Output Name") do |val|
337
+ options['buildOutputName'] = val.to_s
338
+ end
339
+ opts.on('--conversionFormats VALUE', String, "Conversion Formats ie. ovf, qcow2, vhd") do |val|
340
+ options['conversionFormats'] = val.to_s
341
+ end
342
+ opts.on('--keepResults VALUE', String, "Keep only the most recent builds. Older executions will be deleted along with their associated Virtual Images. The value 0 disables this functionality.") do |val|
343
+ options['keepResults'] = val.to_i
344
+ end
345
+ build_common_options(opts, options, [:options, :json, :dry_run, :quiet])
346
+ end
347
+ optparse.parse!(args)
348
+ if args.count > 1
349
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 0-1 and received #{args.count} #{args.inspect}\n#{optparse}"
350
+ return 1
351
+ end
352
+ connect(options)
353
+ begin
354
+ options.merge!(options[:options]) if options[:options] # so -O var= works..
355
+
356
+ # use the -g GROUP or active group by default
357
+ # options['group'] ||= @active_group_id
358
+
359
+ # support first arg as name instead of --name
360
+ if args[0] && !options['name']
361
+ options['name'] = args[0]
362
+ end
363
+
364
+ image_build_payload = prompt_new_image_build(options)
365
+ return 1 if !image_build_payload
366
+ payload = {'imageBuild' => image_build_payload}
367
+
368
+ if options[:dry_run]
369
+ print_dry_run @image_builds_interface.dry.create(payload)
370
+ return
371
+ end
372
+ json_response = @image_builds_interface.create(payload)
373
+ if options[:json]
374
+ print JSON.pretty_generate(json_response)
375
+ print "\n"
376
+ elsif !options[:quiet]
377
+ new_image_build = json_response['imageBuild']
378
+ print_green_success "Added image build #{new_image_build['name']}"
379
+ get([new_image_build['id']])
380
+ # list([])
381
+ end
382
+
383
+ rescue RestClient::Exception => e
384
+ print_rest_exception(e, options)
385
+ return 1
386
+ end
387
+ end
388
+
389
+ def update(args)
390
+ options = {}
391
+ optparse = OptionParser.new do|opts|
392
+ opts.banner = subcommand_usage("[image-build] [options]")
393
+ # build_option_type_options(opts, options, update_image_build_option_types(false))
394
+ # cannot update type
395
+ opts.on( '-t', '--type TYPE', "Image Build Type" ) do |val|
396
+ options['type'] = val
397
+ end
398
+ opts.on('--name VALUE', String, "New Name") do |val|
399
+ options['name'] = val
400
+ end
401
+ opts.on('--description VALUE', String, "Description") do |val|
402
+ options['description'] = val
403
+ end
404
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
405
+ options['group'] = val
406
+ end
407
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
408
+ options['cloud'] = val
409
+ end
410
+ # opts.on( '-e', '--env ENVIRONMENT', "Environment" ) do |val|
411
+ # options[:cloud] = val
412
+ # end
413
+ opts.on('--config JSON', String, "Instance Config JSON") do |val|
414
+ options['config'] = JSON.parse(val.to_s)
415
+ end
416
+ opts.on('--config-yaml YAML', String, "Instance Config YAML") do |val|
417
+ options['config'] = YAML.load(val.to_s)
418
+ end
419
+ opts.on('--config-file FILE', String, "Instance Config from a local JSON or YAML file") do |val|
420
+ options['configFile'] = val.to_s
421
+ end
422
+ # opts.on('--configFile FILE', String, "Instance Config from a local file") do |val|
423
+ # options['configFile'] = val.to_s
424
+ # end
425
+ opts.on('--bootScript VALUE', String, "Boot Script ID") do |val|
426
+ options['bootScript'] = val.to_s
427
+ end
428
+ opts.on('--bootCommand VALUE', String, "Boot Command. This can be used in place of a bootScript") do |val|
429
+ options['bootCommand'] = val.to_s
430
+ end
431
+ opts.on('--preseedScript VALUE', String, "Preseed Script ID") do |val|
432
+ options['preseedScript'] = val.to_s
433
+ end
434
+ opts.on('--scripts LIST', String, "Additional Scripts (comma separated names or ids)") do |val|
435
+ # uh don't put commas or leading/trailing spaces in script names pl
436
+ options['scripts'] = val.to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
437
+ end
438
+ opts.on('--sshUsername VALUE', String, "SSH Username") do |val|
439
+ options['sshUsername'] = val.to_s
440
+ end
441
+ opts.on('--sshPassword VALUE', String, "SSH Password") do |val|
442
+ options['sshPassword'] = val.to_s
443
+ end
444
+ opts.on('--storageProvider VALUE', String, "Storage Provider ID") do |val|
445
+ options['storageProvider'] = val.to_s
446
+ end
447
+ opts.on('--isCloudInit [on|off]', String, "Cloud Init?") do |val|
448
+ options['isCloudInit'] = (val.to_s == 'on' || val.to_s == 'true')
449
+ end
450
+ opts.on('--buildOutputName VALUE', String, "Build Output Name") do |val|
451
+ options['buildOutputName'] = val.to_s
452
+ end
453
+ opts.on('--conversionFormats VALUE', String, "Conversion Formats ie. ovf, qcow2, vhd") do |val|
454
+ options['conversionFormats'] = val.to_s
455
+ end
456
+ # opts.on('--keepResultsEnabled [on|off]', String, "Delete Old Results. Enables the Keep Results option") do |val|
457
+ # options['keepResultsEnabled'] = (val.to_s == 'on' || val.to_s == 'true')
458
+ # end
459
+ opts.on('--keepResults VALUE', String, "Keep only the most recent builds. Older executions will be deleted along with their associated Virtual Images. The value 0 disables this functionality.") do |val|
460
+ options['keepResults'] = val.to_i
461
+ # 0 disables it
462
+ # options['deleteOldResults'] = (options['keepResults'] > 0)
463
+ end
464
+ build_common_options(opts, options, [:options, :json, :dry_run, :quiet])
465
+ end
466
+ optparse.parse!(args)
467
+ if args.count != 1
468
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
469
+ return 1
470
+ end
471
+ connect(options)
472
+ begin
473
+ image_build = find_image_build_by_name_or_id(args[0])
474
+ return 1 if !image_build
475
+ image_build_payload = prompt_edit_image_build(image_build, options)
476
+ return 1 if !image_build_payload
477
+ payload = {imageBuild: image_build_payload}
478
+ if options[:dry_run]
479
+ print_dry_run @image_builds_interface.dry.update(image_build["id"], payload)
480
+ return
481
+ end
482
+ json_response = @image_builds_interface.update(image_build["id"], payload)
483
+ if options[:json]
484
+ print JSON.pretty_generate(json_response)
485
+ print "\n"
486
+ else
487
+ print_green_success "Updated image build #{image_build['name']}"
488
+ get([image_build['id']])
489
+ end
490
+ return 0
491
+ rescue RestClient::Exception => e
492
+ print_rest_exception(e, options)
493
+ return 1
494
+ end
495
+ end
496
+
497
+ def remove(args)
498
+ options = {}
499
+ query_params = {}
500
+ optparse = OptionParser.new do|opts|
501
+ opts.banner = subcommand_usage("[image-build]")
502
+ opts.on( '-K', '--keep-virtual-images', "Preserve associated virtual images" ) do
503
+ query_params['keepVirtualImages'] = 'on'
504
+ end
505
+ build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run])
506
+ end
507
+ optparse.parse!(args)
508
+
509
+ if args.count < 1
510
+ print_error Morpheus::Terminal.angry_prompt
511
+ puts_error "#{command_name} missing argument: [image-build]\n#{optparse}"
512
+ return 1
513
+ end
514
+
515
+ connect(options)
516
+ begin
517
+ image_build = find_image_build_by_name_or_id(args[0])
518
+ return 1 if image_build.nil?
519
+
520
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the image build: #{image_build['name']}?")
521
+ return 9, "aborted command"
522
+ end
523
+ if query_params['keepVirtualImages'].nil?
524
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'keepVirtualImages', 'type' => 'checkbox', 'fieldLabel' => 'Keep Virtual Images?', 'required' => false, 'defaultValue' => false, 'description' => 'Preserve associated virtual images. By default, they are deleted as well.'}],options,@api_client,{})
525
+ query_params['keepVirtualImages'] = v_prompt['keepVirtualImages']
526
+ end
527
+ if options[:dry_run]
528
+ print_dry_run @image_builds_interface.dry.destroy(image_build['id'], query_params)
529
+ return 0
530
+ end
531
+ json_response = @image_builds_interface.destroy(image_build['id'], query_params)
532
+ if options[:json]
533
+ print JSON.pretty_generate(json_response)
534
+ print "\n"
535
+ else
536
+ print_green_success "Removed image build #{image_build['name']}"
537
+ # list([])
538
+ end
539
+ return 0
540
+ rescue RestClient::Exception => e
541
+ print_rest_exception(e, options)
542
+ return 1
543
+ end
544
+ end
545
+
546
+ def run(args)
547
+ options = {}
548
+ query_params = {}
549
+ optparse = OptionParser.new do|opts|
550
+ opts.banner = subcommand_usage("[image-build]")
551
+ build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run])
552
+ end
553
+ optparse.parse!(args)
554
+
555
+ if args.count < 1
556
+ print_error Morpheus::Terminal.angry_prompt
557
+ puts_error "#{command_name} missing argument: [image-build]\n#{optparse}"
558
+ return 1
559
+ end
560
+
561
+ connect(options)
562
+ begin
563
+ image_build = find_image_build_by_name_or_id(args[0])
564
+ return 1 if image_build.nil?
565
+
566
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to run the image build: #{image_build['name']}?")
567
+ return 9, "aborted command"
568
+ end
569
+ if options[:dry_run]
570
+ print_dry_run @image_builds_interface.dry.run(image_build['id'], query_params)
571
+ return 0
572
+ end
573
+ json_response = @image_builds_interface.run(image_build['id'], query_params)
574
+ if options[:json]
575
+ print JSON.pretty_generate(json_response)
576
+ print "\n"
577
+ else
578
+ print_green_success "New run started for image build #{image_build['name']}"
579
+ get([image_build['id']])
580
+ end
581
+ return 0
582
+ rescue RestClient::Exception => e
583
+ print_rest_exception(e, options)
584
+ return 1
585
+ end
586
+ end
587
+
588
+ def list_executions(args)
589
+ options = {}
590
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
591
+ opts.banner = subcommand_usage("[image-build]")
592
+ build_common_options(opts, options, [:list, :json, :dry_run])
593
+ opts.footer = "List executions for an image build."
594
+ opts.footer = "Display a list of executions for an image build.\n"
595
+ "[image-build] is the name or id of an image build."
596
+ end
597
+ optparse.parse!(args)
598
+ if args.count > 1
599
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
600
+ return 1
601
+ end
602
+ connect(options)
603
+
604
+ image_build = find_image_build_by_name_or_id(args[0])
605
+ return 1 if image_build.nil?
606
+
607
+
608
+ params = {}
609
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
610
+ params[k] = options[k] unless options[k].nil?
611
+ end
612
+ if options[:dry_run]
613
+ print_dry_run @image_builds_interface.dry.list_executions(image_build['id'], params)
614
+ return 0
615
+ end
616
+ json_response = @image_builds_interface.list_executions(image_build['id'], params)
617
+ if options[:json]
618
+ puts JSON.pretty_generate(json_response)
619
+ return 0
620
+ end
621
+ image_build = json_response['imageBuild']
622
+ image_build_executions = json_response['imageBuildExecutions']
623
+ print_h1 "Image Build Executions: [#{image_build['id']}] #{image_build['name']}"
624
+ print cyan
625
+ if image_build_executions && image_build_executions.size > 0
626
+ print_image_build_executions_table(image_build_executions, opts={})
627
+ print_results_pagination(json_response, {:label => "execution", :n_label => "executions"})
628
+ end
629
+
630
+ return 0
631
+ end
632
+
633
+ def delete_execution(args)
634
+ puts "todo: implement me"
635
+ return 0
636
+ end
637
+
638
+ private
639
+
640
+ def get_available_image_build_types()
641
+ # todo: api call
642
+ [
643
+ {'name' => 'VMware', 'code' => 'vmware', 'instanceType' => {'code' => 'vmware'}}
644
+ ]
645
+ end
646
+
647
+ def get_available_image_build_types_dropdown(group=nil, cloud=nil)
648
+ get_available_image_build_types().collect {|it|
649
+ {'name' => it['name'], 'value' => it['code']}
650
+ }
651
+ end
652
+
653
+ def find_image_build_type(val)
654
+ if val.nil? || val.to_s.empty?
655
+ return nil
656
+ else
657
+ return get_available_image_build_types().find { |it|
658
+ (it['code'].to_s.downcase == val.to_s.downcase) ||
659
+ (it['name'].to_s.downcase == val.to_s.downcase)
660
+ }
661
+ end
662
+ end
663
+
664
+ def add_image_build_option_types(connected=true)
665
+ [
666
+ {'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => get_available_image_build_types_dropdown(), 'required' => true, 'description' => 'Choose the type of image build.'},
667
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this image build.'},
668
+ {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false},
669
+ {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => true},
670
+ #{'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'selectOptions' => [], 'required' => true},
671
+ # tons more, this isn't used anymore though..
672
+ ]
673
+ end
674
+
675
+ def update_image_build_option_types(connected=true)
676
+ list = add_image_build_option_types(connected)
677
+ # list = list.reject {|it| ["group"].include? it['fieldName'] }
678
+ list.each {|it| it['required'] = false }
679
+ list
680
+ end
681
+
682
+ def find_image_build_by_name_or_id(val)
683
+ if val.to_s =~ /\A\d{1,}\Z/
684
+ return find_image_build_by_id(val)
685
+ else
686
+ return find_image_build_by_name(val)
687
+ end
688
+ end
689
+
690
+ def find_image_build_by_id(id)
691
+ begin
692
+ json_response = @image_builds_interface.get(id.to_i)
693
+ return json_response['imageBuild']
694
+ rescue RestClient::Exception => e
695
+ if e.response && e.response.code == 404
696
+ print_red_alert "Image Build not found by id #{id}"
697
+ return nil
698
+ else
699
+ raise e
700
+ end
701
+ end
702
+ end
703
+
704
+ def find_image_build_by_name(name)
705
+ image_builds = @image_builds_interface.list({name: name.to_s})['imageBuilds']
706
+ if image_builds.empty?
707
+ print_red_alert "Image Build not found by name #{name}"
708
+ return nil
709
+ elsif image_builds.size > 1
710
+ print_red_alert "#{image_builds.size} image builds found by name #{name}"
711
+ # print_image_builds_table(image_builds, {color: red})
712
+ rows = image_builds.collect do |it|
713
+ {id: it['id'], name: it['name']}
714
+ end
715
+ print red
716
+ tp rows, [:id, :name]
717
+ print reset,"\n"
718
+ return nil
719
+ else
720
+ return image_builds[0]
721
+ end
722
+ end
723
+
724
+ # def find_group_by_name(name)
725
+ # group_results = @groups_interface.get(name)
726
+ # if group_results['groups'].empty?
727
+ # print_red_alert "Group not found by name #{name}"
728
+ # return nil
729
+ # end
730
+ # return group_results['groups'][0]
731
+ # end
732
+
733
+ # def find_cloud_by_name(group_id, name)
734
+ # option_results = @options_interface.options_for_source('clouds',{groupId: group_id})
735
+ # match = option_results['data'].find { |grp| grp['value'].to_s == name.to_s || grp['name'].downcase == name.downcase}
736
+ # if match.nil?
737
+ # print_red_alert "Cloud not found by name #{name}"
738
+ # return nil
739
+ # else
740
+ # return match['value']
741
+ # end
742
+ # end
743
+
744
+
745
+ def format_image_build_status(image_build, return_color=cyan)
746
+ out = ""
747
+ return out if !image_build
748
+ if image_build && image_build['lastResult']
749
+ out << format_image_build_execution_status(image_build['lastResult'])
750
+ else
751
+ out << ""
752
+ end
753
+ out
754
+ end
755
+
756
+ def format_image_build_execution_status(image_build_execution, return_color=cyan)
757
+ return "" if !image_build_execution
758
+ out = ""
759
+ status_string = image_build_execution['status']
760
+ if status_string == 'running'
761
+ out << "#{cyan}#{bold}#{status_string.upcase}#{reset}#{return_color}"
762
+ elsif status_string == 'success'
763
+ out << "#{green}#{status_string.upcase}#{return_color}"
764
+ elsif status_string == 'failed'
765
+ out << "#{red}#{status_string.upcase}#{return_color}"
766
+ elsif status_string == 'pending'
767
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
768
+ elsif !status_string.to_s.empty?
769
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
770
+ else
771
+ #out << "#{cyan}No Executions#{return_color}"
772
+ end
773
+ out
774
+ end
775
+
776
+ def format_image_build_execution_result(image_build_execution, return_color=cyan)
777
+ return "" if !image_build_execution
778
+ out = ""
779
+ status_string = image_build_execution['status']
780
+ if status_string == 'running' # || status_string == 'pending'
781
+ out << generate_usage_bar(image_build_execution['statusPercent'], 100, {max_bars: 10, bar_color: cyan})
782
+ out << return_color if return_color
783
+ end
784
+ if image_build_execution['tempInstance'].to_s != ''
785
+ out << " Instance:"
786
+ out << " [#{image_build_execution['tempInstance']['id']}] #{image_build_execution['tempInstance']['name']}"
787
+ out << " -"
788
+ end
789
+ if image_build_execution['statusMessage'].to_s != ''
790
+ out << " #{image_build_execution['statusMessage']}"
791
+ end
792
+ if image_build_execution['errorMessage'].to_s != ''
793
+ out << " #{red}#{image_build_execution['errorMessage']}#{return_color}"
794
+ end
795
+ if image_build_execution['virtualImages']
796
+ img_count = image_build_execution['virtualImages'].size
797
+ if img_count == 1
798
+ out << " Virtual Image:"
799
+ elsif img_count > 1
800
+ out << "(#{img_count}) Virtual Images:"
801
+ end
802
+ image_build_execution['virtualImages'].each do |virtual_image|
803
+ out << " [#{virtual_image['id']}] #{virtual_image['name']}#{return_color}"
804
+ end
805
+ end
806
+ out.strip #.squeeze(' ')
807
+ end
808
+
809
+ def print_image_build_executions_table(executions, opts={})
810
+ table_color = opts[:color] || cyan
811
+ rows = executions.collect do |execution|
812
+ {
813
+ id: execution['id'],
814
+ build: execution['buildNumber'],
815
+ createdBy: execution['createdBy'] ? execution['createdBy']['username'] : nil,
816
+ start: execution['startDate'] ? format_local_dt(execution['startDate']) : '',
817
+ end: execution['endDate'] ? format_local_dt(execution['endDate']) : '',
818
+ duration: format_duration(execution['startDate'], execution['endDate']),
819
+ status: format_image_build_execution_status(execution, table_color),
820
+ result: format_image_build_execution_result(execution, table_color)
821
+ }
822
+ end
823
+
824
+ term_width = current_terminal_width()
825
+ result_col_width = 60
826
+ if term_width > 250
827
+ result_col_width += 100
828
+ end
829
+ columns = [
830
+ #:id,
831
+ :build,
832
+ {:createdBy => {:display_name => "CREATED BY"} },
833
+ :start,
834
+ :end,
835
+ :duration,
836
+ {:status => {:display_name => "STATUS"} },
837
+ {:result => {:display_name => "RESULT", :max_width => result_col_width} }
838
+ ]
839
+ # # custom pretty table columns ...
840
+ # if options[:include_fields]
841
+ # columns = options[:include_fields]
842
+ # end
843
+ print table_color
844
+ print as_pretty_table(rows, columns, opts)
845
+ print reset
846
+ end
847
+
848
+ # def get_available_boot_scripts()
849
+ # boot_scripts_dropdown = []
850
+ # scripts = @boot_scripts_interface.list({max:1000})['bootScripts']
851
+ # scripts.each do |it|
852
+ # boot_scripts_dropdown << {'name'=>it['fileName'],'value'=>it['id']}
853
+ # end
854
+ # boot_scripts_dropdown << {'name'=>'Custom','value'=> 'custom'}
855
+ # return boot_scripts_dropdown
856
+ # end
857
+
858
+ def get_available_boot_scripts(refresh=false)
859
+ if !@available_boot_scripts || refresh
860
+ # option_results = options_interface.options_for_source('bootScripts',{})['data']
861
+ boot_scripts_dropdown = []
862
+ scripts = @boot_scripts_interface.list({max:1000})['bootScripts']
863
+ scripts.each do |it|
864
+ boot_scripts_dropdown << {'name'=>it['fileName'],'value'=>it['id'],'id'=>it['id']}
865
+ end
866
+ boot_scripts_dropdown << {'name'=>'Custom','value'=> 'custom','id'=> 'custom'}
867
+ @available_boot_scripts = boot_scripts_dropdown
868
+ end
869
+ #puts "available_boot_scripts() rtn: #{@available_boot_scripts.inspect}"
870
+ return @available_boot_scripts
871
+ end
872
+
873
+ def find_boot_script(val)
874
+ if val.nil? || val.to_s.empty?
875
+ return nil
876
+ else
877
+ return get_available_boot_scripts().find { |it|
878
+ (it['id'].to_s.downcase == val.to_s.downcase) ||
879
+ (it['name'].to_s.downcase == val.to_s.downcase)
880
+ }
881
+ end
882
+ end
883
+
884
+ def get_available_preseed_scripts(refresh=false)
885
+ if !@available_preseed_scripts || refresh
886
+ # option_results = options_interface.options_for_source('preseedScripts',{})['data']
887
+ preseed_scripts_dropdown = []
888
+ scripts = @preseed_scripts_interface.list({max:1000})['preseedScripts']
889
+ scripts.each do |it|
890
+ preseed_scripts_dropdown << {'name'=>it['fileName'],'value'=>it['id'],'id'=>it['id']}
891
+ end
892
+ # preseed_scripts_dropdown << {'name'=>'Custom','value'=> 'custom','value'=> 'custom'}
893
+ @available_preseed_scripts = preseed_scripts_dropdown
894
+ end
895
+ #puts "available_preseed_scripts() rtn: #{@available_preseed_scripts.inspect}"
896
+ return @available_preseed_scripts
897
+ end
898
+
899
+ def find_preseed_script(val)
900
+ if val.nil? || val.to_s.empty?
901
+ return nil
902
+ else
903
+ return get_available_preseed_scripts().find { |it|
904
+ (it['id'].to_s.downcase == val.to_s.downcase) ||
905
+ (it['name'].to_s.downcase == val.to_s.downcase)
906
+ }
907
+ end
908
+ end
909
+
910
+ def prompt_new_image_build(options={}, default_values={}, do_require=true)
911
+ payload = {}
912
+
913
+ # Summary / Settings Tab
914
+
915
+ # Image Build Type
916
+ image_build_type = nil
917
+ if options['type']
918
+ image_build_type = find_image_build_type(options['type'])
919
+ else
920
+ 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)
921
+ image_build_type = find_image_build_type(v_prompt['type'])
922
+ end
923
+ if !image_build_type
924
+ print_red_alert "Image Build Type not found!"
925
+ return false
926
+ end
927
+ payload['type'] = image_build_type['code'] # = {'id'=> image_build_type['id']}
928
+
929
+ # Name
930
+ if options['name']
931
+ payload['name'] = options['name']
932
+ else
933
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => do_require, 'description' => 'Enter a name for this image build.', 'defaultValue' => default_values['name'], :fmt=>:natural}], options, @api_client)
934
+ payload['name'] = v_prompt['name']
935
+ end
936
+
937
+ # Description
938
+ if options['description']
939
+ payload['description'] = options['description']
940
+ else
941
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'defaultValue' => default_values['description'], :fmt=>:natural}], options, @api_client)
942
+ payload['description'] = v_prompt['description']
943
+ end
944
+
945
+ # Group
946
+ group = nil
947
+ if options['group']
948
+ group = find_group_by_name_or_id_for_provisioning(options['group'])
949
+ else
950
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => get_available_groups(), 'required' => do_require, 'description' => 'Select Group.', 'defaultValue' => default_values['group'], :fmt=>:natural}],options,@api_client,{})
951
+ group = find_group_by_name_or_id_for_provisioning(v_prompt['group'])
952
+ end
953
+ if !group
954
+ #print_red_alert "Group not found!"
955
+ return false
956
+ end
957
+ # pick one
958
+ #payload['group'] = {'id' => group['id']}
959
+ payload['site'] = {'id' => group['id']}
960
+
961
+ # Cloud
962
+ cloud = nil
963
+ if options['cloud']
964
+ cloud = find_cloud_by_name_or_id_for_provisioning(group['id'], options['cloud'])
965
+ else
966
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => get_available_clouds(group['id']), 'required' => do_require, 'description' => 'Select Cloud.', 'defaultValue' => default_values['cloud'], :fmt=>:natural}],options,@api_client,{groupId: group['id']})
967
+ cloud = find_cloud_by_name_or_id_for_provisioning(group['id'], v_prompt['cloud'])
968
+ end
969
+ if !cloud
970
+ # print_red_alert "Cloud not found!"
971
+ return false
972
+ end
973
+ # pick one
974
+ #payload['cloud'] = {'id' => cloud['id']}
975
+ payload['zone'] = {'id' => cloud['id']}
976
+
977
+
978
+ # Configure Tab
979
+ # either pass --config, --configFile or be prompted..
980
+ if options['config']
981
+ payload['config'] = options['config']
982
+ elsif options['configFile']
983
+ config_file = File.expand_path(options['configFile'])
984
+ if !File.exists?(config_file) || !File.file?(config_file)
985
+ print_red_alert "File not found: #{config_file}"
986
+ return false
987
+ end
988
+ if config_file =~ /\.ya?ml\Z/
989
+ payload['config'] = YAML.load_file(config_file)
990
+ else
991
+ payload['config'] = JSON.parse(File.read(config_file))
992
+ end
993
+ elsif default_values['config']
994
+ # for now, skip this config prompting if updating existing record
995
+ # this is problematic, if they are changing the group or cloud though...
996
+ # payload['config'] = default_values['config']
997
+ else
998
+ # Instance Type is derived from the image build type
999
+ instance_type_code = image_build_type['instanceType']['code']
1000
+ instance_type = find_instance_type_by_code(instance_type_code)
1001
+ return false if !instance_type
1002
+
1003
+ instance_config_options = options.dup
1004
+ #instance_config_options[:no_prompt] = options[:no_prompt]
1005
+ # use active group by default
1006
+ instance_config_options[:group] = group['id']
1007
+ instance_config_options[:cloud] = cloud['id']
1008
+ instance_config_options[:instance_type_code] = instance_type["code"] # instance_type_code
1009
+ instance_config_options[:name_required] = false
1010
+ # this provisioning helper method handles all (most) of the parsing and prompting
1011
+ # puts "instance_config_options is: #{instance_config_options.inspect}"
1012
+ instance_config_payload = prompt_new_instance(instance_config_options)
1013
+ # strip all empty string and nil, would be problematic for update()
1014
+ instance_config_payload.deep_compact!
1015
+
1016
+ payload['config'] = instance_config_payload
1017
+ end
1018
+
1019
+ # merge group and cloud parameters into config..
1020
+ payload['config'] ||= {}
1021
+ payload['config']['zoneId'] = cloud['id']
1022
+ # payload['config']['siteId'] = group['id']
1023
+ payload['config']['instance'] ||= {}
1024
+ payload['config']['instance']['site'] = {'id' => group['id']}
1025
+
1026
+
1027
+ # Scripts tab
1028
+ boot_script = nil
1029
+ boot_script_id = nil
1030
+ boot_command = nil
1031
+ if options['bootScript']
1032
+ boot_script = find_boot_script(options['bootScript'])
1033
+ if !boot_script
1034
+ print_red_alert "Boot Script not found: #{options['bootScript']}"
1035
+ return false
1036
+ end
1037
+ boot_script_id = boot_script['id']
1038
+ else
1039
+ boot_script_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootScript', 'type' => 'select', 'fieldLabel' => 'Boot Script', 'selectOptions' => get_available_boot_scripts(), 'required' => do_require, 'description' => 'Select Boot Script.', 'defaultValue' => default_values['bootScript'], :fmt=>:natural}],options,api_client,{})
1040
+ # boot_script_id = boot_script_prompt['bootScript']
1041
+ boot_script = find_boot_script(boot_script_prompt['bootScript'])
1042
+ if !boot_script
1043
+ print_red_alert "Boot Script not found: '#{boot_script_prompt['bootScript']}'"
1044
+ return false
1045
+ end
1046
+ boot_script_id = boot_script['id']
1047
+ end
1048
+
1049
+ if boot_script_id == "custom" || options['bootCommand']
1050
+ if options['bootCommand']
1051
+ boot_command = options['bootCommand']
1052
+ else
1053
+ boot_command_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'bootCommand', 'type' => 'code-editor', 'fieldLabel' => 'Boot Command', 'required' => do_require, 'description' => 'Enter a boot command.', :fmt=>:natural}],options,api_client,{})
1054
+ boot_command = boot_command_prompt['bootCommand']
1055
+ end
1056
+ boot_script_id = nil
1057
+ else
1058
+ # boot_command = nil
1059
+ end
1060
+
1061
+ if boot_script_id
1062
+ # payload['bootScript'] = boot_script_id
1063
+ if boot_script_id == ""
1064
+ payload['bootScript'] = nil
1065
+ else
1066
+ payload['bootScript'] = {id: boot_script_id}
1067
+ end
1068
+ elsif boot_command
1069
+ payload['bootCommand'] = boot_command
1070
+ end
1071
+
1072
+ # Preseed Script
1073
+ preseed_script = nil
1074
+ preseed_script_id = nil
1075
+ if options['preseedScript']
1076
+ preseed_script = find_preseed_script(options['preseedScript'])
1077
+ if !preseed_script
1078
+ print_red_alert "Preseed Script not found: #{options['preseedScript']}"
1079
+ return false
1080
+ end
1081
+ preseed_script_id = preseed_script['id']
1082
+ else
1083
+ preseed_script_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'preseedScript', 'type' => 'select', 'fieldLabel' => 'Preseed Script', 'selectOptions' => get_available_preseed_scripts(), 'required' => false, 'description' => 'Select Preseed Script.', 'defaultValue' => default_values['preseedScript'], :fmt=>:natural}],options,api_client,{})
1084
+ # preseed_script_id = preseed_script_prompt['preseedScript']
1085
+ preseed_script = find_preseed_script(preseed_script_prompt['preseedScript'])
1086
+ if !preseed_script
1087
+ print_red_alert "Preseed Script not found: '#{preseed_script_prompt['preseedScript']}'"
1088
+ return false
1089
+ end
1090
+ preseed_script_id = preseed_script['id']
1091
+ end
1092
+ if preseed_script_id
1093
+ # payload['preseedScript'] = preseed_script_id
1094
+ if preseed_script_id == ""
1095
+ payload['preseedScript'] = nil
1096
+ else
1097
+ payload['preseedScript'] = {id: preseed_script_id}
1098
+ end
1099
+ end
1100
+
1101
+ # Additional Scripts
1102
+ if options['scripts']
1103
+ payload['scripts'] = options['scripts'] #.to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
1104
+ else
1105
+ scripts_default_value = default_values['scripts']
1106
+ if scripts_default_value.kind_of?(Array)
1107
+ scripts_default_value = scripts_default_value.collect {|it| it["name"] }.join(", ")
1108
+ end
1109
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'scripts', 'fieldLabel' => 'Additional Scripts', 'type' => 'text', 'description' => 'Additional Scripts (comma separated names or ids)', 'defaultValue' => scripts_default_value, :fmt=>:natural}], options, @api_client)
1110
+ payload['scripts'] = v_prompt['scripts'].to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
1111
+ end
1112
+
1113
+ # SSH Username
1114
+ if options['sshUsername']
1115
+ payload['sshUsername'] = options['sshUsername']
1116
+ else
1117
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'required' => do_require, 'type' => 'text', 'description' => 'SSH Username', 'defaultValue' => default_values['sshUsername'], :fmt=>:natural}], options, @api_client)
1118
+ payload['sshUsername'] = v_prompt['sshUsername']
1119
+ end
1120
+
1121
+ # SSH Password
1122
+ if options['sshPassword']
1123
+ payload['sshPassword'] = options['sshPassword']
1124
+ else
1125
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'required' => do_require, 'type' => 'password', 'description' => 'SSH Password', 'defaultValue' => default_values['sshPassword'], :fmt=>:natural}], options, @api_client)
1126
+ payload['sshPassword'] = v_prompt['sshPassword']
1127
+ end
1128
+
1129
+ # Storage Provider
1130
+ if options['storageProvider']
1131
+ payload['storageProvider'] = options['storageProvider']
1132
+ else
1133
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'storageProvider', 'fieldLabel' => 'Storage Provider', 'type' => 'select', 'optionSource' => 'storageProviders', 'description' => 'Storage Provider', 'defaultValue' => default_values['storageProvider'], :fmt=>:natural}], options, @api_client, {})
1134
+ payload['storageProvider'] = v_prompt['storageProvider']
1135
+ end
1136
+
1137
+ # Cloud Init
1138
+ if options['isCloudInit']
1139
+ payload['isCloudInit'] = options['isCloudInit']
1140
+ else
1141
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'isCloudInit', 'fieldLabel' => 'Cloud Init?', 'type' => 'checkbox', 'description' => 'Cloud Init', 'defaultValue' => (default_values['isCloudInit'].nil? ? false : default_values['isCloudInit']), :fmt=>:natural}], options, @api_client, {})
1142
+ payload['isCloudInit'] = v_prompt['isCloudInit']
1143
+ end
1144
+
1145
+ # Build Output Name
1146
+ if options['buildOutputName']
1147
+ payload['buildOutputName'] = options['buildOutputName']
1148
+ else
1149
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'buildOutputName', 'fieldLabel' => 'Build Output Name', 'type' => 'text', 'description' => 'Build Output Name', 'defaultValue' => default_values['buildOutputName'], :fmt=>:natural}], options, @api_client)
1150
+ payload['buildOutputName'] = v_prompt['buildOutputName']
1151
+ end
1152
+
1153
+ # Conversion Formats
1154
+ if options['conversionFormats']
1155
+ payload['conversionFormats'] = options['conversionFormats']
1156
+ else
1157
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'conversionFormats', 'fieldLabel' => 'Conversion Formats', 'type' => 'text', 'description' => 'Conversion Formats ie. ovf, qcow2, vhd', 'defaultValue' => default_values['conversionFormats'], :fmt=>:natural}], options, @api_client)
1158
+ payload['conversionFormats'] = v_prompt['conversionFormats']
1159
+ end
1160
+
1161
+ ## Retention
1162
+
1163
+ # Delete Old Builds?
1164
+ # Keep Results
1165
+ if options['keepResults']
1166
+ payload['keepResults'] = options['keepResults'].to_i
1167
+ else
1168
+ default_keep_results = default_values['keepResults'] ? default_values['keepResults'] : nil # 0
1169
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'keepResults', 'fieldLabel' => 'Keep Results', 'type' => 'text', 'description' => 'Keep only the most recent builds. Older executions will be deleted along with their associated Virtual Images. The value 0 disables this functionality.', 'defaultValue' => default_keep_results, :fmt=>:natural}], options, @api_client)
1170
+ payload['keepResults'] = v_prompt['keepResults'].to_i
1171
+ end
1172
+
1173
+ return payload
1174
+ end
1175
+
1176
+ def prompt_edit_image_build(image_build, options={}, do_require=false)
1177
+ # populate default prompt values with the existing image build
1178
+ default_values = image_build.dup # lazy, but works as long as GET matches POST api structure
1179
+ if image_build['type'].kind_of?(Hash)
1180
+ default_values['type'] = image_build['type']['code']
1181
+ end
1182
+ if image_build['group'].kind_of?(Hash)
1183
+ default_values['group'] = image_build['group']['name'] # ['id']
1184
+ elsif image_build['site'].kind_of?(Hash)
1185
+ default_values['group'] = image_build['site']['name'] # ['id']
1186
+ end
1187
+ if image_build['cloud'].kind_of?(Hash)
1188
+ default_values['cloud'] = image_build['cloud']['name']# ['id']
1189
+ elsif image_build['zone'].kind_of?(Hash)
1190
+ default_values['cloud'] = image_build['zone']['name'] # ['id']
1191
+ end
1192
+ if image_build['bootCommand'] && !image_build['bootScript']
1193
+ default_values['bootScript'] = 'custom'
1194
+ end
1195
+ if image_build['bootScript'].kind_of?(Hash)
1196
+ default_values['bootScript'] = image_build['bootScript']['fileName'] # ['id']
1197
+ end
1198
+ if image_build['preseedScript'].kind_of?(Hash)
1199
+ default_values['preseedScript'] = image_build['preseedScript']['fileName'] # ['id']
1200
+ end
1201
+ if image_build['storageProvider']
1202
+ default_values['storageProvider'] = image_build['storageProvider']['name'] # ['id']
1203
+ end
1204
+ # any other mismatches? preseedScript, bootScript?
1205
+ return prompt_new_image_build(options, default_values, do_require)
1206
+ end
1207
+
1208
+ end