morpheus-cli 2.11.0 → 2.11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 943b71ed0cd1182919d30c80a340e4dddd348469
4
- data.tar.gz: 5240605a00d07cfae6f73cc53789ad456ba99713
3
+ metadata.gz: 7a9c598b82ebbf8eea995f58a454bf8a52defef0
4
+ data.tar.gz: 6a81fff1ddc710ca3d56488670586caf7844ab93
5
5
  SHA512:
6
- metadata.gz: 03b911517a40f1254ebb91e14d69d840038c80df028966b0cd23035b0fbc54eff8e22aa6bfa75cbc70584b26c858290c2e905b8115a1b7312066a3431fb56305
7
- data.tar.gz: 93de8368cae2dd82312741584dc4d688fc9dc5a6defadccdcede1f58254a92765f65739d43d82e300c72547fb0dfb630eda587bac858537262c6a0e7ce90b783
6
+ metadata.gz: 5821cf3be29b11830eab7cf6e87f15ebb3f0e783d3d49664c5dbd6b824e775e6546b2da09a52983edf1dfd60058c51f40c9ae3cd591ea61fb5fb730b2766da6a
7
+ data.tar.gz: 356f77e67812b6f8b63e85f3c1a95c0206e5a5f456c912a65aae311f0040ad1eea4fae2d77d840a85be1a03bbc342db0b1097dfa07eb623ef1bb71e699b21786
@@ -64,6 +64,10 @@ class Morpheus::APIClient
64
64
  Morpheus::InstancesInterface.new(@access_token, @refresh_token, @expires_at, @base_url)
65
65
  end
66
66
 
67
+ def containers
68
+ Morpheus::ContainersInterface.new(@access_token, @refresh_token, @expires_at, @base_url)
69
+ end
70
+
67
71
  def instance_types
68
72
  Morpheus::InstanceTypesInterface.new(@access_token, @refresh_token, @expires_at, @base_url)
69
73
  end
@@ -0,0 +1,128 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ # Containers API interface.
4
+ # All of the PUT methods support passing an array of IDs.
5
+ class Morpheus::ContainersInterface < Morpheus::APIClient
6
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
7
+ @access_token = access_token
8
+ @refresh_token = refresh_token
9
+ @base_url = base_url
10
+ @expires_at = expires_at
11
+ end
12
+
13
+ # not used atm.. index api needs some work, we should implement it
14
+ # so it just paginates all containers.
15
+ # right now you can to pass params as {:ids => [1,2,3]}
16
+ def list(params={})
17
+ url = "#{@base_url}/api/containers"
18
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
19
+ opts = {method: :get, url: url, headers: headers}
20
+ execute(opts)
21
+ end
22
+
23
+ def get(container_id)
24
+ url = "#{@base_url}/api/containers/#{container_id}"
25
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
26
+ opts = {method: :get, url: url, headers: headers}
27
+ execute(opts)
28
+ end
29
+
30
+ def stop(container_id, payload={})
31
+ url, params = "", {}
32
+ if container_id.is_a?(Array)
33
+ url = "#{@base_url}/api/containers/stop"
34
+ params = {ids: container_id}
35
+ else
36
+ url = "#{@base_url}/api/containers/#{container_id}/stop"
37
+ params = {}
38
+ end
39
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
40
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
41
+ execute(opts)
42
+ end
43
+
44
+ def start(container_id, payload={})
45
+ url, params = "", {}
46
+ if container_id.is_a?(Array)
47
+ url = "#{@base_url}/api/containers/start"
48
+ params = {ids: container_id}
49
+ else
50
+ url = "#{@base_url}/api/containers/#{container_id}/start"
51
+ params = {}
52
+ end
53
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
54
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
55
+ execute(opts)
56
+ end
57
+
58
+ def restart(container_id, payload={})
59
+ url, params = "", {}
60
+ if container_id.is_a?(Array)
61
+ url = "#{@base_url}/api/containers/restart"
62
+ params = {ids: container_id}
63
+ else
64
+ url = "#{@base_url}/api/containers/#{container_id}/restart"
65
+ params = {}
66
+ end
67
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
68
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
69
+ execute(opts)
70
+ end
71
+
72
+ def suspend(container_id, payload={})
73
+ url, params = "", {}
74
+ if container_id.is_a?(Array)
75
+ url = "#{@base_url}/api/containers/suspend"
76
+ params = {ids: container_id}
77
+ else
78
+ url = "#{@base_url}/api/containers/#{container_id}/suspend"
79
+ params = {}
80
+ end
81
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
82
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
83
+ execute(opts)
84
+ end
85
+
86
+ def eject(container_id, payload={})
87
+ url, params = "", {}
88
+ if container_id.is_a?(Array)
89
+ url = "#{@base_url}/api/containers/eject"
90
+ params = {ids: container_id}
91
+ else
92
+ url = "#{@base_url}/api/containers/#{container_id}/eject"
93
+ params = {}
94
+ end
95
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
96
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
97
+ execute(opts)
98
+ end
99
+
100
+ def available_actions(container_id)
101
+ url, params = "", {}
102
+ if container_id.is_a?(Array)
103
+ url = "#{@base_url}/api/containers/actions"
104
+ params = {ids: container_id}
105
+ else
106
+ url = "#{@base_url}/api/containers/#{container_id}/actions"
107
+ params = {}
108
+ end
109
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
110
+ opts = {method: :get, url: url, headers: headers}
111
+ execute(opts)
112
+ end
113
+
114
+ def action(container_id, action_code, payload={})
115
+ url, params = "", {}
116
+ if container_id.is_a?(Array)
117
+ url = "#{@base_url}/api/containers/action"
118
+ params = {ids: container_id, code: action_code}
119
+ else
120
+ url = "#{@base_url}/api/containers/#{container_id}/action"
121
+ params = {code: action_code}
122
+ end
123
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
124
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
125
+ execute(opts)
126
+ end
127
+
128
+ end
@@ -103,6 +103,34 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
103
103
  execute(opts)
