morpheus-cli 5.5.2.1 → 5.5.3

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/Dockerfile +1 -1
  4. data/README.md +57 -4
  5. data/Rakefile +9 -0
  6. data/bin/morpheus +4 -4
  7. data/lib/morpheus/api/api_client.rb +8 -2
  8. data/lib/morpheus/api/archive_buckets_interface.rb +1 -1
  9. data/lib/morpheus/api/archive_files_interface.rb +3 -3
  10. data/lib/morpheus/api/clients_interface.rb +2 -2
  11. data/lib/morpheus/api/clusters_interface.rb +8 -1
  12. data/lib/morpheus/api/containers_interface.rb +29 -16
  13. data/lib/morpheus/api/custom_instance_types_interface.rb +0 -2
  14. data/lib/morpheus/api/doc_interface.rb +8 -6
  15. data/lib/morpheus/api/file_copy_request_interface.rb +1 -1
  16. data/lib/morpheus/api/health_interface.rb +1 -1
  17. data/lib/morpheus/api/image_builder_interface.rb +3 -3
  18. data/lib/morpheus/api/instances_interface.rb +25 -0
  19. data/lib/morpheus/api/logs_interface.rb +2 -4
  20. data/lib/morpheus/api/monitoring_interface.rb +6 -6
  21. data/lib/morpheus/api/packages_interface.rb +1 -1
  22. data/lib/morpheus/api/reports_interface.rb +1 -1
  23. data/lib/morpheus/api/servers_interface.rb +9 -1
  24. data/lib/morpheus/api/storage_providers_interface.rb +2 -2
  25. data/lib/morpheus/api/virtual_images_interface.rb +1 -1
  26. data/lib/morpheus/api.rb +2 -0
  27. data/lib/morpheus/benchmarking.rb +1 -1
  28. data/lib/morpheus/cli/cli_command.rb +69 -36
  29. data/lib/morpheus/cli/cli_registry.rb +19 -10
  30. data/lib/morpheus/cli/commands/access_token_command.rb +1 -1
  31. data/lib/morpheus/cli/commands/apps.rb +1 -1
  32. data/lib/morpheus/cli/commands/archives_command.rb +25 -33
  33. data/lib/morpheus/cli/commands/blueprints_command.rb +10 -21
  34. data/lib/morpheus/cli/commands/boot_scripts_command.rb +2 -2
  35. data/lib/morpheus/cli/commands/cat_command.rb +1 -1
  36. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +12 -12
  37. data/lib/morpheus/cli/commands/clouds.rb +3 -3
  38. data/lib/morpheus/cli/commands/clusters.rb +154 -3
  39. data/lib/morpheus/cli/commands/containers_command.rb +398 -253
  40. data/lib/morpheus/cli/commands/deployments.rb +1 -1
  41. data/lib/morpheus/cli/commands/deploys.rb +9 -9
  42. data/lib/morpheus/cli/commands/doc.rb +15 -16
  43. data/lib/morpheus/cli/commands/execution_request_command.rb +2 -2
  44. data/lib/morpheus/cli/commands/file_copy_request_command.rb +5 -5
  45. data/lib/morpheus/cli/commands/groups.rb +2 -2
  46. data/lib/morpheus/cli/commands/health_command.rb +4 -4
  47. data/lib/morpheus/cli/commands/hosts.rb +43 -5
  48. data/lib/morpheus/cli/commands/image_builder_command.rb +1 -1
  49. data/lib/morpheus/cli/commands/instances.rb +419 -148
  50. data/lib/morpheus/cli/commands/integrations_command.rb +22 -20
  51. data/lib/morpheus/cli/commands/key_pairs.rb +2 -2
  52. data/lib/morpheus/cli/commands/library_container_scripts_command.rb +2 -2
  53. data/lib/morpheus/cli/commands/library_container_templates_command.rb +2 -2
  54. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -3
  55. data/lib/morpheus/cli/commands/library_spec_templates_command.rb +2 -2
  56. data/lib/morpheus/cli/commands/login.rb +1 -1
  57. data/lib/morpheus/cli/commands/man_command.rb +32 -18
  58. data/lib/morpheus/cli/commands/packages_command.rb +11 -11
  59. data/lib/morpheus/cli/commands/plugins.rb +1 -1
  60. data/lib/morpheus/cli/commands/policies_command.rb +4 -4
  61. data/lib/morpheus/cli/commands/preseed_scripts_command.rb +2 -2
  62. data/lib/morpheus/cli/commands/remote.rb +1 -1
  63. data/lib/morpheus/cli/commands/reports_command.rb +3 -3
  64. data/lib/morpheus/cli/commands/roles.rb +6 -3
  65. data/lib/morpheus/cli/commands/security_groups.rb +1 -1
  66. data/lib/morpheus/cli/commands/shell.rb +40 -62
  67. data/lib/morpheus/cli/commands/snapshots.rb +3 -5
  68. data/lib/morpheus/cli/commands/source_command.rb +8 -16
  69. data/lib/morpheus/cli/commands/storage_providers_command.rb +7 -7
  70. data/lib/morpheus/cli/commands/tasks.rb +2 -2
  71. data/lib/morpheus/cli/commands/vdi_pools_command.rb +6 -6
  72. data/lib/morpheus/cli/commands/view.rb +5 -1
  73. data/lib/morpheus/cli/commands/whitelabel_settings_command.rb +4 -4
  74. data/lib/morpheus/cli/commands/whoami.rb +2 -2
  75. data/lib/morpheus/cli/credentials.rb +30 -8
  76. data/lib/morpheus/cli/dot_file.rb +8 -15
  77. data/lib/morpheus/cli/error_handler.rb +16 -0
  78. data/lib/morpheus/cli/errors.rb +8 -1
  79. data/lib/morpheus/cli/mixins/print_helper.rb +17 -13
  80. data/lib/morpheus/cli/mixins/rest_command.rb +18 -18
  81. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +12 -12
  82. data/lib/morpheus/cli/option_parser.rb +5 -1
  83. data/lib/morpheus/cli/option_types.rb +59 -12
  84. data/lib/morpheus/cli/version.rb +1 -1
  85. data/lib/morpheus/cli.rb +26 -16
  86. data/lib/morpheus/ext/rest_client.rb +3 -2
  87. data/lib/morpheus/formatters.rb +1 -1
  88. data/lib/morpheus/logging.rb +4 -4
  89. data/lib/morpheus/morpkg.rb +4 -4
  90. data/lib/morpheus/rest_client.rb +2 -2
  91. data/lib/morpheus/routes.rb +2 -2
  92. data/lib/morpheus/terminal.rb +65 -16
  93. data/lib/morpheus.rb +1 -1
  94. data/morpheus-cli.gemspec +1 -0
  95. data/test/api/containers_interface_test.rb +68 -0
  96. data/test/api/doc_interface_test.rb +35 -0
  97. data/test/api/instances_interface_test.rb +22 -0
  98. data/test/api/whoami_interface_test.rb +14 -0
  99. data/test/cli/access_token_test.rb +36 -0
  100. data/test/cli/auth_test.rb +82 -0
  101. data/test/cli/cli_test.rb +48 -0
  102. data/test/cli/containers_test.rb +92 -0
  103. data/test/cli/doc_test.rb +35 -0
  104. data/test/cli/help_test.rb +25 -0
  105. data/test/cli/instances_test.rb +36 -0
  106. data/test/cli/man_test.rb +14 -0
  107. data/test/cli/remote_test.rb +89 -0
  108. data/test/cli/roles_test.rb +34 -0
  109. data/test/cli/shell_test.rb +81 -0
  110. data/test/cli/version_test.rb +23 -0
  111. data/test/cli/view_test.rb +55 -0
  112. data/test/cli/whoami_test.rb +17 -0
  113. data/test/morpheus_test.rb +16 -0
  114. data/test/test_case.rb +338 -0
  115. data/test/test_config.rb +137 -0
  116. data/test/test_data_helper.rb +97 -0
  117. metadata +61 -3
