kontena-cli 0.15.1 → 0.15.2
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 +6 -6
- data/lib/kontena/cli/apps/common.rb +2 -2
- data/lib/kontena/cli/apps/logs_command.rb +33 -42
- data/lib/kontena/cli/apps/yaml/reader.rb +16 -5
- data/lib/kontena/cli/apps/yaml/validations.rb +6 -3
- data/lib/kontena/cli/grids/logs_command.rb +17 -55
- data/lib/kontena/cli/helpers/log_helper.rb +68 -0
- data/lib/kontena/cli/services/logs_command.rb +12 -36
- data/lib/kontena/scripts/completer +3 -3
- data/spec/fixtures/kontena-internal-extend.yml +8 -0
- data/spec/fixtures/kontena_numeric_version.yml +9 -0
- data/spec/kontena/cli/app/common_spec.rb +12 -1
- data/spec/kontena/cli/app/logs_command_spec.rb +133 -0
- data/spec/kontena/cli/app/yaml/reader_spec.rb +47 -32
- data/spec/kontena/cli/app/yaml/validator_spec.rb +20 -0
- data/spec/kontena/cli/helpers/log_helper_spec.rb +58 -0
- metadata +32 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0799760c154ff891c9e67c2a893b47f47d9012c6
|
4
|
+
data.tar.gz: 075dcb4a30e995da47f86153b02d54b66e6576b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 943320e9d2f861ec68da9b0f4796bd268ec302bde7094ef440af6b578bc0f9b997ddf5dd913289cbe4fb40b78b049e0babdc52ec42de10cb3c64bec6543c449d
|
7
|
+
data.tar.gz: 74450cc8ab63055a6bd109f967d7c730e16c437236fb1c7b43825b73ade44dcad10fcbfdc9048eaa32d4c00e8f22585ea70a0035bea79898b973b1c1c92db018
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.15.
|
1
|
+
0.15.2
|
data/kontena-cli.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["info@kontena.io"]
|
11
11
|
spec.summary = %q{Kontena command line tool}
|
12
12
|
spec.description = %q{Kontena command line tool}
|
13
|
-
spec.homepage = "
|
13
|
+
spec.homepage = "https://www.kontena.io"
|
14
14
|
spec.license = "Apache-2.0"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -23,11 +23,11 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
25
|
spec.add_runtime_dependency "excon", "~> 0.49.0"
|
26
|
-
spec.add_runtime_dependency "colorize"
|
27
|
-
spec.add_runtime_dependency "clamp"
|
28
|
-
spec.add_runtime_dependency "highline"
|
29
|
-
spec.add_runtime_dependency "shell-spinner"
|
30
|
-
spec.add_runtime_dependency "ruby_dig"
|
26
|
+
spec.add_runtime_dependency "colorize", "~> 0.8.1"
|
27
|
+
spec.add_runtime_dependency "clamp", "~> 1.0.0"
|
28
|
+
spec.add_runtime_dependency "highline", "~> 1.7.8"
|
29
|
+
spec.add_runtime_dependency "shell-spinner", "~> 1.0.4"
|
30
|
+
spec.add_runtime_dependency "ruby_dig", "~> 0.0.2"
|
31
31
|
spec.add_runtime_dependency "dry-validation", "~> 0.8.0"
|
32
32
|
spec.add_runtime_dependency "dry-configurable", "~> 0.1.6"
|
33
33
|
end
|
@@ -33,7 +33,7 @@ module Kontena::Cli::Apps
|
|
33
33
|
# @return [Hash]
|
34
34
|
def generate_services(yaml_services, version)
|
35
35
|
services = {}
|
36
|
-
if version ==
|
36
|
+
if version.to_i == 2
|
37
37
|
generator_klass = ServiceGeneratorV2
|
38
38
|
else
|
39
39
|
generator_klass = ServiceGenerator
|
@@ -63,7 +63,7 @@ module Kontena::Cli::Apps
|
|
63
63
|
def project_name_from_yaml(file)
|
64
64
|
reader = YAML::Reader.new(file, true)
|
65
65
|
outcome = reader.execute
|
66
|
-
if outcome[:version] ==
|
66
|
+
if outcome[:version].to_i == 2
|
67
67
|
outcome[:name]
|
68
68
|
else
|
69
69
|
nil
|
@@ -1,9 +1,12 @@
|
|
1
1
|
require_relative 'common'
|
2
|
+
require_relative '../helpers/log_helper'
|
2
3
|
|
3
4
|
module Kontena::Cli::Apps
|
4
5
|
class LogsCommand < Clamp::Command
|
5
6
|
include Kontena::Cli::Common
|
6
7
|
include Kontena::Cli::GridOptions
|
8
|
+
include Kontena::Cli::Helpers::LogHelper
|
9
|
+
|
7
10
|
include Common
|
8
11
|
|
9
12
|
option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
|
@@ -13,60 +16,48 @@ module Kontena::Cli::Apps
|
|
13
16
|
option ["-t", "--tail"], :flag, "Tail (follow) logs", default: false
|
14
17
|
parameter "[SERVICE] ...", "Show only specified service logs"
|
15
18
|
|
16
|
-
attr_reader :services
|
17
|
-
|
18
19
|
def execute
|
19
20
|
require_config_file(filename)
|
20
21
|
|
21
|
-
|
22
|
-
if services.size > 0
|
23
|
-
show_logs(services)
|
24
|
-
elsif !service_list.empty?
|
25
|
-
puts "No such service: #{service_list.join(', ')}".colorize(:red)
|
26
|
-
end
|
22
|
+
services = services_from_yaml(filename, service_list, service_prefix)
|
27
23
|
|
28
|
-
|
24
|
+
if services.empty? && !service_list.empty?
|
25
|
+
signal_error "No such service: #{service_list.join(', ')}"
|
26
|
+
elsif services.empty?
|
27
|
+
signal_error "No services for application"
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
services
|
39
|
-
|
40
|
-
|
41
|
-
logs = logs + result['logs'] if result && result['logs']
|
42
|
-
end
|
43
|
-
logs.sort!{|x,y| DateTime.parse(x['created_at']) <=> DateTime.parse(y['created_at'])}
|
44
|
-
logs.each do |log|
|
45
|
-
color = color_for_container(log['name'])
|
46
|
-
prefix = "#{log['created_at']} #{log['name']}:".colorize(color)
|
47
|
-
puts "#{prefix} #{log['data']}"
|
48
|
-
last_id = log['id']
|
49
|
-
end
|
50
|
-
break unless tail?
|
51
|
-
sleep(2)
|
30
|
+
query_services = services.map{|service_name, opts| prefixed_name(service_name)}.join ','
|
31
|
+
query_params = {
|
32
|
+
services: query_services,
|
33
|
+
limit: lines,
|
34
|
+
}
|
35
|
+
query_params[:since] = since if since
|
36
|
+
|
37
|
+
if tail?
|
38
|
+
tail_logs(services, query_params)
|
39
|
+
else
|
40
|
+
show_logs(services, query_params)
|
52
41
|
end
|
53
42
|
end
|
54
43
|
|
55
|
-
def
|
56
|
-
|
57
|
-
|
44
|
+
def tail_logs(services, query_params)
|
45
|
+
stream_logs("grids/#{current_grid}/container_logs", query_params) do |log|
|
46
|
+
show_log(log)
|
47
|
+
end
|
58
48
|
end
|
59
49
|
|
60
|
-
def
|
61
|
-
|
50
|
+
def show_logs(services, query_params)
|
51
|
+
result = client(token).get("grids/#{current_grid}/container_logs", query_params)
|
52
|
+
result['logs'].each do |log|
|
53
|
+
show_log(log)
|
54
|
+
end
|
62
55
|
end
|
63
56
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
@colors
|
57
|
+
def show_log(log)
|
58
|
+
color = color_for_container(log['name'])
|
59
|
+
prefix = "#{log['created_at']} #{log['name']}:".colorize(color)
|
60
|
+
puts "#{prefix} #{log['data']}"
|
70
61
|
end
|
71
62
|
end
|
72
63
|
end
|
@@ -126,15 +126,26 @@ module Kontena::Cli::Apps
|
|
126
126
|
# @param [Hash] service_config
|
127
127
|
# @return [Hash] updated service config
|
128
128
|
def extend_config(service_config)
|
129
|
-
|
129
|
+
extended_service = extended_service(service_config['extends'])
|
130
|
+
return unless extended_service
|
130
131
|
filename = service_config['extends']['file']
|
131
132
|
if filename
|
132
|
-
|
133
|
+
parent_config = from_external_file(filename, extended_service)
|
133
134
|
else
|
134
|
-
abort("Service '#{
|
135
|
-
|
135
|
+
abort("Service '#{extended_service}' not found in #{file}".colorize(:red)) unless services.key?(extended_service)
|
136
|
+
parent_config = process_config(services[extended_service])
|
137
|
+
end
|
138
|
+
ServiceExtender.new(service_config).extend(parent_config)
|
139
|
+
end
|
140
|
+
|
141
|
+
def extended_service(extend_config)
|
142
|
+
if extend_config.is_a?(Hash)
|
143
|
+
extend_config['service']
|
144
|
+
elsif extend_config.is_a?(String)
|
145
|
+
extend_config
|
146
|
+
else
|
147
|
+
nil
|
136
148
|
end
|
137
|
-
ServiceExtender.new(service_config).extend(parent_service)
|
138
149
|
end
|
139
150
|
|
140
151
|
def from_external_file(filename, service_name)
|
@@ -4,9 +4,12 @@ module Kontena::Cli::Apps::YAML
|
|
4
4
|
def append_common_validations(base)
|
5
5
|
base.optional('image').maybe(:str?)
|
6
6
|
|
7
|
-
base.optional('extends')
|
8
|
-
|
9
|
-
|
7
|
+
base.optional('extends') { str? | type?(Hash) }
|
8
|
+
base.rule(when_extends_is_hash: ['extends']) do |extends|
|
9
|
+
extends.type?(Hash) > extends.schema do
|
10
|
+
required('service').filled(:str?)
|
11
|
+
optional('file').value(:str?)
|
12
|
+
end
|
10
13
|
end
|
11
14
|
|
12
15
|
base.optional('stateful') { bool? }
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require_relative '../helpers/log_helper'
|
2
|
+
|
1
3
|
module Kontena::Cli::Grids
|
2
4
|
class LogsCommand < Clamp::Command
|
3
5
|
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::Helpers::LogHelper
|
4
7
|
|
5
8
|
option ["-t", "--tail"], :flag, "Tail (follow) logs", default: false
|
6
9
|
option "--lines", "LINES", "Number of lines to show from the end of the logs"
|
@@ -9,9 +12,13 @@ module Kontena::Cli::Grids
|
|
9
12
|
option "--service", "SERVICE", "Filter by service name", multivalued: true
|
10
13
|
option ["-c", "--container"], "CONTAINER", "Filter by container", multivalued: true
|
11
14
|
|
15
|
+
# @return [String]
|
16
|
+
def token
|
17
|
+
@token ||= require_token
|
18
|
+
end
|
19
|
+
|
12
20
|
def execute
|
13
21
|
require_api_url
|
14
|
-
token = require_token
|
15
22
|
|
16
23
|
query_params = {}
|
17
24
|
query_params[:nodes] = node_list.join(",") unless node_list.empty?
|
@@ -21,15 +28,13 @@ module Kontena::Cli::Grids
|
|
21
28
|
query_params[:since] = since if since
|
22
29
|
|
23
30
|
if tail?
|
24
|
-
|
25
|
-
query_params[:follow] = 1
|
26
|
-
stream_logs(token, query_params)
|
31
|
+
tail_logs(query_params)
|
27
32
|
else
|
28
|
-
list_logs(
|
33
|
+
list_logs(query_params)
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
|
-
def list_logs(
|
37
|
+
def list_logs(query_params)
|
33
38
|
result = client(token).get("grids/#{current_grid}/container_logs", query_params)
|
34
39
|
result['logs'].each do |log|
|
35
40
|
color = color_for_container(log['name'])
|
@@ -41,56 +46,13 @@ module Kontena::Cli::Grids
|
|
41
46
|
end
|
42
47
|
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
unless chunk.empty?
|
51
|
-
log = JSON.parse(chunk)
|
52
|
-
end
|
53
|
-
@buffer = ''
|
54
|
-
rescue => exc
|
55
|
-
@buffer << chunk
|
56
|
-
end
|
57
|
-
if log
|
58
|
-
@last_seen = log['id']
|
59
|
-
color = color_for_container(log['name'])
|
60
|
-
puts "#{log['name'].colorize(color)} | #{log['data']}"
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
begin
|
65
|
-
if @last_seen
|
66
|
-
query_params[:from] = @last_seen
|
67
|
-
end
|
68
|
-
result = client(token).get_stream(
|
69
|
-
"grids/#{current_grid}/container_logs", streamer, query_params
|
70
|
-
)
|
71
|
-
rescue => exc
|
72
|
-
if exc.cause.is_a?(EOFError) # Excon wraps the EOFerror into SockerError
|
73
|
-
retry
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
def color_for_container(container_id)
|
80
|
-
color_maps[container_id] = colors.shift unless color_maps[container_id]
|
81
|
-
color_maps[container_id].to_sym
|
82
|
-
end
|
83
|
-
|
84
|
-
def color_maps
|
85
|
-
@color_maps ||= {}
|
86
|
-
end
|
87
|
-
|
88
|
-
def colors
|
89
|
-
if(@colors.nil? || @colors.size == 0)
|
90
|
-
@colors = [:green, :yellow, :magenta, :cyan, :red,
|
91
|
-
:light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
|
49
|
+
# @param [String] token
|
50
|
+
# @param [Hash] query_params
|
51
|
+
def tail_logs(query_params)
|
52
|
+
stream_logs("grids/#{current_grid}/container_logs", query_params) do |log|
|
53
|
+
color = color_for_container(log['name'])
|
54
|
+
puts "#{log['name'].colorize(color)} | #{log['data']}"
|
92
55
|
end
|
93
|
-
@colors
|
94
56
|
end
|
95
57
|
end
|
96
58
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Kontena::Cli::Helpers
|
2
|
+
module LogHelper
|
3
|
+
|
4
|
+
# @param [String] url
|
5
|
+
# @param [Hash] query_params
|
6
|
+
def stream_logs(url, query_params)
|
7
|
+
last_seen = nil
|
8
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
9
|
+
log = buffered_log_json(chunk)
|
10
|
+
if log
|
11
|
+
yield log
|
12
|
+
last_seen = log['id']
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
begin
|
17
|
+
query_params[:follow] = 1
|
18
|
+
query_params[:from] = last_seen if last_seen
|
19
|
+
result = client(token).get_stream(url, streamer, query_params)
|
20
|
+
rescue => exc
|
21
|
+
retry if exc.cause.is_a?(EOFError) # Excon wraps the EOFerror into SocketError
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [String] chunk
|
27
|
+
# @return [Hash,NilClass]
|
28
|
+
def buffered_log_json(chunk)
|
29
|
+
@buffer = '' if @buffer.nil?
|
30
|
+
return if @buffer.empty? && chunk.strip.empty?
|
31
|
+
begin
|
32
|
+
orig_chunk = chunk
|
33
|
+
unless @buffer.empty?
|
34
|
+
chunk = @buffer + chunk
|
35
|
+
end
|
36
|
+
unless chunk.empty?
|
37
|
+
log = JSON.parse(chunk)
|
38
|
+
end
|
39
|
+
@buffer = ''
|
40
|
+
log
|
41
|
+
rescue => exc
|
42
|
+
@buffer << orig_chunk
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String] container_id
|
48
|
+
# @return [Symbol]
|
49
|
+
def color_for_container(container_id)
|
50
|
+
color_maps[container_id] = colors.shift unless color_maps[container_id]
|
51
|
+
color_maps[container_id].to_sym
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Hash]
|
55
|
+
def color_maps
|
56
|
+
@color_maps ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Array<Symbol>]
|
60
|
+
def colors
|
61
|
+
if(@colors.nil? || @colors.size == 0)
|
62
|
+
@colors = [:green, :yellow, :magenta, :cyan, :red,
|
63
|
+
:light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
|
64
|
+
end
|
65
|
+
@colors
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require_relative 'services_helper'
|
2
|
+
require_relative '../helpers/log_helper'
|
2
3
|
|
3
4
|
module Kontena::Cli::Services
|
4
5
|
class LogsCommand < Clamp::Command
|
5
6
|
include Kontena::Cli::Common
|
6
7
|
include Kontena::Cli::GridOptions
|
8
|
+
include Kontena::Cli::Helpers::LogHelper
|
7
9
|
include ServicesHelper
|
8
10
|
|
9
11
|
parameter "NAME", "Service name"
|
@@ -15,7 +17,7 @@ module Kontena::Cli::Services
|
|
15
17
|
def execute
|
16
18
|
require_api_url
|
17
19
|
token = require_token
|
18
|
-
|
20
|
+
|
19
21
|
|
20
22
|
query_params = {}
|
21
23
|
query_params[:limit] = lines if lines
|
@@ -47,56 +49,30 @@ module Kontena::Cli::Services
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
52
|
+
# @param [String] token
|
53
|
+
# @param [Hash] query_params
|
50
54
|
def stream_logs(token, query_params)
|
55
|
+
last_seen = nil
|
51
56
|
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
52
|
-
|
53
|
-
unless @buffer.empty?
|
54
|
-
chunk = @buffer + chunk
|
55
|
-
end
|
56
|
-
unless chunk.empty?
|
57
|
-
log = JSON.parse(chunk)
|
58
|
-
end
|
59
|
-
@buffer = ''
|
60
|
-
rescue => exc
|
61
|
-
@buffer << chunk
|
62
|
-
end
|
57
|
+
log = buffered_log_json(chunk)
|
63
58
|
if log
|
64
|
-
|
59
|
+
last_seen = log['id']
|
65
60
|
render_log_line(log)
|
66
61
|
end
|
67
62
|
end
|
68
63
|
|
69
64
|
begin
|
70
65
|
query_params[:follow] = true
|
71
|
-
if
|
72
|
-
query_params[:from] =
|
66
|
+
if last_seen
|
67
|
+
query_params[:from] = last_seen
|
73
68
|
end
|
74
69
|
result = client(token).get_stream(
|
75
70
|
"services/#{current_grid}/#{name}/container_logs", streamer, query_params
|
76
71
|
)
|
77
72
|
rescue => exc
|
78
|
-
if exc.cause.is_a?(EOFError) # Excon wraps the EOFerror into
|
79
|
-
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
|
85
|
-
def color_for_container(container_id)
|
86
|
-
color_maps[container_id] = colors.shift unless color_maps[container_id]
|
87
|
-
color_maps[container_id].to_sym
|
88
|
-
end
|
89
|
-
|
90
|
-
def color_maps
|
91
|
-
@color_maps ||= {}
|
92
|
-
end
|
93
|
-
|
94
|
-
def colors
|
95
|
-
if(@colors.nil? || @colors.size == 0)
|
96
|
-
@colors = [:green, :yellow, :magenta, :cyan, :red,
|
97
|
-
:light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
|
73
|
+
retry if exc.cause.is_a?(EOFError) # Excon wraps the EOFerror into SocketError
|
74
|
+
raise
|
98
75
|
end
|
99
|
-
@colors
|
100
76
|
end
|
101
77
|
end
|
102
78
|
end
|
@@ -55,7 +55,7 @@ class Helper
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def yml_services
|
58
|
-
if File.exist?('kontena.yml')
|
58
|
+
if File.exist?('kontena.yml')
|
59
59
|
yaml = YAML.load(File.read('kontena.yml'))
|
60
60
|
if yaml['version'] == '2'
|
61
61
|
services = yaml['services']
|
@@ -100,7 +100,7 @@ if words.size > 0
|
|
100
100
|
end
|
101
101
|
when 'node'
|
102
102
|
completion.clear
|
103
|
-
sub_commands = %w(list show remove
|
103
|
+
sub_commands = %w(list show remove)
|
104
104
|
if words[1]
|
105
105
|
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
106
106
|
completion.push helper.nodes
|
@@ -109,7 +109,7 @@ if words.size > 0
|
|
109
109
|
end
|
110
110
|
when 'master'
|
111
111
|
completion.clear
|
112
|
-
sub_commands = %w(list use
|
112
|
+
sub_commands = %w(list use)
|
113
113
|
if words[1] && words[1] == 'use'
|
114
114
|
completion.push helper.master_names
|
115
115
|
elsif words[1] && words[1] == 'users'
|
@@ -13,6 +13,10 @@ describe Kontena::Cli::Apps::Common do
|
|
13
13
|
fixture('kontena.yml')
|
14
14
|
end
|
15
15
|
|
16
|
+
let(:kontena_numeric_version_yml) do
|
17
|
+
fixture('kontena_numeric_version.yml')
|
18
|
+
end
|
19
|
+
|
16
20
|
let(:kontena_v2_yml) do
|
17
21
|
fixture('kontena_v2.yml')
|
18
22
|
end
|
@@ -61,7 +65,7 @@ describe Kontena::Cli::Apps::Common do
|
|
61
65
|
end
|
62
66
|
end
|
63
67
|
|
64
|
-
describe '#
|
68
|
+
describe '#services_from_yaml' do
|
65
69
|
before(:each) do
|
66
70
|
allow(File).to receive(:read).with("#{Dir.getwd}/kontena.yml").and_return(kontena_yml)
|
67
71
|
allow(File).to receive(:read).with("#{Dir.getwd}/health.yml").and_return(health_yml)
|
@@ -96,5 +100,12 @@ describe Kontena::Cli::Apps::Common do
|
|
96
100
|
expect(services['web']).not_to be_nil
|
97
101
|
expect(services['web']['health_check']).not_to be_nil
|
98
102
|
end
|
103
|
+
|
104
|
+
it 'allows version to be numeric' do
|
105
|
+
allow(File).to receive(:read).with("#{Dir.getwd}/kontena-numeric-version.yml").and_return(kontena_numeric_version_yml)
|
106
|
+
services = subject.services_from_yaml('kontena-numeric-version.yml', [], '')
|
107
|
+
expect(services.dig('bar', 'build', 'context')).to eq(Dir.pwd)
|
108
|
+
expect(services.dig('bar', 'build', 'dockerfile')).to eq('Dockerfile')
|
109
|
+
end
|
99
110
|
end
|
100
111
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require_relative "../../../spec_helper"
|
2
|
+
require 'kontena/cli/grid_options'
|
3
|
+
require "kontena/cli/apps/logs_command"
|
4
|
+
|
5
|
+
describe Kontena::Cli::Apps::LogsCommand do
|
6
|
+
include FixturesHelpers
|
7
|
+
|
8
|
+
let(:subject) do
|
9
|
+
described_class.new(File.basename($0))
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:client) do
|
13
|
+
double("client")
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:token) do
|
17
|
+
"testtoken"
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:current_grid) do
|
21
|
+
'test-grid'
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:service_prefix) do
|
25
|
+
'test'
|
26
|
+
end
|
27
|
+
|
28
|
+
before (:each) do
|
29
|
+
allow(subject).to receive(:token) { token }
|
30
|
+
allow(subject).to receive(:client) { client }
|
31
|
+
allow(subject).to receive(:service_prefix) { service_prefix }
|
32
|
+
allow(subject).to receive(:current_grid) { current_grid }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with multiple services' do
|
36
|
+
let(:kontena_yml) do
|
37
|
+
fixture('kontena.yml')
|
38
|
+
end
|
39
|
+
|
40
|
+
let(:docker_compose_yml) do
|
41
|
+
fixture('docker-compose.yml')
|
42
|
+
end
|
43
|
+
|
44
|
+
# globally ordered logs across multiple services
|
45
|
+
let (:logs) do
|
46
|
+
[
|
47
|
+
{
|
48
|
+
'id' => '57cff2e8cfee65c8b6efc8bd',
|
49
|
+
'name' => 'test-mysql-1',
|
50
|
+
'created_at' => '2016-09-07T15:19:04.362690',
|
51
|
+
'data' => "mysql log message 1",
|
52
|
+
},
|
53
|
+
{
|
54
|
+
'id' => '57cff2e8cfee65c8b6efc8be',
|
55
|
+
'name' => 'test-mysql-1',
|
56
|
+
'created_at' => '2016-09-07T15:19:04.500000',
|
57
|
+
'data' => "mysql log message 2",
|
58
|
+
},
|
59
|
+
{
|
60
|
+
'id' => '57cff2e8cfee65c8b6efc8bf',
|
61
|
+
'name' => 'test-wordpress-1',
|
62
|
+
'created_at' => '2016-09-07T15:19:05.362690',
|
63
|
+
'data' => "wordpress log message 1-1",
|
64
|
+
},
|
65
|
+
{
|
66
|
+
'id' => '57cff2e8cfee65c8b6efc8c1',
|
67
|
+
'name' => 'test-mysql-1',
|
68
|
+
'created_at' => '2016-09-07T15:19:06.100000',
|
69
|
+
'data' => "mysql log message 3",
|
70
|
+
},
|
71
|
+
{
|
72
|
+
'id' => '57cff2e8cfee65c8b6efc8c2',
|
73
|
+
'name' => 'test-wordpress-1',
|
74
|
+
'created_at' => '2016-09-07T15:19:07.100000',
|
75
|
+
'data' => "wordpress log message 1-2",
|
76
|
+
},
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
before (:each) do
|
81
|
+
# mock kontena.yml services
|
82
|
+
expect(subject).to receive(:require_config_file).with("kontena.yml")
|
83
|
+
allow(File).to receive(:read).with("#{Dir.getwd}/kontena.yml").and_return(kontena_yml)
|
84
|
+
allow(File).to receive(:read).with("#{Dir.getwd}/docker-compose.yml").and_return(docker_compose_yml)
|
85
|
+
|
86
|
+
# collect show_log() output
|
87
|
+
@logs = []
|
88
|
+
|
89
|
+
allow(subject).to receive(:show_log) do |log|
|
90
|
+
@logs << log
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "shows all service logs" do
|
95
|
+
expect(client).to receive(:get).with('grids/test-grid/container_logs', {
|
96
|
+
services: 'test-wordpress,test-mysql',
|
97
|
+
limit: '100',
|
98
|
+
}) { { 'logs' => logs } }
|
99
|
+
|
100
|
+
subject.run([])
|
101
|
+
|
102
|
+
expect(@logs).to eq logs
|
103
|
+
end
|
104
|
+
|
105
|
+
it "shows logs for one service" do
|
106
|
+
mysql_logs = logs.select{|log| log['name'] =~ /test-mysql-/ }
|
107
|
+
|
108
|
+
expect(client).to receive(:get).with('grids/test-grid/container_logs', {
|
109
|
+
services: 'test-mysql',
|
110
|
+
limit: '100',
|
111
|
+
}) { { 'logs' => mysql_logs } }
|
112
|
+
|
113
|
+
subject.run(["mysql"])
|
114
|
+
|
115
|
+
expect(@logs).to eq mysql_logs
|
116
|
+
end
|
117
|
+
|
118
|
+
it "shows logs since time" do
|
119
|
+
since = '2016-09-07T15:19:05.362690'
|
120
|
+
since_logs = logs.select{|log| log['created_at'] > since }
|
121
|
+
|
122
|
+
expect(client).to receive(:get).with('grids/test-grid/container_logs', {
|
123
|
+
services: 'test-wordpress,test-mysql',
|
124
|
+
limit: '100',
|
125
|
+
since: since,
|
126
|
+
}) { { 'logs' => since_logs } }
|
127
|
+
|
128
|
+
subject.run(["--since=#{since}"])
|
129
|
+
|
130
|
+
expect(@logs).to eq since_logs
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -194,38 +194,53 @@ describe Kontena::Cli::Apps::YAML::Reader do
|
|
194
194
|
.and_return(fixture('docker-compose.yml'))
|
195
195
|
end
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
'
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
'
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
.
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
.
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
197
|
+
context 'when extending services' do
|
198
|
+
it 'extends services from external file' do
|
199
|
+
docker_compose_yml = YAML.load(fixture('docker-compose.yml') % { project: 'test' })
|
200
|
+
wordpress_options = {
|
201
|
+
'extends' => {
|
202
|
+
'file' => 'docker-compose.yml',
|
203
|
+
'service' => 'wordpress'
|
204
|
+
},
|
205
|
+
'stateful' => true,
|
206
|
+
'environment' => ['WORDPRESS_DB_PASSWORD=test_secret'],
|
207
|
+
'instances' => 2,
|
208
|
+
'deploy' => { 'strategy' => 'ha' }
|
209
|
+
}
|
210
|
+
mysql_options = {
|
211
|
+
'extends' => {
|
212
|
+
'file' => 'docker-compose.yml',
|
213
|
+
'service' => 'mysql'
|
214
|
+
},
|
215
|
+
'stateful' => true,
|
216
|
+
'environment' => ['MYSQL_ROOT_PASSWORD=test_secret']
|
217
|
+
}
|
218
|
+
expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
|
219
|
+
.with(wordpress_options)
|
220
|
+
.once
|
221
|
+
.and_return(service_extender)
|
222
|
+
expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
|
223
|
+
.with(mysql_options)
|
224
|
+
.once
|
225
|
+
.and_return(service_extender)
|
226
|
+
expect(service_extender).to receive(:extend).with(docker_compose_yml['wordpress'])
|
227
|
+
expect(service_extender).to receive(:extend).with(docker_compose_yml['mysql'])
|
228
|
+
|
229
|
+
subject.execute
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'extends services from the same file' do
|
233
|
+
allow(File).to receive(:read)
|
234
|
+
.with(absolute_yaml_path('kontena.yml'))
|
235
|
+
.and_return(fixture('kontena-internal-extend.yml'))
|
236
|
+
kontena_yml = YAML.load(fixture('kontena-internal-extend.yml') % { project: 'test' })
|
237
|
+
expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
|
238
|
+
.with(kontena_yml['app'])
|
239
|
+
.once
|
240
|
+
.and_return(service_extender)
|
241
|
+
expect(service_extender).to receive(:extend).with(kontena_yml['base'])
|
242
|
+
subject.execute
|
243
|
+
end
|
229
244
|
end
|
230
245
|
|
231
246
|
context 'environment variables' do
|
@@ -206,6 +206,26 @@ describe Kontena::Cli::Apps::YAML::Validator do
|
|
206
206
|
end
|
207
207
|
end
|
208
208
|
|
209
|
+
context 'validates extends' do
|
210
|
+
it 'accepts string value' do
|
211
|
+
result = subject.validate_options('extends' => 'web')
|
212
|
+
expect(result.messages.key?('extends')).to be_falsey
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'when value is hash' do
|
216
|
+
it 'must contain service' do
|
217
|
+
result = subject.validate_options('extends' => { 'file' => 'docker_compose.yml'})
|
218
|
+
expect(result.messages.key?('extends')).to be_truthy
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context 'when value is not string or hash' do
|
223
|
+
it 'returns error' do
|
224
|
+
result = subject.validate_options('extends' => ['array is invalid'])
|
225
|
+
expect(result.messages.key?('extends')).to be_truthy
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
209
229
|
context 'validates hooks' do
|
210
230
|
context 'validates pre_build' do
|
211
231
|
it 'must be array' do
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "../../../spec_helper"
|
2
|
+
require "kontena/cli/helpers/log_helper"
|
3
|
+
|
4
|
+
describe Kontena::Cli::Helpers::LogHelper do
|
5
|
+
|
6
|
+
let(:described_class) do
|
7
|
+
Class.new do
|
8
|
+
include Kontena::Cli::Helpers::LogHelper
|
9
|
+
|
10
|
+
def buffer
|
11
|
+
@buffer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#buffered_log_json' do
|
17
|
+
it 'returns has on valid json' do
|
18
|
+
chunk = {"foo" => "bar"}
|
19
|
+
log = subject.buffered_log_json(chunk.to_json)
|
20
|
+
expect(log).to eq(chunk)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'combines multi part json chunks to valid json' do
|
24
|
+
chunk1 = '{"foo": "'
|
25
|
+
chunk2 = 'bar'
|
26
|
+
chunk3 = '"}'
|
27
|
+
log = subject.buffered_log_json(chunk1)
|
28
|
+
expect(log).to be_nil
|
29
|
+
log = subject.buffered_log_json(chunk2)
|
30
|
+
expect(log).to be_nil
|
31
|
+
log = subject.buffered_log_json(chunk3)
|
32
|
+
expect(log).to eq({"foo" => "bar"})
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'handles big log messages' do
|
36
|
+
chunk1 = '{"foo": "' << "lol" * 10000
|
37
|
+
chunk2 = 'lol'
|
38
|
+
chunk3 = 'lol"}'
|
39
|
+
log = subject.buffered_log_json(chunk1)
|
40
|
+
expect(log).to be_nil
|
41
|
+
log = subject.buffered_log_json(chunk2)
|
42
|
+
expect(log).to be_nil
|
43
|
+
log = subject.buffered_log_json(chunk3)
|
44
|
+
expect(log).to eq(JSON.parse(chunk1 + chunk2 + chunk3))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not append to buffer if buffer is empty and chunk has just whitespace' do
|
48
|
+
log = subject.buffered_log_json(' ')
|
49
|
+
expect(log).to be_nil
|
50
|
+
expect(subject.buffer).to eq('')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns nil on invalid json' do
|
54
|
+
log = subject.buffered_log_json('{"foo": "')
|
55
|
+
expect(log).to be_nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
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: 0.15.
|
4
|
+
version: 0.15.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,72 +56,72 @@ dependencies:
|
|
56
56
|
name: colorize
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.8.1
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.8.1
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: clamp
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 1.0.0
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 1.0.0
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: highline
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 1.7.8
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 1.7.8
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: shell-spinner
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 1.0.4
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 1.0.4
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: ruby_dig
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- - "
|
115
|
+
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version:
|
117
|
+
version: 0.0.2
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- - "
|
122
|
+
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
|
-
version:
|
124
|
+
version: 0.0.2
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: dry-validation
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -248,6 +248,7 @@ files:
|
|
248
248
|
- lib/kontena/cli/grids/users/add_command.rb
|
249
249
|
- lib/kontena/cli/grids/users/list_command.rb
|
250
250
|
- lib/kontena/cli/grids/users/remove_command.rb
|
251
|
+
- lib/kontena/cli/helpers/log_helper.rb
|
251
252
|
- lib/kontena/cli/login_command.rb
|
252
253
|
- lib/kontena/cli/logout_command.rb
|
253
254
|
- lib/kontena/cli/master/current_command.rb
|
@@ -352,11 +353,13 @@ files:
|
|
352
353
|
- spec/fixtures/docker-compose_v2.yml
|
353
354
|
- spec/fixtures/health.yml
|
354
355
|
- spec/fixtures/kontena-build.yml
|
356
|
+
- spec/fixtures/kontena-internal-extend.yml
|
355
357
|
- spec/fixtures/kontena-invalid.yml
|
356
358
|
- spec/fixtures/kontena-with-env-file.yml
|
357
359
|
- spec/fixtures/kontena-with-variables.yml
|
358
360
|
- spec/fixtures/kontena.yml
|
359
361
|
- spec/fixtures/kontena_build_v2.yml
|
362
|
+
- spec/fixtures/kontena_numeric_version.yml
|
360
363
|
- spec/fixtures/kontena_v2.yml
|
361
364
|
- spec/fixtures/mysql.yml
|
362
365
|
- spec/fixtures/wordpress-scaled.yml
|
@@ -367,6 +370,7 @@ files:
|
|
367
370
|
- spec/kontena/cli/app/deploy_command_spec.rb
|
368
371
|
- spec/kontena/cli/app/docker_helper_spec.rb
|
369
372
|
- spec/kontena/cli/app/init_command_spec.rb
|
373
|
+
- spec/kontena/cli/app/logs_command_spec.rb
|
370
374
|
- spec/kontena/cli/app/scale_spec.rb
|
371
375
|
- spec/kontena/cli/app/service_generator_spec.rb
|
372
376
|
- spec/kontena/cli/app/service_generator_v2_spec.rb
|
@@ -379,6 +383,7 @@ files:
|
|
379
383
|
- spec/kontena/cli/grids/trusted_subnets/add_command_spec.rb
|
380
384
|
- spec/kontena/cli/grids/trusted_subnets/list_command_spec.rb
|
381
385
|
- spec/kontena/cli/grids/trusted_subnets/remove_command_spec.rb
|
386
|
+
- spec/kontena/cli/helpers/log_helper_spec.rb
|
382
387
|
- spec/kontena/cli/login_command_spec.rb
|
383
388
|
- spec/kontena/cli/master/current_command_spec.rb
|
384
389
|
- spec/kontena/cli/master/use_command_spec.rb
|
@@ -403,7 +408,7 @@ files:
|
|
403
408
|
- spec/support/client_helpers.rb
|
404
409
|
- spec/support/fixtures_helpers.rb
|
405
410
|
- tasks/rspec.rake
|
406
|
-
homepage:
|
411
|
+
homepage: https://www.kontena.io
|
407
412
|
licenses:
|
408
413
|
- Apache-2.0
|
409
414
|
metadata: {}
|
@@ -433,11 +438,13 @@ test_files:
|
|
433
438
|
- spec/fixtures/docker-compose_v2.yml
|
434
439
|
- spec/fixtures/health.yml
|
435
440
|
- spec/fixtures/kontena-build.yml
|
441
|
+
- spec/fixtures/kontena-internal-extend.yml
|
436
442
|
- spec/fixtures/kontena-invalid.yml
|
437
443
|
- spec/fixtures/kontena-with-env-file.yml
|
438
444
|
- spec/fixtures/kontena-with-variables.yml
|
439
445
|
- spec/fixtures/kontena.yml
|
440
446
|
- spec/fixtures/kontena_build_v2.yml
|
447
|
+
- spec/fixtures/kontena_numeric_version.yml
|
441
448
|
- spec/fixtures/kontena_v2.yml
|
442
449
|
- spec/fixtures/mysql.yml
|
443
450
|
- spec/fixtures/wordpress-scaled.yml
|
@@ -448,6 +455,7 @@ test_files:
|
|
448
455
|
- spec/kontena/cli/app/deploy_command_spec.rb
|
449
456
|
- spec/kontena/cli/app/docker_helper_spec.rb
|
450
457
|
- spec/kontena/cli/app/init_command_spec.rb
|
458
|
+
- spec/kontena/cli/app/logs_command_spec.rb
|
451
459
|
- spec/kontena/cli/app/scale_spec.rb
|
452
460
|
- spec/kontena/cli/app/service_generator_spec.rb
|
453
461
|
- spec/kontena/cli/app/service_generator_v2_spec.rb
|
@@ -460,6 +468,7 @@ test_files:
|
|
460
468
|
- spec/kontena/cli/grids/trusted_subnets/add_command_spec.rb
|
461
469
|
- spec/kontena/cli/grids/trusted_subnets/list_command_spec.rb
|
462
470
|
- spec/kontena/cli/grids/trusted_subnets/remove_command_spec.rb
|
471
|
+
- spec/kontena/cli/helpers/log_helper_spec.rb
|
463
472
|
- spec/kontena/cli/login_command_spec.rb
|
464
473
|
- spec/kontena/cli/master/current_command_spec.rb
|
465
474
|
- spec/kontena/cli/master/use_command_spec.rb
|