morpheus-cli 4.2.21 → 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +30 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +16 -2
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +14 -0
  14. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  15. data/lib/morpheus/api/usage_interface.rb +18 -0
  16. data/lib/morpheus/cli.rb +7 -3
  17. data/lib/morpheus/cli/apps.rb +6 -27
  18. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  19. data/lib/morpheus/cli/backups_command.rb +3 -0
  20. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  21. data/lib/morpheus/cli/cli_command.rb +70 -21
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -24
  28. data/lib/morpheus/cli/cypher_command.rb +6 -2
  29. data/lib/morpheus/cli/deploy.rb +199 -90
  30. data/lib/morpheus/cli/deployments.rb +341 -28
  31. data/lib/morpheus/cli/deploys.rb +206 -41
  32. data/lib/morpheus/cli/error_handler.rb +7 -0
  33. data/lib/morpheus/cli/forgot_password.rb +133 -0
  34. data/lib/morpheus/cli/groups.rb +1 -1
  35. data/lib/morpheus/cli/health_command.rb +59 -2
  36. data/lib/morpheus/cli/hosts.rb +265 -34
  37. data/lib/morpheus/cli/instances.rb +186 -100
  38. data/lib/morpheus/cli/invoices_command.rb +33 -16
  39. data/lib/morpheus/cli/jobs_command.rb +28 -6
  40. data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
  41. data/lib/morpheus/cli/library_option_types_command.rb +5 -2
  42. data/lib/morpheus/cli/logs_command.rb +9 -6
  43. data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
  44. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  45. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  46. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  47. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
  49. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  50. data/lib/morpheus/cli/option_types.rb +271 -22
  51. data/lib/morpheus/cli/ping.rb +0 -1
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +453 -113
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  57. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  58. data/lib/morpheus/cli/setup.rb +1 -1
  59. data/lib/morpheus/cli/shell.rb +33 -11
  60. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  61. data/lib/morpheus/cli/tasks.rb +29 -32
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +429 -254
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +75 -7
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +14 -2
@@ -94,6 +94,7 @@ EOT
94
94
  "Linux Key Pair" => lambda {|it| it['linuxKeyPairId'] },
95
95
  "Windows Username" => lambda {|it| it['windowsUsername'] },
96
96
  "Windows Password" => lambda {|it| it['windowsPassword'] },
97
+ "Default Persona" => lambda {|it| it['defaultPersona'] ? it['defaultPersona']['name'] : '' },
97
98
  }
98
99
  print_description_list(description_cols, user_settings)
99
100
 
@@ -161,12 +161,22 @@ class Morpheus::Cli::Users
161
161
  options[:include_app_templates_access] = true
162
162
  params['includeAccess'] = true
163
163
  end
164
+ opts.on(nil,'--catalog-item-type-access', "Display Catalog Item Type Access") do
165
+ options[:include_catalog_item_types_access] = true
166
+ params['includeAccess'] = true
167
+ end
168
+ opts.on(nil,'--personas', "Display Persona Access") do
169
+ options[:include_personas_access] = true
170
+ params['includeAccess'] = true
171
+ end
164
172
  opts.on(nil,'--all', "Display All Access Lists") do
165
173
  options[:include_features_access] = true
166
174
  options[:include_sites_access] = true
167
175
  options[:include_zones_access] = true
168
176
  options[:include_instance_types_access] = true
169
177
  options[:include_app_templates_access] = true
178
+ options[:include_catalog_item_types_access] = true
179
+ options[:include_personas_access] = true
170
180
  params['includeAccess'] = true
171
181
  end
172
182
  opts.on('-i', '--include-none-access', "Include Items with 'None' Access in Access List") do
@@ -241,7 +251,8 @@ EOT
241
251
  puts yellow,"No permissions found.",reset
242
252
  end
243
253
  else
244
- available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type', 'app_templates' => 'Blueprint'}
254
+ available_field_options = {'features' => 'Feature', 'sites' => 'Group', 'zones' => 'Cloud', 'instance_types' => 'Instance Type',
255
+ 'app_templates' => 'Blueprint', 'catalog_item_types' => 'Catalog Item Types', 'personas' => 'Personas'}
245
256
  available_field_options.each do |field, label|
246
257
  if !(field == 'sites' && is_tenant_account) && options["include_#{field}_access".to_sym]
247
258
  access = user['access'][field.split('_').enum_for(:each_with_index).collect {|word, idx| idx == 0 ? word : word.capitalize}.join]
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "4.2.21"
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
@@ -27,6 +28,7 @@ class Morpheus::Cli::VirtualImages
27
28
  end
28
29
 
29
30
  def list(args)
31
+ params = {}
30
32
  options = {}
31
33
  optparse = Morpheus::Cli::OptionParser.new do |opts|
32
34
  opts.banner = subcommand_usage()
@@ -42,42 +44,48 @@ class Morpheus::Cli::VirtualImages
42
44
  opts.on('--system', "System Images" ) do
43
45
  options[:filterType] = 'System'
44
46
  end
45
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
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
58
+ build_standard_list_options(opts, options)
46
59
  opts.footer = "List virtual images."
47
60
  end
48
61
  optparse.parse!(args)
