morpheus-cli 4.2.20 → 5.0.2

Sign up to get free protection for your applications and to get access to all the features.
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