@@ -6,13 +6,15 @@ class Morpheus::Cli::ContainersCommand
6
6
  include Morpheus::Cli::LogsHelper
7
7
 
8
8
  set_command_name :containers
9
-
10
- register_subcommands :get, :stop, :start, :restart, :suspend, :eject, :action, :actions, :logs
11
- register_subcommands :exec => :execution_request
9
+ set_command_description "View and manage containers (nodes)."
10
+ register_subcommands :get, :stop, :start, :restart, :suspend, :eject, :action, :actions, :logs,
11
+ {:exec => :execution_request}, :clone_image, :import
12
12
 
13
13
  def connect(opts)
14
14
  @api_client = establish_remote_appliance_connection(opts)
15
15
  @containers_interface = @api_client.containers
16
+ @instances_interface = @api_client.instances
17
+ @provision_types_interface = @api_client.provision_types
16
18
  @logs_interface = @api_client.logs
17
19
  @execution_request_interface = @api_client.execution_request
18
20
  end
@@ -40,44 +42,40 @@ class Morpheus::Cli::ContainersCommand
40
42
  opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
41
43
  options[:refresh_until_status] = val.to_s.downcase
42
44
  end
43
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
45
+ build_standard_get_options(opts, options)
46
+ opts.footer = <<-EOT
47
+ Get details about a container.
48
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
49
+ If more than one [id] is given, the command will execute for each one sequentially.
50
+
51
+ Examples:
52
+ containers get 42
53
+ containers get 1 2 3
54
+ containers get 42 --refresh
55
+ containers get 42 --refresh 10 --refresh-until stopped
56
+ EOT
44
57
  end
45
58
  optparse.parse!(args)
46
- if args.count < 1
47
- puts_error "[id] argument is required"
48
- puts_error optparse
49
- return 1
50
- end
59
+ verify_args!(args:args, optparse:optparse, min:1)
51
60
  connect(options)
52
61
  id_list = parse_id_list(args)
62
+ validate_container_ids!(id_list)
53
63
  return run_command_for_each_arg(id_list) do |arg|
54
64
  _get(arg, options)
55
65
  end
56
66
  end
57
67
 
58
68
  def _get(arg, options)
59
- begin
60
- @containers_interface.setopts(options)
61
- if options[:dry_run]
62
- print_dry_run @containers_interface.dry.get(arg.to_i)
63
- return
64
- end
65
- #container = find_container_by_id(arg)
66
- #return 1 if container.nil?
67
- json_response = @containers_interface.get(arg.to_i)
68
- if options[:json]
69
- puts as_json(json_response, options, "container")
70
- return 0
71
- elsif options[:yaml]
72
- puts as_yaml(json_response, options, "container")
73
- return 0
74
- end
75
-
76
- if options[:csv]
77
- puts records_as_csv([json_response['container']], options)
78
- return 0
79
- end
80
- container = json_response['container']
69
+ @containers_interface.setopts(options)
70
+ if options[:dry_run]
71
+ print_dry_run @containers_interface.dry.get(arg.to_i)
72
+ return
73
+ end
74
+ #container = find_container_by_id(arg)
75
+ #return 1 if container.nil?
76
+ json_response = @containers_interface.get(arg.to_i)
77
+ container = json_response['container']
78
+ render_response(json_response, options, "container") do
81
79
  # stats = json_response['stats'] || {}