49
62
  connect(options)
50
- begin
51
- params = {}
52
- params.merge!(parse_list_options(options))
53
- if options[:imageType]
54
- params[:imageType] = options[:imageType]
55
- end
56
- if options[:filterType]
57
- params[:filterType] = options[:filterType]
58
- end
59
- @virtual_images_interface.setopts(options)
60
- if options[:dry_run]
61
- print_dry_run @virtual_images_interface.dry.get(params)
62
- return
63
- end
64
- json_response = @virtual_images_interface.get(params)
65
-
66
- if options[:json]
67
- puts as_json(json_response, options, "virtualImages")
68
- return 0
69
- elsif options[:yaml]
70
- puts as_yaml(json_response, options, "virtualImages")
71
- return 0
72
- elsif options[:csv]
73
- puts records_as_csv(json_response["virtualImages"], options)
74
- return 0
63
+ # verify_args!(args:args, optparse:optparse, count:0)
64
+ if args.count > 0
65
+ options[:phrase] = args.join(" ")
66
+ end
67
+ params.merge!(parse_list_options(options))
68
+ if options[:imageType]
69
+ params[:imageType] = options[:imageType]
70
+ end
71
+ if options[:filterType]
72
+ params[:filterType] = options[:filterType]
73
+ end
74
+ if options[:tags]
75
+ options[:tags].each do |k,v|
76
+ params['tags.' + k] = v
75
77
  end
76
-
77
-
78
- images = json_response['virtualImages']
78
+ end
79
+ @virtual_images_interface.setopts(options)
80
+ if options[:dry_run]
81
+ print_dry_run @virtual_images_interface.dry.list(params)
82
+ return
83
+ end
84
+ json_response = @virtual_images_interface.list(params)
85
+ images = json_response['virtualImages']
86
+ render_response(json_response, options, 'virtualImages') do
79
87
  title = "Morpheus Virtual Images"
80
- subtitles = []
88
+ subtitles = parse_list_subtitles(options)
81
89
  if options[:imageType]
82
90
  subtitles << "Image Type: #{options[:imageType]}".strip
83
91
  end
@@ -91,129 +99,188 @@ class Morpheus::Cli::VirtualImages
91
99
  if images.empty?
92
100
  print cyan,"No virtual images found.",reset,"\n"
93
101
  else
