morpheus-cli 4.2.22 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Dockerfile +1 -1
  4. data/lib/morpheus/api/api_client.rb +30 -0
  5. data/lib/morpheus/api/billing_interface.rb +34 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  7. data/lib/morpheus/api/deploy_interface.rb +1 -1
  8. data/lib/morpheus/api/deployments_interface.rb +20 -1
  9. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  10. data/lib/morpheus/api/instances_interface.rb +16 -2
  11. data/lib/morpheus/api/rest_interface.rb +0 -6
  12. data/lib/morpheus/api/roles_interface.rb +14 -0
  13. data/lib/morpheus/api/search_interface.rb +13 -0
  14. data/lib/morpheus/api/servers_interface.rb +14 -0
  15. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  16. data/lib/morpheus/api/usage_interface.rb +18 -0
  17. data/lib/morpheus/cli.rb +7 -3
  18. data/lib/morpheus/cli/apps.rb +6 -27
  19. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  20. data/lib/morpheus/cli/backups_command.rb +3 -0
  21. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  22. data/lib/morpheus/cli/cli_command.rb +70 -21
  23. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  24. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  25. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  26. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  27. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  28. data/lib/morpheus/cli/containers_command.rb +14 -24
  29. data/lib/morpheus/cli/cypher_command.rb +6 -2
  30. data/lib/morpheus/cli/deploy.rb +199 -90
  31. data/lib/morpheus/cli/deployments.rb +341 -28
  32. data/lib/morpheus/cli/deploys.rb +206 -41
  33. data/lib/morpheus/cli/error_handler.rb +7 -0
  34. data/lib/morpheus/cli/forgot_password.rb +133 -0
  35. data/lib/morpheus/cli/groups.rb +1 -1
  36. data/lib/morpheus/cli/health_command.rb +59 -2
  37. data/lib/morpheus/cli/hosts.rb +295 -35
  38. data/lib/morpheus/cli/instances.rb +247 -130
  39. data/lib/morpheus/cli/invoices_command.rb +37 -19
  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 +108 -5
  49. data/lib/morpheus/cli/option_types.rb +271 -22
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/remote.rb +35 -12
  52. data/lib/morpheus/cli/reports_command.rb +99 -30
  53. data/lib/morpheus/cli/roles.rb +453 -113
  54. data/lib/morpheus/cli/search_command.rb +182 -0
  55. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  56. data/lib/morpheus/cli/setup.rb +1 -1
  57. data/lib/morpheus/cli/shell.rb +33 -11
  58. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  59. data/lib/morpheus/cli/tasks.rb +29 -32
  60. data/lib/morpheus/cli/usage_command.rb +203 -0
  61. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  62. data/lib/morpheus/cli/users.rb +12 -1
  63. data/lib/morpheus/cli/version.rb +1 -1
  64. data/lib/morpheus/cli/virtual_images.rb +429 -254
  65. data/lib/morpheus/cli/whoami.rb +6 -6
  66. data/lib/morpheus/cli/workflows.rb +33 -40
  67. data/lib/morpheus/formatters.rb +75 -7
  68. data/lib/morpheus/terminal.rb +6 -2
  69. 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.22"
4
+ VERSION = "5.2.1"
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 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