82
80
  stats = container['stats'] || {}
83
81
 
@@ -139,49 +137,47 @@ class Morpheus::Cli::ContainersCommand
139
137
  }
140
138
  print_description_list(cost_columns, container)
141
139
  end
142
-
143
140
  print reset, "\n"
144
-
145
- # refresh until a status is reached
146
- if options[:refresh_until_status]
147
- if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
148
- options[:refresh_interval] = default_refresh_interval
149
- end
150
- statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
151
- if !statuses.include?(container['status'])
152
- print cyan
153
- print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
154
- sleep_with_dots(options[:refresh_interval])
155
- print "\n"
156
- _get(arg, options)
157
- end
141
+ end
142
+ # refresh until a status is reached
143
+ if options[:refresh_until_status]
144
+ if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
145
+ options[:refresh_interval] = default_refresh_interval
146
+ end
147
+ statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
148
+ if !statuses.include?(container['status'])
149
+ print cyan
150
+ print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
151
+ sleep_with_dots(options[:refresh_interval])
152
+ print "\n"
153
+ _get(arg, options)
158
154
  end
159
-
160
- return 0
161
- rescue RestClient::Exception => e
162
- print_rest_exception(e, options)
163
- return 1 # , e
164
155
  end
156
+ return 0, nil
165
157
  end
166
158
 
167
159
 
168
160
  def stop(args)
169
161
  options = {}
170
162
  optparse = Morpheus::Cli::OptionParser.new do |opts|
171
- opts.banner = subcommand_usage("[id list]")
172
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
163
+ opts.banner = subcommand_usage("[id]")
164
+ build_standard_update_options(opts, options, [:auto_confirm])
165
+ opts.footer = <<-EOT
166
+ Stop a container.
167
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
168
+ If more than one [id] is given, the command will execute for each one sequentially.
169
+
170
+ Examples:
171
+ containers stop 42
172
+ containers stop 1 2 3 -y
173
+ EOT
173
174
  end
174
175
  optparse.parse!(args)
175
- if args.count < 1
176
- puts_error "[id] argument is required"
177
- puts_error optparse
178
- return 1
179
- end
176
+ verify_args!(args:args, optparse:optparse, min:1)
180
177
  connect(options)
181
178
  id_list = parse_id_list(args)