94
- rows = images.collect do |image|
95
- image_type = virtual_image_type_for_name_or_code(image['imageType'])
96
- image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
97
- {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")
98
129
  end
99
- columns = [:id, :name, :type, :storage, :size, :source]
100
- columns = options[:include_fields] if options[:include_fields]
101
- print cyan
102
- print as_pretty_table(rows, columns, options)
130
+ print as_pretty_table(images, virtual_image_column_definitions.upcase_keys!, options)
103
131
  print_results_pagination(json_response)
104
132
  end
105
133
  print reset,"\n"
106
-
107
- return 0
108
- rescue RestClient::Exception => e
109
- print_rest_exception(e, options)
110
- exit 1
134
+ end
135
+ if images.empty?
136
+ return -1, "no virtual images found"
137
+ else
138
+ return 0, nil
111
139
  end
112
140
  end
113
141
 
114
142
  def get(args)
143
+ params = {}
115
144
  options = {}
116
- show_details = false
117
145
  optparse = Morpheus::Cli::OptionParser.new do |opts|
118
- opts.banner = subcommand_usage("[name]")
119
- opts.on('--details', "Show more details." ) do
120
- show_details = true
121
- end
122
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
123
- opts.footer = "Get details about a virtual image." + "\n" +
124
- "[name] is required. This is the name or id of a virtual image."
146
+ opts.banner = subcommand_usage("[image]")
147
+ opts.on('-a', '--details', "Show more details." ) do
148
+ options[:details] = true
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
153
+ build_standard_get_options(opts, options)
154
+ opts.footer = <<-EOT
155
+ Get details about a virtual image.
156
+ [image] is required. This is the name or id of a virtual image.
157
+ EOT
125
158
  end
126
159
  optparse.parse!(args)
127
- if args.count < 1
128
- puts optparse
129
- exit 1
130
- end
131
- image_name = args[0]
160
+ verify_args!(args:args, optparse:optparse, min:1)
132
161
  connect(options)
133
- begin
134
- @virtual_images_interface.setopts(options)
135
- if options[:dry_run]
136
- if args[0].to_s =~ /\A\d{1,}\Z/
137
- print_dry_run @virtual_images_interface.dry.get(args[0].to_i)
162
+ id_list = parse_id_list(args)
163
+ # lookup IDs if names are given
164
+ id_list = id_list.collect do |id|
165
+ if id.to_s =~ /\A\d{1,}\Z/
166
+ id
167
+ else
168
+ image = find_virtual_image_by_name_or_id(id)
169
+ if image
170
+ image['id']
138
171
  else
139
- print_dry_run @virtual_images_interface.dry.get({name:args[0]})
172
+ raise_command_error "virtual image not found for name '#{id}'"
140
173
  end
141
- return
142
- end
143
- image = find_virtual_image_by_name_or_id(image_name)
144
- return 1 if image.nil?
145
- # refetch
146
- json_response = @virtual_images_interface.get(image['id'])
147
- if options[:json]
148
- puts as_json(json_response, options, "virtualImage")
149
- return 0
150
- elsif options[:yaml]
151
- puts as_yaml(json_response, options, "virtualImage")
152
- return 0
153
- elsif options[:csv]
154
- puts records_as_csv([json_response["virtualImage"]], options)
155
- return 0
156
174
  end
175
+ end
176
+ return run_command_for_each_arg(id_list) do |arg|
177
+ _get(arg, params, options)
178
+ end
179
+ end
157
180
 
181
+ def _get(id, params, options)
182
+ @virtual_images_interface.setopts(options)
183
+ if options[:dry_run]
184
+ print_dry_run @virtual_images_interface.dry.get(id.to_i)
185
+ return
186
+ end
187
+ json_response = @virtual_images_interface.get(id.to_i)
158
188
  image = json_response['virtualImage']
189
+ image_config = image['config'] || {}
190
+ image_volumes = image['volumes'] || []
159
191
  image_files = json_response['cloudFiles'] || json_response['files']
160
-
161
-
162
192
  image_type = virtual_image_type_for_name_or_code(image['imageType'])
163
193
  image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
164
- print_h1 "Virtual Image Details"
165
- print cyan
166
- description_cols = {
167
- "ID" => 'id',
168
- "Name" => 'name',
169
- "Type" => lambda {|it| image_type_display },
170
- "Storage" => lambda {|it| !image['storageProvider'].nil? ? image['storageProvider']['name'] : 'Default' },
171
- "Size" => lambda {|it| image['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{image['rawSize']} B").pretty}" },
172
- "Source" => lambda {|it| image['userUploaded'] ? "#{green}UPLOADED#{cyan}" : (image['systemImage'] ? 'SYSTEM' : "#{white}SYNCED#{cyan}") },
173
- # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
174
- # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
175
- }
176
- advanced_description_cols = {
177
- "OS Type" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
178
- "Min Memory" => lambda {|it| it['minRam'].to_i != 0 ? Filesize.from("#{it['minRam']} B").pretty : "" },
179
- "Cloud Init?" => lambda {|it| format_boolean it['osType'] },
180
- "Install Agent?" => lambda {|it| format_boolean it['osType'] },
181
- "SSH Username" => lambda {|it| it['sshUsername'] },
182
- "SSH Password" => lambda {|it| it['sshPassword'] },
183
- "User Data" => lambda {|it| it['userData'] },
184
- "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
185
- "Tenants" => lambda {|it| format_tenants(it['accounts']) },
186
- "Auto Join Domain?" => lambda {|it| format_boolean it['isAutoJoinDomain'] },
187
- "VirtIO Drivers Loaded?" => lambda {|it| format_boolean it['virtioSupported'] },
188
- "VM Tools Installed?" => lambda {|it| format_boolean it['vmToolsInstalled'] },
189
- "Force Guest Customization?" => lambda {|it| format_boolean it['isForceCustomization'] },
190
- "Trial Version" => lambda {|it| format_boolean it['trialVersion'] },
191
- "Sysprep Enabled?" => lambda {|it| format_boolean it['isSysprep'] },
192
- }
193
- if show_details
194
- description_cols.merge!(advanced_description_cols)
195
- end
196
- print_description_list(description_cols, image)
197
-
198
- if image_files
199
- print_h2 "Files (#{image_files.size})"
200
- # image_files.each {|image_file|
201
- # pretty_filesize = Filesize.from("#{image_file['size']} B").pretty
202
- # print cyan," = #{image_file['name']} [#{pretty_filesize}]", "\n"
203
- # }
204
- image_file_rows = image_files.collect do |image_file|
205
-
206
- {filename: image_file['name'], size: Filesize.from("#{image_file['size']} B").pretty}
194
+ render_response(json_response, options, 'virtualImage') do
195
+ print_h1 "Virtual Image Details", [], options
196
+ description_cols = {
197
+ "ID" => 'id',
198
+ "Name" => 'name',
199
+ "Type" => lambda {|it| image_type_display },
200
+ "Operating System" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
201
+ "Storage" => lambda {|it| !image['storageProvider'].nil? ? image['storageProvider']['name'] : 'Default' },
202
+ "Size" => lambda {|it| image['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{image['rawSize']} B").pretty}" },
203
+ "Azure Publisher" => lambda {|it| image_config['publisher'] },
204
+ "Azure Offer" => lambda {|it| image_config['offer'] },
205
+ "Azure Sku" => lambda {|it| image_config['sku'] },
206
+ "Azure Version" => lambda {|it| image_config['version'] },
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']) }
211
+ }
212
+ description_cols.delete("Tags") if image['tags'].nil? || image['tags'].empty?
213
+ if image['imageType'] == "azure-reference" || image['imageType'] == "azure"
214
+ description_cols.delete("Size")
215
+ description_cols.delete("Storage")
216
+ description_cols["Source"] = lambda {|it| "#{bold}#{cyan}AZURE#{reset}#{cyan}" }
217
+ else
218
+ description_cols.delete("Azure Publisher")
219
+ description_cols.delete("Azure Sku")
220
+ description_cols.delete("Azure Offer")
221
+ description_cols.delete("Azure Version")
222
+ end
223
+ advanced_description_cols = {
224
+ #"OS Type" => lambda {|it| it['osType'] ? it['osType']['name'] : "" }, # displayed above as Operating System
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 : "" },
227
+ "Cloud Init?" => lambda {|it| format_boolean it['osType'] },
228
+ "Install Agent?" => lambda {|it| format_boolean it['osType'] },
229
+ "SSH Username" => lambda {|it| it['sshUsername'] },
230
+ "SSH Password" => lambda {|it| it['sshPassword'] },
231
+ "User Data" => lambda {|it| it['userData'] },
232
+ "Owner" => lambda {|it| it['tenant'].instance_of?(Hash) ? it['tenant']['name'] : it['ownerId'] },
233
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
234
+ "Tenants" => lambda {|it| format_tenants(it['accounts']) },
235
+ "Auto Join Domain?" => lambda {|it| format_boolean it['isAutoJoinDomain'] },
236
+ "VirtIO Drivers Loaded?" => lambda {|it| format_boolean it['virtioSupported'] },
237
+ "VM Tools Installed?" => lambda {|it| format_boolean it['vmToolsInstalled'] },
238
+ "Force Guest Customization?" => lambda {|it| format_boolean it['isForceCustomization'] },
239
+ "Trial Version" => lambda {|it| format_boolean it['trialVersion'] },
240
+ "Sysprep Enabled?" => lambda {|it| format_boolean it['isSysprep'] },
241
+ }
242
+ if options[:details]
243
+ description_cols.merge!(advanced_description_cols)
244
+ end
245
+ print_description_list(description_cols, image)
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
207
256
  end
208
- print cyan
209
- print as_pretty_table(image_file_rows, [:filename, :size])
210
- # print reset,"\n"
257
+
258
+ if image_files
259
+ print_h2 "Files (#{image_files.size})"
260
+ # image_files.each {|image_file|
261
+ # pretty_filesize = Filesize.from("#{image_file['size']} B").pretty
262
+ # print cyan," = #{image_file['name']} [#{pretty_filesize}]", "\n"
263
+ # }
264
+ # size property changed to GB to match volumes
265
+ # contentLength is bytes
266
+ image_file_rows = image_files.collect do |image_file|
267
+ {filename: image_file['name'], size: Filesize.from("#{image_file['contentLength'] || image_file['size']} B").pretty}
268
+ end
269
+ print cyan
270
+ print as_pretty_table(image_file_rows, [:filename, :size])
271
+ # print reset,"\n"
272
+ end
273
+
274
+ if options[:details] && image_config && !image_config.empty?
275
+ print_h2 "Config", options
276
+ print cyan
277
+ print as_description_list(image_config, image_config.keys, options)
278
+ # print "\n", reset
279
+ end
280
+
281
+ print reset,"\n"
211
282
  end
212
- print reset,"\n"
213
- rescue RestClient::Exception => e
214
- print_rest_exception(e, options)
215
- exit 1
216
- end
283
+ return 0, nil
217
284
  end
218
285
 
219
286
  def update(args)
@@ -229,60 +296,68 @@ class Morpheus::Cli::VirtualImages
229
296
  tenants_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
230
297
  end
231
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
232
308
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
233
309
  opts.footer = "Update a virtual image." + "\n" +
234
310
  "[name] is required. This is the name or id of a virtual image."
235
311
  end
236
312
  optparse.parse!(args)
237
- if args.count < 1
238
- puts optparse
239
- exit 1
240
- end
313
+ verify_args!(args:args, optparse:optparse, count:1)
241
314
 
242
315
  connect(options)
243
- begin
244
- image = find_virtual_image_by_name_or_id(image_name)
245
- return 1 if image.nil?
246
-
247
- payload = nil
248
- if options[:payload]
249
- payload = options[:payload]
250
- # support -O OPTION switch on top of --payload
251
- if options[:options]
252
- payload['virtualImage'] ||= {}
253
- payload['virtualImage'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
254
- end
316
+
317
+ virtual_image = find_virtual_image_by_name_or_id(image_name)
318
+ return 1 if virtual_image.nil?
319
+
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])
255
333
  else
256
- params = options[:options] || {}
257
- if params.empty? && tenants_list.nil?
258
- puts optparse
259
- option_lines = update_virtual_image_option_types().collect {|it| "\t-O #{it['fieldContext'] ? (it['fieldContext'] + '.') : ''}#{it['fieldName']}=\"value\"" }.join("\n")
260
- puts "\nAvailable Options:\n#{option_lines}\n\n"
261
- exit 1
262
- end
263
- if tenants_list
264
- params['accounts'] = tenants_list
265
- end
266
- payload = {'virtualImage' => params}
334
+ # tags = prompt_metadata(options)
335
+ # payload[virtual_image_object_key]['tags'] = tags of tags
267
336
  end
268
- @virtual_images_interface.setopts(options)
269
- if options[:dry_run]
270
- print_dry_run @virtual_images_interface.dry.update(image['id'], payload)
271
- return
337
+ # metadata tags
338
+ if options[:add_tags]
339
+ virtual_image_payload['addTags'] = parse_metadata(options[:add_tags])
272
340
  end
273
- response = @virtual_images_interface.update(image['id'], payload)
274
- if options[:json]
275
- print JSON.pretty_generate(json_response)
276
- if !response['success']
277
- exit 1
278
- end
279
- else
280
- 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])
281
343
  end
282
- rescue RestClient::Exception => e
283
- print_rest_exception(e, options)
284
- 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}
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)
285
358
  end
