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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d82df3e1f060653421ebc2ba56833b28881e6d6e
4
- data.tar.gz: 867c7924eb2a780599392f21fdbc5e6d8e30961d
3
+ metadata.gz: 0799760c154ff891c9e67c2a893b47f47d9012c6
4
+ data.tar.gz: 075dcb4a30e995da47f86153b02d54b66e6576b7
5
5
  SHA512:
6
- metadata.gz: 788d94e130ecad93547fb194564039d70a8b4686f2c8029649beea8f5e0d52c8ce42eba53aab463de49e5f11647c92f0b9d33659cd0339f7338c9892043d6cba
7
- data.tar.gz: 2ce7bd1b2bc78591a7405ba7d09d6c4f192a290a15ed6c2f79e4cfdecc12ae69f1b224cca96ae842a2265c7626b68f66842d9c4d908ff85080409a81db386e82
6
+ metadata.gz: 943320e9d2f861ec68da9b0f4796bd268ec302bde7094ef440af6b578bc0f9b997ddf5dd913289cbe4fb40b78b049e0babdc52ec42de10cb3c64bec6543c449d
7
+ data.tar.gz: 74450cc8ab63055a6bd109f967d7c730e16c437236fb1c7b43825b73ade44dcad10fcbfdc9048eaa32d4c00e8f22585ea70a0035bea79898b973b1c1c92db018
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.15.1
1
+ 0.15.2
@@ -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 = "http://www.kontena.io"
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 == '2'
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] == '2'
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
- @services = services_from_yaml(filename, service_list, service_prefix)
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
- end
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
- def show_logs(services)
31
- last_id = nil
32
- loop do
33
- query_params = []
34
- query_params << "from=#{last_id}" unless last_id.nil?
35
- query_params << "limit=#{lines}"
36
- query_params << "since=#{since}" if !since.nil? && last_id.nil?
37
- logs = []
38
- services.each do |service_name, opts|
39
- service = get_service(token, prefixed_name(service_name)) rescue false
40
- result = client(token).get("services/#{service['id']}/container_logs?#{query_params.join('&')}") if service
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 color_for_container(container_id)
56
- color_maps[container_id] = colors.shift unless color_maps[container_id]
57
- color_maps[container_id].to_sym
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 color_maps
61
- @color_maps ||= {}
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 colors
65
- if(@colors.nil? || @colors.size == 0)
66
- @colors = [:green, :magenta, :yellow, :cyan, :red,
67
- :light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
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
- service_name = service_config['extends']['service']
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
- parent_service = from_external_file(filename, service_name)
133
+ parent_config = from_external_file(filename, extended_service)
133
134
  else
134
- abort("Service '#{service_name}' not found in #{file}".colorize(:red)) unless services.key?(service_name)
135
- parent_service = process_config(services[service_name])
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').schema do
8
- required('service').filled(:str?)
9
- optional('file').value(:str?)
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
- @buffer = ''
25
- query_params[:follow] = 1
26
- stream_logs(token, query_params)
31
+ tail_logs(query_params)
27
32
  else
28
- list_logs(token, query_params)
33
+ list_logs(query_params)
29
34
  end
30
35
  end
31
36
 
32
- def list_logs(token, query_params)
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
- def stream_logs(token, query_params)
45
- streamer = lambda do |chunk, remaining_bytes, total_bytes|
46
- begin
47
- unless @buffer.empty?
48
- chunk = @buffer + chunk
49
- end
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
- begin
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
- @last_seen = log['id']
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 @last_seen
72
- query_params[:from] = @last_seen
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 SockerError
79
- retry
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 vagrant digitalocean azure aws)
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 vagrant digitalocean azure aws users)
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'
@@ -0,0 +1,8 @@
1
+ base:
2
+ stateful: true
3
+ instances: 2
4
+ deploy:
5
+ strategy: ha
6
+ app:
7
+ extends: base
8
+ stateful: true
@@ -0,0 +1,9 @@
1
+ version: 2
2
+ name: foo
3
+
4
+ services:
5
+ bar:
6
+ image: konttisample
7
+ build:
8
+ context: .
9
+ dockerfile: Dockerfile
@@ -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 '#load_from_yaml' do
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
- it 'extends services' do
198
- docker_compose_yml = YAML.load(fixture('docker-compose.yml') % { project: 'test' })
199
- wordpress_options = {
200
- 'extends' => {
201
- 'file' => 'docker-compose.yml',
202
- 'service' => 'wordpress'
203
- },
204
- 'stateful' => true,
205
- 'environment' => ['WORDPRESS_DB_PASSWORD=test_secret'],
206
- 'instances' => 2,
207
- 'deploy' => { 'strategy' => 'ha' }
208
- }
209
- mysql_options = {
210
- 'extends' => {
211
- 'file' => 'docker-compose.yml',
212
- 'service' => 'mysql'
213
- },
214
- 'stateful' => true,
215
- 'environment' => ['MYSQL_ROOT_PASSWORD=test_secret']
216
- }
217
- expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
218
- .with(wordpress_options)
219
- .once
220
- .and_return(service_extender)
221
- expect(Kontena::Cli::Apps::YAML::ServiceExtender).to receive(:new)
222
- .with(mysql_options)
223
- .once
224
- .and_return(service_extender)
225
- expect(service_extender).to receive(:extend).with(docker_compose_yml['wordpress'])
226
- expect(service_extender).to receive(:extend).with(docker_compose_yml['mysql'])
227
-
228
- subject.execute
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.1
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-01 00:00:00.000000000 Z
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: '0'
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: http://www.kontena.io
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