morpheus-cli 5.0.0 → 5.0.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/billing_interface.rb +1 -0
  5. data/lib/morpheus/api/deploy_interface.rb +1 -1
  6. data/lib/morpheus/api/deployments_interface.rb +20 -1
  7. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  8. data/lib/morpheus/api/instances_interface.rb +7 -0
  9. data/lib/morpheus/api/search_interface.rb +13 -0
  10. data/lib/morpheus/api/servers_interface.rb +7 -0
  11. data/lib/morpheus/api/usage_interface.rb +18 -0
  12. data/lib/morpheus/cli.rb +4 -1
  13. data/lib/morpheus/cli/cli_command.rb +26 -9
  14. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  15. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  16. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  17. data/lib/morpheus/cli/deploy.rb +199 -90
  18. data/lib/morpheus/cli/deployments.rb +341 -28
  19. data/lib/morpheus/cli/deploys.rb +206 -41
  20. data/lib/morpheus/cli/error_handler.rb +7 -0
  21. data/lib/morpheus/cli/forgot_password.rb +133 -0
  22. data/lib/morpheus/cli/health_command.rb +2 -2
  23. data/lib/morpheus/cli/hosts.rb +169 -32
  24. data/lib/morpheus/cli/instances.rb +83 -32
  25. data/lib/morpheus/cli/invoices_command.rb +33 -16
  26. data/lib/morpheus/cli/logs_command.rb +9 -6
  27. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
  28. data/lib/morpheus/cli/mixins/print_helper.rb +0 -21
  29. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  30. data/lib/morpheus/cli/option_types.rb +266 -17
  31. data/lib/morpheus/cli/remote.rb +35 -10
  32. data/lib/morpheus/cli/reports_command.rb +99 -30
  33. data/lib/morpheus/cli/search_command.rb +182 -0
  34. data/lib/morpheus/cli/setup.rb +1 -1
  35. data/lib/morpheus/cli/shell.rb +33 -11
  36. data/lib/morpheus/cli/tasks.rb +20 -21
  37. data/lib/morpheus/cli/usage_command.rb +64 -11
  38. data/lib/morpheus/cli/version.rb +1 -1
  39. data/lib/morpheus/cli/virtual_images.rb +280 -199
  40. data/lib/morpheus/cli/whoami.rb +6 -6
  41. data/lib/morpheus/cli/workflows.rb +33 -40
  42. data/lib/morpheus/formatters.rb +22 -0
  43. data/lib/morpheus/terminal.rb +6 -2
  44. metadata +7 -2
@@ -9,7 +9,7 @@ class Morpheus::Cli::Setup
9
9
 
10
10
  set_command_name :setup
11
11
 
12
- register_subcommands :init
12
+ # register_subcommands :init
13
13
 
14
14
  # no authorization needed
15
15
  def connect(options={})
@@ -92,6 +92,13 @@ class Morpheus::Cli::Shell
92
92
  def recalculate_auto_complete_commands
93
93
  @morpheus_commands = Morpheus::Cli::CliRegistry.all.keys.reject {|k| [:shell].include?(k) }
94
94
  @shell_commands = [:clear, :history, :reload, :help, :exit]
95
+ @shell_command_descriptions = {
96
+ :clear => "Clear terminal output and move cursor to the top",
97
+ :history => "View morpheus shell command history",
98
+ :reload => "Reload the shell, can be useful when developing",
99
+ :help => "Print this help",
100
+ :exit => "Exit the morpheus shell"
101
+ }
95
102
  @alias_commands = Morpheus::Cli::CliRegistry.all_aliases.keys
96
103
  @exploded_commands = []
97
104
  Morpheus::Cli::CliRegistry.all.each do |cmd, klass|
@@ -336,6 +343,11 @@ class Morpheus::Cli::Shell
336
343
  #Morpheus::Logging::DarkPrinter.puts "Shell command: #{input}"
337
344
  input = input.to_s.strip
338
345
 
346
+ # allow pasting in commands that have 'morpheus ' prefix
347
+ if input[0..(prog_name.size)] == "#{prog_name} "
348
+ input = input[(prog_name.size + 1)..-1] || ""
349
+ end
350
+
339
351
  if !input.empty?
340
352
 
341
353
  if input == 'exit'