359
+ return 0, nil
360
+
286
361
  end
287
362
 
288
363
  def virtual_image_types(args)
@@ -339,6 +414,19 @@ class Morpheus::Cli::VirtualImages
339
414
  opts.on( '-U', '--url URL', "Image File URL. This can be used instead of uploading local files." ) do |val|
340
415
  file_url = val
341
416
  end
417
+ opts.on( '-c', '--cloud CLOUD', "Cloud to scope image to, certain types require a cloud to be selected, eg. Azure Reference" ) do |val|
418
+ # options[:cloud] = val
419
+ options[:options]['cloud'] = val
420
+ end
421
+ opts.on( '--azure-offer OFFER', String, "Azure Reference offer value, only applies to Azure Reference" ) do |val|
422
+ options[:options]['offer'] = val
423
+ end
424
+ opts.on( '--azure-sku SKU', String, "Azure SKU value, only applies to Azure Reference" ) do |val|
425
+ options[:options]['sku'] = val
426
+ end
427
+ opts.on( '--azure-version VERSION', String, "Azure Version value, only applies to Azure Reference" ) do |val|
428
+ options[:options]['version'] = val
429
+ end
342
430
  opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
343
431
  if list.size == 1 && list[0] == 'null' # hacky way to clear it
344
432
  tenants_list = []