182
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to stop #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
183
- return 9, "aborted command"
184
- end
179
+ validate_container_ids!(id_list)
180
+ confirm!("Are you sure you would like to stop #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
185
181
  return run_command_for_each_arg(id_list) do |arg|
186
182
  _stop(arg, options)
187
183
  end
@@ -196,32 +192,33 @@ class Morpheus::Cli::ContainersCommand
196
192
  return 0
197
193
  end
198
194
  json_response = @containers_interface.stop(container['id'])
199
- # just assume json_response["success"] == true, it always is with 200 OK
200
- if options[:json]
201
- puts as_json(json_response, options)
202
- elsif !options[:quiet]
195
+ render_response(json_response, options) do
203
196
  print green, "Stopping container #{container['id']}", reset, "\n"
204
197
  end
205
- return 0
198
+ return 0, nil
206
199
  end
207
200
 
208
201
  def start(args)
209
202
  options = {}
210
203
  optparse = Morpheus::Cli::OptionParser.new do |opts|
211
- opts.banner = subcommand_usage("[id list]")
212
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
204
+ opts.banner = subcommand_usage("[id]")
205
+ build_standard_update_options(opts, options, [:auto_confirm])
206
+ opts.footer = <<-EOT
207
+ Start a container.
208
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
209
+ If more than one [id] is given, the command will execute for each one sequentially.
210
+
211
+ Examples:
212
+ containers start 42
213
+ containers start 1 2 3 -y
214
+ EOT
213
215
  end
214
216
  optparse.parse!(args)
215
- if args.count < 1
216
- puts_error "[id] argument is required"
217
- puts_error optparse
218
- return 1
219
- end
217
+ verify_args!(args:args, optparse:optparse, min:1)
220
218
  connect(options)
221
219
  id_list = parse_id_list(args)
222
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to start #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
223
- return 9, "aborted command"
224
- end
220
+ validate_container_ids!(id_list)
221
+ confirm!("Are you sure you would like to start #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
225
222
  return run_command_for_each_arg(id_list) do |arg|
226
223
  _start(arg, options)
227
224
  end
@@ -236,32 +233,33 @@ class Morpheus::Cli::ContainersCommand
236
233
  return 0
237
234
  end
238
235
  json_response = @containers_interface.start(container['id'])
239
- # just assume json_response["success"] == true, it always is with 200 OK
240
- if options[:json]
241
- puts as_json(json_response, options)
242
- elsif !options[:quiet]
236
+ render_response(json_response, options) do
243
237
  print green, "Starting container #{container['id']}", reset, "\n"
244
238
  end
245
- return 0
239
+ return 0, nil
246
240
  end
247
241
 
248
242
  def restart(args)
249
243
  options = {}
250
244
  optparse = Morpheus::Cli::OptionParser.new do |opts|
251
- opts.banner = subcommand_usage("[id list]")
252
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
245
+ opts.banner = subcommand_usage("[id]")
246
+ build_standard_update_options(opts, options, [:auto_confirm])
247
+ opts.footer = <<-EOT
248
+ Restart a container.
249
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
250
+ If more than one [id] is given, the command will execute for each one sequentially.
251
+
252
+ Examples:
253
+ containers restart 42
254
+ containers restart 1 2 3 -y
255
+ EOT
253
256
  end
254
257
  optparse.parse!(args)
255
- if args.count < 1
256
- puts_error "[id] argument is required"
257
- puts_error optparse
258
- return 1
259
- end
258
+ verify_args!(args:args, optparse:optparse, min:1)
260
259
  connect(options)
261
260
  id_list = parse_id_list(args)
262
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to restart #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
263
- return 9, "aborted command"
264
- end
261
+ validate_container_ids!(id_list)
262
+ confirm!("Are you sure you would like to restart #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
265
263
  return run_command_for_each_arg(id_list) do |arg|
266
264
  _restart(arg, options)
267
265
  end
@@ -276,32 +274,33 @@ class Morpheus::Cli::ContainersCommand
276
274
  return 0
277
275
  end
278
276
  json_response = @containers_interface.restart(container['id'])
279
- # just assume json_response["success"] == true, it always is with 200 OK
280
- if options[:json]
281
- puts as_json(json_response, options)
282
- elsif !options[:quiet]
277
+ render_response(json_response, options) do
283
278
  print green, "Restarting container #{container['id']}", reset, "\n"
284
279
  end
285
- return 0
280
+ return 0, nil
286
281
  end
287
282
 
288
283
  def suspend(args)
289
284
  options = {}
290
285
  optparse = Morpheus::Cli::OptionParser.new do |opts|
291
- opts.banner = subcommand_usage("[id list]")
292
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
286
+ opts.banner = subcommand_usage("[id]")
287
+ build_standard_update_options(opts, options, [:auto_confirm])
288
+ opts.footer = <<-EOT
289
+ Suspend a container.
290
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
291
+ If more than one [id] is given, the command will execute for each one sequentially.
292
+
293
+ Examples:
294
+ containers suspend 42
295
+ containers suspend 1 2 3 -y
296
+ EOT
293
297
  end
294
298
  optparse.parse!(args)
295
- if args.count < 1
296
- puts_error "[id] argument is required"
297
- puts_error optparse
298
- return 1
299
- end
299
+ verify_args!(args:args, optparse:optparse, min:1)
300
300
  connect(options)
301
301
  id_list = parse_id_list(args)
302
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to suspend #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
303
- return 9, "aborted command"
304
- end
302
+ validate_container_ids!(id_list)
303
+ confirm!("Are you sure you would like to suspend #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
305
304
  return run_command_for_each_arg(id_list) do |arg|
306
305
  _suspend(arg, options)
307
306
  end
@@ -316,32 +315,33 @@ class Morpheus::Cli::ContainersCommand
316
315
  return 0
317
316
  end
318
317
  json_response = @containers_interface.suspend(container['id'])
319
- # just assume json_response["success"] == true, it always is with 200 OK
320
- if options[:json]
321
- puts as_json(json_response, options)
322
- elsif !options[:quiet]
318
+ render_response(json_response, options) do
323
319
  print green, "Suspending container #{container['id']}", reset, "\n"
324
320
  end
325
- return 0
321
+ return 0, nil
326
322
  end
327
323
 
328
324
  def eject(args)
329
325
  options = {}
330
326
  optparse = Morpheus::Cli::OptionParser.new do |opts|
331
- opts.banner = subcommand_usage("[id list]")
332
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
327
+ opts.banner = subcommand_usage("[id]")
328
+ build_standard_update_options(opts, options, [:auto_confirm])
329
+ opts.footer = <<-EOT
330
+ Eject a container.
331
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
332
+ If more than one [id] is given, the command will execute for each one sequentially.
333
+
334
+ Examples:
335
+ containers eject 42
336
+ containers eject 1 2 3 -y
337
+ EOT
333
338
  end
334
339
  optparse.parse!(args)
335
- if args.count < 1
336
- puts_error "[id] argument is required"
337
- puts_error optparse
338
- return 1
339
- end
340
+ verify_args!(args:args, optparse:optparse, min:1)
340
341
  connect(options)
341
342
  id_list = parse_id_list(args)
342
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to eject #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
343
- return 9, "aborted command"
344
- end
343
+ validate_container_ids!(id_list)
344
+ confirm!("Are you sure you would like to eject #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
345
345
  return run_command_for_each_arg(id_list) do |arg|
346
346
  _eject(arg, options)
347
347
  end
@@ -356,29 +356,31 @@ class Morpheus::Cli::ContainersCommand
356
356
  return 0
357
357
  end
358
358
  json_response = @containers_interface.eject(container['id'])
359
- # just assume json_response["success"] == true, it always is with 200 OK
360
- if options[:json]
361
- puts as_json(json_response, options)
362
- elsif !options[:quiet]
359
+ render_response(json_response, options) do
363
360
  print green, "Ejecting container #{container['id']}", reset, "\n"
364
361
  end
365
- return 0
362
+ return 0, nil
366
363
  end
367
364
 
368
365
  def actions(args)
369
366
  options = {}
370
367
  optparse = Morpheus::Cli::OptionParser.new do|opts|
371
- opts.banner = subcommand_usage("[id list]")
372
- opts.footer = "List the actions available to specified container(s)."
373
- build_common_options(opts, options, [:json, :dry_run, :remote])
368
+ opts.banner = subcommand_usage("[id]")
369
+ build_standard_get_options(opts, options)
370
+ opts.footer = <<-EOT
371
+ List the actions available to specified container(s).
372
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
373
+
374
+ Examples:
375
+ containers actions 42
376
+ containers actions 1 2 3
377
+ EOT
374
378
  end
375
379
  optparse.parse!(args)
376
- if args.count < 1
377
- puts optparse
378
- exit 1
379
- end
380
+ verify_args!(args:args, optparse:optparse, min:1)
380
381
  connect(options)
381
382
  id_list = parse_id_list(args)
383
+ validate_container_ids!(id_list)
382
384
  containers = []
383
385
  id_list.each do |container_id|
384
386
  container = find_container_by_id(container_id)
@@ -393,58 +395,53 @@ class Morpheus::Cli::ContainersCommand
393
395
  return 1
394
396
  end
395
397
  container_ids = containers.collect {|container| container["id"] }
396
- begin
397
- # container = find_container_by_name_or_id(args[0])
398
- @containers_interface.setopts(options)
399
- if options[:dry_run]
400
- print_dry_run @containers_interface.dry.available_actions(container_ids)
401
- return 0
402
- end
403
- json_response = @containers_interface.available_actions(container_ids)
404
- if options[:json]
405
- puts as_json(json_response, options)
398
+ # container = find_container_by_name_or_id(args[0])
399
+ @containers_interface.setopts(options)
400
+ if options[:dry_run]
401
+ print_dry_run @containers_interface.dry.available_actions(container_ids.size == 1 ? container_ids[0] : container_ids)
402
+ return 0
403
+ end
404
+ json_response = @containers_interface.available_actions(container_ids.size == 1 ? container_ids[0] : container_ids)
405
+ render_response(json_response, options) do
406
+ title = "Container Actions: #{anded_list(id_list)}"
407
+ print_h1 title
408
+ available_actions = json_response["actions"]
409
+ if (available_actions && available_actions.size > 0)
410
+ print as_pretty_table(available_actions, [:name, :code])
411
+ print reset, "\n"
406
412
  else
407
- title = "Container Actions: #{anded_list(id_list)}"
408
- print_h1 title
409
- available_actions = json_response["actions"]
410
- if (available_actions && available_actions.size > 0)
411
- print as_pretty_table(available_actions, [:name, :code])
412
- print reset, "\n"
413
+ if container_ids.size > 1
414
+ print "#{yellow}The specified containers have no available actions in common.#{reset}\n\n"
413
415
  else
414
- if container_ids.size > 1
415
- print "#{yellow}The specified containers have no available actions in common.#{reset}\n\n"
416
- else
417
- print "#{yellow}No available actions#{reset}\n\n"
418
- end
416
+ print "#{yellow}No available actions#{reset}\n\n"
419
417
  end
420
418
  end
421
- return 0
422
- rescue RestClient::Exception => e
423
- print_rest_exception(e, options)
424
- exit 1
425
419
  end
420
+ return 0, nil
426
421
  end
427
422
 
428
423
  def action(args)
429
424
  options = {}
430
425
  action_id = nil
431
426
  optparse = Morpheus::Cli::OptionParser.new do|opts|
432
- opts.banner = subcommand_usage("[id list] -a CODE")
427
+ opts.banner = subcommand_usage("[id] -a CODE")
433
428
  opts.on('-a', '--action CODE', "Container Action CODE to execute") do |val|
434
429
  action_id = val.to_s
435
430
  end
436
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
437
- opts.footer = "Execute an action for a container or containers"
431
+ build_standard_update_options(opts, options, [:auto_confirm])
432
+ opts.footer = <<-EOT
433
+ Execute an action for a container or containers
434
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
435
+
436
+ Examples:
437
+ containers action 42 -a docker-remove-node
438
+ EOT
438
439
  end
439
440
  optparse.parse!(args)
440
- if args.count < 1
441
- print_error Morpheus::Terminal.angry_prompt
442
- puts_error "[id list] argument is required"
443
- puts_error optparse
444
- return 1
445
- end
441
+ verify_args!(args:args, optparse:optparse, min:1)
446
442
  connect(options)
447
443
  id_list = parse_id_list(args)
444
+ validate_container_ids!(id_list)
448
445
  containers = []
449
446
  id_list.each do |container_id|
450
447
  container = find_container_by_id(container_id)
@@ -489,29 +486,21 @@ class Morpheus::Cli::ContainersCommand
489
486
  end
490
487
 
491
488
  action_display_name = "#{container_action['name']} [#{container_action['code']}]"
492
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to perform action #{action_display_name} on #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
493
- return 9, "aborted command"
494
- end
489
+ confirm!("Are you sure you would like to perform action #{action_display_name} on #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}?", options)
495
490
 
496
491
  # return run_command_for_each_arg(containers) do |arg|
497
492
  # _action(arg, action_id, options)
498
493
  # end
499
494
  @containers_interface.setopts(options)
500
495
  if options[:dry_run]
501
- print_dry_run @containers_interface.dry.action(container_ids, action_id)
496
+ print_dry_run @containers_interface.dry.action(container_ids.size == 1 ? container_ids[0] : container_ids, action_id)
502
497
  return 0
503
498
  end
504
- json_response = @containers_interface.action(container_ids, action_id)
505
- # just assume json_response["success"] == true, it always is with 200 OK
506
- if options[:json]
507
- puts as_json(json_response, options)
508
- elsif !options[:quiet]
509
- # containers.each do |container|
510
- # print green, "Action #{action_display_name} performed on container #{container['id']}", reset, "\n"
511
- # end
499
+ json_response = @containers_interface.action(container_ids.size == 1 ? container_ids[0] : container_ids, action_id)
500
+ render_response(json_response, options) do
512
501
  print green, "Action #{action_display_name} performed on #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}", reset, "\n"
513
502
  end
514
- return 0
503
+ return 0, nil
515
504
  end
516
505
 
517
506
  def logs(args)
@@ -534,35 +523,35 @@ class Morpheus::Cli::ContainersCommand
534
523
  opts.on('-a', '--all', "Display all details: entire message." ) do
535
524
  options[:details] = true
536
525
  end
537
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
538
- opts.footer = "List logs for a container.\n" +
539
- "[id] is required. This is the id of a container."
526
+ build_standard_list_options(opts, options)
527
+ opts.footer = <<-EOT
528
+ List logs for a container.
529
+ [id] is required. This is the id of a container. Supports multiple [id] arguments.
530
+
531
+ Examples:
532
+ containers logs 42 --level ERROR
533
+ EOT
540
534
  end
541
535
  optparse.parse!(args)
542
- if args.count < 1
543
- puts_error "[id] argument is required"
544
- puts_error optparse
545
- return 1
546
- end
536
+ verify_args!(args:args, optparse:optparse, min:1)
547
537
  connect(options)
548
538
  id_list = parse_id_list(args)
549
- begin
550
- containers = id_list # heh
551
- params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
552
- params.merge!(parse_list_options(options))
553
- params['query'] = params.delete('phrase') if params['phrase']
554
- params[:order] = params[:direction] unless params[:direction].nil? # old api version expects order instead of direction
555
- params['startMs'] = (options[:start].to_i * 1000) if options[:start]
556
- params['endMs'] = (options[:end].to_i * 1000) if options[:end]
557
- @logs_interface.setopts(options)
558
- if options[:dry_run]
559
- print_dry_run @logs_interface.dry.container_logs(containers, params)
560
- return
561
- end
562
- json_response = @logs_interface.container_logs(containers, params)
563
- render_result = json_response['logs'] ? render_with_format(json_response, options, 'logs') : render_with_format(json_response, options, 'data')
564
- return 0 if render_result
565
-
539
+ validate_container_ids!(id_list)
540
+
541
+ containers = id_list # heh
542
+ params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
543
+ params.merge!(parse_list_options(options))
544
+ params['query'] = params.delete('phrase') if params['phrase']
545
+ params[:order] = params[:direction] unless params[:direction].nil? # old api version expects order instead of direction
546
+ params['startMs'] = (options[:start].to_i * 1000) if options[:start]
547
+ params['endMs'] = (options[:end].to_i * 1000) if options[:end]
548
+ @logs_interface.setopts(options)
549
+ if options[:dry_run]
550
+ print_dry_run @logs_interface.dry.container_logs(containers, params)
551
+ return
552
+ end
553
+ json_response = @logs_interface.container_logs(containers, params)
554
+ render_response(json_response, options, "logs") do
566
555
  logs = json_response
567
556
  title = "Container Logs: #{containers.join(', ')}"
568
557
  subtitles = parse_list_subtitles(options)
@@ -587,11 +576,8 @@ class Morpheus::Cli::ContainersCommand
587
576
  print_results_pagination({'meta'=>{'total'=>(json_response['total']['value'] rescue json_response['total']),'size'=>logs.size,'max'=>(json_response['max'] || options[:max]),'offset'=>(json_response['offset'] || options[:offset] || 0)}})
588
577
  end
589
578
  print reset,"\n"
590
- return 0
591
- rescue RestClient::Exception => e
592
- print_rest_exception(e, options)
593
- exit 1
594
579
  end
580
+ return 0, nil
595
581
  end
596
582
 
597
583
  def execution_request(args)
@@ -606,7 +592,7 @@ class Morpheus::Cli::ContainersCommand
606
592
  end
607
593
  opts.on('--file FILE', "File containing the script. This can be used instead of --script" ) do |filename|
608
594
  full_filename = File.expand_path(filename)
609
- if File.exists?(full_filename)
595
+ if File.exist?(full_filename)
610
596
  script_content = File.read(full_filename)
611
597
  else
612
598
  print_red_alert "File not found: #{full_filename}"
@@ -617,51 +603,46 @@ class Morpheus::Cli::ContainersCommand
617
603
  do_refresh = false
618
604
  end
619
605
  #build_option_type_options(opts, options, add_user_source_option_types())
620
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
621
- opts.footer = "Execute an arbitrary command or script on a container." + "\n" +
622
- "[id] is required. This is the id a container." + "\n" +
623
- "[script] is required. This is the script that is to be executed."
606
+ build_standard_update_options(opts, options)
607
+ opts.footer = <<-EOT
608
+ Execute an arbitrary command or script on a container.
609
+ [id] is required. This is the id of a container.
610
+ [script] is required and can be passed as --script of --file instead. This is the script that is to be executed.
611
+
612
+ Examples:
613
+ containers exec 42 "uname -a"
614
+ EOT
624
615
  end
625
616
  optparse.parse!(args)
626
617
  connect(options)
627
- if args.count != 1
628
- print_error Morpheus::Terminal.angry_prompt
629
- puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
630
- return 1
631
- end
618
+ verify_args!(args:args, optparse:optparse, count:1)
632
619
 
633
620
 
634
- begin
635
- container = find_container_by_id(args[0])
636
- return 1 if container.nil?
637
- params['containerId'] = container['id']
638
- # construct payload
639
- payload = {}
640
- if options[:payload]
641
- payload = options[:payload]
642
- else
643
- payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
644
- # prompt for Script
645
- if script_content.nil?
646
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'script', 'type' => 'code-editor', 'fieldLabel' => 'Script', 'required' => true, 'description' => 'The script content'}], options[:options])
647
- script_content = v_prompt['script']
648
- end
649
- payload['script'] = script_content
650
- end
651
- @execution_request_interface.setopts(options)
652
- if options[:dry_run]
653
- print_dry_run @execution_request_interface.dry.create(params, payload)
654
- return 0
655
- end
656
- # do it
657
- json_response = @execution_request_interface.create(params, payload)
658
- # print and return result
659
- if options[:quiet]
660
- return 0
661
- elsif options[:json]
662
- puts as_json(json_response, options)
663
- return 0
621
+ container = find_container_by_id(args[0])
622
+ return 1 if container.nil?
623
+ params['containerId'] = container['id']
624
+ # construct payload
625
+ payload = {}
626
+ if options[:payload]
627
+ payload = options[:payload]
628
+ else
629
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
630
+ # prompt for Script
631
+ if script_content.nil?
632
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'script', 'type' => 'code-editor', 'fieldLabel' => 'Script', 'required' => true, 'description' => 'The script content'}], options[:options])
633
+ script_content = v_prompt['script']
664
634
  end
