kontena-cli 1.3.0.pre2 → 1.3.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/kontena-cli.gemspec +1 -0
- data/lib/kontena/cli/common.rb +32 -2
- data/lib/kontena/cli/containers/exec_command.rb +25 -6
- data/lib/kontena/cli/helpers/exec_helper.rb +61 -0
- data/lib/kontena/cli/master/config/get_command.rb +6 -8
- data/lib/kontena/cli/master/ssh_command.rb +39 -14
- data/lib/kontena/cli/services/exec_command.rb +95 -35
- data/lib/kontena/cli/stacks/yaml/opto/service_instances_resolver.rb +3 -0
- data/lib/kontena_cli.rb +1 -1
- data/spec/kontena/cli/services/exec_command_spec.rb +130 -44
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1aa18c17b13a8b1887ca08c0a4a97f01d99b6df
|
4
|
+
data.tar.gz: 396d0b859359808ee9bcc11b0f92c9a6f9c3119b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c8e6b7eb2767919326db9290c952457303c7c390622fc6d762d9453df15d9d2e5deb9993e7731aff7d7b310cb325dd178da401051c8088d90472595c159d2e1
|
7
|
+
data.tar.gz: 02a1e6756340210bd7de9c552b9f67464165db55476c55defe14a907c9da0ca55a7d05921bf4417b752e1e4712f23aab8dfb5589c1afc724c7e7029831096cb6
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.0.
|
1
|
+
1.3.0.rc1
|
data/kontena-cli.gemspec
CHANGED
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_runtime_dependency "safe_yaml", "~> 1.0"
|
35
35
|
spec.add_runtime_dependency "liquid", "~> 4.0.0"
|
36
36
|
spec.add_runtime_dependency "tty-table", "~> 0.8.0"
|
37
|
+
spec.add_runtime_dependency "websocket-client-simple", "~> 0.3.0"
|
37
38
|
end
|
data/lib/kontena/cli/common.rb
CHANGED
@@ -248,14 +248,44 @@ module Kontena
|
|
248
248
|
def any_key_to_continue_with_timeout(timeout=9)
|
249
249
|
return nil if running_silent?
|
250
250
|
return nil unless $stdout.tty?
|
251
|
-
|
251
|
+
start_time = Time.now.to_i
|
252
|
+
end_time = start_time + timeout
|
253
|
+
Thread.main['any_key.timed_out'] = false
|
254
|
+
msg = "Press any key to continue or ctrl-c to cancel.. (Automatically continuing in ? seconds)"
|
255
|
+
|
256
|
+
reader_thread = Thread.new do
|
257
|
+
Thread.main['any_key.char'] = $stdin.getch
|
258
|
+
end
|
259
|
+
|
260
|
+
countdown_thread = Thread.new do
|
261
|
+
time_left = timeout
|
262
|
+
while time_left > 0 && Thread.main['any_key.char'].nil?
|
263
|
+
print "\r#{pastel.bright_white("#{msg.sub("?", time_left.to_s)}")} "
|
264
|
+
time_left = end_time - Time.now.to_i
|
265
|
+
sleep 0.1
|
266
|
+
end
|
267
|
+
print "\r#{' ' * msg.length} \r"
|
268
|
+
reader_thread.kill if reader_thread.alive?
|
269
|
+
end
|
270
|
+
|
271
|
+
countdown_thread.join
|
272
|
+
|
273
|
+
if Thread.main['any_key.char'] == "\u0003"
|
274
|
+
error "Canceled"
|
275
|
+
end
|
252
276
|
end
|
253
277
|
|
254
278
|
def any_key_to_continue(timeout = nil)
|
255
279
|
return nil if running_silent?
|
256
280
|
return nil unless $stdout.tty?
|
257
281
|
return any_key_to_continue_with_timeout(timeout) if timeout
|
258
|
-
|
282
|
+
msg = "Press any key to continue or ctrl-c to cancel.. "
|
283
|
+
print pastel.bright_cyan("#{msg}")
|
284
|
+
char = $stdin.getch
|
285
|
+
print "\r#{' ' * msg.length}\r"
|
286
|
+
if char == "\u0003"
|
287
|
+
error "Canceled"
|
288
|
+
end
|
259
289
|
end
|
260
290
|
|
261
291
|
def display_account_login_info
|
@@ -1,21 +1,40 @@
|
|
1
|
+
require_relative '../helpers/exec_helper'
|
2
|
+
|
1
3
|
module Kontena::Cli::Containers
|
2
4
|
class ExecCommand < Kontena::Command
|
3
5
|
include Kontena::Cli::Common
|
4
6
|
include Kontena::Cli::GridOptions
|
7
|
+
include Kontena::Cli::Helpers::ExecHelper
|
5
8
|
|
6
9
|
parameter "CONTAINER_ID", "Container id"
|
7
10
|
parameter "CMD ...", "Command"
|
8
11
|
|
12
|
+
option ["--shell"], :flag, "Execute as a shell command"
|
13
|
+
option ["--interactive"], :flag, "Keep stdin open"
|
14
|
+
|
9
15
|
def execute
|
16
|
+
require 'websocket-client-simple'
|
17
|
+
|
10
18
|
require_api_url
|
11
19
|
token = require_token
|
20
|
+
cmd = JSON.dump({cmd: cmd_list})
|
21
|
+
base = self
|
22
|
+
ws = connect(ws_url("#{current_grid}/#{container_id}"), token)
|
23
|
+
ws.on :message do |msg|
|
24
|
+
base.handle_message(msg)
|
25
|
+
end
|
26
|
+
ws.on :open do
|
27
|
+
ws.send(cmd)
|
28
|
+
end
|
29
|
+
ws.on :close do |e|
|
30
|
+
exit 1
|
31
|
+
end
|
12
32
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
exit result[2]
|
33
|
+
if interactive?
|
34
|
+
stream_stdin_to_ws(ws).join
|
35
|
+
else
|
36
|
+
sleep
|
37
|
+
end
|
19
38
|
end
|
20
39
|
end
|
21
40
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Kontena::Cli::Helpers
|
2
|
+
module ExecHelper
|
3
|
+
|
4
|
+
# @param [WebSocket::Client::Simple] ws
|
5
|
+
# @return [Thread]
|
6
|
+
def stream_stdin_to_ws(ws)
|
7
|
+
Thread.new {
|
8
|
+
STDIN.raw {
|
9
|
+
while char = STDIN.readpartial(1024)
|
10
|
+
ws.send(JSON.dump({ stdin: char }))
|
11
|
+
end
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Websocket::Frame::Incoming] msg
|
17
|
+
def handle_message(msg)
|
18
|
+
data = parse_message(msg)
|
19
|
+
if data.is_a?(Hash)
|
20
|
+
if data.has_key?('exit')
|
21
|
+
exit data['exit'].to_i
|
22
|
+
elsif data.has_key?('stream')
|
23
|
+
if data['stream'] == 'stdout'
|
24
|
+
$stdout << data['chunk']
|
25
|
+
else
|
26
|
+
$stderr << data['chunk']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue => exc
|
31
|
+
$stderr << "#{exc.class.name}: #{exc.message}"
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param [Websocket::Frame::Incoming] msg
|
35
|
+
def parse_message(msg)
|
36
|
+
JSON.parse(msg.data)
|
37
|
+
rescue JSON::ParserError
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [String] container_id
|
42
|
+
# @return [String]
|
43
|
+
def ws_url(container_id)
|
44
|
+
url = require_current_master.url
|
45
|
+
url << '/' unless url.end_with?('/')
|
46
|
+
"#{url.sub('http', 'ws')}v1/containers/#{container_id}/exec"
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param [String] url
|
50
|
+
# @param [String] token
|
51
|
+
# @return [WebSocket::Client::Simple]
|
52
|
+
def connect(url, token)
|
53
|
+
WebSocket::Client::Simple.connect(url, {
|
54
|
+
headers: {
|
55
|
+
'Authorization' => "Bearer #{token.access_token}",
|
56
|
+
'Accept' => 'application/json'
|
57
|
+
}
|
58
|
+
})
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -12,16 +12,14 @@ module Kontena::Cli::Master::Config
|
|
12
12
|
|
13
13
|
option ['-p', '--pair'], :flag, "Print key=value instead of only value"
|
14
14
|
|
15
|
-
|
15
|
+
def response
|
16
|
+
client.get("config/#{key}")
|
17
|
+
end
|
16
18
|
|
17
19
|
def execute
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
return client.get("config/#{self.key}")[self.key]
|
22
|
-
else
|
23
|
-
puts client.get("config/#{self.key}")[self.key]
|
24
|
-
end
|
20
|
+
value = response[key]
|
21
|
+
print(key + '=') if pair?
|
22
|
+
puts value
|
25
23
|
end
|
26
24
|
end
|
27
25
|
end
|
@@ -15,25 +15,50 @@ module Kontena::Cli::Master
|
|
15
15
|
URI.parse(current_master.url).host
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def master_provider_vagrant?
|
19
|
+
require 'kontena/cli/master/config/get_command'
|
20
|
+
cmd = Kontena::Cli::Master::Config::GetCommand.new([])
|
21
|
+
cmd.parse(['server.provider'])
|
22
|
+
cmd.response['server.provider'] == 'vagrant'
|
23
|
+
rescue => ex
|
24
|
+
false
|
20
25
|
end
|
21
26
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
def vagrant_plugin_installed?
|
28
|
+
Kontena::PluginManager.instance.plugins.any? { |plugin| plugin.name == 'kontena-plugin-vagrant' }
|
29
|
+
end
|
30
|
+
|
31
|
+
def master_is_vagrant?
|
32
|
+
if master_provider_vagrant?
|
33
|
+
unless vagrant_plugin_installed?
|
34
|
+
exit_with_error 'You need to install vagrant plugin to ssh into this master. Use: kontena plugin install vagrant'
|
26
35
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
logger.debug { "Master config server.provider is vagrant" }
|
37
|
+
true
|
38
|
+
elsif vagrant_plugin_installed? && current_master.url.include?('192.168.66.')
|
39
|
+
logger.debug { "Vagrant plugin installed and current_master url looks like vagrant" }
|
40
|
+
true
|
30
41
|
else
|
31
|
-
|
32
|
-
|
33
|
-
cmd += ["-i", identity_file] if identity_file
|
34
|
-
cmd += commands_list
|
35
|
-
exec(*cmd)
|
42
|
+
logger.debug { "Assuming non-vagrant master host" }
|
43
|
+
false
|
36
44
|
end
|
37
45
|
end
|
46
|
+
|
47
|
+
def run_ssh
|
48
|
+
cmd = ['ssh']
|
49
|
+
cmd << "#{user}@#{master_host}"
|
50
|
+
cmd += ["-i", identity_file] if identity_file
|
51
|
+
cmd += commands_list
|
52
|
+
logger.debug { "Executing #{cmd.inspect}" }
|
53
|
+
exec(*cmd)
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_vagrant_ssh
|
57
|
+
Kontena.run!(['vagrant', 'master', 'ssh'] + commands_list)
|
58
|
+
end
|
59
|
+
|
60
|
+
def execute
|
61
|
+
master_is_vagrant? ? run_vagrant_ssh : run_ssh
|
62
|
+
end
|
38
63
|
end
|
39
64
|
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
require 'shellwords'
|
1
2
|
require_relative 'services_helper'
|
3
|
+
require_relative '../helpers/exec_helper'
|
2
4
|
|
3
5
|
module Kontena::Cli::Services
|
4
6
|
class ExecCommand < Kontena::Command
|
5
7
|
include Kontena::Cli::Common
|
6
8
|
include Kontena::Cli::GridOptions
|
9
|
+
include Kontena::Cli::Helpers::ExecHelper
|
7
10
|
include ServicesHelper
|
8
11
|
|
9
12
|
parameter "NAME", "Service name"
|
@@ -12,6 +15,7 @@ module Kontena::Cli::Services
|
|
12
15
|
option ["-i", "--instance"], "INSTANCE", "Exec on given numbered instance, default first running" do |value| Integer(value) end
|
13
16
|
option ["-a", "--all"], :flag, "Exec on all running instances"
|
14
17
|
option ["--shell"], :flag, "Execute as a shell command"
|
18
|
+
option ["--interactive"], :flag, "Keep stdin open"
|
15
19
|
option ["--skip"], :flag, "Skip failed instances when executing --all"
|
16
20
|
option ["--silent"], :flag, "Do not show exec status"
|
17
21
|
option ["--verbose"], :flag, "Show exec status"
|
@@ -19,42 +23,16 @@ module Kontena::Cli::Services
|
|
19
23
|
requires_current_master
|
20
24
|
requires_current_grid
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
if shell?
|
25
|
-
cmd = ['sh', '-c', cmd_list.join(' ')]
|
26
|
-
else
|
27
|
-
cmd = cmd_list
|
28
|
-
end
|
29
|
-
|
30
|
-
stdout = stderr = exit_status = nil
|
31
|
-
|
32
|
-
if !silent? && (verbose? || all?)
|
33
|
-
spinner "Executing command on #{container['name']}" do
|
34
|
-
stdout, stderr, exit_status = client.post("containers/#{container['id']}/exec", {cmd: cmd})
|
35
|
-
|
36
|
-
raise Kontena::Cli::SpinAbort if exit_status != 0
|
37
|
-
end
|
38
|
-
else
|
39
|
-
stdout, stderr, exit_status = client.post("containers/#{container['id']}/exec", {cmd: cmd})
|
40
|
-
end
|
41
|
-
|
42
|
-
stdout.each do |chunk| $stdout.write chunk end
|
43
|
-
stderr.each do |chunk| $stderr.write chunk end
|
26
|
+
def execute
|
27
|
+
require 'websocket-client-simple'
|
44
28
|
|
45
|
-
|
29
|
+
exit_with_error "--interactive cannot be used with --all" if all? && interactive?
|
46
30
|
|
47
|
-
return exit_status == 0
|
48
|
-
end
|
49
|
-
|
50
|
-
def execute
|
51
31
|
service_containers = client.get("services/#{parse_service_id(name)}/containers")['containers']
|
52
32
|
service_containers.sort_by! { |container| container['instance_number'] }
|
53
33
|
running_containers = service_containers.select{|container| container['status'] == 'running' }
|
54
|
-
|
55
|
-
if running_containers.empty?
|
56
|
-
exit_with_error "Service #{name} does not have any running containers"
|
57
|
-
end
|
34
|
+
|
35
|
+
exit_with_error "Service #{name} does not have any running containers" if running_containers.empty?
|
58
36
|
|
59
37
|
if all?
|
60
38
|
ret = true
|
@@ -69,16 +47,98 @@ module Kontena::Cli::Services
|
|
69
47
|
end
|
70
48
|
return ret
|
71
49
|
elsif instance
|
72
|
-
if !(container = service_containers.find{|
|
50
|
+
if !(container = service_containers.find{|c| c['instance_number'] == instance})
|
73
51
|
exit_with_error "Service #{name} does not have container instance #{instance}"
|
74
52
|
elsif container['status'] != 'running'
|
75
53
|
exit_with_error "Service #{name} container #{container['name']} is not running, it is #{container['status']}"
|
76
|
-
|
54
|
+
elsif interactive?
|
55
|
+
interactive_exec(container)
|
56
|
+
else
|
77
57
|
exec_container(container)
|
78
58
|
end
|
79
59
|
else
|
80
|
-
|
60
|
+
if interactive?
|
61
|
+
interactive_exec(running_containers.first)
|
62
|
+
else
|
63
|
+
exec_container(running_containers.first)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Exits if exec returns with non-zero
|
69
|
+
# @param [Hash] container
|
70
|
+
def exec_container(container)
|
71
|
+
exit_status = nil
|
72
|
+
if !silent? && (verbose? || all?)
|
73
|
+
spinner "Executing command on #{container['name']}" do
|
74
|
+
exit_status = normal_exec(container)
|
75
|
+
|
76
|
+
raise Kontena::Cli::SpinAbort if exit_status != 0
|
77
|
+
end
|
78
|
+
else
|
79
|
+
exit_status = normal_exec(container)
|
80
|
+
end
|
81
|
+
|
82
|
+
exit exit_status if exit_status != 0 && !skip?
|
83
|
+
|
84
|
+
return exit_status == 0
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param [Hash] container
|
88
|
+
# @return [Boolean]
|
89
|
+
def normal_exec(container)
|
90
|
+
base = self
|
91
|
+
cmd = JSON.dump({ cmd: cmd_list })
|
92
|
+
exit_status = nil
|
93
|
+
token = require_token
|
94
|
+
url = ws_url(container['id'])
|
95
|
+
url << '?shell=true' if shell?
|
96
|
+
ws = connect(url, token)
|
97
|
+
ws.on :message do |msg|
|
98
|
+
data = base.parse_message(msg)
|
99
|
+
if data
|
100
|
+
if data['exit']
|
101
|
+
exit_status = data['exit'].to_i
|
102
|
+
elsif data['stream'] == 'stdout'
|
103
|
+
$stdout << data['chunk']
|
104
|
+
else
|
105
|
+
$stderr << data['chunk']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
ws.on :open do
|
110
|
+
ws.send(cmd)
|
111
|
+
end
|
112
|
+
ws.on :close do |e|
|
113
|
+
exit_status = 1
|
114
|
+
end
|
115
|
+
|
116
|
+
sleep 0.01 until !exit_status.nil?
|
117
|
+
|
118
|
+
exit_status
|
119
|
+
end
|
120
|
+
|
121
|
+
# @param [Hash] container
|
122
|
+
def interactive_exec(container)
|
123
|
+
require 'io/console'
|
124
|
+
|
125
|
+
token = require_token
|
126
|
+
cmd = JSON.dump({ cmd: cmd_list })
|
127
|
+
base = self
|
128
|
+
url = ws_url(container['id']) << '?interactive=true'
|
129
|
+
url << '&shell=true' if shell?
|
130
|
+
ws = connect(url, token)
|
131
|
+
ws.on :message do |msg|
|
132
|
+
base.handle_message(msg)
|
133
|
+
end
|
134
|
+
ws.on :open do
|
135
|
+
ws.send(cmd)
|
136
|
+
end
|
137
|
+
ws.on :close do |e|
|
138
|
+
exit 1
|
81
139
|
end
|
140
|
+
|
141
|
+
stream_stdin_to_ws(ws).join
|
82
142
|
end
|
83
143
|
end
|
84
|
-
end
|
144
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module Kontena::Cli::Stacks
|
2
2
|
module YAML
|
3
3
|
class Opto::Resolvers::ServiceInstances < ::Opto::Resolver
|
4
|
+
include Kontena::Cli::Common
|
5
|
+
|
4
6
|
def resolve
|
5
7
|
return nil unless current_master && current_grid
|
8
|
+
require 'kontena/cli/stacks/show_command'
|
6
9
|
read_command = Kontena::Cli::Stacks::ShowCommand.new([self.stack])
|
7
10
|
stack = read_command.fetch_stack(self.stack)
|
8
11
|
service = stack['services'].find { |s| s['name'] == hint }
|
data/lib/kontena_cli.rb
CHANGED
@@ -34,7 +34,7 @@ module Kontena
|
|
34
34
|
else
|
35
35
|
command = cmdline
|
36
36
|
end
|
37
|
-
logger.debug { "Running Kontena.run(#{command.inspect}" }
|
37
|
+
logger.debug { "Running Kontena.run(#{command.inspect})" }
|
38
38
|
result = Kontena::MainCommand.new(File.basename(__FILE__)).run(command)
|
39
39
|
logger.debug { "Command completed, result: #{result.inspect} status: 0" }
|
40
40
|
result
|
@@ -1,23 +1,53 @@
|
|
1
|
+
require 'websocket-client-simple'
|
1
2
|
require 'kontena/cli/services/exec_command'
|
2
3
|
|
3
4
|
describe Kontena::Cli::Services::ExecCommand do
|
4
5
|
include ClientHelpers
|
5
6
|
include OutputHelpers
|
6
7
|
|
7
|
-
let
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
let(:ws_client_class) do
|
9
|
+
Class.new do
|
10
|
+
|
11
|
+
Event = Struct.new(:data)
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@callbacks = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def on(callback, &block)
|
18
|
+
@callbacks[callback] = block
|
19
|
+
if callback == :open
|
20
|
+
Thread.new {
|
21
|
+
sleep 0.01
|
22
|
+
@callbacks[:open].call
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def receive_message(msg)
|
28
|
+
@callbacks[:message].call(Event.new(JSON.dump(msg)))
|
29
|
+
rescue => exc
|
30
|
+
STDERR.puts exc.message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:ws_client) do
|
36
|
+
ws_client_class.new
|
37
|
+
end
|
38
|
+
|
39
|
+
let(:master_url) do
|
40
|
+
subject.require_current_master.url.gsub('http', 'ws')
|
41
|
+
end
|
42
|
+
|
43
|
+
def respond_ok(ws_client)
|
44
|
+
ws_client.receive_message({'stream' => 'stdout', 'chunk' => "ok\n"})
|
45
|
+
ws_client.receive_message({'exit' => 0})
|
13
46
|
end
|
14
47
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
["error\n"],
|
19
|
-
1, # exit
|
20
|
-
]
|
48
|
+
def respond_error(ws_client)
|
49
|
+
ws_client.receive_message({'stream' => 'stderr', 'chunk' => "error\n"})
|
50
|
+
ws_client.receive_message({'exit' => 1})
|
21
51
|
end
|
22
52
|
|
23
53
|
context "For a service with one running instance" do
|
@@ -37,11 +67,15 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
37
67
|
end
|
38
68
|
|
39
69
|
it "Executes on the running container by default" do
|
40
|
-
expect(
|
41
|
-
|
42
|
-
|
43
|
-
'
|
44
|
-
|
70
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
71
|
+
expect(ws_client).to receive(:send) do |foo|
|
72
|
+
ws_client.receive_message({'stream' => 'stdout', 'chunk' => "ok\n"})
|
73
|
+
ws_client.receive_message({'exit' => 0})
|
74
|
+
end
|
75
|
+
|
76
|
+
expect {
|
77
|
+
subject.run(['test-service', 'test'])
|
78
|
+
}.to output("ok\n").to_stdout
|
45
79
|
end
|
46
80
|
end
|
47
81
|
|
@@ -71,30 +105,46 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
71
105
|
|
72
106
|
it "Executes on the first running container by default" do
|
73
107
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
74
|
-
expect(
|
75
|
-
|
76
|
-
|
108
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
109
|
+
expect(ws_client).to receive(:send) do
|
110
|
+
respond_ok(ws_client)
|
111
|
+
end
|
112
|
+
expect {
|
113
|
+
subject.run(['test-service', 'test'])
|
114
|
+
}.to output("ok\n").to_stdout
|
77
115
|
end
|
78
116
|
|
79
117
|
it "Executes on the first running container, even if they are ordered differently" do
|
80
118
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return({'containers' => service_containers['containers'].reverse })
|
81
|
-
expect(
|
82
|
-
|
83
|
-
|
119
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
120
|
+
expect(ws_client).to receive(:send) do
|
121
|
+
respond_ok(ws_client)
|
122
|
+
end
|
123
|
+
expect {
|
124
|
+
subject.run(['test-service', 'test'])
|
125
|
+
}.to output("ok\n").to_stdout
|
84
126
|
end
|
85
127
|
|
86
128
|
it "Executes on the first running container if given" do
|
87
129
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
88
|
-
expect(
|
89
|
-
|
90
|
-
|
130
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
131
|
+
expect(ws_client).to receive(:send) do
|
132
|
+
respond_ok(ws_client)
|
133
|
+
end
|
134
|
+
expect {
|
135
|
+
subject.run(['test-service', 'test'])
|
136
|
+
}.to output("ok\n").to_stdout
|
91
137
|
end
|
92
138
|
|
93
139
|
it "Executes on the second running container if given" do
|
94
140
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
95
|
-
expect(
|
96
|
-
|
97
|
-
|
141
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-2/exec", anything).and_return(ws_client)
|
142
|
+
expect(ws_client).to receive(:send) do
|
143
|
+
respond_ok(ws_client)
|
144
|
+
end
|
145
|
+
expect {
|
146
|
+
subject.run(['--instance', '2', 'test-service', 'test'])
|
147
|
+
}.to output("ok\n").to_stdout
|
98
148
|
end
|
99
149
|
|
100
150
|
it "Errors on a nonexistant container if given" do
|
@@ -105,35 +155,71 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
105
155
|
|
106
156
|
it "Executes on each running container" do
|
107
157
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
108
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-1/exec', { cmd: ['test'] }).and_return(exec_ok)
|
109
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-2/exec', { cmd: ['test'] }).and_return(exec_ok)
|
110
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-3/exec', { cmd: ['test'] }).and_return(exec_ok)
|
111
158
|
|
112
|
-
|
159
|
+
3.times do |i|
|
160
|
+
ws_client = ws_client_class.new
|
161
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i + 1}/exec", anything).and_return(ws_client)
|
162
|
+
expect(ws_client).to receive(:send) do
|
163
|
+
ws_client.receive_message({'stream' => 'stdout', 'chunk' => "test#{i + 1}\n"})
|
164
|
+
ws_client.receive_message({'exit' => 0})
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
expect {
|
169
|
+
subject.run(['--silent', '--all', 'test-service', 'test'])
|
170
|
+
}.to output("test1\ntest2\ntest3\n").to_stdout
|
113
171
|
end
|
114
172
|
|
115
173
|
it "Stops if the first container fails" do
|
116
174
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
117
|
-
expect(
|
118
|
-
|
119
|
-
|
175
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
176
|
+
expect(ws_client).to receive(:send) do
|
177
|
+
respond_error(ws_client)
|
178
|
+
end
|
179
|
+
expect {
|
180
|
+
subject.run(['--silent', '--all', 'test-service', 'test'])
|
181
|
+
}.to output("error\n").to_stderr
|
120
182
|
end
|
121
183
|
|
122
184
|
it "Stops if the second container fails" do
|
123
185
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
186
|
+
i = 1
|
187
|
+
[:ok, :err].each do |status|
|
188
|
+
ws_client = ws_client_class.new
|
189
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
|
190
|
+
expect(ws_client).to receive(:send) do
|
191
|
+
if status == :ok
|
192
|
+
respond_ok(ws_client)
|
193
|
+
else
|
194
|
+
respond_error(ws_client)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
i += 1
|
198
|
+
end
|
199
|
+
expect {
|
200
|
+
subject.run(['--silent', '--all', 'test-service', 'test'])
|
201
|
+
}.to output("ok\n").to_stdout.and output("error\n").to_stderr
|
128
202
|
end
|
129
203
|
|
130
204
|
it "Keeps going if the second container fails when using --skip" do
|
131
205
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
132
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-1/exec', { cmd: ['test'] }).and_return(exec_ok)
|
133
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-2/exec', { cmd: ['test'] }).and_return(exec_fail)
|
134
|
-
expect(client).to receive(:post).with('containers/test-grid/host/test-service.container-3/exec', { cmd: ['test'] }).and_return(exec_ok)
|
135
206
|
|
136
|
-
|
207
|
+
i = 1
|
208
|
+
[:ok, :err, :ok].each do |status|
|
209
|
+
ws_client = ws_client_class.new
|
210
|
+
expect(WebSocket::Client::Simple).to receive(:connect).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
|
211
|
+
expect(ws_client).to receive(:send) do
|
212
|
+
if status == :ok
|
213
|
+
respond_ok(ws_client)
|
214
|
+
else
|
215
|
+
respond_error(ws_client)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
i += 1
|
219
|
+
end
|
220
|
+
expect {
|
221
|
+
subject.run(['--silent', '--all', '--skip', 'test-service', 'test'])
|
222
|
+
}.to output("ok\nok\n").to_stdout.and output("error\n").to_stderr
|
137
223
|
end
|
138
224
|
end
|
139
225
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kontena-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.0.
|
4
|
+
version: 1.3.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - "~>"
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: 0.8.0
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: websocket-client-simple
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - "~>"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 0.3.0
|
216
|
+
type: :runtime
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - "~>"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: 0.3.0
|
209
223
|
description: Command-line client for the Kontena container and microservices platform
|
210
224
|
email:
|
211
225
|
- info@kontena.io
|
@@ -339,6 +353,7 @@ files:
|
|
339
353
|
- lib/kontena/cli/grids/users/add_command.rb
|
340
354
|
- lib/kontena/cli/grids/users/list_command.rb
|
341
355
|
- lib/kontena/cli/grids/users/remove_command.rb
|
356
|
+
- lib/kontena/cli/helpers/exec_helper.rb
|
342
357
|
- lib/kontena/cli/helpers/health_helper.rb
|
343
358
|
- lib/kontena/cli/helpers/log_helper.rb
|
344
359
|
- lib/kontena/cli/localhost_web_server.rb
|