morpheus-cli 2.12.5 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +5 -0
- data/lib/morpheus/api/api_client.rb +15 -30
- data/lib/morpheus/api/app_templates_interface.rb +34 -7
- data/lib/morpheus/api/apps_interface.rb +20 -1
- data/lib/morpheus/api/archive_buckets_interface.rb +124 -0
- data/lib/morpheus/api/archive_files_interface.rb +182 -0
- data/lib/morpheus/api/{network_pools_interface.rb → image_builder_boot_scripts_interface.rb} +6 -6
- data/lib/morpheus/api/{policies_interface.rb → image_builder_image_builds_interface.rb} +20 -15
- data/lib/morpheus/api/image_builder_interface.rb +26 -0
- data/lib/morpheus/api/{network_proxies_interface.rb → image_builder_preseed_scripts_interface.rb} +6 -6
- data/lib/morpheus/cli.rb +10 -9
- data/lib/morpheus/cli/alias_command.rb +10 -9
- data/lib/morpheus/cli/app_templates.rb +1566 -457
- data/lib/morpheus/cli/apps.rb +284 -108
- data/lib/morpheus/cli/archives_command.rb +2184 -0
- data/lib/morpheus/cli/boot_scripts_command.rb +382 -0
- data/lib/morpheus/cli/cli_command.rb +9 -35
- data/lib/morpheus/cli/error_handler.rb +2 -0
- data/lib/morpheus/cli/hosts.rb +15 -3
- data/lib/morpheus/cli/image_builder_command.rb +1208 -0
- data/lib/morpheus/cli/instances.rb +118 -47
- data/lib/morpheus/cli/man_command.rb +27 -24
- data/lib/morpheus/cli/mixins/print_helper.rb +19 -5
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +20 -20
- data/lib/morpheus/cli/option_types.rb +45 -14
- data/lib/morpheus/cli/preseed_scripts_command.rb +381 -0
- data/lib/morpheus/cli/remote.rb +1 -0
- data/lib/morpheus/cli/roles.rb +2 -2
- data/lib/morpheus/cli/shell.rb +3 -2
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/ext/hash.rb +22 -0
- data/lib/morpheus/formatters.rb +33 -0
- data/lib/morpheus/terminal.rb +1 -1
- metadata +13 -21
- data/lib/morpheus/api/cloud_policies_interface.rb +0 -47
- data/lib/morpheus/api/group_policies_interface.rb +0 -47
- data/lib/morpheus/api/network_domains_interface.rb +0 -47
- data/lib/morpheus/api/network_groups_interface.rb +0 -47
- data/lib/morpheus/api/network_pool_servers_interface.rb +0 -47
- data/lib/morpheus/api/network_services_interface.rb +0 -47
- data/lib/morpheus/api/networks_interface.rb +0 -54
- data/lib/morpheus/cli/network_domains_command.rb +0 -571
- data/lib/morpheus/cli/network_groups_command.rb +0 -602
- data/lib/morpheus/cli/network_pool_servers_command.rb +0 -430
- data/lib/morpheus/cli/network_pools_command.rb +0 -495
- data/lib/morpheus/cli/network_proxies_command.rb +0 -594
- data/lib/morpheus/cli/network_services_command.rb +0 -148
- data/lib/morpheus/cli/networks_command.rb +0 -855
- data/lib/morpheus/cli/policies_command.rb +0 -847
- data/scripts/generate_morpheus_commands_help.morpheus +0 -1313
data/lib/morpheus/cli/hosts.rb
CHANGED
@@ -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] [-
|
609
|
-
opts.on( '-f', '--force', "Force
|
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
|