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.
@@ -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
- if args.count > 1
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
- if args.count != 1
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
- if args.count < 1
747
- puts optparse
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
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "5.0.2"
4
+ VERSION = "5.2.0"
5
5
  end
6
6
  end
@@ -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
- # print as_pretty_table(images, virtual_image_column_definitions.upcase_keys!, options)
86
- rows = images.collect do |image|
87
- image_type = virtual_image_type_for_name_or_code(image['imageType'])
88
- image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
89
- {name: image['name'], id: image['id'], type: image_type_display, source: image['userUploaded'] ? "#{green}UPLOADED#{cyan}" : (image['systemImage'] ? 'SYSTEM' : "#{white}SYNCED#{cyan}"), storage: !image['storageProvider'].nil? ? image['storageProvider']['name'] : 'Default', size: image['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{image['rawSize']} B").pretty}"}
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
- columns = [:id, :name, :type, :storage, :size, :source]
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| image['userUploaded'] ? "#{green}UPLOADED#{cyan}" : (image['systemImage'] ? 'SYSTEM' : "#{white}SYNCED#{cyan}") },
167
- # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
168
- # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
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 Marketplace")
176
- description_cols.delete("Azure Marketplace Publisher")
177
- description_cols.delete("Azure Marketplace Sku")
178
- description_cols.delete("Azure Marketplace Offer")
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
- if args.count < 1
247
- puts optparse
248
- exit 1
249
- end
313
+ verify_args!(args:args, optparse:optparse, count:1)
250
314
 
251
315
  connect(options)
252
- begin
253
- image = find_virtual_image_by_name_or_id(image_name)
254
- return 1 if image.nil?
316
+
317
+ virtual_image = find_virtual_image_by_name_or_id(image_name)
318
+ return 1 if virtual_image.nil?
255
319
 
256
- payload = nil
257
- if options[:payload]
258
- payload = options[:payload]
259
- # support -O OPTION switch on top of --payload
260
- if options[:options]
261
- payload['virtualImage'] ||= {}
262
- payload['virtualImage'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
263
- end
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
- params = options[:options] || {}
266
- if params.empty? && tenants_list.nil?
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
- @virtual_images_interface.setopts(options)
278
- if options[:dry_run]
279
- print_dry_run @virtual_images_interface.dry.update(image['id'], payload)
280
- return
337
+ # metadata tags
338
+ if options[:add_tags]
339
+ virtual_image_payload['addTags'] = parse_metadata(options[:add_tags])
281
340
  end
282
- response = @virtual_images_interface.update(image['id'], payload)
283
- if options[:json]
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
- rescue RestClient::Exception => e
292
- print_rest_exception(e, options)
293
- exit 1
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' => 'OS Type', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false, 'description' => 'Select OS Type.', 'displayOrder' => 3},
723
- {'fieldName' => 'minRam', 'fieldLabel' => 'Minimum Memory (MB)', 'type' => 'number', 'required' => false, 'description' => 'Minimum Memory (MB)', 'displayOrder' => 4},
724
- {'fieldName' => 'isCloudInit', 'fieldLabel' => 'Cloud Init Enabled?', 'type' => 'checkbox', 'required' => false, 'description' => 'Cloud Init Enabled?', 'displayOrder' => 5},
725
- {'fieldName' => 'installAgent', 'fieldLabel' => 'Install Agent?', 'type' => 'checkbox', 'required' => false, 'description' => 'Install Agent?', 'displayOrder' => 6},
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| it['required'] = false }
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
@@ -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 = sprintf("%.#{sigdig}f", n)
370
- if pad_zeros != true
371
- v = v.to_f.to_s
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 || 'usd').to_sym).symbol
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='usd', opts={})
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="and", limit=nil)
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
- return items.join(", ") + (conjunction.to_s.empty? ? ", " : " #{conjunction} ") + "#{last_item}" + ((limit && limit < (items.size+1)) ? " ..." : "")
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