kontena-cli 1.3.0.rc2 → 1.3.0.rc3
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/VERSION +1 -1
- data/lib/kontena/autoload_core.rb +18 -0
- data/lib/kontena/callbacks/master/deploy/05_before_deploy_configuration_wizard.rb +1 -0
- data/lib/kontena/cli/cloud/master/update_command.rb +1 -1
- data/lib/kontena/cli/common.rb +6 -2
- data/lib/kontena/cli/containers/exec_command.rb +17 -12
- data/lib/kontena/cli/helpers/exec_helper.rb +43 -23
- data/lib/kontena/cli/services/exec_command.rb +34 -20
- data/lib/kontena/plugin_manager.rb +11 -27
- data/lib/kontena/plugin_manager/rubygems_client.rb +40 -0
- data/lib/kontena/websocket/client/connection.rb +1 -1
- data/lib/kontena_cli.rb +2 -1
- data/spec/kontena/cli/helpers/exec_helper_spec.rb +44 -0
- data/spec/kontena/cli/services/exec_command_spec.rb +17 -17
- data/spec/kontena/plugin_manager/rubygems_client_spec.rb +50 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56f2fe0867115aff48b183538824e13eb77810f8
|
|
4
|
+
data.tar.gz: 67d15d8bcf84c128a38b7c70ea65d65f8444d696
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dcb08e8ae2a117aee8c7e7e47b332d8fc5cd493bafd1e12c02e70e14d2a47295c10e71f33772d5806a13fc01bf807d42c4314d160e8e54f97bdfb2b0bdda8980
|
|
7
|
+
data.tar.gz: bec4e80d3179561182a5d59bd09f5d5d8c8f7ba47812b57fca560a80cb9e33980d76f2c3c098776a2042fdde114e3a17c94c2c29492ad63f210c9aadc4623f1e
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.3.0.
|
|
1
|
+
1.3.0.rc3
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# stdlib
|
|
2
|
+
autoload :JSON, 'json'
|
|
3
|
+
autoload :YAML, 'safe_yaml'
|
|
4
|
+
autoload :URI, 'uri'
|
|
5
|
+
autoload :Logger, 'logger'
|
|
6
|
+
autoload :FileUtils, 'fileutils'
|
|
7
|
+
autoload :Forwardable, 'forwardable'
|
|
8
|
+
autoload :Singleton, 'singleton'
|
|
9
|
+
autoload :Observable, 'observer'
|
|
10
|
+
autoload :Pathname, 'pathname'
|
|
11
|
+
autoload :DateTime, 'date'
|
|
12
|
+
autoload :Date, 'date'
|
|
13
|
+
autoload :Base64, 'base64'
|
|
14
|
+
autoload :SecureRandom, 'securerandom'
|
|
15
|
+
|
|
16
|
+
# dependencies
|
|
17
|
+
autoload :Excon, 'excon'
|
|
18
|
+
autoload :Opto, 'opto'
|
|
@@ -37,7 +37,7 @@ module Kontena::Cli::Cloud::Master
|
|
|
37
37
|
attrs["owner"] = self.owner if self.owner
|
|
38
38
|
|
|
39
39
|
response = cloud_client.put(
|
|
40
|
-
"
|
|
40
|
+
"user/masters/#{master_id}",
|
|
41
41
|
{ data: { attributes: attrs.reject{ |k, _| ['client-id', 'client-secret'].include?(k) } } }
|
|
42
42
|
)
|
|
43
43
|
|
data/lib/kontena/cli/common.rb
CHANGED
|
@@ -2,7 +2,13 @@ require 'forwardable'
|
|
|
2
2
|
require 'kontena_cli'
|
|
3
3
|
|
|
4
4
|
module Kontena
|
|
5
|
+
autoload :Client, 'kontena/client'
|
|
6
|
+
|
|
5
7
|
module Cli
|
|
8
|
+
autoload :ShellSpinner, 'kontena/cli/spinner'
|
|
9
|
+
autoload :Spinner, 'kontena/cli/spinner'
|
|
10
|
+
autoload :Config, 'kontena/cli/config'
|
|
11
|
+
|
|
6
12
|
module Common
|
|
7
13
|
extend Forwardable
|
|
8
14
|
|
|
@@ -27,12 +33,10 @@ module Kontena
|
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
def spinner(msg, &block)
|
|
30
|
-
require 'kontena/cli/spinner' unless Kontena::Cli.const_defined?(:Spinner)
|
|
31
36
|
Kontena::Cli::Spinner.spin(msg, &block)
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
def config
|
|
35
|
-
require 'kontena/cli/config' unless Kontena::Cli.const_defined?(:Config)
|
|
36
40
|
Kontena::Cli::Config.instance
|
|
37
41
|
end
|
|
38
42
|
|
|
@@ -10,36 +10,41 @@ module Kontena::Cli::Containers
|
|
|
10
10
|
parameter "CMD ...", "Command"
|
|
11
11
|
|
|
12
12
|
option ["--shell"], :flag, "Execute as a shell command"
|
|
13
|
-
option ["--interactive"], :flag, "Keep stdin open"
|
|
13
|
+
option ["-i", "--interactive"], :flag, "Keep stdin open"
|
|
14
|
+
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY"
|
|
14
15
|
|
|
15
16
|
def execute
|
|
17
|
+
exit_with_error "the input device is not a TTY" if tty? && !STDIN.tty?
|
|
18
|
+
|
|
16
19
|
require_api_url
|
|
17
20
|
token = require_token
|
|
18
21
|
cmd = JSON.dump({cmd: cmd_list})
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
url
|
|
22
|
+
queue = Queue.new
|
|
23
|
+
stdin_reader = nil
|
|
24
|
+
url = ws_url("#{current_grid}/#{container_id}", interactive: interactive?, shell: shell?, tty: tty?)
|
|
22
25
|
ws = connect(url, token)
|
|
23
|
-
|
|
24
26
|
ws.on :message do |msg|
|
|
25
|
-
|
|
27
|
+
data = parse_message(msg)
|
|
28
|
+
queue << data if data.is_a?(Hash)
|
|
26
29
|
end
|
|
27
30
|
ws.on :open do
|
|
28
31
|
ws.text(cmd)
|
|
32
|
+
stdin_reader = self.stream_stdin_to_ws(ws) if self.interactive?
|
|
29
33
|
end
|
|
30
34
|
ws.on :close do |e|
|
|
31
35
|
if e.reason.include?('code: 404')
|
|
32
|
-
|
|
36
|
+
queue << {'exit' => 1, 'message' => 'Not found'}
|
|
33
37
|
else
|
|
34
|
-
exit 1
|
|
38
|
+
queue << {'exit' => 1}
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
ws.connect
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
else
|
|
41
|
-
sleep
|
|
42
|
+
while msg = queue.pop
|
|
43
|
+
self.handle_message(msg)
|
|
42
44
|
end
|
|
45
|
+
rescue SystemExit
|
|
46
|
+
stdin_reader.kill if stdin_reader
|
|
47
|
+
raise
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
end
|
|
@@ -3,35 +3,41 @@ require_relative '../../websocket/client'
|
|
|
3
3
|
module Kontena::Cli::Helpers
|
|
4
4
|
module ExecHelper
|
|
5
5
|
|
|
6
|
-
# @param [WebSocket::Client::Simple] ws
|
|
6
|
+
# @param [WebSocket::Client::Simple] ws
|
|
7
7
|
# @return [Thread]
|
|
8
8
|
def stream_stdin_to_ws(ws)
|
|
9
9
|
require 'io/console'
|
|
10
10
|
Thread.new {
|
|
11
|
-
STDIN.
|
|
12
|
-
|
|
11
|
+
if STDIN.tty?
|
|
12
|
+
STDIN.raw {
|
|
13
|
+
while char = STDIN.readpartial(1024)
|
|
14
|
+
ws.text(JSON.dump({ stdin: char }))
|
|
15
|
+
end
|
|
16
|
+
}
|
|
17
|
+
else
|
|
18
|
+
while char = STDIN.gets
|
|
13
19
|
ws.text(JSON.dump({ stdin: char }))
|
|
14
20
|
end
|
|
15
|
-
|
|
21
|
+
ws.text(JSON.dump({ stdin: nil }))
|
|
22
|
+
end
|
|
16
23
|
}
|
|
17
24
|
end
|
|
18
25
|
|
|
19
|
-
# @param [
|
|
26
|
+
# @param [Hash] msg
|
|
20
27
|
def handle_message(msg)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
if msg.has_key?('exit')
|
|
29
|
+
if msg['message']
|
|
30
|
+
exit_with_error(msg['message'])
|
|
31
|
+
else
|
|
32
|
+
exit msg['exit'].to_i
|
|
33
|
+
end
|
|
34
|
+
elsif msg.has_key?('stream')
|
|
35
|
+
if msg['stream'] == 'stdout'
|
|
36
|
+
$stdout << msg['chunk']
|
|
37
|
+
else
|
|
38
|
+
$stderr << msg['chunk']
|
|
31
39
|
end
|
|
32
40
|
end
|
|
33
|
-
rescue => exc
|
|
34
|
-
$stderr << "#{exc.class.name}: #{exc.message}"
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
# @param [Websocket::Frame::Incoming] msg
|
|
@@ -41,12 +47,26 @@ module Kontena::Cli::Helpers
|
|
|
41
47
|
nil
|
|
42
48
|
end
|
|
43
49
|
|
|
44
|
-
# @param [String]
|
|
50
|
+
# @param container_id [String] The container id
|
|
51
|
+
# @param interactive [Boolean] Interactive TTY on/off
|
|
52
|
+
# @param shell [Boolean] Shell on/of
|
|
53
|
+
# @param tty [Boolean] TTY on/of
|
|
45
54
|
# @return [String]
|
|
46
|
-
def ws_url(container_id)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
def ws_url(container_id, interactive: false, shell: false, tty: false)
|
|
56
|
+
require 'uri' unless Object.const_defined?(:URI)
|
|
57
|
+
extend Kontena::Cli::Common unless self.respond_to?(:require_current_master)
|
|
58
|
+
|
|
59
|
+
url = URI.parse(require_current_master.url)
|
|
60
|
+
url.scheme = url.scheme.sub('http', 'ws')
|
|
61
|
+
url.path = "/v1/containers/#{container_id}/exec"
|
|
62
|
+
if shell || interactive || tty
|
|
63
|
+
query = {}
|
|
64
|
+
query.merge!(interactive: true) if interactive
|
|
65
|
+
query.merge!(shell: true) if shell
|
|
66
|
+
query.merge!(tty: true) if tty
|
|
67
|
+
url.query = URI.encode_www_form(query)
|
|
68
|
+
end
|
|
69
|
+
url.to_s
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
# @param [String] url
|
|
@@ -64,4 +84,4 @@ module Kontena::Cli::Helpers
|
|
|
64
84
|
Kontena::Websocket::Client.new(url, options)
|
|
65
85
|
end
|
|
66
86
|
end
|
|
67
|
-
end
|
|
87
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'shellwords'
|
|
2
|
+
require 'json'
|
|
2
3
|
require_relative 'services_helper'
|
|
3
4
|
require_relative '../helpers/exec_helper'
|
|
4
5
|
|
|
@@ -12,10 +13,11 @@ module Kontena::Cli::Services
|
|
|
12
13
|
parameter "NAME", "Service name"
|
|
13
14
|
parameter "CMD ...", "Command"
|
|
14
15
|
|
|
15
|
-
option ["
|
|
16
|
+
option ["--instance"], "INSTANCE", "Exec on given numbered instance, default first running" do |value| Integer(value) end
|
|
16
17
|
option ["-a", "--all"], :flag, "Exec on all running instances"
|
|
17
18
|
option ["--shell"], :flag, "Execute as a shell command"
|
|
18
|
-
option ["--interactive"], :flag, "Keep stdin open"
|
|
19
|
+
option ["-i", "--interactive"], :flag, "Keep stdin open"
|
|
20
|
+
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY"
|
|
19
21
|
option ["--skip"], :flag, "Skip failed instances when executing --all"
|
|
20
22
|
option ["--silent"], :flag, "Do not show exec status"
|
|
21
23
|
option ["--verbose"], :flag, "Show exec status"
|
|
@@ -24,12 +26,13 @@ module Kontena::Cli::Services
|
|
|
24
26
|
requires_current_grid
|
|
25
27
|
|
|
26
28
|
def execute
|
|
29
|
+
exit_with_error "the input device is not a TTY" if tty? && !STDIN.tty?
|
|
27
30
|
exit_with_error "--interactive cannot be used with --all" if all? && interactive?
|
|
28
31
|
|
|
29
32
|
service_containers = client.get("services/#{parse_service_id(name)}/containers")['containers']
|
|
30
33
|
service_containers.sort_by! { |container| container['instance_number'] }
|
|
31
34
|
running_containers = service_containers.select{|container| container['status'] == 'running' }
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
exit_with_error "Service #{name} does not have any running containers" if running_containers.empty?
|
|
34
37
|
|
|
35
38
|
if all?
|
|
@@ -51,13 +54,13 @@ module Kontena::Cli::Services
|
|
|
51
54
|
exit_with_error "Service #{name} container #{container['name']} is not running, it is #{container['status']}"
|
|
52
55
|
elsif interactive?
|
|
53
56
|
interactive_exec(container)
|
|
54
|
-
else
|
|
57
|
+
else
|
|
55
58
|
exec_container(container)
|
|
56
59
|
end
|
|
57
60
|
else
|
|
58
61
|
if interactive?
|
|
59
62
|
interactive_exec(running_containers.first)
|
|
60
|
-
else
|
|
63
|
+
else
|
|
61
64
|
exec_container(running_containers.first)
|
|
62
65
|
end
|
|
63
66
|
end
|
|
@@ -89,17 +92,15 @@ module Kontena::Cli::Services
|
|
|
89
92
|
cmd = JSON.dump({ cmd: cmd_list })
|
|
90
93
|
exit_status = nil
|
|
91
94
|
token = require_token
|
|
92
|
-
|
|
93
|
-
url << 'shell=true' if shell?
|
|
94
|
-
ws = connect(url, token)
|
|
95
|
+
ws = connect(url(container['id']), token)
|
|
95
96
|
ws.on :message do |msg|
|
|
96
97
|
data = base.parse_message(msg)
|
|
97
|
-
if data
|
|
98
|
+
if data
|
|
98
99
|
if data['exit']
|
|
99
100
|
exit_status = data['exit'].to_i
|
|
100
101
|
elsif data['stream'] == 'stdout'
|
|
101
102
|
$stdout << data['chunk']
|
|
102
|
-
else
|
|
103
|
+
else
|
|
103
104
|
$stderr << data['chunk']
|
|
104
105
|
end
|
|
105
106
|
end
|
|
@@ -107,7 +108,7 @@ module Kontena::Cli::Services
|
|
|
107
108
|
ws.on :open do
|
|
108
109
|
ws.text(cmd)
|
|
109
110
|
end
|
|
110
|
-
ws.on :close do |e|
|
|
111
|
+
ws.on :close do |e|
|
|
111
112
|
exit_status = 1 if exit_status.nil? && e.code != 1000
|
|
112
113
|
end
|
|
113
114
|
ws.connect
|
|
@@ -121,22 +122,35 @@ module Kontena::Cli::Services
|
|
|
121
122
|
def interactive_exec(container)
|
|
122
123
|
token = require_token
|
|
123
124
|
cmd = JSON.dump({ cmd: cmd_list })
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
ws = connect(url, token)
|
|
125
|
+
queue = Queue.new
|
|
126
|
+
stdin_stream = nil
|
|
127
|
+
ws = connect(url(container['id']), token)
|
|
128
128
|
ws.on :message do |msg|
|
|
129
|
-
|
|
129
|
+
data = self.parse_message(msg)
|
|
130
|
+
queue << data if data.is_a?(Hash)
|
|
130
131
|
end
|
|
131
132
|
ws.on :open do
|
|
132
133
|
ws.text(cmd)
|
|
134
|
+
stdin_stream = self.stream_stdin_to_ws(ws)
|
|
133
135
|
end
|
|
134
136
|
ws.on :close do |e|
|
|
135
|
-
|
|
137
|
+
if e.code != 1000
|
|
138
|
+
queue << {'exit' => 1}
|
|
139
|
+
else
|
|
140
|
+
queue << {'exit' => 0}
|
|
141
|
+
end
|
|
136
142
|
end
|
|
137
143
|
ws.connect
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
while msg = queue.pop
|
|
145
|
+
self.handle_message(msg)
|
|
146
|
+
end
|
|
147
|
+
rescue SystemExit
|
|
148
|
+
stdin_stream.kill if stdin_stream
|
|
149
|
+
raise
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def url(container_id)
|
|
153
|
+
ws_url(container_id, shell: shell?, interactive: interactive?, tty: tty?)
|
|
140
154
|
end
|
|
141
155
|
end
|
|
142
|
-
end
|
|
156
|
+
end
|
|
@@ -2,6 +2,14 @@ require 'singleton'
|
|
|
2
2
|
|
|
3
3
|
module Kontena
|
|
4
4
|
class PluginManager
|
|
5
|
+
autoload :RubygemsClient, 'kontena/plugin_manager/rubygems_client'
|
|
6
|
+
Gem.autoload :DependencyInstaller, 'rubygems/dependency_installer'
|
|
7
|
+
Gem.autoload :Requirement, 'rubygems/requirement'
|
|
8
|
+
Gem.autoload :Uninstaller, 'rubygems/uninstaller'
|
|
9
|
+
Gem.autoload :Commands, 'rubygems/command'
|
|
10
|
+
Gem.autoload :DefaultUserInteraction, 'rubygems/user_interaction'
|
|
11
|
+
Gem.autoload :StreamUI, 'rubygems/user_interaction'
|
|
12
|
+
Gem::Commands.autoload :CleanupCommand, 'rubygems/commands/cleanup_command'
|
|
5
13
|
|
|
6
14
|
include Singleton
|
|
7
15
|
|
|
@@ -22,9 +30,6 @@ module Kontena
|
|
|
22
30
|
# @param pre [Boolean] install a prerelease version if available
|
|
23
31
|
# @param version [String] install a specific version
|
|
24
32
|
def install_plugin(plugin_name, pre: false, version: nil)
|
|
25
|
-
require 'rubygems/dependency_installer'
|
|
26
|
-
require 'rubygems/requirement'
|
|
27
|
-
|
|
28
33
|
cmd = Gem::DependencyInstaller.new(
|
|
29
34
|
document: false,
|
|
30
35
|
force: true,
|
|
@@ -42,7 +47,6 @@ module Kontena
|
|
|
42
47
|
installed = installed(plugin_name)
|
|
43
48
|
raise "Plugin #{plugin_name} not installed" unless installed
|
|
44
49
|
|
|
45
|
-
require 'rubygems/uninstaller'
|
|
46
50
|
cmd = Gem::Uninstaller.new(
|
|
47
51
|
installed.name,
|
|
48
52
|
all: true,
|
|
@@ -56,31 +60,13 @@ module Kontena
|
|
|
56
60
|
# Search rubygems for kontena plugins
|
|
57
61
|
# @param pattern [String] optional search pattern
|
|
58
62
|
def search_plugins(pattern = nil)
|
|
59
|
-
|
|
60
|
-
response = client.get(
|
|
61
|
-
path: "/api/v1/search.json?query=#{prefix(pattern)}",
|
|
62
|
-
headers: {
|
|
63
|
-
'Content-Type' => 'application/json',
|
|
64
|
-
'Accept' => 'application/json'
|
|
65
|
-
}
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
JSON.parse(response.body) rescue nil
|
|
63
|
+
RubygemsClient.new.search(prefix(pattern))
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
# Retrieve plugin versions from rubygems
|
|
72
67
|
# @param plugin_name [String]
|
|
73
68
|
def gem_versions(plugin_name)
|
|
74
|
-
|
|
75
|
-
response = client.get(
|
|
76
|
-
path: "/api/v1/versions/#{prefix(plugin_name)}.json",
|
|
77
|
-
headers: {
|
|
78
|
-
'Content-Type' => 'application/json',
|
|
79
|
-
'Accept' => 'application/json'
|
|
80
|
-
}
|
|
81
|
-
)
|
|
82
|
-
versions = JSON.parse(response.body)
|
|
83
|
-
versions.map { |version| Gem::Version.new(version["number"]) }.sort.reverse
|
|
69
|
+
RubygemsClient.new.versions(prefix(plugin_name))
|
|
84
70
|
end
|
|
85
71
|
|
|
86
72
|
# Get the latest version number from rubygems
|
|
@@ -120,7 +106,6 @@ module Kontena
|
|
|
120
106
|
# Runs gem cleanup, removes remains from previous versions
|
|
121
107
|
# @param plugin_name [String]
|
|
122
108
|
def cleanup_plugin(plugin_name)
|
|
123
|
-
require 'rubygems/commands/cleanup_command'
|
|
124
109
|
cmd = Gem::Commands::CleanupCommand.new
|
|
125
110
|
options = []
|
|
126
111
|
options += ['-q', '--no-verbose'] unless ENV["DEBUG"]
|
|
@@ -199,7 +184,7 @@ module Kontena
|
|
|
199
184
|
$stderr.puts " To update the plugin, run 'kontena plugin install #{plugin_name}'"
|
|
200
185
|
end
|
|
201
186
|
rescue ScriptError, StandardError => ex
|
|
202
|
-
warn " [#{Kontena.pastel.red('error')}] Failed to load plugin: #{spec.name}\n\tRerun the command with environment DEBUG=true set to get the full exception."
|
|
187
|
+
warn " [#{Kontena.pastel.red('error')}] Failed to load plugin: #{spec.name} from #{spec.gem_dir}\n\tRerun the command with environment DEBUG=true set to get the full exception."
|
|
203
188
|
Kontena.logger.error(ex)
|
|
204
189
|
end
|
|
205
190
|
end
|
|
@@ -221,7 +206,6 @@ module Kontena
|
|
|
221
206
|
end
|
|
222
207
|
|
|
223
208
|
def use_dummy_ui
|
|
224
|
-
require 'rubygems/user_interaction'
|
|
225
209
|
Gem::DefaultUserInteraction.ui = dummy_ui
|
|
226
210
|
end
|
|
227
211
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'excon'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
module Kontena
|
|
5
|
+
class PluginManager
|
|
6
|
+
class RubygemsClient
|
|
7
|
+
|
|
8
|
+
RUBYGEMS_URL = 'https://rubygems.org'
|
|
9
|
+
HEADERS = {
|
|
10
|
+
'Content-Type' => 'application/json',
|
|
11
|
+
'Accept' => 'application/json'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
attr_reader :client
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@client = Excon.new(RUBYGEMS_URL)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def search(pattern = nil)
|
|
21
|
+
response = client.get(
|
|
22
|
+
path: "/api/v1/search.json?query=#{pattern}",
|
|
23
|
+
headers: HEADERS
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
JSON.parse(response.body)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def versions(gem_name)
|
|
30
|
+
response = client.get(
|
|
31
|
+
path: "/api/v1/versions/#{gem_name}.json",
|
|
32
|
+
headers: HEADERS
|
|
33
|
+
)
|
|
34
|
+
versions = JSON.parse(response.body)
|
|
35
|
+
versions.map { |version| Gem::Version.new(version["number"]) }.sort.reverse
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/kontena_cli.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'kontena/autoload_core'
|
|
2
2
|
|
|
3
3
|
$KONTENA_START_TIME = Time.now.to_f
|
|
4
4
|
at_exit do
|
|
@@ -9,6 +9,7 @@ end
|
|
|
9
9
|
module Kontena
|
|
10
10
|
module Cli
|
|
11
11
|
autoload :Config, 'kontena/cli/config'
|
|
12
|
+
autoload :ShellSpinner, 'kontena/cli/spinner'
|
|
12
13
|
autoload :Spinner, 'kontena/cli/spinner'
|
|
13
14
|
autoload :Common, 'kontena/cli/common'
|
|
14
15
|
autoload :TableGenerator, 'kontena/cli/table_generator'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require "kontena/cli/helpers/exec_helper"
|
|
2
|
+
|
|
3
|
+
describe Kontena::Cli::Helpers::ExecHelper do
|
|
4
|
+
|
|
5
|
+
include ClientHelpers
|
|
6
|
+
|
|
7
|
+
let(:described_class) do
|
|
8
|
+
Class.new do
|
|
9
|
+
include Kontena::Cli::Helpers::ExecHelper
|
|
10
|
+
def initialize(*args)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '#ws_url' do
|
|
16
|
+
it 'returns an exec url for a container id' do
|
|
17
|
+
expect(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl/'))
|
|
18
|
+
expect(subject.ws_url('abcd1234')).to eq 'ws://someurl/v1/containers/abcd1234/exec'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'also works when the url does not have a trailing slash' do
|
|
22
|
+
expect(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl'))
|
|
23
|
+
expect(subject.ws_url('abcd1234')).to eq 'ws://someurl/v1/containers/abcd1234/exec'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'query params' do
|
|
27
|
+
before(:each) do
|
|
28
|
+
allow(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl'))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'can add the interactive query param' do
|
|
32
|
+
expect(subject.ws_url('abcd1234', interactive: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?interactive=true'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'can add the shell query param' do
|
|
36
|
+
expect(subject.ws_url('abcd1234', shell: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?shell=true'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'can add both query params' do
|
|
40
|
+
expect(subject.ws_url('abcd1234', shell: true, interactive: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?interactive=true&shell=true'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -17,9 +17,9 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
17
17
|
def on(callback, &block)
|
|
18
18
|
@callbacks[callback] = block
|
|
19
19
|
if callback == :open
|
|
20
|
-
Thread.new {
|
|
21
|
-
sleep 0.01
|
|
22
|
-
@callbacks[:open].call
|
|
20
|
+
Thread.new {
|
|
21
|
+
sleep 0.01
|
|
22
|
+
@callbacks[:open].call
|
|
23
23
|
}
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -28,7 +28,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
28
28
|
|
|
29
29
|
def receive_message(msg)
|
|
30
30
|
@callbacks[:message].call(Event.new(JSON.dump(msg)))
|
|
31
|
-
rescue => exc
|
|
31
|
+
rescue => exc
|
|
32
32
|
STDERR.puts exc.message
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -69,12 +69,12 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
it "Executes on the running container by default" do
|
|
72
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec
|
|
72
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
|
73
73
|
expect(ws_client).to receive(:text) do |foo|
|
|
74
74
|
ws_client.receive_message({'stream' => 'stdout', 'chunk' => "ok\n"})
|
|
75
75
|
ws_client.receive_message({'exit' => 0})
|
|
76
76
|
end
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
expect {
|
|
79
79
|
subject.run(['test-service', 'test'])
|
|
80
80
|
}.to output("ok\n").to_stdout
|
|
@@ -107,7 +107,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
107
107
|
|
|
108
108
|
it "Executes on the first running container by default" do
|
|
109
109
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
|
110
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec
|
|
110
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
|
111
111
|
expect(ws_client).to receive(:text) do
|
|
112
112
|
respond_ok(ws_client)
|
|
113
113
|
end
|
|
@@ -118,7 +118,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
118
118
|
|
|
119
119
|
it "Executes on the first running container, even if they are ordered differently" do
|
|
120
120
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return({'containers' => service_containers['containers'].reverse })
|
|
121
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec
|
|
121
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
|
122
122
|
expect(ws_client).to receive(:text) do
|
|
123
123
|
respond_ok(ws_client)
|
|
124
124
|
end
|
|
@@ -129,7 +129,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
129
129
|
|
|
130
130
|
it "Executes on the first running container if given" do
|
|
131
131
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
|
132
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec
|
|
132
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
|
133
133
|
expect(ws_client).to receive(:text) do
|
|
134
134
|
respond_ok(ws_client)
|
|
135
135
|
end
|
|
@@ -140,7 +140,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
140
140
|
|
|
141
141
|
it "Executes on the second running container if given" do
|
|
142
142
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
|
143
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-2/exec
|
|
143
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-2/exec", anything).and_return(ws_client)
|
|
144
144
|
expect(ws_client).to receive(:text) do
|
|
145
145
|
respond_ok(ws_client)
|
|
146
146
|
end
|
|
@@ -160,13 +160,13 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
160
160
|
|
|
161
161
|
3.times do |i|
|
|
162
162
|
ws_client = ws_client_class.new
|
|
163
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i + 1}/exec
|
|
163
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i + 1}/exec", anything).and_return(ws_client)
|
|
164
164
|
expect(ws_client).to receive(:text) do
|
|
165
165
|
ws_client.receive_message({'stream' => 'stdout', 'chunk' => "test#{i + 1}\n"})
|
|
166
166
|
ws_client.receive_message({'exit' => 0})
|
|
167
167
|
end
|
|
168
168
|
end
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
expect {
|
|
171
171
|
subject.run(['--silent', '--all', 'test-service', 'test'])
|
|
172
172
|
}.to output("test1\ntest2\ntest3\n").to_stdout
|
|
@@ -174,7 +174,7 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
174
174
|
|
|
175
175
|
it "Stops if the first container fails" do
|
|
176
176
|
expect(client).to receive(:get).with('services/test-grid/null/test-service/containers').and_return(service_containers)
|
|
177
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec
|
|
177
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-1/exec", anything).and_return(ws_client)
|
|
178
178
|
expect(ws_client).to receive(:text) do
|
|
179
179
|
respond_error(ws_client)
|
|
180
180
|
end
|
|
@@ -188,11 +188,11 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
188
188
|
i = 1
|
|
189
189
|
[:ok, :err].each do |status|
|
|
190
190
|
ws_client = ws_client_class.new
|
|
191
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec
|
|
191
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
|
|
192
192
|
expect(ws_client).to receive(:text) do
|
|
193
193
|
if status == :ok
|
|
194
194
|
respond_ok(ws_client)
|
|
195
|
-
else
|
|
195
|
+
else
|
|
196
196
|
respond_error(ws_client)
|
|
197
197
|
end
|
|
198
198
|
end
|
|
@@ -209,11 +209,11 @@ describe Kontena::Cli::Services::ExecCommand do
|
|
|
209
209
|
i = 1
|
|
210
210
|
[:ok, :err, :ok].each do |status|
|
|
211
211
|
ws_client = ws_client_class.new
|
|
212
|
-
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec
|
|
212
|
+
expect(Kontena::Websocket::Client).to receive(:new).with("#{master_url}v1/containers/test-grid/host/test-service.container-#{i}/exec", anything).and_return(ws_client)
|
|
213
213
|
expect(ws_client).to receive(:text) do
|
|
214
214
|
if status == :ok
|
|
215
215
|
respond_ok(ws_client)
|
|
216
|
-
else
|
|
216
|
+
else
|
|
217
217
|
respond_error(ws_client)
|
|
218
218
|
end
|
|
219
219
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'kontena/plugin_manager/rubygems_client'
|
|
2
|
+
require 'json'
|
|
3
|
+
|
|
4
|
+
describe Kontena::PluginManager::RubygemsClient do
|
|
5
|
+
let(:subject) { described_class.new }
|
|
6
|
+
let(:client) { double }
|
|
7
|
+
|
|
8
|
+
before(:each) do
|
|
9
|
+
allow(subject).to receive(:client).and_return(client)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
context '#search' do
|
|
13
|
+
it 'searches rubygems and returns a hash' do
|
|
14
|
+
expect(client)
|
|
15
|
+
.to receive(:get)
|
|
16
|
+
.with(
|
|
17
|
+
hash_including(
|
|
18
|
+
path: "/api/v1/search.json?query=foofoo",
|
|
19
|
+
headers: hash_including(
|
|
20
|
+
'Content-Type' => 'application/json',
|
|
21
|
+
'Accept' => 'application/json'
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
.and_return(double(body: JSON.dump(foo: 'bar')))
|
|
26
|
+
expect(subject.search('foofoo')['foo']).to eq 'bar'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context '#versions' do
|
|
31
|
+
it 'fetches version list from rubygems and returns an array of Gem::Versions' do
|
|
32
|
+
expect(client)
|
|
33
|
+
.to receive(:get)
|
|
34
|
+
.with(
|
|
35
|
+
hash_including(
|
|
36
|
+
path: "/api/v1/versions/foofoo.json",
|
|
37
|
+
headers: hash_including(
|
|
38
|
+
'Content-Type' => 'application/json',
|
|
39
|
+
'Accept' => 'application/json'
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
.and_return(double(body: JSON.dump([{'number' => '0.1.0'}, {'number' => '0.2.0'}])))
|
|
44
|
+
versions = subject.versions('foofoo')
|
|
45
|
+
expect(versions.first).to be_kind_of Gem::Version
|
|
46
|
+
expect(versions.first.to_s).to eq '0.2.0'
|
|
47
|
+
expect(versions.last.to_s).to eq '0.1.0'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
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.rc3
|
|
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-06-
|
|
11
|
+
date: 2017-06-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -251,6 +251,7 @@ files:
|
|
|
251
251
|
- examples/kontena-plugin-hello/lib/kontena_cli_plugin.rb
|
|
252
252
|
- kontena-cli.gemspec
|
|
253
253
|
- kontena-docker.sh
|
|
254
|
+
- lib/kontena/autoload_core.rb
|
|
254
255
|
- lib/kontena/callback.rb
|
|
255
256
|
- lib/kontena/callbacks/.gitkeep
|
|
256
257
|
- lib/kontena/callbacks/auth/01_list_and_select_grid_after_master_auth.rb
|
|
@@ -516,6 +517,7 @@ files:
|
|
|
516
517
|
- lib/kontena/machine/random_name.rb
|
|
517
518
|
- lib/kontena/main_command.rb
|
|
518
519
|
- lib/kontena/plugin_manager.rb
|
|
520
|
+
- lib/kontena/plugin_manager/rubygems_client.rb
|
|
519
521
|
- lib/kontena/presets/github_auth_provider.yml
|
|
520
522
|
- lib/kontena/presets/kontena_auth_provider.yml
|
|
521
523
|
- lib/kontena/scripts/completer
|
|
@@ -608,6 +610,7 @@ files:
|
|
|
608
610
|
- spec/kontena/cli/grids/trusted_subnets/remove_command_spec.rb
|
|
609
611
|
- spec/kontena/cli/grids/update_command_spec.rb
|
|
610
612
|
- spec/kontena/cli/grids/use_command_spec.rb
|
|
613
|
+
- spec/kontena/cli/helpers/exec_helper_spec.rb
|
|
611
614
|
- spec/kontena/cli/helpers/log_helper_spec.rb
|
|
612
615
|
- spec/kontena/cli/main_command_spec.rb
|
|
613
616
|
- spec/kontena/cli/master/current_command_spec.rb
|
|
@@ -667,6 +670,7 @@ files:
|
|
|
667
670
|
- spec/kontena/config_spec.rb
|
|
668
671
|
- spec/kontena/kontena_cli_spec.rb
|
|
669
672
|
- spec/kontena/main_command_spec.rb
|
|
673
|
+
- spec/kontena/plugin_manager/rubygems_client_spec.rb
|
|
670
674
|
- spec/kontena/plugin_manager_spec.rb
|
|
671
675
|
- spec/spec_helper.rb
|
|
672
676
|
- spec/support/client_helpers.rb
|
|
@@ -761,6 +765,7 @@ test_files:
|
|
|
761
765
|
- spec/kontena/cli/grids/trusted_subnets/remove_command_spec.rb
|
|
762
766
|
- spec/kontena/cli/grids/update_command_spec.rb
|
|
763
767
|
- spec/kontena/cli/grids/use_command_spec.rb
|
|
768
|
+
- spec/kontena/cli/helpers/exec_helper_spec.rb
|
|
764
769
|
- spec/kontena/cli/helpers/log_helper_spec.rb
|
|
765
770
|
- spec/kontena/cli/main_command_spec.rb
|
|
766
771
|
- spec/kontena/cli/master/current_command_spec.rb
|
|
@@ -820,6 +825,7 @@ test_files:
|
|
|
820
825
|
- spec/kontena/config_spec.rb
|
|
821
826
|
- spec/kontena/kontena_cli_spec.rb
|
|
822
827
|
- spec/kontena/main_command_spec.rb
|
|
828
|
+
- spec/kontena/plugin_manager/rubygems_client_spec.rb
|
|
823
829
|
- spec/kontena/plugin_manager_spec.rb
|
|
824
830
|
- spec/spec_helper.rb
|
|
825
831
|
- spec/support/client_helpers.rb
|