@@ -345,26 +357,36 @@ class Morpheus::Cli::Shell
345
357
  return 0
346
358
  #exit 0
347
359
  elsif input == 'help'
360
+ out = ""
348
361
  if @temporary_shell_mode
349
- puts "You are in a (temporary) morpheus shell"
362
+ out << "You are in a (temporary) morpheus shell\n"
350
363
  else
351
- puts "You are in a morpheus shell."
364
+ out << "You are in a morpheus shell.\n"
352
365
  end
353
- puts "See the available commands below."
366
+ out << "See the available commands below.\n"
354
367
 
355
- puts "\nCommands:"
368
+ out << "\nCommands:"
356
369
  # commands = @morpheus_commands + @shell_commands
357
- @morpheus_commands.sort.each {|cmd|
358
- puts "\t#{cmd.to_s}"
370
+ # @morpheus_commands.sort.each {|cmd|
371
+ # out << "\t#{cmd.to_s}\n"
372
+ # }
373
+ sorted_commands = Morpheus::Cli::CliRegistry.all.values.sort { |x,y| x.command_name.to_s <=> y.command_name.to_s }
374
+ sorted_commands.each {|cmd|
375
+ # JD: not ready to show description yet, gotta finish filling in every command first
376
+ # maybe change 'View and manage' to something more concise like 'Manage'
377
+ # out << "\t#{cmd.command_name.to_s.ljust(28, ' ')} #{cmd.command_description}\n"
378
+ out << "\t#{cmd.command_name.to_s}\n"
359
379
  }
360
380
  #puts "\n"
361
- puts "\nShell Commands:"
381
+ out << "\nShell Commands:\n"
362
382
  @shell_commands.each {|cmd|
363
- puts "\t#{cmd.to_s}"
383
+ # out << "\t#{cmd.to_s.ljust(28, ' ')} #{@shell_command_descriptions ? @shell_command_descriptions[cmd] : ''}\n"
384
+ out << "\t#{cmd.to_s}\n"
364
385
  }
365
- puts "\n"
366
- puts "For more information, see https://github.com/gomorpheus/morpheus-cli/wiki"
367
- #print "\n"
386
+ out << "\n"
387
+ out << "For more information, see https://github.com/gomorpheus/morpheus-cli/wiki"
388
+ out << "\n"
389
+ print out
368
390
  return 0
369
391
  elsif input =~ /^\s*#/
370
392
  Morpheus::Logging::DarkPrinter.puts "ignored comment: #{input}" if Morpheus::Logging.debug?
@@ -27,34 +27,31 @@ class Morpheus::Cli::Tasks
27
27
  params = {}
28
28
  options = {}
29
29
  optparse = Morpheus::Cli::OptionParser.new do |opts|
30
- opts.banner = subcommand_usage()
30
+ opts.banner = subcommand_usage("[search]")
31
31
  opts.on('-t', '--type x,y,z', Array, "Filter by task type code(s)") do |val|
32
32
  params['taskTypeCodes'] = val
33
33
  end
34
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
34
+ build_standard_list_options(opts, options)
35
+ opts.footer = "List tasks."
35
36
  end
36
37
  optparse.parse!(args)
38
+ connect(options)
37
39
  if args.count > 0
38
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
40
+ options[:phrase] = args.join(" ")
39
41
  end
40
- connect(options)
41
- begin
42
- params.merge!(parse_list_options(options))
43
- @tasks_interface.setopts(options)
44
- if options[:dry_run]
45
- print_dry_run @tasks_interface.dry.list(params)
46
- return
47
- end
48
- json_response = @tasks_interface.list(params)
49
-
50
- render_result = render_with_format(json_response, options, 'tasks')
51
- return 0 if render_result
52
-
42
+ params.merge!(parse_list_options(options))
43
+ @tasks_interface.setopts(options)
44
+ if options[:dry_run]
45
+ print_dry_run @tasks_interface.dry.list(params)
46
+ return
47
+ end
48
+ json_response = @tasks_interface.list(params)
49
+ tasks = json_response['tasks']
50
+ render_response(json_response, options, 'tasks') do
53
51
  title = "Morpheus Tasks"
54
52
  subtitles = []
55
53
  subtitles += parse_list_subtitles(options)
56
54
  print_h1 title, subtitles
57
- tasks = json_response['tasks']
58
55
  if tasks.empty?
59
56
  print cyan,"No tasks found.",reset,"\n"
60
57
  else
@@ -63,11 +60,13 @@ class Morpheus::Cli::Tasks
63
60
  print_results_pagination(json_response)
64
61
  end
65
62
  print reset,"\n"
66
- return 0
67
- rescue RestClient::Exception => e
68
- print_rest_exception(e, options)
69
- exit 1
70
63
  end
64
+ if tasks.empty?
65
+ return 1, "no tasks found"
66
+ else
67
+ return 0, nil
68
+ end
69
+
71
70
  end
72
71
 
73
72
  def get(args)
@@ -2,18 +2,18 @@ require 'morpheus/cli/cli_command'
2
2
 
3
3
  # CLI command usages
4
4
  # UI is Costing - Usage
5
- # API is /billing and returns usages
5
+ # API is /usage and returns usages
6
6
  class Morpheus::Cli::UsageCommand
7
7
  include Morpheus::Cli::CliCommand
8
8
  include Morpheus::Cli::OptionSourceHelper
9
9
 
10
10
  set_command_name :'usage'
11
11
 
12
- register_subcommands :list #, :list_tenant, :list_clouds, :list_zones, :list_zones, :list_zones, :list_zones
12
+ register_subcommands :list, :get
13
13
 
14
14
  def connect(opts)
15
15
  @api_client = establish_remote_appliance_connection(opts)
16
- @billing_interface = @api_client.billing
16
+ @usage_interface = @api_client.usage
17
17
  end
18
18
 
19
19
  def handle(args)
@@ -33,10 +33,10 @@ class Morpheus::Cli::UsageCommand
33
33
  options[:cloud] = val
34
34
  end
35
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
36
+ params['startDate'] = val # parse_time(val).utc.iso8601
37
37
  end
38
- opts.on('--end DATE', String, "End date in the format YYYY-MM-DD. Default is now.") do |val|
39
- params['endDate'] = val #parse_time(val).utc.iso8601
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
40
  end
41
41
  opts.on('--sigdig DIGITS', "Significant digits when rounding cost values for display as currency. Default is 5.") do |val|
42
42
  options[:sigdig] = val.to_i
@@ -64,12 +64,12 @@ class Morpheus::Cli::UsageCommand
64
64
  }