@@ -346,7 +434,13 @@ class Morpheus::Cli::VirtualImages
346
434
  tenants_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
347
435
  end
348
436
  end
349
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
437
+ opts.on('--tags LIST', String, "Metadata tags in the format 'name:value, name:value'") do |val|
438
+ options[:tags] = val
439
+ end
440
+ # build_option_type_options(opts, options, add_virtual_image_option_types)
441
+ # build_option_type_options(opts, options, add_virtual_image_advanced_option_types)
442
+ build_standard_add_options(opts, options)
443
+
350
444
  opts.footer = "Create a virtual image."
351
445
  end
352
446
  optparse.parse!(args)
@@ -368,26 +462,43 @@ class Morpheus::Cli::VirtualImages
368
462
  options[:options]['name'] ||= image_name
369
463
  end
370
464
 
371
- if image_type_name
372
- image_type = virtual_image_type_for_name_or_code(image_type_name)
373
- # fix issue with api returning imageType vmware instead of vmdk
374
- if image_type.nil? && image_type_name == 'vmware'
375
- image_type = virtual_image_type_for_name_or_code('vmdk')
376
- elsif image_type.nil? && image_type_name == 'vmdk'
377
- image_type = virtual_image_type_for_name_or_code('vmware')
465
+ payload = {}
466
+ if options[:payload]
467
+ payload = options[:payload]
468
+ payload.deep_merge!({'virtualImage' => parse_passed_options(options)})
469
+ else
470
+ payload.deep_merge!({'virtualImage' => parse_passed_options(options)})
471
+ virtual_image_payload = {}
472
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt(add_virtual_image_option_types, options[:options], @api_client, options[:params])
473
+ if image_type_name
474
+ image_type = virtual_image_type_for_name_or_code(image_type_name)
475
+ # fix issue with api returning imageType vmware instead of vmdk
476
+ if image_type.nil? && image_type_name == 'vmware'
477
+ image_type = virtual_image_type_for_name_or_code('vmdk')
478
+ elsif image_type.nil? && image_type_name == 'vmdk'
479
+ image_type = virtual_image_type_for_name_or_code('vmware')
480
+ end
481
+ if image_type.nil?
482
+ print_red_alert "Virtual Image Type not found by code '#{image_type_name}'"
483
+ return 1
484
+ end
485
+ # options[:options] ||= {}
486
+ # options[:options]['imageType'] ||= image_type['code']
487
+ else
488
+ image_type_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'imageType', 'fieldLabel' => 'Image Type', 'type' => 'select', 'optionSource' => 'virtualImageTypes', 'required' => true, 'description' => 'Select Virtual Image Type.', 'displayOrder' => 2}],options[:options],@api_client,{})
489
+ image_type = virtual_image_type_for_name_or_code(image_type_prompt['imageType'])
378
490
  end
379
- if image_type.nil?
380
- print_red_alert "Virtual Image Type not found by code '#{image_type_name}'"
381
- return 1
491
+
492
+ # azure requires us to search the marketplace to select publisher, cloud, offerm sku
493
+ if image_type['code'] == "azure-reference" || image_type['code'] == "azure"
494
+ cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'optionSource' => 'clouds', 'required' => true, 'description' => 'Select Azure Cloud.', :fmt=>:natural}],options[:options],@api_client, {zoneTypeWhiteList: 'azure'})
495
+ cloud_id = cloud_prompt['cloud'].to_i
496
+
497
+ marketplace_config = prompt_azure_marketplace(cloud_id, options)
498
+ virtual_image_payload['config'] ||= {}
499
+ virtual_image_payload['config'].deep_merge!(marketplace_config)
382
500
  end
