morpheus-cli 5.0.2 → 5.2.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 +1 -1
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/instances_interface.rb +9 -2
- data/lib/morpheus/api/servers_interface.rb +7 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/cli.rb +2 -1
- data/lib/morpheus/cli/apps.rb +3 -23
- data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
- data/lib/morpheus/cli/cli_command.rb +25 -1
- data/lib/morpheus/cli/containers_command.rb +0 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/health_command.rb +57 -0
- data/lib/morpheus/cli/hosts.rb +85 -9
- data/lib/morpheus/cli/instances.rb +85 -68
- data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +76 -0
- data/lib/morpheus/cli/option_types.rb +10 -10
- data/lib/morpheus/cli/roles.rb +193 -155
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/tasks.rb +9 -11
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +162 -68
- data/lib/morpheus/formatters.rb +34 -9
- metadata +5 -4
- data/lib/morpheus/cli/mixins/catalog_helper.rb +0 -66
data/lib/morpheus/cli/tasks.rb
CHANGED
@@ -333,10 +333,9 @@ class Morpheus::Cli::Tasks
|
|
333
333
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
|
334
334
|
end
|
335
335
|
optparse.parse!(args)
|
336
|
-
|
337
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
338
|
-
end
|
336
|
+
#verify_args!(args:args, count:1, optparse:optparse)
|
339
337
|
if args[0]
|
338
|
+
# task_name = args[0]
|
340
339
|
task_name = args[0]
|
341
340
|
end
|
342
341
|
# if task_name.nil? || task_type_name.nil?
|
@@ -424,6 +423,9 @@ class Morpheus::Cli::Tasks
|
|
424
423
|
has_file_content = true
|
425
424
|
it['fieldContext'] = nil
|
426
425
|
it['fieldName'] = 'file'
|
426
|
+
# this should be required right!? fix api optionType data plz
|
427
|
+
it['required'] = true
|
428
|
+
it['defaultValue'] = 'local'
|
427
429
|
else
|
428
430
|
if it['fieldContext'].nil? || it['fieldContext'] == ''
|
429
431
|
it['fieldContext'] = 'taskOptions'
|
@@ -657,9 +659,7 @@ class Morpheus::Cli::Tasks
|
|
657
659
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
|
658
660
|
end
|
659
661
|
optparse.parse!(args)
|
660
|
-
|
661
|
-
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
662
|
-
end
|
662
|
+
verify_args!(args:args, count:1, optparse:optparse)
|
663
663
|
task_name = args[0]
|
664
664
|
connect(options)
|
665
665
|
begin
|
@@ -733,7 +733,6 @@ class Morpheus::Cli::Tasks
|
|
733
733
|
|
734
734
|
def remove(args)
|
735
735
|
params = {}
|
736
|
-
task_name = args[0]
|
737
736
|
options = {}
|
738
737
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
739
738
|
opts.banner = subcommand_usage("[task]")
|
@@ -743,10 +742,8 @@ class Morpheus::Cli::Tasks
|
|
743
742
|
end
|
744
743
|
end
|
745
744
|
optparse.parse!(args)
|
746
|
-
|
747
|
-
|
748
|
-
exit 1
|
749
|
-
end
|
745
|
+
verify_args!(args:args, count:1, optparse:optparse)
|
746
|
+
task_name = args[0]
|
750
747
|
connect(options)
|
751
748
|
begin
|
752
749
|
task = find_task_by_name_or_id(task_name)
|
@@ -819,6 +816,7 @@ class Morpheus::Cli::Tasks
|
|
819
816
|
if args.count != 1
|
820
817
|
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
821
818
|
end
|
819
|
+
verify_args!(args:args, count:1, optparse:optparse)
|
822
820
|
task_name = args[0]
|
823
821
|
connect(options)
|
824
822
|
begin
|
data/lib/morpheus/cli/version.rb
CHANGED
@@ -8,6 +8,7 @@ require 'morpheus/cli/cli_command'
|
|
8
8
|
|
9
9
|
class Morpheus::Cli::VirtualImages
|
10
10
|
include Morpheus::Cli::CliCommand
|
11
|
+
include Morpheus::Cli::ProvisioningHelper
|
11
12
|
|
12
13
|
register_subcommands :list, :get, :add, :add_file, :remove_file, :update, :remove, :types => :virtual_image_types
|
13
14
|
alias_subcommand :details, :get
|
@@ -43,6 +44,17 @@ class Morpheus::Cli::VirtualImages
|
|
43
44
|
opts.on('--system', "System Images" ) do
|
44
45
|
options[:filterType] = 'System'
|
45
46
|
end
|
47
|
+
opts.on('--tags Name=Value',String, "Filter by tags (metadata name value pairs).") do |val|
|
48
|
+
val.split(",").each do |value_pair|
|
49
|
+
k,v = value_pair.strip.split("=")
|
50
|
+
options[:tags] ||= {}
|
51
|
+
options[:tags][k] ||= []
|
52
|
+
options[:tags][k] << (v || '')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
opts.on('-a', '--details', "Show more details." ) do
|
56
|
+
options[:details] = true
|
57
|
+
end
|
46
58
|
build_standard_list_options(opts, options)
|
47
59
|
opts.footer = "List virtual images."
|
48
60
|
end
|
@@ -59,6 +71,11 @@ class Morpheus::Cli::VirtualImages
|
|
59
71
|
if options[:filterType]
|
60
72
|
params[:filterType] = options[:filterType]
|
61
73
|
end
|
74
|
+
if options[:tags]
|
75
|
+
options[:tags].each do |k,v|
|
76
|
+
params['tags.' + k] = v
|
77
|
+
end
|
78
|
+
end
|
62
79
|
@virtual_images_interface.setopts(options)
|
63
80
|
if options[:dry_run]
|
64
81
|
print_dry_run @virtual_images_interface.dry.list(params)
|
@@ -82,16 +99,35 @@ class Morpheus::Cli::VirtualImages
|
|
82
99
|
if images.empty?
|
83
100
|
print cyan,"No virtual images found.",reset,"\n"
|
84
101
|
else
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
102
|
+
virtual_image_column_definitions = {
|
103
|
+
"ID" => 'id',
|
104
|
+
"Name" => 'name',
|
105
|
+
"Type" => lambda {|it|
|
106
|
+
# yick, api should return the type with every virtualImage
|
107
|
+
image_type = virtual_image_type_for_name_or_code(it['imageType'])
|
108
|
+
image_type ? "#{image_type['name']}" : it['imageType']
|
109
|
+
},
|
110
|
+
"Operating System" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
|
111
|
+
"Storage" => lambda {|it| !it['storageProvider'].nil? ? it['storageProvider']['name'] : 'Default' },
|
112
|
+
"Size" => lambda {|it| it['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{it['rawSize']} B").pretty}" },
|
113
|
+
"Visibility" => lambda {|it| it['visibility'] },
|
114
|
+
# "Tenant" => lambda {|it| it['account'].instance_of?(Hash) ? it['account']['name'] : it['ownerId'] },
|
115
|
+
"Tenants" => lambda {|it| format_list(it['accounts'].collect {|a| a['name'] }, '', 3) rescue '' },
|
116
|
+
"Source" => lambda {|it| format_virtual_image_source(it) },
|
117
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
118
|
+
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
119
|
+
"Tags" => lambda {|it| it['tags'] ? it['tags'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
120
|
+
}
|
121
|
+
if json_response['multiTenant'] != true
|
122
|
+
virtual_image_column_definitions.delete("Visibility")
|
123
|
+
virtual_image_column_definitions.delete("Tenants")
|
124
|
+
end
|
125
|
+
if options[:details] != true
|
126
|
+
virtual_image_column_definitions.delete("Tags")
|
127
|
+
virtual_image_column_definitions.delete("Created")
|
128
|
+
virtual_image_column_definitions.delete("Updated")
|
90
129
|
end
|
91
|
-
|
92
|
-
columns = options[:include_fields] if options[:include_fields]
|
93
|
-
print cyan
|
94
|
-
print as_pretty_table(rows, columns, options)
|
130
|
+
print as_pretty_table(images, virtual_image_column_definitions.upcase_keys!, options)
|
95
131
|
print_results_pagination(json_response)
|
96
132
|
end
|
97
133
|
print reset,"\n"
|
@@ -108,9 +144,12 @@ class Morpheus::Cli::VirtualImages
|
|
108
144
|
options = {}
|
109
145
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
110
146
|
opts.banner = subcommand_usage("[image]")
|
111
|
-
opts.on('--details', "Show more details." ) do
|
147
|
+
opts.on('-a', '--details', "Show more details." ) do
|
112
148
|
options[:details] = true
|
113
149
|
end
|
150
|
+
opts.on('--tags LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
|
151
|
+
options[:tags] = val
|
152
|
+
end
|
114
153
|
build_standard_get_options(opts, options)
|
115
154
|
opts.footer = <<-EOT
|
116
155
|
Get details about a virtual image.
|
@@ -148,6 +187,7 @@ EOT
|
|
148
187
|
json_response = @virtual_images_interface.get(id.to_i)
|
149
188
|
image = json_response['virtualImage']
|
150
189
|
image_config = image['config'] || {}
|
190
|
+
image_volumes = image['volumes'] || []
|
151
191
|
image_files = json_response['cloudFiles'] || json_response['files']
|
152
192
|
image_type = virtual_image_type_for_name_or_code(image['imageType'])
|
153
193
|
image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
|
@@ -157,35 +197,39 @@ EOT
|
|
157
197
|
"ID" => 'id',
|
158
198
|
"Name" => 'name',
|
159
199
|
"Type" => lambda {|it| image_type_display },
|
200
|
+
"Operating System" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
|
160
201
|
"Storage" => lambda {|it| !image['storageProvider'].nil? ? image['storageProvider']['name'] : 'Default' },
|
161
202
|
"Size" => lambda {|it| image['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{image['rawSize']} B").pretty}" },
|
162
203
|
"Azure Publisher" => lambda {|it| image_config['publisher'] },
|
163
204
|
"Azure Offer" => lambda {|it| image_config['offer'] },
|
164
205
|
"Azure Sku" => lambda {|it| image_config['sku'] },
|
165
206
|
"Azure Version" => lambda {|it| image_config['version'] },
|
166
|
-
"Source" => lambda {|it|
|
167
|
-
|
168
|
-
|
207
|
+
"Source" => lambda {|it| format_virtual_image_source(it) },
|
208
|
+
"Tags" => lambda {|it| it['tags'] ? it['tags'].collect {|m| "#{m['name']}: #{m['value']}" }.join(', ') : '' },
|
209
|
+
"Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
210
|
+
"Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
|
169
211
|
}
|
212
|
+
description_cols.delete("Tags") if image['tags'].nil? || image['tags'].empty?
|
170
213
|
if image['imageType'] == "azure-reference" || image['imageType'] == "azure"
|
171
214
|
description_cols.delete("Size")
|
172
215
|
description_cols.delete("Storage")
|
173
216
|
description_cols["Source"] = lambda {|it| "#{bold}#{cyan}AZURE#{reset}#{cyan}" }
|
174
217
|
else
|
175
|
-
description_cols.delete("Azure
|
176
|
-
description_cols.delete("Azure
|
177
|
-
description_cols.delete("Azure
|
178
|
-
description_cols.delete("Azure
|
179
|
-
description_cols.delete("Azure Marketplace Version")
|
218
|
+
description_cols.delete("Azure Publisher")
|
219
|
+
description_cols.delete("Azure Sku")
|
220
|
+
description_cols.delete("Azure Offer")
|
221
|
+
description_cols.delete("Azure Version")
|
180
222
|
end
|
181
223
|
advanced_description_cols = {
|
182
|
-
"OS Type" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
|
224
|
+
#"OS Type" => lambda {|it| it['osType'] ? it['osType']['name'] : "" }, # displayed above as Operating System
|
183
225
|
"Min Memory" => lambda {|it| it['minRam'].to_i != 0 ? Filesize.from("#{it['minRam']} B").pretty : "" },
|
226
|
+
"Min Disk" => lambda {|it| it['minDisk'].to_i != 0 ? Filesize.from("#{it['minDisk']} B").pretty : "" },
|
184
227
|
"Cloud Init?" => lambda {|it| format_boolean it['osType'] },
|
185
228
|
"Install Agent?" => lambda {|it| format_boolean it['osType'] },
|
186
229
|
"SSH Username" => lambda {|it| it['sshUsername'] },
|
187
230
|
"SSH Password" => lambda {|it| it['sshPassword'] },
|
188
231
|
"User Data" => lambda {|it| it['userData'] },
|
232
|
+
"Owner" => lambda {|it| it['tenant'].instance_of?(Hash) ? it['tenant']['name'] : it['ownerId'] },
|
189
233
|
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
190
234
|
"Tenants" => lambda {|it| format_tenants(it['accounts']) },
|
191
235
|
"Auto Join Domain?" => lambda {|it| format_boolean it['isAutoJoinDomain'] },
|
@@ -200,14 +244,27 @@ EOT
|
|
200
244
|
end
|
201
245
|
print_description_list(description_cols, image)
|
202
246
|
|
247
|
+
if image_volumes && !image_volumes.empty?
|
248
|
+
print_h2 "Volumes", options
|
249
|
+
image_volume_rows = image_volumes.collect do |image_volume|
|
250
|
+
{name: image_volume['name'], size: Filesize.from("#{image_volume['rawSize']} B").pretty}
|
251
|
+
end
|
252
|
+
print cyan
|
253
|
+
print as_pretty_table(image_volume_rows, [:name, :size])
|
254
|
+
print cyan
|
255
|
+
# print "\n", reset
|
256
|
+
end
|
257
|
+
|
203
258
|
if image_files
|
204
259
|
print_h2 "Files (#{image_files.size})"
|
205
260
|
# image_files.each {|image_file|
|
206
261
|
# pretty_filesize = Filesize.from("#{image_file['size']} B").pretty
|
207
262
|
# print cyan," = #{image_file['name']} [#{pretty_filesize}]", "\n"
|
208
263
|
# }
|
264
|
+
# size property changed to GB to match volumes
|
265
|
+
# contentLength is bytes
|
209
266
|
image_file_rows = image_files.collect do |image_file|
|
210
|
-
{filename: image_file['name'], size: Filesize.from("#{image_file['size']} B").pretty}
|
267
|
+
{filename: image_file['name'], size: Filesize.from("#{image_file['contentLength'] || image_file['size']} B").pretty}
|
211
268
|
end
|
212
269
|
print cyan
|
213
270
|
print as_pretty_table(image_file_rows, [:filename, :size])
|
@@ -218,8 +275,9 @@ EOT
|
|
218
275
|
print_h2 "Config", options
|
219
276
|
print cyan
|
220
277
|
print as_description_list(image_config, image_config.keys, options)
|
221
|
-
print "\n", reset
|
278
|
+
# print "\n", reset
|
222
279
|
end
|
280
|
+
|
223
281
|
print reset,"\n"
|
224
282
|
end
|
225
283
|
return 0, nil
|
@@ -238,60 +296,68 @@ EOT
|
|
238
296
|
tenants_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
239
297
|
end
|
240
298
|
end
|
299
|
+
opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
|
300
|
+
options[:tags] = val
|
301
|
+
end
|
302
|
+
opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update project tags.") do |val|
|
303
|
+
options[:add_tags] = val
|
304
|
+
end
|
305
|
+
opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
|
306
|
+
options[:remove_tags] = val
|
307
|
+
end
|
241
308
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
242
309
|
opts.footer = "Update a virtual image." + "\n" +
|
243
310
|
"[name] is required. This is the name or id of a virtual image."
|
244
311
|
end
|
245
312
|
optparse.parse!(args)
|
246
|
-
|
247
|
-
puts optparse
|
248
|
-
exit 1
|
249
|
-
end
|
313
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
250
314
|
|
251
315
|
connect(options)
|
252
|
-
|
253
|
-
|
254
|
-
|
316
|
+
|
317
|
+
virtual_image = find_virtual_image_by_name_or_id(image_name)
|
318
|
+
return 1 if virtual_image.nil?
|
255
319
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
320
|
+
passed_options = parse_passed_options(options)
|
321
|
+
payload = nil
|
322
|
+
if options[:payload]
|
323
|
+
payload = options[:payload]
|
324
|
+
payload.deep_merge!({virtual_image_object_key => passed_options}) unless passed_options.empty?
|
325
|
+
else
|
326
|
+
virtual_image_payload = passed_options
|
327
|
+
if tenants_list
|
328
|
+
virtual_image_payload['accounts'] = tenants_list
|
329
|
+
end
|
330
|
+
# metadata tags
|
331
|
+
if options[:tags]
|
332
|
+
virtual_image_payload['tags'] = parse_metadata(options[:tags])
|
264
333
|
else
|
265
|
-
|
266
|
-
|
267
|
-
puts optparse
|
268
|
-
option_lines = update_virtual_image_option_types().collect {|it| "\t-O #{it['fieldContext'] ? (it['fieldContext'] + '.') : ''}#{it['fieldName']}=\"value\"" }.join("\n")
|
269
|
-
puts "\nAvailable Options:\n#{option_lines}\n\n"
|
270
|
-
exit 1
|
271
|
-
end
|
272
|
-
if tenants_list
|
273
|
-
params['accounts'] = tenants_list
|
274
|
-
end
|
275
|
-
payload = {'virtualImage' => params}
|
334
|
+
# tags = prompt_metadata(options)
|
335
|
+
# payload[virtual_image_object_key]['tags'] = tags of tags
|
276
336
|
end
|
277
|
-
|
278
|
-
if options[:
|
279
|
-
|
280
|
-
return
|
337
|
+
# metadata tags
|
338
|
+
if options[:add_tags]
|
339
|
+
virtual_image_payload['addTags'] = parse_metadata(options[:add_tags])
|
281
340
|
end
|
282
|
-
|
283
|
-
|
284
|
-
print JSON.pretty_generate(json_response)
|
285
|
-
if !response['success']
|
286
|
-
exit 1
|
287
|
-
end
|
288
|
-
else
|
289
|
-
print "\n", cyan, "Virtual Image #{image['name']} updated", reset, "\n\n"
|
341
|
+
if options[:remove_tags]
|
342
|
+
virtual_image_payload['removeTags'] = parse_metadata(options[:remove_tags])
|
290
343
|
end
|
291
|
-
|
292
|
-
|
293
|
-
|
344
|
+
if virtual_image_payload.empty?
|
345
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
346
|
+
end
|
347
|
+
payload = {'virtualImage' => virtual_image_payload}
|
294
348
|
end
|
349
|
+
@virtual_images_interface.setopts(options)
|
350
|
+
if options[:dry_run]
|
351
|
+
print_dry_run @virtual_images_interface.dry.update(virtual_image['id'], payload)
|
352
|
+
return
|
353
|
+
end
|
354
|
+
json_response = @virtual_images_interface.update(virtual_image['id'], payload)
|
355
|
+
render_response(json_response, options, 'virtualImage') do
|
356
|
+
print_green_success "Updated virtual image #{virtual_image['name']}"
|
357
|
+
_get(virtual_image["id"], {}, options)
|
358
|
+
end
|
359
|
+
return 0, nil
|
360
|
+
|
295
361
|
end
|
296
362
|
|
297
363
|
def virtual_image_types(args)
|
@@ -368,6 +434,9 @@ EOT
|
|
368
434
|
tenants_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
369
435
|
end
|
370
436
|
end
|
437
|
+
opts.on('--tags LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
|
438
|
+
options[:tags] = val
|
439
|
+
end
|
371
440
|
# build_option_type_options(opts, options, add_virtual_image_option_types)
|
372
441
|
# build_option_type_options(opts, options, add_virtual_image_advanced_option_types)
|
373
442
|
build_standard_add_options(opts, options)
|
@@ -450,6 +519,14 @@ EOT
|
|
450
519
|
if tenants_list
|
451
520
|
virtual_image_payload['accounts'] = tenants_list
|
452
521
|
end
|
522
|
+
# metadata tags
|
523
|
+
if options[:tags]
|
524
|
+
tags = parse_metadata(options[:tags])
|
525
|
+
virtual_image_payload['tags'] = tags if tags
|
526
|
+
else
|
527
|
+
# tags = prompt_metadata(options)
|
528
|
+
# virtual_image_payload['tags'] = tags of tags
|
529
|
+
end
|
453
530
|
# fix issue with api returning imageType vmware instead of vmdk
|
454
531
|
if virtual_image_payload && virtual_image_payload['imageType'] == 'vmware'
|
455
532
|
virtual_image_payload['imageType'] == 'vmdk'
|
@@ -719,16 +796,17 @@ EOT
|
|
719
796
|
tmp_option_types = [
|
720
797
|
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
|
721
798
|
#{'fieldName' => 'imageType', 'fieldLabel' => 'Image Type', 'type' => 'select', 'optionSource' => 'virtualImageTypes', 'required' => true, 'description' => 'Select Virtual Image Type.', 'displayOrder' => 2},
|
722
|
-
{'fieldName' => 'osType', 'fieldLabel' => '
|
723
|
-
{'fieldName' => '
|
724
|
-
{'fieldName' => '
|
725
|
-
{'fieldName' => '
|
799
|
+
{'fieldName' => 'osType', 'fieldLabel' => 'Operating System', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false, 'description' => 'Select Operating System.', 'displayOrder' => 3},
|
800
|
+
{'fieldName' => 'minRamGB', 'fieldLabel' => 'Minimum Memory (GB)', 'type' => 'number', 'required' => false, 'description' => 'Minimum Memory (GB)', 'displayOrder' => 4},
|
801
|
+
# {'fieldName' => 'minDiskGB', 'fieldLabel' => 'Minimum Disk (GB)', 'type' => 'number', 'required' => false, 'description' => 'Minimum Memory (GB)', 'displayOrder' => 4},
|
802
|
+
{'fieldName' => 'isCloudInit', 'fieldLabel' => 'Cloud Init Enabled?', 'type' => 'checkbox', 'defaultValue' => 'off', 'required' => false, 'description' => 'Cloud Init Enabled?', 'displayOrder' => 5},
|
803
|
+
{'fieldName' => 'installAgent', 'fieldLabel' => 'Install Agent?', 'type' => 'checkbox', 'defaultValue' => 'off', 'required' => false, 'description' => 'Install Agent?', 'displayOrder' => 6},
|
726
804
|
{'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text', 'required' => false, 'description' => 'Enter an SSH Username', 'displayOrder' => 7},
|
727
805
|
{'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false, 'description' => 'Enter an SSH Password', 'displayOrder' => 8},
|
728
806
|
{'fieldName' => 'storageProviderId', 'type' => 'select', 'fieldLabel' => 'Storage Provider', 'optionSource' => 'storageProviders', 'required' => false, 'description' => 'Select Storage Provider.', 'displayOrder' => 9},
|
729
807
|
{'fieldName' => 'userData', 'fieldLabel' => 'Cloud-Init User Data', 'type' => 'textarea', 'required' => false, 'displayOrder' => 10},
|
730
808
|
{'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}], 'required' => false, 'description' => 'Visibility', 'category' => 'permissions', 'defaultValue' => 'private', 'displayOrder' => 40},
|
731
|
-
{'fieldName' => 'isAutoJoinDomain', 'fieldLabel' => 'Auto Join Domain?', 'type' => 'checkbox', 'required' => false, 'description' => 'Auto Join Domain?', 'category' => 'advanced', 'displayOrder' => 40},
|
809
|
+
{'fieldName' => 'isAutoJoinDomain', 'fieldLabel' => 'Auto Join Domain?', 'type' => 'checkbox', 'defaultValue' => 'off', 'required' => false, 'description' => 'Auto Join Domain?', 'category' => 'advanced', 'displayOrder' => 40},
|
732
810
|
{'fieldName' => 'virtioSupported', 'fieldLabel' => 'VirtIO Drivers Loaded?', 'type' => 'checkbox', 'defaultValue' => 'on', 'required' => false, 'description' => 'VirtIO Drivers Loaded?', 'category' => 'advanced', 'displayOrder' => 40},
|
733
811
|
{'fieldName' => 'vmToolsInstalled', 'fieldLabel' => 'VM Tools Installed?', 'type' => 'checkbox', 'defaultValue' => 'on', 'required' => false, 'description' => 'VM Tools Installed?', 'category' => 'advanced', 'displayOrder' => 40},
|
734
812
|
{'fieldName' => 'isForceCustomization', 'fieldLabel' => 'Force Guest Customization?', 'type' => 'checkbox', 'defaultValue' => 'off', 'required' => false, 'description' => 'Force Guest Customization?', 'category' => 'advanced', 'displayOrder' => 40},
|
@@ -767,7 +845,10 @@ EOT
|
|
767
845
|
|
768
846
|
def update_virtual_image_option_types(image_type = nil)
|
769
847
|
list = add_virtual_image_option_types(image_type)
|
770
|
-
list.each {|it|
|
848
|
+
list.each {|it|
|
849
|
+
it.delete('required')
|
850
|
+
it.delete('defaultValue')
|
851
|
+
}
|
771
852
|
list
|
772
853
|
end
|
773
854
|
|
@@ -817,4 +898,17 @@ EOT
|
|
817
898
|
return rtn
|
818
899
|
end
|
819
900
|
|
901
|
+
def format_virtual_image_source(virtual_image, return_color=cyan)
|
902
|
+
out = ""
|
903
|
+
if virtual_image['userUploaded']
|
904
|
+
# out << "#{green}UPLOADED#{return_color}"
|
905
|
+
out << "#{cyan}UPLOADED#{return_color}"
|
906
|
+
elsif virtual_image['systemImage']
|
907
|
+
out << "#{cyan}SYSTEM#{return_color}"
|
908
|
+
else
|
909
|
+
out << "#{cyan}SYNCED#{return_color}"
|
910
|
+
end
|
911
|
+
out
|
912
|
+
end
|
913
|
+
|
820
914
|
end
|
data/lib/morpheus/formatters.rb
CHANGED
@@ -352,7 +352,7 @@ def format_number(n, opts={})
|
|
352
352
|
delim = opts[:delimiter] || ','
|
353
353
|
out = ""
|
354
354
|
parts = n.to_s.split(".")
|
355
|
-
whole_number = parts[0]
|
355
|
+
whole_number = parts[0].to_s
|
356
356
|
decimal = parts[1] ? parts[1..-1].join('.') : nil
|
357
357
|
i = 0
|
358
358
|
whole_number.reverse.each_char do |c|
|
@@ -366,10 +366,16 @@ def format_number(n, opts={})
|
|
366
366
|
end
|
367
367
|
|
368
368
|
def format_sig_dig(n, sigdig=3, min_sigdig=nil, pad_zeros=false)
|
369
|
-
v =
|
370
|
-
if
|
371
|
-
v =
|
369
|
+
v = ""
|
370
|
+
if sigdig && sigdig > 0
|
371
|
+
# v = n.to_i.round(sigdig).to_s
|
372
|
+
v = sprintf("%.#{sigdig}f", n)
|
373
|
+
else
|
374
|
+
v = n.to_i.round().to_s
|
372
375
|
end
|
376
|
+
# if pad_zeros != true
|
377
|
+
# v = v.to_f.to_s
|
378
|
+
# end
|
373
379
|
if min_sigdig
|
374
380
|
v_parts = v.split(".")
|
375
381
|
decimal_str = v_parts[1]
|
@@ -383,12 +389,18 @@ def format_sig_dig(n, sigdig=3, min_sigdig=nil, pad_zeros=false)
|
|
383
389
|
end
|
384
390
|
|
385
391
|
def currency_sym(currency)
|
386
|
-
Money::Currency.new((currency || '
|
392
|
+
Money::Currency.new((currency || 'USD').to_sym).symbol
|
387
393
|
end
|
388
394
|
|
389
395
|
# returns currency amount formatted like "$4,5123.00". 0.00 is formatted as "$0"
|
390
396
|
# this is not ideal
|
391
|
-
def format_currency(amount, currency='
|
397
|
+
def format_currency(amount, currency='USD', opts={})
|
398
|
+
# currency '' should probably error, like money gem does
|
399
|
+
if currency.to_s.empty?
|
400
|
+
currency = 'USD'
|
401
|
+
end
|
402
|
+
currency = currency.to_s.upcase
|
403
|
+
|
392
404
|
amount = amount.to_f
|
393
405
|
if amount == 0
|
394
406
|
return currency_sym(currency).to_s + "0"
|
@@ -397,7 +409,7 @@ def format_currency(amount, currency='usd', opts={})
|
|
397
409
|
# return currency_sym(currency).to_s + "#{amount}"
|
398
410
|
else
|
399
411
|
sigdig = opts[:sigdig] ? opts[:sigdig].to_i : 2 # max decimal digits
|
400
|
-
min_sigdig = opts[:min_sigdig] ? opts[:min_sigdig].to_i : 2 # min decimal digits
|
412
|
+
min_sigdig = opts[:min_sigdig] ? opts[:min_sigdig].to_i : (sigdig || 2) # min decimal digits
|
401
413
|
display_value = format_sig_dig(amount, sigdig, min_sigdig, opts[:pad_zeros])
|
402
414
|
display_value = format_number(display_value) # commas
|
403
415
|
rtn = currency_sym(currency).to_s + display_value
|
@@ -417,8 +429,9 @@ alias :format_money :format_currency
|
|
417
429
|
# format_currency(amount, currency, opts)
|
418
430
|
# end
|
419
431
|
|
420
|
-
def format_list(items, conjunction="
|
432
|
+
def format_list(items, conjunction="", limit=nil)
|
421
433
|
items = items ? items.clone : []
|
434
|
+
num_items = items.size
|
422
435
|
if limit
|
423
436
|
items = items.first(limit)
|
424
437
|
end
|
@@ -426,7 +439,11 @@ def format_list(items, conjunction="and", limit=nil)
|
|
426
439
|
if items.empty?
|
427
440
|
return "#{last_item}"
|
428
441
|
else
|
429
|
-
|
442
|
+
if limit && limit < num_items
|
443
|
+
items << last_item
|
444
|
+
last_item = "(#{num_items - items.size} more)"
|
445
|
+
end
|
446
|
+
return items.join(", ") + (conjunction.to_s.empty? ? ", " : " #{conjunction} ") + "#{last_item}"
|
430
447
|
end
|
431
448
|
end
|
432
449
|
|
@@ -437,3 +454,11 @@ end
|
|
437
454
|
def ored_list(items, limit=nil)
|
438
455
|
format_list(items, "or", limit)
|
439
456
|
end
|
457
|
+
|
458
|
+
def format_name_values(obj)
|
459
|
+
if obj.is_a?(Hash)
|
460
|
+
obj.collect {|k,v| "#{k}: #{v}"}.join(", ")
|
461
|
+
else
|
462
|
+
""
|
463
|
+
end
|
464
|
+
end
|