65
65
  end
66
66
 
67
- @billing_interface.setopts(options)
67
+ @usage_interface.setopts(options)
68
68
  if options[:dry_run]
69
- print_dry_run @billing_interface.dry.list(params)
69
+ print_dry_run @usage_interface.dry.list(params)
70
70
  return
71
71
  end
72
- json_response = @billing_interface.list(params)
72
+ json_response = @usage_interface.list(params)
73
73
  usages = json_response[usage_list_key]
74
74
  render_response(json_response, options, usage_list_key) do
75
75
  print_h1 "Morpheus Usages", parse_list_subtitles(options), options
@@ -85,6 +85,7 @@ class Morpheus::Cli::UsageCommand
85
85
  "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
86
86
  "End Date" => lambda {|it| format_local_dt(it['endDate']) },
87
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)}) },
88
89
  "Usage Price" => lambda {|it| format_money(it['price'], it['currency'] || 'USD', {sigdig: (options[:sigdig] || 5)}) },
89
90
  }
90
91
  print as_pretty_table(usages, list_columns.upcase_keys!, options)
@@ -99,6 +100,58 @@ class Morpheus::Cli::UsageCommand
99
100
  end
100
101
  end
101
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
102
155
 
103
156
  private
104
157
 
@@ -141,9 +194,9 @@ class Morpheus::Cli::UsageCommand
141
194
  #return usage['status'].to_s.capitalize
142
195
  status_string = usage['status'].to_s
143
196
  if status_string == 'stopped'
144
- return "#{cyan}#{status_string.upcase}#{return_color}"
197
+ return "#{red}#{status_string.upcase}#{return_color}"
145
198
  else
146
- return "#{cyan}#{status_string.upcase}#{return_color}"
199
+ return "#{green}#{status_string.upcase}#{return_color}"
147
200
  end
148
201
  end
149
202
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "5.0.0"
4
+ VERSION = "5.0.1"
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