morpheus-cli 2.11.0 → 2.11.1

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