383
- # options[:options] ||= {}
384
- # options[:options]['imageType'] ||= image_type['code']
385
- else
386
- image_type_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'imageType', 'fieldLabel' => 'Image Type', 'type' => 'select', 'optionSource' => 'virtualImageTypes', 'required' => true, 'description' => 'Select Virtual Image Type.', 'displayOrder' => 2}],options[:options],@api_client,{})
387
- image_type = virtual_image_type_for_name_or_code(image_type_prompt['imageType'])
388
- end
389
501
 
390
- begin
391
502
  my_option_types = add_virtual_image_option_types(image_type, !file_url)
392
503
  # if options[:no_prompt]
393
504
  # my_option_types.each do |it|
@@ -396,9 +507,9 @@ class Morpheus::Cli::VirtualImages
396
507
  # end
397
508
  # end
398
509
  # end
399
- params = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, options[:params])
400
- params.deep_compact!
401
- virtual_image_payload = {}.merge(params)
510
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, options[:params])
511
+ v_prompt.deep_compact!
512
+ virtual_image_payload.deep_merge!(v_prompt)
402
513
  virtual_image_files = virtual_image_payload.delete('virtualImageFiles')
403
514
  virtual_image_payload['imageType'] = image_type['code']
404
515
  storage_provider_id = virtual_image_payload.delete('storageProviderId')
@@ -408,65 +519,74 @@ class Morpheus::Cli::VirtualImages
408
519
  if tenants_list
409
520
  virtual_image_payload['accounts'] = tenants_list
410
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
411
530
  # fix issue with api returning imageType vmware instead of vmdk
412
531
  if virtual_image_payload && virtual_image_payload['imageType'] == 'vmware'
413
532
  virtual_image_payload['imageType'] == 'vmdk'
414
533
  end
415
- payload = {virtualImage: virtual_image_payload}
416
- @virtual_images_interface.setopts(options)
417
- if options[:dry_run]
418
- print_dry_run @virtual_images_interface.dry.create(payload)
419
- if file_url
420
- print_dry_run @virtual_images_interface.dry.upload_by_url(":id", file_url, file_name)
421
- elsif virtual_image_files && !virtual_image_files.empty?
422
- virtual_image_files.each do |key, filepath|
423
- print_dry_run @virtual_images_interface.dry.upload(":id", "(Contents of file #{filepath})")
424
- end
425
- end
426
- return
427
- end
428
-
429
- json_response = @virtual_images_interface.create(payload)
430
- virtual_image = json_response['virtualImage']
431
-
432
- if options[:json]
433
- print JSON.pretty_generate(json_response)
434
- elsif !options[:quiet]
435
- print "\n", cyan, "Virtual Image #{virtual_image['name']} created successfully", reset, "\n\n"
436
- end
534
+ #payload = {'virtualImage' => virtual_image_payload}
535
+ payload.deep_merge!({'virtualImage' => virtual_image_payload})
536
+ end
437
537
 
438
- # now upload the file, do this in the background maybe?
538
+ @virtual_images_interface.setopts(options)
539
+ if options[:dry_run]
540
+ print_dry_run @virtual_images_interface.dry.create(payload)
439
541
  if file_url
440
- unless options[:quiet]
441
- print cyan, "Uploading file by url #{file_url} ...", reset, "\n"
442
- end
443
- upload_json_response = @virtual_images_interface.upload_by_url(virtual_image['id'], file_url, file_name)
444
- if options[:json]
445
- print JSON.pretty_generate(upload_json_response)
446
- end
542
+ print_dry_run @virtual_images_interface.dry.upload_by_url(":id", file_url, file_name)
447
543
  elsif virtual_image_files && !virtual_image_files.empty?
448
544
  virtual_image_files.each do |key, filepath|
449
- unless options[:quiet]
450
- print cyan, "Uploading file (#{key}) #{filepath} ...", reset, "\n"
451
- end
452
- image_file = File.new(filepath, 'rb')
453
- upload_json_response = @virtual_images_interface.upload(virtual_image['id'], image_file, file_name)
454
- if options[:json]
455
- print JSON.pretty_generate(upload_json_response)
456
- end
545
+ print_dry_run @virtual_images_interface.dry.upload(":id", "(Contents of file #{filepath})")
457
546
  end
458
- else
459
- puts cyan, "No files uploaded.", reset
460
547
  end
548
+ return
549
+ end
550
+
551
+ json_response = @virtual_images_interface.create(payload)
552
+ virtual_image = json_response['virtualImage']
553
+
554
+ # if options[:json]
555
+ # print JSON.pretty_generate(json_response)
556
+ # elsif !options[:quiet]
557
+ # print "\n", cyan, "Virtual Image #{virtual_image['name']} created successfully", reset, "\n\n"
558
+ # end
461
559
 
