morpheus-cli 5.5.2.1 → 5.5.3

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