635
+ payload['script'] = script_content
636
+ end
637
+ @execution_request_interface.setopts(options)
638
+ if options[:dry_run]
639
+ print_dry_run @execution_request_interface.dry.create(params, payload)
640
+ return 0
641
+ end
642
+ # do it
643
+ json_response = @execution_request_interface.create(params, payload)
644
+ # print and return result
645
+ render_response(json_response, options) do
665
646
  execution_request = json_response['executionRequest']
666
647
  print_green_success "Executing request #{execution_request['uniqueId']}"
667
648
  if do_refresh
@@ -669,11 +650,127 @@ class Morpheus::Cli::ContainersCommand
669
650
  else
670
651
  Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_request['uniqueId']]+ (options[:remote] ? ["-r",options[:remote]] : []))
671
652
  end
672
- return 0
673
- rescue RestClient::Exception => e
674
- print_rest_exception(e, options)
675
- exit 1
676
653
  end
654
+ return 0, nil
655
+ end
656
+
657
+ def import(args)
658
+ options = {}
659
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
660
+ opts.banner = subcommand_usage("[id] [image]")
661
+ opts.on( '--storage-provider VALUE', String, "Optional storage provider to use" ) do |val|
662
+ options[:options]['storageProviderId'] = val
663
+ end
664
+ build_standard_update_options(opts, options, [:auto_confirm])
665
+ opts.footer = <<-EOT
666
+ Import image template for a container.
667
+ [id] is required. This is the id of a container.
668
+ EOT
669
+ end
670
+ optparse.parse!(args)
671
+ verify_args!(args:args, optparse:optparse, count:1)
672
+ connect(options)
673
+ container = find_container_by_id(args[0])
674
+ return 1 if container.nil?
675
+ instance = find_instance_by_name_or_id(container['instance']['id'])
676
+ return 1 if instance.nil?
677
+ # need to GET provision type for exportServer == true
678
+ provision_type = load_container_provision_type(container, instance)
679
+ # todo: add this exportServer to the api too obviously (oh it is there.. but clone-image is not)
680
+ # if provision_type['exportServer'] != true
681
+ # raise_command_error "import is not supported by provision type #{provision_type['name']}"
682
+ # end
683
+ payload = parse_payload(options)
684
+ if payload.nil?
685
+ payload = parse_passed_options(options)
686
+ container_import_option_types = [
687
+ {'fieldName' => 'storageProviderId', 'type' => 'select', 'fieldLabel' => 'Storage Provider', 'optionSource' => 'storageProviders', 'required' => false, 'description' => 'Select Storage Provider.'}
688
+ ]
689
+ payload.deep_merge! Morpheus::Cli::OptionTypes.prompt(container_import_option_types, options[:options], @api_client, {})
690
+ end
691
+ confirm!("Are you sure you would like to import container #{container['id']}?", options)
692
+ @containers_interface.setopts(options)
693
+ if options[:dry_run]
694
+ print_dry_run @containers_interface.dry.import(container['id'], payload)
695
+ return
696
+ end
697
+ json_response = @containers_interface.import(container['id'], payload)
698
+ render_response(json_response, options) do
699
+ print_green_success "Import initiated for container [#{container['id']}] #{container['name']}"
700
+ end
701
+ return 0, nil
702
+ end
703
+
704
+ def clone_image(args)
705
+ options = {}
706
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
707
+ opts.banner = subcommand_usage("[id]")
708
+ opts.on( '--name VALUE', String, "Image Name (Template Name). Default is server name + timestamp" ) do |val|
709
+ options[:options]['templateName'] = val
710
+ end
711
+ opts.on( '--folder VALUE', String, "Folder externalId or '/' to use the root folder" ) do |val|
712
+ options[:options]['zoneFolder'] = val
713
+ end
714
+ build_standard_update_options(opts, options, [:auto_confirm])
715
+ opts.footer = <<-EOT
716
+ Clone to image (template) for a container.
717
+ [id] is required. This is the id of a container.
718
+ EOT
719
+ end
720
+ optparse.parse!(args)
721
+ verify_args!(args:args, optparse:optparse, count:1)
722
+ connect(options)
723
+ container = find_container_by_id(args[0])
724
+ return 1 if container.nil?
725
+ # need to GET provision type for hasFolders == true and cloneTemplte == true
726
+ instance = find_instance_by_name_or_id(container['instance']['id'])
727
+ return 1 if instance.nil?
728
+ provision_type = load_container_provision_type(container, instance)
729
+ # todo: add this cloneTemplate check to the api too obviously
730
+ # if provision_type['cloneTemplate'] != true
731
+ # raise_command_error "clone-image is not supported by provision type #{provision_type['name']}"
732
+ # end
733
+ payload = parse_payload(options)
734
+ if payload.nil?
735
+ payload = parse_passed_options(options)
736
+ if payload['templateName'].nil?
737
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'templateName', 'type' => 'text', 'fieldLabel' => 'Image Name', 'description' => 'Choose a name for the new image template. Default is the server name + timestamp'}], options[:options])
738
+ if v_prompt['templateName'].to_s != ''
739
+ payload['templateName'] = v_prompt['templateName']
740
+ end
741
+ end
742
+ #if provision_type['code'] == 'vmware'
743
+ if provision_type && provision_type["hasFolders"]
744
+ if payload['zoneFolder'].nil?
745
+ # vmwareFolders moved from /api/options/vmwareFolders to /api/options/vmware/vmwareFolders
746
+ folder_prompt = nil
747
+ begin
748
+ folder_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zoneFolder', 'type' => 'select', 'optionSource' => 'vmwareFolders', 'optionSourceType' => 'vmware', 'fieldLabel' => 'Folder', 'description' => "Folder externalId or '/' to use the root folder", 'required' => true}], options[:options], @api_client, {siteId: instance['group']['id'], zoneId: instance['cloud']['id']})
749
+ rescue RestClient::Exception => e
750
+ Morpheus::Logging::DarkPrinter.puts "Failed to load folder options" if Morpheus::Logging.debug?
751
+ begin
752
+ folder_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zoneFolder', 'type' => 'select', 'optionSource' => 'vmwareFolders', 'fieldLabel' => 'Folder', 'description' => "Folder externalId or '/' to use the root folder", 'required' => true}], options[:options], @api_client, {siteId: instance['group']['id'], zoneId: instance['cloud']['id']})
753
+ rescue RestClient::Exception => e2
754
+ Morpheus::Logging::DarkPrinter.puts "Failed to load folder options from alternative endpoint too" if Morpheus::Logging.debug?
755
+ end
756
+ end
757
+ if folder_prompt && folder_prompt['zoneFolder'].to_s != ''
758
+ payload['zoneFolder'] = folder_prompt['zoneFolder']
759
+ end
760
+ end
761
+ end
762
+ end
763
+ confirm!("Are you sure you would like to clone as image container #{container['id']}?", options)
764
+ @containers_interface.setopts(options)
765
+ if options[:dry_run]
766
+ print_dry_run @containers_interface.dry.clone_image(container['id'], payload)
767
+ return
768
+ end
769
+ json_response = @containers_interface.clone_image(container['id'], payload)
770
+ render_response(json_response, options) do
771
+ print_green_success "Clone Image initiated for container [#{container['id']}] #{container['name']}"
772
+ end
773
+ return 0, nil
677
774
  end
