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 +4 -4
- data/lib/morpheus/api/api_client.rb +4 -0
- data/lib/morpheus/api/containers_interface.rb +128 -0
- data/lib/morpheus/api/instances_interface.rb +63 -0
- data/lib/morpheus/cli.rb +1 -0
- data/lib/morpheus/cli/cli_command.rb +15 -3
- data/lib/morpheus/cli/containers_command.rb +519 -0
- data/lib/morpheus/cli/credentials.rb +4 -13
- data/lib/morpheus/cli/error_handler.rb +9 -0
- data/lib/morpheus/cli/instances.rb +964 -178
- data/lib/morpheus/cli/logout.rb +1 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +153 -139
- data/lib/morpheus/cli/option_types.rb +27 -10
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whoami.rb +8 -7
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a9c598b82ebbf8eea995f58a454bf8a52defef0
|
4
|
+
data.tar.gz: 6a81fff1ddc710ca3d56488670586caf7844ab93
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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 '#{
|
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
|