morpheus-cli 4.2.20 → 5.0.2

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 +26 -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 +7 -0
  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 +7 -0
  14. data/lib/morpheus/api/usage_interface.rb +18 -0
  15. data/lib/morpheus/cli.rb +6 -3
  16. data/lib/morpheus/cli/apps.rb +3 -4
  17. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  18. data/lib/morpheus/cli/backups_command.rb +3 -0
  19. data/lib/morpheus/cli/budgets_command.rb +4 -4
  20. data/lib/morpheus/cli/catalog_command.rb +507 -0
  21. data/lib/morpheus/cli/cli_command.rb +45 -20
  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 -0
  28. data/lib/morpheus/cli/deploy.rb +199 -90
  29. data/lib/morpheus/cli/deployments.rb +342 -29
  30. data/lib/morpheus/cli/deploys.rb +206 -41
  31. data/lib/morpheus/cli/error_handler.rb +7 -0
  32. data/lib/morpheus/cli/forgot_password.rb +133 -0
  33. data/lib/morpheus/cli/groups.rb +1 -1
  34. data/lib/morpheus/cli/health_command.rb +2 -2
  35. data/lib/morpheus/cli/hosts.rb +181 -26
  36. data/lib/morpheus/cli/instances.rb +102 -33
  37. data/lib/morpheus/cli/invoices_command.rb +33 -16
  38. data/lib/morpheus/cli/jobs_command.rb +28 -6
  39. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  40. data/lib/morpheus/cli/logs_command.rb +9 -6
  41. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  42. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  43. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  44. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  45. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  46. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  47. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  48. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  49. data/lib/morpheus/cli/option_types.rb +266 -17
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  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 +305 -3
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  57. data/lib/morpheus/cli/setup.rb +1 -1
  58. data/lib/morpheus/cli/shell.rb +33 -11
  59. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  60. data/lib/morpheus/cli/tasks.rb +20 -21
  61. data/lib/morpheus/cli/tenants_command.rb +1 -1
  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 +280 -199
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +48 -5
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +13 -2
@@ -295,7 +295,7 @@ EOT
295
295
  [
296
296
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
297
297
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
298
- {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda {
298
+ {'fieldContext' => 'role', 'fieldName' => 'id', 'fieldLabel' => 'Base Role', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
299
299
  @roles_interface.list(nil, {roleType:'account'})['roles'].collect {|it|
300
300
  {"name" => (it["authority"] || it["name"]), "value" => it["id"]}
301
301
  }
@@ -0,0 +1,203 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ # CLI command usages
4
+ # UI is Costing - Usage
5
+ # API is /usage and returns usages
6
+ class Morpheus::Cli::UsageCommand
7
+ include Morpheus::Cli::CliCommand
8
+ include Morpheus::Cli::OptionSourceHelper
9
+
10
+ set_command_name :'usage'
11
+
12
+ register_subcommands :list, :get
13
+
14
+ def connect(opts)
15
+ @api_client = establish_remote_appliance_connection(opts)
16
+ @usage_interface = @api_client.usage
17
+ end
18
+
19
+ def handle(args)
20
+ handle_subcommand(args)
21
+ end
22
+
23
+ def list(args)
24
+ options = {}
25
+ params = {}
26
+ ref_ids = []
27
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
28
+ opts.banner = subcommand_usage("[search]")
29
+ opts.on( '-t', '--type TYPE', "Filter by type" ) do |val|
30
+ params['type'] = parse_usage_type(val)
31
+ end
32
+ opts.on( '-c', '--cloud CLOUD', "Filter by cloud" ) do |val|
33
+ options[:cloud] = val
34
+ end
35
+ opts.on('--start DATE', String, "Start date in the format YYYY-MM-DD.") do |val|
36
+ params['startDate'] = val # parse_time(val).utc.iso8601
37
+ end
38
+ opts.on('--end DATE', String, "End date in the format YYYY-MM-DD. Default is the current date.") do |val|
39
+ params['endDate'] = val # parse_time(val).utc.iso8601
40
+ end
41
+ opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 5.") do |val|
42
+ options[:sigdig] = val.to_i
43
+ end
44
+ build_standard_list_options(opts, options)
45
+ opts.footer = "List usages."
46
+ end
47
+ optparse.parse!(args)
48
+ connect(options)
49
+ # verify_args!(args:args, optparse:optparse, count:0)
50
+ if args.count > 0
51
+ options[:phrase] = args.join(" ")
52
+ end
53
+ params.merge!(parse_list_options(options))
54
+ # --cloud
55
+ if options[:cloud]
56
+ params['cloud'] = parse_id_list(options[:cloud]).collect {|cloud_id|
57
+ if cloud_id.to_s =~ /\A\d{1,}\Z/
58
+ cloud_id
59
+ else
60
+ cloud = find_cloud_option(cloud_id)
61
+ return 1 if cloud.nil?
62
+ cloud['id']
63
+ end
64
+ }
65
+ end
66
+
67
+ @usage_interface.setopts(options)
68
+ if options[:dry_run]
69
+ print_dry_run @usage_interface.dry.list(params)
70
+ return
71
+ end
72
+ json_response = @usage_interface.list(params)
73
+ usages = json_response[usage_list_key]
74
+ render_response(json_response, options, usage_list_key) do
75
+ print_h1 "Morpheus Usages", parse_list_subtitles(options), options
76
+ if usages.empty?
77
+ print cyan,"No usages found.",reset,"\n"
78
+ else
79
+ list_columns = {
80
+ "ID" => 'id',
81
+ "Cloud" => 'zoneName',
82
+ "Type" => lambda {|it| format_usage_type(it) },
83
+ "Name" => 'name',
84
+ "Plan" => 'planName',
85
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
86
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
87
+ "Usage Status" => lambda {|it| format_usage_status(it) },
88
+ "Usage Cost" => lambda {|it| format_money(it['costDetails']['cost'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
89
+ "Usage Price" => lambda {|it| format_money(it['price'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
90
+ }
91
+ print as_pretty_table(usages, list_columns.upcase_keys!, options)
92
+ print_results_pagination(json_response)
93
+ end
94
+ print reset,"\n"
95
+ end
96
+ if usages.empty?
97
+ return 1, "no usages found"
98
+ else
99
+ return 0, nil
100
+ end
101
+ end
102
+
103
+ def get(args)
104
+ params = {}
105
+ options = {}
106
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
107
+ opts.banner = subcommand_usage("[usage]")
108
+ build_standard_get_options(opts, options)
109
+ opts.footer = <<-EOT
110
+ Get details about a specific usage.
111
+ [usage] is required. This is the id of a usage record.
112
+ EOT
113
+ end
114
+ optparse.parse!(args)
115
+ verify_args!(args:args, optparse:optparse, min:1)
116
+ connect(options)
117
+ id_list = parse_id_list(args)
118
+ return run_command_for_each_arg(id_list) do |arg|
119
+ _get(arg, params, options)
120
+ end
121
+ end
122
+
123
+ def _get(id, params, options)
124
+ usage = nil
125
+ @usage_interface.setopts(options)
126
+ if options[:dry_run]
127
+ print_dry_run @usage_interface.dry.get(id, params)
128
+ return
129
+ end
130
+ json_response = @usage_interface.get(id, params)
131
+ usage = json_response[usage_object_key]
132
+ render_response(json_response, options, usage_object_key) do
133
+ print_h1 "Usage Details", [], options
134
+ print cyan
135
+ show_columns = {
136
+ "ID" => 'id',
137
+ "Cloud" => 'zoneName',
138
+ "Type" => lambda {|it| format_usage_type(it) },
139
+ "Name" => 'name',
140
+ "Plan" => 'planName',
141
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
142
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
143
+ "Usage Status" => lambda {|it| format_usage_status(it) },
144
+ "Usage Cost" => lambda {|it| format_money(it['costDetails']['cost'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
145
+ "Usage Price" => lambda {|it| format_money(it['price'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
146
+ }
147
+ print_description_list(show_columns, usage)
148
+
149
+ # print_h2 "Applicable Prices"
150
+
151
+ print reset,"\n"
152
+ end
153
+ return 0, nil
154
+ end
155
+
156
+ private
157
+
158
+ def usage_object_key
159
+ 'usage'
160
+ end
161
+
162
+ def usage_list_key
163
+ 'usages'
164
+ end
165
+
166
+ def format_usage_type(usage)
167
+ #return usage['costDetails']['refType']
168
+ ref_type = usage['costDetails'] ? usage['costDetails']['refType'].to_s : ''
169
+ if ref_type == 'discoveredServer'
170
+ 'Discovered'
171
+ elsif ref_type == 'computeServer'
172
+ 'Host'
173
+ elsif ref_type == 'container'
174
+ 'Container'
175
+ else
176
+ ref_type.to_s
177
+ end
178
+ end
179
+
180
+ def parse_usage_type(val)
181
+ type_string = val.to_s.downcase
182
+ if type_string == 'discoveredServer'
183
+ 'discoveredServer'
184
+ elsif type_string == 'host'
185
+ 'computeServer'
186
+ elsif type_string == 'container'
187
+ 'container'
188
+ else
189
+ val
190
+ end
191
+ end
192
+
193
+ def format_usage_status(usage, return_color=cyan)
194
+ #return usage['status'].to_s.capitalize
195
+ status_string = usage['status'].to_s
196
+ if status_string == 'stopped'
197
+ return "#{red}#{status_string.upcase}#{return_color}"
198
+ else
199
+ return "#{green}#{status_string.upcase}#{return_color}"
200
+ end
201
+ end
202
+
203
+ end
@@ -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.20"
4
+ VERSION = "5.0.2"
5
5
  end
6
6
  end
@@ -27,6 +27,7 @@ class Morpheus::Cli::VirtualImages
27
27
  end
28
28
 
29
29
  def list(args)
30
+ params = {}
30
31
  options = {}
31
32
  optparse = Morpheus::Cli::OptionParser.new do |opts|
32
33
  opts.banner = subcommand_usage()
@@ -42,42 +43,32 @@ class Morpheus::Cli::VirtualImages
42
43
  opts.on('--system', "System Images" ) do
43
44
  options[:filterType] = 'System'
44
45
  end
45
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
46
+ build_standard_list_options(opts, options)
46
47
  opts.footer = "List virtual images."
47
48
  end
48
49
  optparse.parse!(args)
49
50
  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
75
- end
76
-
77
-
78
- images = json_response['virtualImages']
51
+ # verify_args!(args:args, optparse:optparse, count:0)
52
+ if args.count > 0
53
+ options[:phrase] = args.join(" ")
54
+ end
55
+ params.merge!(parse_list_options(options))
56
+ if options[:imageType]
57
+ params[:imageType] = options[:imageType]
58
+ end
59
+ if options[:filterType]
60
+ params[:filterType] = options[:filterType]
61
+ end
62
+ @virtual_images_interface.setopts(options)
63
+ if options[:dry_run]
64
+ print_dry_run @virtual_images_interface.dry.list(params)
65
+ return
66
+ end
67
+ json_response = @virtual_images_interface.list(params)
68
+ images = json_response['virtualImages']
69
+ render_response(json_response, options, 'virtualImages') do
79
70
  title = "Morpheus Virtual Images"
80
- subtitles = []
71
+ subtitles = parse_list_subtitles(options)
81
72
  if options[:imageType]
82
73
  subtitles << "Image Type: #{options[:imageType]}".strip
83
74
  end
@@ -91,6 +82,7 @@ class Morpheus::Cli::VirtualImages
91
82
  if images.empty?
92
83
  print cyan,"No virtual images found.",reset,"\n"
93
84
  else
85
+ # print as_pretty_table(images, virtual_image_column_definitions.upcase_keys!, options)
94
86
  rows = images.collect do |image|
95
87
  image_type = virtual_image_type_for_name_or_code(image['imageType'])
96
88
  image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
@@ -103,117 +95,134 @@ class Morpheus::Cli::VirtualImages
103
95
  print_results_pagination(json_response)
104
96
  end
105
97
  print reset,"\n"
106
-
107
- return 0
108
- rescue RestClient::Exception => e
109
- print_rest_exception(e, options)
110
- exit 1
98
+ end
99
+ if images.empty?
100
+ return -1, "no virtual images found"
101
+ else
102
+ return 0, nil
111
103
  end
112
104
  end
113
105
 
114
106
  def get(args)
107
+ params = {}
115
108
  options = {}
116
- show_details = false
117
109
  optparse = Morpheus::Cli::OptionParser.new do |opts|
118
- opts.banner = subcommand_usage("[name]")
110
+ opts.banner = subcommand_usage("[image]")
119
111
  opts.on('--details', "Show more details." ) do
120
- show_details = true
112
+ options[:details] = true
121
113
  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."
114
+ build_standard_get_options(opts, options)
115
+ opts.footer = <<-EOT
116
+ Get details about a virtual image.
117
+ [image] is required. This is the name or id of a virtual image.
118
+ EOT
125
119
  end
126
120
  optparse.parse!(args)
127
- if args.count < 1
128
- puts optparse
129
- exit 1
130
- end
131
- image_name = args[0]
121
+ verify_args!(args:args, optparse:optparse, min:1)
132
122
  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)
123
+ id_list = parse_id_list(args)
124
+ # lookup IDs if names are given
125
+ id_list = id_list.collect do |id|
126
+ if id.to_s =~ /\A\d{1,}\Z/
127
+ id
128
+ else
129
+ image = find_virtual_image_by_name_or_id(id)
130
+ if image
131
+ image['id']
138
132
  else
139
- print_dry_run @virtual_images_interface.dry.get({name:args[0]})
133
+ raise_command_error "virtual image not found for name '#{id}'"
140
134
  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
135
  end
136
+ end
137
+ return run_command_for_each_arg(id_list) do |arg|
138
+ _get(arg, params, options)
139
+ end
140
+ end
157
141
 
142
+ def _get(id, params, options)
143
+ @virtual_images_interface.setopts(options)
144
+ if options[:dry_run]
145
+ print_dry_run @virtual_images_interface.dry.get(id.to_i)
146
+ return
147
+ end
148
+ json_response = @virtual_images_interface.get(id.to_i)
158
149
  image = json_response['virtualImage']
150
+ image_config = image['config'] || {}
159
151
  image_files = json_response['cloudFiles'] || json_response['files']
160
-
161
-
162
152
  image_type = virtual_image_type_for_name_or_code(image['imageType'])
163
153
  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}
154
+ render_response(json_response, options, 'virtualImage') do
155
+ print_h1 "Virtual Image Details", [], options
156
+ description_cols = {
157
+ "ID" => 'id',
158
+ "Name" => 'name',
159
+ "Type" => lambda {|it| image_type_display },
160
+ "Storage" => lambda {|it| !image['storageProvider'].nil? ? image['storageProvider']['name'] : 'Default' },
161
+ "Size" => lambda {|it| image['rawSize'].nil? ? 'Unknown' : "#{Filesize.from("#{image['rawSize']} B").pretty}" },
162
+ "Azure Publisher" => lambda {|it| image_config['publisher'] },
163
+ "Azure Offer" => lambda {|it| image_config['offer'] },
164
+ "Azure Sku" => lambda {|it| image_config['sku'] },
165
+ "Azure Version" => lambda {|it| image_config['version'] },
166
+ "Source" => lambda {|it| image['userUploaded'] ? "#{green}UPLOADED#{cyan}" : (image['systemImage'] ? 'SYSTEM' : "#{white}SYNCED#{cyan}") },
167
+ # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
168
+ # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
169
+ }
170
+ if image['imageType'] == "azure-reference" || image['imageType'] == "azure"
171
+ description_cols.delete("Size")
172
+ description_cols.delete("Storage")
173
+ description_cols["Source"] = lambda {|it| "#{bold}#{cyan}AZURE#{reset}#{cyan}" }
174
+ else
175
+ description_cols.delete("Azure Marketplace")
176
+ description_cols.delete("Azure Marketplace Publisher")
177
+ description_cols.delete("Azure Marketplace Sku")
178
+ description_cols.delete("Azure Marketplace Offer")
179
+ description_cols.delete("Azure Marketplace Version")
207
180
  end
208
- print cyan
209
- print as_pretty_table(image_file_rows, [:filename, :size])
210
- # print reset,"\n"
181
+ advanced_description_cols = {
182
+ "OS Type" => lambda {|it| it['osType'] ? it['osType']['name'] : "" },
183
+ "Min Memory" => lambda {|it| it['minRam'].to_i != 0 ? Filesize.from("#{it['minRam']} B").pretty : "" },
184
+ "Cloud Init?" => lambda {|it| format_boolean it['osType'] },
185
+ "Install Agent?" => lambda {|it| format_boolean it['osType'] },
186
+ "SSH Username" => lambda {|it| it['sshUsername'] },
187
+ "SSH Password" => lambda {|it| it['sshPassword'] },
188
+ "User Data" => lambda {|it| it['userData'] },
189
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
190
+ "Tenants" => lambda {|it| format_tenants(it['accounts']) },
191
+ "Auto Join Domain?" => lambda {|it| format_boolean it['isAutoJoinDomain'] },
192
+ "VirtIO Drivers Loaded?" => lambda {|it| format_boolean it['virtioSupported'] },
193
+ "VM Tools Installed?" => lambda {|it| format_boolean it['vmToolsInstalled'] },
194
+ "Force Guest Customization?" => lambda {|it| format_boolean it['isForceCustomization'] },
195
+ "Trial Version" => lambda {|it| format_boolean it['trialVersion'] },
196
+ "Sysprep Enabled?" => lambda {|it| format_boolean it['isSysprep'] },
197
+ }
198
+ if options[:details]
199
+ description_cols.merge!(advanced_description_cols)
200
+ end
201
+ print_description_list(description_cols, image)
202
+
203
+ if image_files
204
+ print_h2 "Files (#{image_files.size})"
205
+ # image_files.each {|image_file|
206
+ # pretty_filesize = Filesize.from("#{image_file['size']} B").pretty
207
+ # print cyan," = #{image_file['name']} [#{pretty_filesize}]", "\n"
208
+ # }
209
+ image_file_rows = image_files.collect do |image_file|
210
+ {filename: image_file['name'], size: Filesize.from("#{image_file['size']} B").pretty}
211
+ end
212
+ print cyan
213
+ print as_pretty_table(image_file_rows, [:filename, :size])
214
+ # print reset,"\n"
215
+ end
216
+
217
+ if options[:details] && image_config && !image_config.empty?
218
+ print_h2 "Config", options
219
+ print cyan
220
+ print as_description_list(image_config, image_config.keys, options)
221
+ print "\n", reset
222
+ end
223
+ print reset,"\n"
211
224
  end
212
- print reset,"\n"
213
- rescue RestClient::Exception => e
214
- print_rest_exception(e, options)
215
- exit 1
216
- end
225
+ return 0, nil
217
226
  end
218
227
 
219
228
  def update(args)
@@ -339,6 +348,19 @@ class Morpheus::Cli::VirtualImages
339
348
  opts.on( '-U', '--url URL', "Image File URL. This can be used instead of uploading local files." ) do |val|
340
349
  file_url = val
341
350
  end
351
+ opts.on( '-c', '--cloud CLOUD', "Cloud to scope image to, certain types require a cloud to be selected, eg. Azure Reference" ) do |val|
352
+ # options[:cloud] = val
353
+ options[:options]['cloud'] = val
354
+ end
355
+ opts.on( '--azure-offer OFFER', String, "Azure Reference offer value, only applies to Azure Reference" ) do |val|
356
+ options[:options]['offer'] = val
357
+ end
358
+ opts.on( '--azure-sku SKU', String, "Azure SKU value, only applies to Azure Reference" ) do |val|
359
+ options[:options]['sku'] = val
360
+ end
361
+ opts.on( '--azure-version VERSION', String, "Azure Version value, only applies to Azure Reference" ) do |val|
362
+ options[:options]['version'] = val
363
+ end
342
364
  opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
343
365
  if list.size == 1 && list[0] == 'null' # hacky way to clear it
344
366
  tenants_list = []
@@ -346,7 +368,10 @@ class Morpheus::Cli::VirtualImages
346
368
  tenants_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
347
369
  end
348
370
  end
349
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
371
+ # build_option_type_options(opts, options, add_virtual_image_option_types)
372
+ # build_option_type_options(opts, options, add_virtual_image_advanced_option_types)
373
+ build_standard_add_options(opts, options)
374
+
350
375
  opts.footer = "Create a virtual image."
351
376
  end
352
377
  optparse.parse!(args)
@@ -368,26 +393,43 @@ class Morpheus::Cli::VirtualImages
368
393
  options[:options]['name'] ||= image_name
369
394
  end
370
395
 
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')
396
+ payload = {}
397
+ if options[:payload]
398
+ payload = options[:payload]
399
+ payload.deep_merge!({'virtualImage' => parse_passed_options(options)})
400
+ else
401
+ payload.deep_merge!({'virtualImage' => parse_passed_options(options)})
402
+ virtual_image_payload = {}
403
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt(add_virtual_image_option_types, options[:options], @api_client, options[:params])
404
+ if image_type_name
405
+ image_type = virtual_image_type_for_name_or_code(image_type_name)
406
+ # fix issue with api returning imageType vmware instead of vmdk
407
+ if image_type.nil? && image_type_name == 'vmware'
408
+ image_type = virtual_image_type_for_name_or_code('vmdk')
409
+ elsif image_type.nil? && image_type_name == 'vmdk'
410
+ image_type = virtual_image_type_for_name_or_code('vmware')
411
+ end
412
+ if image_type.nil?
413
+ print_red_alert "Virtual Image Type not found by code '#{image_type_name}'"
414
+ return 1
415
+ end
416
+ # options[:options] ||= {}
417
+ # options[:options]['imageType'] ||= image_type['code']
418
+ else
419
+ 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,{})
420
+ image_type = virtual_image_type_for_name_or_code(image_type_prompt['imageType'])
378
421
  end
379
- if image_type.nil?
380
- print_red_alert "Virtual Image Type not found by code '#{image_type_name}'"
381
- return 1
422
+
423
+ # azure requires us to search the marketplace to select publisher, cloud, offerm sku
424
+ if image_type['code'] == "azure-reference" || image_type['code'] == "azure"
425
+ 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'})
426
+ cloud_id = cloud_prompt['cloud'].to_i
427
+
428
+ marketplace_config = prompt_azure_marketplace(cloud_id, options)
429
+ virtual_image_payload['config'] ||= {}
430
+ virtual_image_payload['config'].deep_merge!(marketplace_config)
382
431
  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
432
 
390
- begin
391
433
  my_option_types = add_virtual_image_option_types(image_type, !file_url)
392
434
  # if options[:no_prompt]
393
435
  # my_option_types.each do |it|
@@ -396,9 +438,9 @@ class Morpheus::Cli::VirtualImages
396
438
  # end
397
439
  # end
398
440
  # 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)
441
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, options[:params])
442
+ v_prompt.deep_compact!
443
+ virtual_image_payload.deep_merge!(v_prompt)
402
444
  virtual_image_files = virtual_image_payload.delete('virtualImageFiles')
403
445
  virtual_image_payload['imageType'] = image_type['code']
404
446
  storage_provider_id = virtual_image_payload.delete('storageProviderId')
@@ -412,61 +454,62 @@ class Morpheus::Cli::VirtualImages
412
454
  if virtual_image_payload && virtual_image_payload['imageType'] == 'vmware'
413
455
  virtual_image_payload['imageType'] == 'vmdk'
414
456
  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
457
+ #payload = {'virtualImage' => virtual_image_payload}
458
+ payload.deep_merge!({'virtualImage' => virtual_image_payload})
459
+ end
437
460
 
438
- # now upload the file, do this in the background maybe?
461
+ @virtual_images_interface.setopts(options)
462
+ if options[:dry_run]
463
+ print_dry_run @virtual_images_interface.dry.create(payload)
439
464
  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
465
+ print_dry_run @virtual_images_interface.dry.upload_by_url(":id", file_url, file_name)
447
466
  elsif virtual_image_files && !virtual_image_files.empty?
448
467
  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
468
+ print_dry_run @virtual_images_interface.dry.upload(":id", "(Contents of file #{filepath})")
457
469
  end
458
- else
459
- puts cyan, "No files uploaded.", reset
460
470
  end
471
+ return
472
+ end
461
473
 
462
- if !options[:json]
463
- get([virtual_image['id']])
474
+ json_response = @virtual_images_interface.create(payload)
475
+ virtual_image = json_response['virtualImage']
476
+
477
+ # if options[:json]
478
+ # print JSON.pretty_generate(json_response)
479
+ # elsif !options[:quiet]
480
+ # print "\n", cyan, "Virtual Image #{virtual_image['name']} created successfully", reset, "\n\n"
481
+ # end
482
+
483
+ # now upload the file, do this in the background maybe?
484
+ if file_url
485
+ unless options[:quiet]
486
+ print cyan, "Uploading file by url #{file_url} ...", reset, "\n"
487
+ end
488
+ upload_json_response = @virtual_images_interface.upload_by_url(virtual_image['id'], file_url, file_name)
489
+ # if options[:json]
490
+ # print JSON.pretty_generate(upload_json_response)
491
+ # end
492
+ elsif virtual_image_files && !virtual_image_files.empty?
493
+ virtual_image_files.each do |key, filepath|
494
+ unless options[:quiet]
495
+ print cyan, "Uploading file (#{key}) #{filepath} ...", reset, "\n"
496
+ end
497
+ image_file = File.new(filepath, 'rb')
498
+ upload_json_response = @virtual_images_interface.upload(virtual_image['id'], image_file, file_name)
499
+ # if options[:json]
500
+ # print JSON.pretty_generate(upload_json_response)
501
+ # end
464
502
  end
503
+ else
504
+ # puts cyan, "No files uploaded.", reset
505
+ end
465
506
 
466
- rescue RestClient::Exception => e
467
- print_rest_exception(e, options)
468
- exit 1
507
+ render_response(json_response, options, 'virtualImage') do
508
+ print_green_success "Added virtual image #{virtual_image['name']}"
509
+ return _get(virtual_image["id"], {}, options)
469
510
  end
511
+ return 0, nil
512
+
470
513
  end
471
514
 
472
515
  def add_file(args)
@@ -678,11 +721,11 @@ class Morpheus::Cli::VirtualImages
678
721
  #{'fieldName' => 'imageType', 'fieldLabel' => 'Image Type', 'type' => 'select', 'optionSource' => 'virtualImageTypes', 'required' => true, 'description' => 'Select Virtual Image Type.', 'displayOrder' => 2},
679
722
  {'fieldName' => 'osType', 'fieldLabel' => 'OS Type', 'type' => 'select', 'optionSource' => 'osTypes', 'required' => false, 'description' => 'Select OS Type.', 'displayOrder' => 3},
680
723
  {'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},
724
+ {'fieldName' => 'isCloudInit', 'fieldLabel' => 'Cloud Init Enabled?', 'type' => 'checkbox', 'required' => false, 'description' => 'Cloud Init Enabled?', 'displayOrder' => 5},
725
+ {'fieldName' => 'installAgent', 'fieldLabel' => 'Install Agent?', 'type' => 'checkbox', 'required' => false, 'description' => 'Install Agent?', 'displayOrder' => 6},
726
+ {'fieldName' => 'sshUsername', 'fieldLabel' => 'SSH Username', 'type' => 'text', 'required' => false, 'description' => 'Enter an SSH Username', 'displayOrder' => 7},
727
+ {'fieldName' => 'sshPassword', 'fieldLabel' => 'SSH Password', 'type' => 'password', 'required' => false, 'description' => 'Enter an SSH Password', 'displayOrder' => 8},
728
+ {'fieldName' => 'storageProviderId', 'type' => 'select', 'fieldLabel' => 'Storage Provider', 'optionSource' => 'storageProviders', 'required' => false, 'description' => 'Select Storage Provider.', 'displayOrder' => 9},
686
729
  {'fieldName' => 'userData', 'fieldLabel' => 'Cloud-Init User Data', 'type' => 'textarea', 'required' => false, 'displayOrder' => 10},
687
730
  {'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
731
  {'fieldName' => 'isAutoJoinDomain', 'fieldLabel' => 'Auto Join Domain?', 'type' => 'checkbox', 'required' => false, 'description' => 'Auto Join Domain?', 'category' => 'advanced', 'displayOrder' => 40},
@@ -696,22 +739,25 @@ class Morpheus::Cli::VirtualImages
696
739
  image_type_code = image_type ? image_type['code'] : nil
697
740
  if image_type_code
698
741
  if image_type_code == 'ami'
699
- tmp_option_types << {'fieldName' => 'externalId', 'fieldLabel' => 'AMI id', 'type' => 'text', 'required' => false, 'displayOrder' => 10}
742
+ tmp_option_types << {'fieldName' => 'externalId', 'fieldLabel' => 'AMI id', 'type' => 'text', 'required' => false, 'displayOrder' => 11}
700
743
  if include_file_selection
701
- tmp_option_types << {'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'displayOrder' => 10}
744
+ tmp_option_types << {'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'displayOrder' => 12}
702
745
  end
703
746
  elsif image_type_code == 'vmware' || image_type_code == 'vmdk'
704
747
  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}
748
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'OVF File', 'type' => 'file', 'required' => false, 'displayOrder' => 11}
749
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageDescriptorFile', 'fieldLabel' => 'VMDK File', 'type' => 'file', 'required' => false, 'displayOrder' => 12}
707
750
  end
708
751
  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}
752
+ tmp_option_types << {'fieldName' => 'config.menu', 'fieldLabel' => 'Menu', 'type' => 'text', 'required' => false, 'displayOrder' => 11}
753
+ tmp_option_types << {'fieldName' => 'imagePath', 'fieldLabel' => 'Image Path', 'type' => 'text', 'required' => true, 'displayOrder' => 12}
711
754
  tmp_option_types.reject! {|opt| ['isCloudInit', 'installAgent', 'sshUsername', 'sshPassword'].include?(opt['fieldName'])}
755
+ elsif image_type_code == 'azure' || image_type_code == 'azure-reference'
756
+ # Azure Marketplace Prompt happens elsewhere
757
+ tmp_option_types.reject! {|opt| ['storageProviderId', 'userData', 'sshUsername', 'sshPassword'].include?(opt['fieldName'])}
712
758
  else
713
759
  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}
760
+ tmp_option_types << {'fieldContext' => 'virtualImageFiles', 'fieldName' => 'imageFile', 'fieldLabel' => 'Image File', 'type' => 'file', 'required' => false, 'description' => 'Choose an image file to upload', 'displayOrder' => 11}
715
761
  end
716
762
  end
717
763
  end
@@ -735,5 +781,40 @@ class Morpheus::Cli::VirtualImages
735
781
  ""
736
782
  end
737
783
  end
784
+
785
+ def prompt_azure_marketplace(cloud_id, options)
786
+ rtn = {}
787
+ publisher_value, offer_value, sku_value, version_value = nil, nil, nil, nil
788
+
789
+ # Marketplace Publisher & Offer
790
+ marketplace_api_params = {'zoneId' => cloud_id}
791
+ 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)
792
+ # offer_value = v_prompt['marketplace']
793
+ # actually need both offer and publisher of these to query correctly..sigh
794
+ marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
795
+ offer_value = marketplace_option['offer']
796
+ publisher_value = marketplace_option['publisher']
797
+
798
+ # SKU & VERSION
799
+ if options[:options] && options[:options]['sku'] && options[:options]['version']
800
+ # the value to match on is actually sku|version
801
+ options[:options]['sku'] = options[:options]['sku'] + '|' + options[:options]['version']
802
+ end
803
+ sku_api_params = {'zoneId' => cloud_id, publisher: publisher_value, offer: offer_value}
804
+ 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)
805
+ # marketplace_option = Morpheus::Cli::OptionTypes.get_last_select()
806
+ # sku_value = marketplace_option['sku']
807
+ # version_value = marketplace_option['version']
808
+ sku_value = v_prompt['sku']
809
+ if sku_value && sku_value.include?("|")
810
+ sku_value, version_value = sku_value.split("|")
811
+ end
812
+
813
+ rtn['publisher'] = publisher_value
814
+ rtn['offer'] = offer_value
815
+ rtn['sku'] = sku_value
816
+ rtn['version'] = version_value
817
+ return rtn
818
+ end
738
819
 
739
820
  end