462
- if !options[:json]
463
- get([virtual_image['id']])
560
+ # now upload the file, do this in the background maybe?
561
+ if file_url
562
+ unless options[:quiet]
563
+ print cyan, "Uploading file by url #{file_url} ...", reset, "\n"
564
+ end
565
+ upload_json_response = @virtual_images_interface.upload_by_url(virtual_image['id'], file_url, file_name)
566
+ # if options[:json]
567
+ # print JSON.pretty_generate(upload_json_response)
568
+ # end
569
+ elsif virtual_image_files && !virtual_image_files.empty?
570
+ virtual_image_files.each do |key, filepath|
571
+ unless options[:quiet]
572
+ print cyan, "Uploading file (#{key}) #{filepath} ...", reset, "\n"
573
+ end
574
+ image_file = File.new(filepath, 'rb')
575
+ upload_json_response = @virtual_images_interface.upload(virtual_image['id'], image_file, file_name)
576
+ # if options[:json]
577
+ # print JSON.pretty_generate(upload_json_response)
578
+ # end
464
579
  end
580
+ else
581
+ # puts cyan, "No files uploaded.", reset
582
+ end
465
583
 
466
- rescue RestClient::Exception => e
467
- print_rest_exception(e, options)
468
- exit 1
584
+ render_response(json_response, options, 'virtualImage') do
585
+ print_green_success "Added virtual image #{virtual_image['name']}"
586
+ return _get(virtual_image["id"], {}, options)
469
587
  end
588
+ return 0, nil
589
+
470
590
  end
471
591
 
472
592
  def add_file(args)
@@ -676,16 +796,17 @@ class Morpheus::Cli::VirtualImages
676
796
  tmp_option_types = [
677
797
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
678
798
  #{'fieldName' => 'imageType', 'fieldLabel' => 'Image Type', 'type' => 'select', 'optionSource' => 'virtualImageTypes', 'required' => true, 'description' => 'Select Virtual Image Type.', 'displayOrder' => 2},
679
- {'fieldName' => 'osType', 'fieldLabel' => 'OS Type', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false, 'description' => 'Select OS Type.', 'displayOrder' => 3},
680
- {'fieldName' => 'minRam', 'fieldLabel' => 'Minimum Memory (MB)', 'type' => 'number', 'required' => false, 'description' => 'Minimum Memory (MB)', 'displayOrder' => 4},
681
- {'fieldName' => 'isCloudInit', 'fieldLabel' => 'Cloud Init Enabled?', 'type' => 'checkbox', 'required' => false, 'description' => 'Cloud Init Enabled?', 'displayOrder' => 4},
682
- {'fieldName' => 'installAgent', 'fieldLabel' => 'Install Agent?', 'type' => 'checkbox', 'required' => false, 'description' => 'Install Agent?', 'displayOrder' => 4},
683
- {'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text', 'required' => false, 'description' => 'Enter an SSH Username', 'displayOrder' => 5},
684
- {'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false, 'description' => 'Enter an SSH Password', 'displayOrder' => 6},
685
- {'fieldName' => 'storageProviderId', 'type' => 'select', 'fieldLabel' => 'Storage Provider', 'optionSource' => 'storageProviders', 'required' => false, 'description' => 'Select Storage Provider.', 'displayOrder' => 7},
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},
804
+ {'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text', 'required' => false, 'description' => 'Enter an SSH Username', 'displayOrder' => 7},
805
+ {'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false, 'description' => 'Enter an SSH Password', 'displayOrder' => 8},
806
+ {'fieldName' => 'storageProviderId', 'type' => 'select', 'fieldLabel' => 'Storage Provider', 'optionSource' => 'storageProviders', 'required' => false, 'description' => 'Select Storage Provider.', 'displayOrder' => 9},
686
807
  {'fieldName' => 'userData', 'fieldLabel' => 'Cloud-Init User Data', 'type' => 'textarea', 'required' => false, 'displayOrder' => 10},
687
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},
688
- {'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},
689
810
  {'fieldName' => 'virtioSupported', 'fieldLabel' => 'VirtIO Drivers Loaded?', 'type' => 'checkbox', 'defaultValue' => 'on', 'required' => false, 'description' => 'VirtIO Drivers Loaded?', 'category' => 'advanced', 'displayOrder' => 40},
690
811
  {'fieldName' => 'vmToolsInstalled', 'fieldLabel' => 'VM Tools Installed?', 'type' => 'checkbox', 'defaultValue' => 'on', 'required' => false, 'description' => 'VM Tools Installed?', 'category' => 'advanced', 'displayOrder' => 40},
691
812
  {'fieldName' => 'isForceCustomization', 'fieldLabel' => 'Force Guest Customization?', 'type' => 'checkbox', 'defaultValue' => 'off', 'required' => false, 'description' => 'Force Guest Customization?', 'category' => 'advanced', 'displayOrder' => 40},
@@ -696,22 +817,25 @@ class Morpheus::Cli::VirtualImages
696
817
  image_type_code = image_type ? image_type['code'] : nil
697
818
  if image_type_code
698
819
  if image_type_code == 'ami'
699
- tmp_option_types << {'fieldName' => 'externalId', 'fieldLabel' => 'AMI id', 'type' => 'text', 'required' => false, 'displayOrder' => 10}
820
+ tmp_option_types << {'fieldName' => 'externalId', 'fieldLabel' => 'AMI id', 'type' => 'text', 'required' => false, 'displayOrder' => 11}
700
821
  if include_file_selection
701
- tmp_option_types << {'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'displayOrder' => 10}
822
+ tmp_option_types << {'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'displayOrder' => 12}
702
823
  end
703
824
  elsif image_type_code == 'vmware' || image_type_code == 'vmdk'
704
825
  if include_file_selection
705
- tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'OVF File', 'type' => 'file', 'required' => false, 'displayOrder' => 10}
706
- tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageDescriptorFile', 'fieldLabel' => 'VMDK File', 'type' => 'file', 'required' => false, 'displayOrder' => 10}
826
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'OVF File', 'type' => 'file', 'required' => false, 'displayOrder' => 11}
827
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageDescriptorFile', 'fieldLabel' => 'VMDK File', 'type' => 'file', 'required' => false, 'displayOrder' => 12}
707
828
  end
708
829
  elsif image_type_code == 'pxe'
709
- tmp_option_types << {'fieldName' => 'config.menu', 'fieldLabel' => 'Menu', 'type' => 'text', 'required' => false, 'displayOrder' => 10}
710
- tmp_option_types << {'fieldName' => 'imagePath', 'fieldLabel' => 'Image Path', 'type' => 'text', 'required' => true, 'displayOrder' => 10}
830
+ tmp_option_types << {'fieldName' => 'config.menu', 'fieldLabel' => 'Menu', 'type' => 'text', 'required' => false, 'displayOrder' => 11}
831
+ tmp_option_types << {'fieldName' => 'imagePath', 'fieldLabel' => 'Image Path', 'type' => 'text', 'required' => true, 'displayOrder' => 12}
711
832
  tmp_option_types.reject! {|opt| ['isCloudInit', 'installAgent', 'sshUsername', 'sshPassword'].include?(opt['fieldName'])}
833
+ elsif image_type_code == 'azure' || image_type_code == 'azure-reference'
834
+ # Azure Marketplace Prompt happens elsewhere
835
+ tmp_option_types.reject! {|opt| ['storageProviderId', 'userData', 'sshUsername', 'sshPassword'].include?(opt['fieldName'])}
712
836
  else
713
837
  if include_file_selection
714
- tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'description' => 'Choose an image file to upload', 'displayOrder' => 10}
838
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'description' => 'Choose an image file to upload', 'displayOrder' => 11}
715
839
  end