104
104
  end
105
105
 
106
+ def available_actions(id)
107
+ url, params = "", {}
108
+ if id.is_a?(Array)
109
+ url = "#{@base_url}/api/instances/actions"
110
+ params = {ids: id}
111
+ else
112
+ url = "#{@base_url}/api/instances/#{id}/actions"
113
+ params = {}
114
+ end
115
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
116
+ opts = {method: :get, url: url, headers: headers}
117
+ execute(opts)
118
+ end
119
+
120
+ def action(id, action_code, payload={})
121
+ url, params = "", {}
122
+ if id.is_a?(Array)
123
+ url = "#{@base_url}/api/instances/action"
124
+ params = {ids: id, code: action_code}
125
+ else
126
+ url = "#{@base_url}/api/instances/#{id}/action"
127
+ params = {code: action_code}
128
+ end
129
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
130
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
131
+ execute(opts)
132
+ end
133
+
106
134
  def volumes(id)
107
135
  url = "#{@base_url}/api/instances/#{id}/volumes"
108
136
  headers = { :params => {},:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -188,4 +216,39 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
188
216
  execute(opts)
189
217
  end
190
218
 
219
+ def containers(instance_id, params={})
220
+ url = "#{@base_url}/api/instances/#{instance_id}/containers"
221
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
222
+ opts = {method: :get, url: url, headers: headers}
223
+ execute(opts)
224
+ end
225
+
226
+ def threshold(id, params={})
227
+ url = "#{@base_url}/api/instances/#{id}/threshold"
228
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
229
+ opts = {method: :get, url: url, headers: headers}
230
+ execute(opts)
231
+ end
232
+
233
+ def update_threshold(id, payload)
234
+ url = "#{@base_url}/api/instances/#{id}/threshold"
235
+ headers = {authorization: "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
236
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
237
+ execute(opts)
238
+ end
239
+
240
+ def update_load_balancer(id, payload)
241
+ url = "#{@base_url}/api/instances/#{id}/load-balancer"
242
+ headers = {authorization: "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
243
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
244
+ execute(opts)
245
+ end
246
+
247
+ def remove_load_balancer(id, payload={})
248
+ url = "#{@base_url}/api/instances/#{id}/load-balancer"
249
+ headers = {authorization: "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
250
+ opts = {method: :delete, url: url, headers: headers, payload: payload.to_json}
251
+ execute(opts)
252
+ end
253
+
191
254
  end
data/lib/morpheus/cli.rb CHANGED
@@ -69,6 +69,7 @@ module Morpheus
69
69
  load 'morpheus/cli/workflows.rb'
70
70
  load 'morpheus/cli/deployments.rb'
71
71
  load 'morpheus/cli/instances.rb'
72
+ load 'morpheus/cli/containers_command.rb'
72
73
  load 'morpheus/cli/apps.rb'
73
74
  load 'morpheus/cli/app_templates.rb'
74
75
  load 'morpheus/cli/deploys.rb'
@@ -128,7 +128,8 @@ module Morpheus
128
128
 
129
129
  description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{option_type['defaultValue'] ? ' Default: '+option_type['defaultValue'].to_s : ''}"
130
130
  if option_type['description']
131
- description << "\n #{option_type['description']}"
131
+ # description << "\n #{option_type['description']}"
132
+ description << " - #{option_type['description']}"
132
133
  end
133
134
  if option_type['helpBlock']
134
135
  description << "\n #{option_type['helpBlock']}"
@@ -141,7 +142,18 @@ module Morpheus
141
142
  # description = "(Required) #{description}"
142
143
  # end
143
144
 
144
- opts.on("--#{full_field_name} VALUE", String, description) do |val|
145
+ value_label = "VALUE"
146
+ if option_type['placeHolder']
147
+ value_label = option_type['placeHolder']
148
+ elsif option_type['type'] == 'checkbox'
149
+ value_label = 'on|off' # or.. true|false
150
+ elsif option_type['type'] == 'number'
151
+ value_label = 'NUMBER'
152
+ # elsif option_type['type'] == 'select'
153
+ # value_label = 'SELECT'
154
+ # elsif option['type'] == 'select'
155
+ end
156
+ opts.on("--#{full_field_name} #{value_label}", String, description) do |val|
145
157
  cur_namespace = custom_options
146
158
  field_namespace.each do |ns|
147
159
  next if ns.empty?
@@ -519,7 +531,7 @@ module Morpheus
519
531
  if ::Morpheus::Cli::Remote.appliances.empty?
520
532
  raise_command_error "You have no appliances configured. See the `remote add` command."
521
533
  else
522
- raise_command_error "Remote appliance not found by the name '#{appliance_name}'"
534
+ raise_command_error "Remote appliance not found by the name '#{options[:remote]}'"
523
535
  end
524
536
  end
525
537
  else
@@ -0,0 +1,519 @@
1
+ require 'io/console'
2
+ require 'rest_client'
3
+ require 'optparse'
4
+ require 'filesize'
5
+ require 'table_print'
6
+ require 'morpheus/cli/cli_command'
7
+ require 'morpheus/cli/mixins/provisioning_helper'
8
+ require 'morpheus/cli/option_types'
9
+
10
+ class Morpheus::Cli::ContainersCommand
11
+ include Morpheus::Cli::CliCommand
12
+ include Morpheus::Cli::ProvisioningHelper
13
+
14
+ set_command_name :containers
15
+
16
+ register_subcommands :get, :stop, :start, :restart, :suspend, :eject, :action, :actions
17
+
18
+ def connect(opts)
19
+ @api_client = establish_remote_appliance_connection(opts)
20
+ @containers_interface = @api_client.containers
21
+ end
22
+
23
+ def handle(args)
24
+ handle_subcommand(args)
25
+ end
26
+
27
+ def get(args)
28
+ options = {}
29
+ optparse = OptionParser.new do|opts|
30
+ opts.banner = subcommand_usage("[name]")
31
+ opts.on( nil, '--actions', "Display Available Actions" ) do
32
+ options[:include_available_actions] = true
33
+ end
34
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
35
+ end
36
+ optparse.parse!(args)
37
+ if args.count < 1
38
+ puts_error "[id] argument is required"
39
+ puts_error optparse
40
+ return 1
41
+ end
42
+ connect(options)
43
+ id_list = parse_id_list(args)
44
+ return run_command_for_each_arg(id_list) do |arg|
45
+ _get(arg, options)
46
+ end
47
+ end
48
+
49
+ def _get(arg, options)
50
+ begin
51
+ if options[:dry_run]
52
+ print_dry_run @containers_interface.dry.get(arg.to_i)
53
+ return
54
+ end
55
+ #container = find_container_by_id(arg)
56
+ #return 1 if container.nil?
57
+ json_response = @containers_interface.get(arg.to_i)
58
+ if options[:json]
59
+ if options[:include_fields]
60
+ json_response = {"container" => filter_data(json_response["container"], options[:include_fields]) }
61
+ end
62
+ puts as_json(json_response, options)
63
+ return 0
64
+ elsif options[:yaml]
65
+ if options[:include_fields]
66
+ json_response = {"container" => filter_data(json_response["container"], options[:include_fields]) }
67
+ end
68
+ puts as_yaml(json_response, options)
69
+ return 0
70
+ end
71
+
72
+ if options[:csv]
73
+ puts records_as_csv([json_response['container']], options)
74
+ return 0
75
+ end
76
+ container = json_response['container']
77
+ # stats = json_response['stats'] || {}
78
+ stats = container['stats'] || {}
79
+
80
+ # load_balancers = stats = json_response['loadBalancers'] || {}
81
+
82
+ # todo: show as 'VM' instead of 'Container' maybe..err
83
+ # may need to fetch instance by id too..
84
+ # ${[null,'docker'].contains(instance?.layout?.provisionType?.code) ? 'CONTAINERS' : 'VMs'}
85
+
86
+ print_h1 "Container Details"
87
+ print cyan
88
+ description_cols = {
89
+ "ID" => 'id',
90
+ #"Name" => 'name',
91
+ "Name" => lambda {|it| it['server'] ? it['server']['name'] : '(no server)' }, # there is a server.displayName too?
92
+ "Type" => lambda {|it| it['containerType'] ? it['containerType']['name'] : '' },
93
+ "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
94
+ "Instance" => lambda {|it| it['instance'] ? it['instance']['name'] : '' },
95
+ "Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
96
+ "Location" => lambda {|it| format_container_connection_string(it) },
97
+ # "Description" => 'description',
98
+ # "Group" => lambda {|it| it['group'] ? it['group']['name'] : '' },
99
+ # "Cloud" => lambda {|it| it['cloud'] ? it['cloud']['name'] : '' },
100
+ # "Type" => lambda {|it| it['instanceType']['name'] },
101
+ # "Plan" => lambda {|it| it['plan'] ? it['plan']['name'] : '' },
102
+ # "Environment" => 'instanceContext',
103
+ # "Nodes" => lambda {|it| it['containers'] ? it['containers'].count : 0 },
104
+ # "Connection" => lambda {|it| format_container_connection_string(it) },
105
+ #"Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
106
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
107
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
108
+ "Status" => lambda {|it| format_container_status(it) }
109
+ }
110
+ print_description_list(description_cols, container)
111
+
112
+ if (stats)
113
+ print_h2 "Container Usage"
114
+ print_stats_usage(stats)
115
+ end
116
+
117
+ if options[:include_available_actions]
118
+ if (container["availableActions"])
119
+ print_h2 "Available Actions"
120
+ print as_pretty_table(container["availableActions"], [:id, :name])
121
+ print reset, "\n"
122
+ else
123
+ print "#{yellow}No available actions#{reset}\n\n"
124
+ end
125
+ end
126
+
127
+ print reset, "\n"
128
+
129
+ return 0
130
+ rescue RestClient::Exception => e
131
+ print_rest_exception(e, options)
132
+ return 1 # , e
133
+ end
134
+ end
135
+
136
+
137
+ def stop(args)
138
+ options = {}
139
+ optparse = OptionParser.new do|opts|
140
+ opts.banner = subcommand_usage("[id list]")
141
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
142
+ end
143
+ optparse.parse!(args)
144
+ if args.count < 1
145
+ puts_error "[id] argument is required"
146
+ puts_error optparse
147
+ return 1
148
+ end
149
+ connect(options)
150
+ id_list = parse_id_list(args)
151
+ 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)
152
+ return 9, "aborted command"
153
+ end
154
+ return run_command_for_each_arg(id_list) do |arg|
155
+ _stop(arg, options)
156
+ end
157
+ end
158
+
159
+ def _stop(container_id, options)
160
+ container = find_container_by_id(container_id) # could skip this since only id is supported
161
+ return 1 if container.nil?
162
+ if options[:dry_run]
163
+ print_dry_run @containers_interface.dry.stop(container['id'])
164
+ return 0
165
+ end
166
+ json_response = @containers_interface.stop(container['id'])
167
+ # just assume json_response["success"] == true, it always is with 200 OK
168
+ if options[:json]
169
+ puts as_json(json_response, options)
170
+ elsif !options[:quiet]
171
+ print green, "Stopping container #{container['id']}", reset, "\n"
172
+ end
173
+ return 0
174
+ end
175
+
176
+ def start(args)
177
+ options = {}
178
+ optparse = OptionParser.new do|opts|
179
+ opts.banner = subcommand_usage("[id list]")
180
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
181
+ end
182
+ optparse.parse!(args)
183
+ if args.count < 1
184
+ puts_error "[id] argument is required"
185
+ puts_error optparse
186
+ return 1
187
+ end
188
+ connect(options)
189
+ id_list = parse_id_list(args)
190
+ 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)
191
+ return 9, "aborted command"
192
+ end
193
+ return run_command_for_each_arg(id_list) do |arg|
194
+ _start(arg, options)
195
+ end
196
+ end
197
+
198
+ def _start(container_id, options)
199
+ container = find_container_by_id(container_id) # could skip this since only id is supported
200
+ return 1 if container.nil?
201
+ if options[:dry_run]
202
+ print_dry_run @containers_interface.dry.start(container['id'])
203
+ return 0
204
+ end
205
+ json_response = @containers_interface.start(container['id'])
206
+ # just assume json_response["success"] == true, it always is with 200 OK
207
+ if options[:json]
208
+ puts as_json(json_response, options)
209
+ elsif !options[:quiet]
210
+ print green, "Starting container #{container['id']}", reset, "\n"
211
+ end
212
+ return 0
213
+ end
214
+
215
+ def restart(args)
216
+ options = {}
217
+ optparse = OptionParser.new do|opts|
218
+ opts.banner = subcommand_usage("[id list]")
219
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
220
+ end
221
+ optparse.parse!(args)
222
+ if args.count < 1
223
+ puts_error "[id] argument is required"
224
+ puts_error optparse
225
+ return 1
226
+ end
227
+ connect(options)
228
+ id_list = parse_id_list(args)
229
+ 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)
230
+ return 9, "aborted command"
231
+ end
232
+ return run_command_for_each_arg(id_list) do |arg|
233
+ _restart(arg, options)
234
+ end
235
+ end
236
+
237
+ def _restart(container_id, options)
238
+ container = find_container_by_id(container_id) # could skip this since only id is supported
239
+ return 1 if container.nil?
240
+ if options[:dry_run]
241
+ print_dry_run @containers_interface.dry.restart(container['id'])
242
+ return 0
243
+ end
244
+ json_response = @containers_interface.restart(container['id'])
245
+ # just assume json_response["success"] == true, it always is with 200 OK
246
+ if options[:json]
247
+ puts as_json(json_response, options)
248
+ elsif !options[:quiet]
249
+ print green, "Restarting container #{container['id']}", reset, "\n"
250
+ end
251
+ return 0
252
+ end
253
+
254
+ def suspend(args)
255
+ options = {}
256
+ optparse = OptionParser.new do|opts|
257
+ opts.banner = subcommand_usage("[id list]")
258
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
259
+ end
260
+ optparse.parse!(args)
261
+ if args.count < 1
262
+ puts_error "[id] argument is required"
263
+ puts_error optparse
264
+ return 1
265
+ end
266
+ connect(options)
267
+ id_list = parse_id_list(args)
268
+ 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)
269
+ return 9, "aborted command"
270
+ end
271
+ return run_command_for_each_arg(id_list) do |arg|
272
+ _suspend(arg, options)
273
+ end
274
+ end
275
+
276
+ def _suspend(container_id, options)
277
+ container = find_container_by_id(container_id) # could skip this since only id is supported
278
+ return 1 if container.nil?
279
+ if options[:dry_run]
280
+ print_dry_run @containers_interface.dry.suspend(container['id'])
281
+ return 0
282
+ end
283
+ json_response = @containers_interface.suspend(container['id'])
284
+ # just assume json_response["success"] == true, it always is with 200 OK
285
+ if options[:json]
286
+ puts as_json(json_response, options)
287
+ elsif !options[:quiet]
288
+ print green, "Suspending container #{container['id']}", reset, "\n"
289
+ end
290
+ return 0
291
+ end
292
+
293
+ def eject(args)
294
+ options = {}
295
+ optparse = OptionParser.new do|opts|
296
+ opts.banner = subcommand_usage("[id list]")
297
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
298
+ end
299
+ optparse.parse!(args)
300
+ if args.count < 1
301
+ puts_error "[id] argument is required"
302
+ puts_error optparse
303
+ return 1
304
+ end
305
+ connect(options)
306
+ id_list = parse_id_list(args)
307
+ 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)
308
+ return 9, "aborted command"
309
+ end
310
+ return run_command_for_each_arg(id_list) do |arg|
311
+ _eject(arg, options)
312
+ end
313
+ end
314
+
315
+ def _eject(container_id, options)
316
+ container = find_container_by_id(container_id) # could skip this since only id is supported
317
+ return 1 if container.nil?
318
+ if options[:dry_run]
319
+ print_dry_run @containers_interface.dry.eject(container['id'])
320
+ return 0
321
+ end
322
+ json_response = @containers_interface.eject(container['id'])
323
+ # just assume json_response["success"] == true, it always is with 200 OK
324
+ if options[:json]
325
+ puts as_json(json_response, options)
326
+ elsif !options[:quiet]
327
+ print green, "Ejecting container #{container['id']}", reset, "\n"
328
+ end
329
+ return 0
330
+ end
331
+
332
+ def actions(args)
333
+ options = {}
334
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
335
+ opts.banner = subcommand_usage("[id list]")
336
+ opts.footer = "This outputs the list of the actions available to specified container(s)."
337
+ build_common_options(opts, options, [:json, :dry_run, :remote])
338
+ end
339
+ optparse.parse!(args)
340
+ if args.count < 1
341
+ puts optparse
342
+ exit 1
343
+ end
344
+ connect(options)
345
+ id_list = parse_id_list(args)
346
+ containers = []
347
+ id_list.each do |container_id|
348
+ container = find_container_by_id(container_id)
349
+ if container.nil?
350
+ # return 1
351
+ else
352
+ containers << container
353
+ end
354
+ end
355
+ if containers.size != id_list.size
356
+ #puts_error "containers not found"
357
+ return 1
358
+ end
359
+ container_ids = containers.collect {|container| container["id"] }
360
+ begin
361
+ # container = find_container_by_name_or_id(args[0])
362
+ if options[:dry_run]
363
+ print_dry_run @containers_interface.dry.available_actions(container_ids)
364
+ return 0
365
+ end
366
+ json_response = @containers_interface.available_actions(container_ids)
367
+ if options[:json]
368
+ puts as_json(json_response, options)
369
+ else
370
+ title = "Container Actions: #{anded_list(id_list)}"
371
+ print_h1 title
372
+ available_actions = json_response["actions"]
373
+ if (available_actions && available_actions.size > 0)
374
+ print as_pretty_table(available_actions, [:name, :code])
375
+ print reset, "\n"
376
+ else
377
+ if container_ids.size > 1
378
+ print "#{yellow}The specified containers have no available actions in common.#{reset}\n\n"
379
+ else
380
+ print "#{yellow}No available actions#{reset}\n\n"
381
+ end
382
+ end
383
+ end
384
+ return 0
385
+ rescue RestClient::Exception => e
386
+ print_rest_exception(e, options)
387
+ exit 1
388
+ end
389
+ end
390
+
391
+ def action(args)
392
+ options = {}
393
+ action_id = nil
394
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
395
+ opts.banner = subcommand_usage("[id list] -a CODE")
396
+ opts.on('-a', '--action CODE', "Container Action CODE to execute") do |val|
397
+ action_id = val.to_s
398
+ end
399
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
400
+ opts.footer = "Execute an action for a container or containers"
401
+ end
402
+ optparse.parse!(args)
403
+ if args.count < 1
404
+ print_error Morpheus::Terminal.angry_prompt
405
+ puts_error "[id list] argument is required"
406
+ puts_error optparse
407
+ return 1
408
+ end
409
+ connect(options)
410
+ id_list = parse_id_list(args)
411
+ containers = []
412
+ id_list.each do |container_id|
413
+ container = find_container_by_id(container_id)
414
+ if container.nil?
415
+ # return 1
416
+ else
417
+ containers << container
418
+ end
419
+ end
420
+ if containers.size != id_list.size
421
+ #puts_error "containers not found"
422
+ return 1
423
+ end
424
+ container_ids = containers.collect {|container| container["id"] }
425
+
426
+ # figure out what action to run
427
+ # assume that the action is available for all the containers..
428
+ available_actions = containers.first['availableActions']
429
+ if available_actions.empty?
430
+ print_red_alert "Container #{container['id']} has no available actions"
431
+ if container_ids.size > 1
432
+ print_red_alert "The specified containers have no available actions in common"
433
+ else
434
+ print_red_alert "The specified container has no available actions"
435
+ end
436
+ return 1
437
+ end
438
+ container_action = nil
439
+ if action_id.nil?
440
+ available_actions_dropdown = available_actions.collect {|act| {'name' => act["name"], 'value' => act["code"]} } # already sorted
441
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'code', 'type' => 'select', 'fieldLabel' => 'Container Action', 'selectOptions' => available_actions_dropdown, 'required' => true, 'description' => 'Choose the container action to execute'}], options[:options])
442
+ action_id = v_prompt['code']
443
+ container_action = available_actions.find {|act| act['code'].to_s == action_id.to_s }
444
+ else
445
+ container_action = available_actions.find {|act| act['code'].to_s == action_id.to_s || act['name'].to_s.downcase == action_id.to_s.downcase }
446
+ action_id = container_action["code"] if container_action
447
+ end
448
+ if !container_action
449
+ # for testing bogus actions..
450
+ # container_action = {"id" => action_id, "name" => "Unknown"}
451
+ raise_command_error "Container Action '#{action_id}' not found."
452
+ end
453
+
454
+ action_display_name = "#{container_action['name']} [#{container_action['code']}]"
455
+ 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)
456
+ return 9, "aborted command"
457
+ end
458
+
459
+ # return run_command_for_each_arg(containers) do |arg|
460
+ # _action(arg, action_id, options)
461
+ # end
462
+ if options[:dry_run]
463
+ print_dry_run @containers_interface.dry.action(container_ids, action_id)
464
+ return 0
465
+ end
466
+ json_response = @containers_interface.action(container_ids, action_id)
467
+ # just assume json_response["success"] == true, it always is with 200 OK
468
+ if options[:json]
469
+ puts as_json(json_response, options)
470
+ elsif !options[:quiet]
471
+ # containers.each do |container|
472
+ # print green, "Action #{action_display_name} performed on container #{container['id']}", reset, "\n"
473
+ # end
474
+ print green, "Action #{action_display_name} performed on #{id_list.size == 1 ? 'container' : 'containers'} #{anded_list(id_list)}", reset, "\n"
475
+ end
476
+ return 0
477
+ end
478
+
479
+ private
480
+
481
+ def find_container_by_id(id)
482
+ begin
483
+ json_response = @containers_interface.get(id.to_i)
484
+ return json_response['container']
485
+ rescue RestClient::Exception => e
486
+ if e.response && e.response.code == 404
487
+ print_red_alert "Container not found by id #{id}"
488
+ return nil
489
+ else
490
+ raise e
491
+ end
492
+ end
493
+ end
494
+
495
+ def format_container_status(container, return_color=cyan)
496
+ out = ""
497
+ status_string = container['status'].to_s
498
+ if status_string == 'running'
499
+ out << "#{green}#{status_string.upcase}#{return_color}"
500
+ elsif status_string == 'stopped' or status_string == 'failed'
501
+ out << "#{red}#{status_string.upcase}#{return_color}"
502
+ elsif status_string == 'unknown'
503
+ out << "#{white}#{status_string.upcase}#{return_color}"
504
+ else
505
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
506
+ end
507
+ out
508
+ end
509
+
510
+ def format_container_connection_string(container)
511
+ if !container['ports'].nil? && container['ports'].empty? == false
512
+ connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
513
+ else
514
+ # eh? more logic needed here i think, see taglib morph:containerLocationMenu
515
+ connection_string = "#{container['ip']}"
516
+ end
517
+ end
518
+
519
+ end