678
775
 
679
776
  private
@@ -692,4 +789,52 @@ private
692
789
  end
693
790
  end
694
791
 
792
+ def validate_container_ids!(id_list)
793
+ id_list.each { |id| validate_container_id!(id) }
794
+ end
795
+
796
+ def validate_container_id!(id)
797
+ if id.to_s =~ /\A\d{1,}\Z/
798
+ true
799
+ else
800
+ raise_command_error "[id] argument is invalid, expected a number and got '#{id}'" #, args, optparse
801
+ end
802
+ end
803
+
804
+ def load_container_provision_type(container, instance=nil)
805
+ if instance.nil?
806
+ instance = find_instance_by_name_or_id(container['instance']['id'])
807
+ return 1 if instance.nil?
808
+ end
809
+ # todo: should be returned by containers api too, get from instance for old api versions
810
+ provision_type_code = container['containerType']['provisionTypeCode'] rescue nil
811
+ provision_type_code = provision_type_code || container['provisionType']['code'] rescue nil
812
+ if provision_type_code.nil?
813
+ return load_instance_provision_type(instance)
814
+ end
815
+ provision_type = nil
816
+ if provision_type_code
817
+ provision_type = provision_types_interface.list({code:provision_type_code})['provisionTypes'][0]
818
+ if provision_type.nil?
819
+ raise_command_error "Provision Type not found by code #{provision_type_code}"
820
+ end
821
+ else
822
+ raise_command_error "Unable to determine provision type for container #{container['id']}"
823
+ end
824
+ return provision_type
825
+ end
826
+
827
+ def load_instance_provision_type(instance)
828
+ provision_type_code = instance['layout']['provisionTypeCode'] rescue nil
829
+ provision_type = nil
830
+ if provision_type_code
831
+ provision_type = provision_types_interface.list({code:provision_type_code})['provisionTypes'][0]
832
+ if provision_type.nil?
833
+ raise_command_error "Provision Type not found by code #{provision_type_code}"
834
+ end
835
+ else
836
+ raise_command_error "Unable to determine provision type for instance #{instance['id']}"
837
+ end
838
+ return provision_type
839
+ end
695
840
  end