716
840
  end
717
841
  end
@@ -721,7 +845,10 @@ class Morpheus::Cli::VirtualImages
721
845
 
722
846
  def update_virtual_image_option_types(image_type = nil)
723
847
  list = add_virtual_image_option_types(image_type)
724
- list.each {|it| it['required'] = false }
848
+ list.each {|it|
849
+ it.delete('required')
850
+ it.delete('defaultValue')
851
+ }
725
852
  list
726
853
  end
727
854
 
@@ -735,5 +862,53 @@ class Morpheus::Cli::VirtualImages
735
862
  ""
736
863
  end
737
864
  end
865
+
866
+ def prompt_azure_marketplace(cloud_id, options)
867
+ rtn = {}
868
+ publisher_value, offer_value, sku_value, version_value = nil, nil, nil, nil
869
+
870
+ # Marketplace Publisher & Offer
871
+ marketplace_api_params = {'zoneId' => cloud_id}
872
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'offer', 'fieldLabel' => 'Azure Marketplace Offer', 'type' => 'typeahead', 'optionSource' => 'searchAzureMarketplace', 'required' => true, 'description' => "Select Azure Marketplace Offer."}], options[:options],@api_client, marketplace_api_params)
873
+ # offer_value = v_prompt['marketplace']
874
+ # actually need both offer and publisher of these to query correctly..sigh
875
+ marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
876
+ offer_value = marketplace_option['offer']
877
+ publisher_value = marketplace_option['publisher']
878
+
879
+ # SKU & VERSION
880
+ if options[:options] && options[:options]['sku'] && options[:options]['version']
881
+ # the value to match on is actually sku|version
882
+ options[:options]['sku'] = options[:options]['sku'] + '|' + options[:options]['version']
883
+ end
884
+ sku_api_params = {'zoneId' => cloud_id, publisher: publisher_value, offer: offer_value}
885
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'sku', 'fieldLabel' => 'Azure Marketplace SKU', 'type' => 'select', 'optionSource' => 'searchAzureMarketplaceSkus', 'required' => true, 'description' => "Select Azure Marketplace SKU and Version, the format is SKU|Version"}], options[:options],@api_client, sku_api_params)
886
+ # marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
887
+ # sku_value = marketplace_option['sku']
888
+ # version_value = marketplace_option['version']
889
+ sku_value = v_prompt['sku']
890
+ if sku_value && sku_value.include?("|")
891
+ sku_value, version_value = sku_value.split("|")
892
+ end
893
+
894
+ rtn['publisher'] = publisher_value
895
+ rtn['offer'] = offer_value
896
+ rtn['sku'] = sku_value
897
+ rtn['version'] = version_value
898
+ return rtn
899
+ end
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
738
913
 
739
914
  end