kontena-cli 0.15.1 → 0.15.2

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 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