kontena-cli 0.15.0.rc1 → 0.15.0.rc2

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: d2097ac9b19a1b3ffe77759d51f750de06bab2af
4
- data.tar.gz: 0484b872ce20c995506cba6cc651716b7a4ffec3
3
+ metadata.gz: de5064548c891f2a1dc2b8382efcd81ef47a4e3b
4
+ data.tar.gz: 0d1262bfcc83048a591ab04b1040c94cbbb0ca02
5
5
  SHA512:
6
- metadata.gz: 6fce1e0c1cac772357d580e0c05284130ad1632a234c47e48f722028d37bc5ec6c025628e343fe5cc6567e9703775c64bd04a34e46ee71d072c52525d1ace02e
7
- data.tar.gz: 929389765338d0c8db3dbc56973052f4cb5b7c384ea37f6ba2125a393436b83260161c1ff9aa0111aa1f6446e1afe8d9fe3cd5a7f6ccb2928e7369ef03938421
6
+ metadata.gz: 3c540225c876e38452bcf9308fa2ae356464b17e7f0433c65e078afaaf20b9cfeedee96fb76e77a921f2f0d2269a3fbbac127cad33abb81a8b5d4af49829ca06
7
+ data.tar.gz: 2c723b83ee5fe5a81e853f45e22cb44be1decb8dee9e4f8b83defa1ef2c0a3c03af8afc750ac38d3f90d1a6c7c6abd92dda7d921e83a8ac311891a487b23e8af
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.15.0.rc1
1
+ 0.15.0.rc2
@@ -26,7 +26,7 @@ module Kontena::Cli::Apps
26
26
  data = {}
27
27
  data['container_count'] = options['instances']
28
28
  data['image'] = parse_image(options['image'])
29
- data['env'] = merge_env_vars(options)
29
+ data['env'] = options['environment'] if options['environment']
30
30
  data['container_count'] = options['instances']
31
31
  data['links'] = parse_links(options['links'] || [])
32
32
  data['external_links'] = parse_links(options['external_links'] || [])
@@ -73,24 +73,6 @@ module Kontena::Cli::Apps
73
73
  data
74
74
  end
75
75
 
76
- # @param [Hash] options
77
- def merge_env_vars(options)
78
- return options['environment'] unless options['env_file']
79
-
80
- options['env_file'] = [options['env_file']] if options['env_file'].is_a?(String)
81
- options['environment'] = [] unless options['environment']
82
- options['env_file'].each do |env_file|
83
- options['environment'].concat(read_env_file(env_file))
84
- end
85
- options['environment'].uniq {|s| s.split('=').first}
86
- end
87
-
88
-
89
- # @param [String] path
90
- def read_env_file(path)
91
- File.readlines(path).delete_if { |line| line.start_with?('#') || line.strip.empty? }
92
- end
93
-
94
76
  # @param [Array<String>] port_options
95
77
  # @return [Array<Hash>]
96
78
  def parse_stringified_ports(port_options)
@@ -89,6 +89,8 @@ module Kontena::Cli::Apps
89
89
  # @param [Hash] service_config
90
90
  def process_config(service_config)
91
91
  normalize_env_vars(service_config)
92
+ merge_env_vars(service_config)
93
+ expand_build_context(service_config)
92
94
  normalize_build_args(service_config)
93
95
  service_config = extend_config(service_config) if service_config.key?('extends')
94
96
  service_config
@@ -146,7 +148,33 @@ module Kontena::Cli::Apps
146
148
  options['environment'] = options['environment'].map { |k, v| "#{k}=#{v}" }
147
149
  end
148
150
  end
149
-
151
+
152
+ # @param [Hash] options
153
+ def merge_env_vars(options)
154
+ return options['environment'] unless options['env_file']
155
+
156
+ options['env_file'] = [options['env_file']] if options['env_file'].is_a?(String)
157
+ options['environment'] = [] unless options['environment']
158
+ options['env_file'].each do |env_file|
159
+ options['environment'].concat(read_env_file(env_file))
160
+ end
161
+ options.delete('env_file')
162
+ options['environment'].uniq! { |s| s.split('=').first }
163
+ end
164
+
165
+ # @param [String] path
166
+ def read_env_file(path)
167
+ File.readlines(path).map { |line| line.strip }.delete_if { |line| line.start_with?('#') || line.empty? }
168
+ end
169
+
170
+ def expand_build_context(options)
171
+ if options['build'].is_a?(String)
172
+ options['build'] = File.expand_path(options['build'])
173
+ elsif context = options.dig('build', 'context')
174
+ options['build']['context'] = File.expand_path(context)
175
+ end
176
+ end
177
+
150
178
  # @param [Hash] options - service config
151
179
  def normalize_build_args(options)
152
180
  if v2? && options.dig('build', 'args')
@@ -4,8 +4,9 @@ module Kontena::Cli::Grids
4
4
  ##
5
5
  # @param [Hash] grid
6
6
  def print_grid(grid)
7
+ host = ENV['KONTENA_URL'] || self.current_master['url']
7
8
  puts "#{grid['name']}:"
8
- puts " uri: #{self.current_master['url'].sub('http', 'ws')}"
9
+ puts " uri: #{host.sub('http', 'ws')}"
9
10
  puts " token: #{grid['token']}"
10
11
  root_dir = grid['engine_root_dir']
11
12
  nodes = client(require_token).get("grids/#{grid['name']}/nodes")
@@ -12,8 +12,8 @@ module Kontena::Cli::Services
12
12
 
13
13
  grids = client(token).get("grids/#{current_grid}/services")
14
14
  services = grids['services'].sort_by{|s| s['updated_at'] }.reverse
15
- titles = ['NAME', 'INSTANCES', 'STATEFUL', 'STATE', 'HEALTH', 'EXPOSED PORTS']
16
- puts "%-60s %-10s %-8s %-10s %-10s %-50s" % titles
15
+ titles = ['NAME', 'INSTANCES', 'STATEFUL', 'STATE', 'EXPOSED PORTS']
16
+ puts "%-60s %-10s %-8s %-10s %-50s" % titles
17
17
  services.each do |service|
18
18
  stateful = service['stateful'] ? 'yes' : 'no'
19
19
  running = service['instances']['running']
@@ -22,28 +22,15 @@ module Kontena::Cli::Services
22
22
  ports = service['ports'].map{|p|
23
23
  "#{p['ip']}:#{p['node_port']}->#{p['container_port']}/#{p['protocol']}"
24
24
  }.join(", ")
25
- health = 'unknown'
26
- if service['health_status']
27
- icon = "■"
28
- healthy = service.dig('health_status', 'healthy')
29
- total = service.dig('health_status', 'total')
30
- color = :green
31
- if healthy == 0
32
- color = :red
33
- elsif healthy > 0 && healthy < total
34
- color = :yellow
35
- end
36
- health = "■".colorize(color)
37
- end
25
+ health = health_status(service)
38
26
  vars = [
39
- service['name'],
27
+ "#{health_status_icon(health)} #{service['name']}",
40
28
  instances,
41
29
  stateful,
42
30
  service['state'],
43
- health,
44
31
  ports
45
32
  ]
46
- puts "%-60.60s %-10.10s %-8s %-10s %-10s %-50s" % vars
33
+ puts "%-74s %-10.10s %-8s %-10s %-50s" % vars
47
34
  end
48
35
  end
49
36
  end
@@ -180,7 +180,7 @@ module Kontena
180
180
  puts " interval: #{service['health_check']['interval']}"
181
181
  puts " initial_delay: #{service['health_check']['initial_delay']}"
182
182
  end
183
-
183
+
184
184
  if service['health_status']
185
185
  puts " health status:"
186
186
  puts " healthy: #{service['health_status']['healthy']}"
@@ -282,7 +282,7 @@ module Kontena
282
282
  ip: ip,
283
283
  container_port: container_port,
284
284
  node_port: node_port,
285
- protocol: protocol
285
+ protocol: protocol
286
286
  }
287
287
  }
288
288
  end
@@ -399,6 +399,42 @@ module Kontena
399
399
 
400
400
  build_args
401
401
  end
402
+
403
+ # @param [Symbol] health
404
+ # @return [String]
405
+ def health_status_icon(health)
406
+ if health == :unhealthy
407
+ icon = '⊗'.freeze
408
+ icon.colorize(:red)
409
+ elsif health == :partial
410
+ icon = '⊙'.freeze
411
+ icon.colorize(:yellow)
412
+ elsif health == :healthy
413
+ icon = '⊛'.freeze
414
+ icon.colorize(:green)
415
+ else
416
+ icon = '⊝'.freeze
417
+ icon.colorize(:default)
418
+ end
419
+ end
420
+
421
+ # @param [Hash] service
422
+ # @return [Symbol]
423
+ def health_status(service)
424
+ if service['health_status']
425
+ healthy = service.dig('health_status', 'healthy')
426
+ total = service.dig('health_status', 'total')
427
+ if healthy == 0
428
+ :unhealthy
429
+ elsif healthy > 0 && healthy < total
430
+ :partial
431
+ else
432
+ :healthy
433
+ end
434
+ else
435
+ :unknown
436
+ end
437
+ end
402
438
  end
403
439
  end
404
440
  end
@@ -10,10 +10,10 @@ module Kontena::Cli::Vault
10
10
  token = require_token
11
11
  result = client(token).get("grids/#{current_grid}/secrets")
12
12
 
13
- column_width_paddings = '%-54s %-25.25s'
14
- puts column_width_paddings % ['NAME', 'CREATED AT']
13
+ column_width_paddings = '%-54s %-25.25s %-25.25s'
14
+ puts column_width_paddings % ['NAME', 'CREATED AT', 'UPDATED AT']
15
15
  result['secrets'].sort_by { |s| s['name'] }.each do |secret|
16
- puts column_width_paddings % [secret['name'], secret['created_at']]
16
+ puts column_width_paddings % [secret['name'], secret['created_at'], secret['updated_at']]
17
17
  end
18
18
  end
19
19
  end
@@ -10,27 +10,26 @@ class Kontena::Cli::WhoamiCommand < Clamp::Command
10
10
  end
11
11
 
12
12
  require_api_url
13
- puts "Master: #{self.current_master['name']}"
14
- puts "URL: #{api_url}"
15
- puts "Grid: #{current_grid}"
16
- if current_master['email']
17
- puts "User: #{current_master['email']}"
18
- else # In case local storage doesn't have the user email yet
19
- token = require_token
20
- user = client(token).get('user')
21
- puts "User: #{user['email']}"
22
- master = {
23
- 'name' => current_master['name'],
24
- 'url' => current_master['url'],
25
- 'token' => current_master['token'],
26
- 'email' => user['email'],
27
- 'grid' => current_master['grid']
28
- }
13
+ puts "Master: #{ENV['KONTENA_URL'] || self.current_master['name']}"
14
+ puts "URL: #{ENV['KONTENA_URL'] || api_url}"
15
+ puts "Grid: #{ENV['KONTENA_GRID'] || current_grid}"
16
+ unless ENV['KONTENA_URL']
17
+ if current_master['email']
18
+ puts "User: #{current_master['email']}"
19
+ else # In case local storage doesn't have the user email yet
20
+ token = require_token
21
+ user = client(token).get('user')
22
+ puts "User: #{user['email']}"
23
+ master = {
24
+ 'name' => current_master['name'],
25
+ 'url' => current_master['url'],
26
+ 'token' => current_master['token'],
27
+ 'email' => user['email'],
28
+ 'grid' => current_master['grid']
29
+ }
29
30
 
30
- self.add_master(current_master['name'], master)
31
+ self.add_master(current_master['name'], master)
32
+ end
31
33
  end
32
-
33
-
34
34
  end
35
-
36
35
  end
@@ -27,10 +27,11 @@ require_relative 'cli/stack_command'
27
27
  require_relative 'cli/certificate_command'
28
28
 
29
29
  class Kontena::MainCommand < Kontena::Command
30
+ include Kontena::Util
30
31
 
31
32
  subcommand "grid", "Grid specific commands", Kontena::Cli::GridCommand
32
33
  subcommand "app", "App specific commands", Kontena::Cli::AppCommand
33
- subcommand "stack", "Stack specific commands", Kontena::Cli::StackCommand
34
+ subcommand "stack", "Stack specific commands", Kontena::Cli::StackCommand if experimental?
34
35
  subcommand "service", "Service specific commands", Kontena::Cli::ServiceCommand
35
36
  subcommand "vault", "Vault specific commands", Kontena::Cli::VaultCommand
36
37
  subcommand "certificate", "LE Certificate specific commands", Kontena::Cli::CertificateCommand
data/lib/kontena/util.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  module Kontena
2
2
  module Util
3
3
 
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
4
8
  # @param [String] cmd
5
9
  def which(cmd)
6
10
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
@@ -13,5 +17,12 @@ module Kontena
13
17
  return nil
14
18
  end
15
19
  module_function(:which)
20
+
21
+ module ClassMethods
22
+ def experimental?
23
+ ENV.has_key?('KONTENA_EXPERIMENTAL')
24
+ end
25
+ end
26
+
16
27
  end
17
28
  end
@@ -0,0 +1,18 @@
1
+ wordpress:
2
+ extends:
3
+ file: docker-compose.yml
4
+ service: wordpress
5
+ stateful: true
6
+ env_file: .env
7
+ environment:
8
+ WORDPRESS_DB_PASSWORD: %{project}_secret
9
+ instances: 2
10
+ deploy:
11
+ strategy: ha
12
+ mysql:
13
+ extends:
14
+ file: docker-compose.yml
15
+ service: mysql
16
+ stateful: true
17
+ environment:
18
+ - MYSQL_ROOT_PASSWORD=%{project}_secret
@@ -6,12 +6,6 @@ describe Kontena::Cli::Apps::ServiceGenerator do
6
6
  described_class.new({})
7
7
  end
8
8
 
9
- let(:env_file) do
10
- 'APIKEY=12345
11
- MYSQL_ROOT_PASSWORD=secret
12
- WP_ADMIN_PASSWORD=verysecret'.split(/\r?\n/)
13
- end
14
-
15
9
  describe '#parse_data' do
16
10
  context 'volumes' do
17
11
  it 'returns volumes if set' do
@@ -387,50 +381,5 @@ WP_ADMIN_PASSWORD=verysecret'.split(/\r?\n/)
387
381
  expect(result['secrets']).to be_nil
388
382
  end
389
383
  end
390
- end
391
-
392
- describe '#read_env_file' do
393
- before(:each) do
394
- allow(File).to receive(:readlines).with('.env').and_return(env_file)
395
- end
396
- it 'reads given file' do
397
- expect(File).to receive(:readlines).with('.env').and_return(env_file)
398
- subject.send(:read_env_file, '.env')
399
- end
400
-
401
- it 'returns array' do
402
- variables = subject.send(:read_env_file, '.env')
403
- expect(variables).to eq([
404
- 'APIKEY=12345',
405
- 'MYSQL_ROOT_PASSWORD=secret',
406
- 'WP_ADMIN_PASSWORD=verysecret'
407
- ])
408
- end
409
-
410
- it 'discards comment lines' do
411
- result = env_file
412
- result << "#COMMENTLINE"
413
- allow(File).to receive(:readlines).with('.env').and_return(result)
414
-
415
- variables = subject.send(:read_env_file, '.env')
416
- expect(variables).to eq([
417
- 'APIKEY=12345',
418
- 'MYSQL_ROOT_PASSWORD=secret',
419
- 'WP_ADMIN_PASSWORD=verysecret'
420
- ])
421
- end
422
-
423
- it 'discards empty lines' do
424
- result = env_file
425
- result << '
426
- '
427
- allow(File).to receive(:readlines).with('.env').and_return(result)
428
- variables = subject.send(:read_env_file, '.env')
429
- expect(variables).to eq([
430
- 'APIKEY=12345',
431
- 'MYSQL_ROOT_PASSWORD=secret',
432
- 'WP_ADMIN_PASSWORD=verysecret'
433
- ])
434
- end
435
- end
384
+ end
436
385
  end
@@ -16,6 +16,12 @@ describe Kontena::Cli::Apps::YAML::Reader do
16
16
  spy
17
17
  end
18
18
 
19
+ let(:env_file) do
20
+ ['APIKEY=12345
21
+ ', 'MYSQL_ROOT_PASSWORD=secret
22
+ ', 'WP_ADMIN_PASSWORD=verysecret']
23
+ end
24
+
19
25
  let(:valid_result) do
20
26
  {
21
27
  'wordpress' => {
@@ -232,6 +238,57 @@ describe Kontena::Cli::Apps::YAML::Reader do
232
238
  result = subject.execute[:services]
233
239
  expect(result['mysql']['environment']).to eq(['MYSQL_ROOT_PASSWORD=test_secret'])
234
240
  end
241
+
242
+ context 'when introduced env_file' do
243
+ before(:each) do
244
+ allow(File).to receive(:read)
245
+ .with(absolute_yaml_path('kontena.yml'))
246
+ .and_return(fixture('kontena-with-env-file.yml'))
247
+ allow(File).to receive(:readlines).with('.env').and_return(env_file)
248
+ end
249
+
250
+ it 'reads given file' do
251
+ expect(File).to receive(:readlines).with('.env').and_return(env_file)
252
+ subject.send(:read_env_file, '.env')
253
+ end
254
+
255
+ it 'discards comment lines' do
256
+ result = env_file
257
+ result << "#COMMENTLINE"
258
+ allow(File).to receive(:readlines).with('.env').and_return(result)
259
+
260
+ variables = subject.send(:read_env_file, '.env')
261
+ expect(variables).to eq([
262
+ 'APIKEY=12345',
263
+ 'MYSQL_ROOT_PASSWORD=secret',
264
+ 'WP_ADMIN_PASSWORD=verysecret'
265
+ ])
266
+ end
267
+
268
+ it 'discards empty lines' do
269
+ result = env_file
270
+ result << '
271
+ '
272
+ allow(File).to receive(:readlines).with('.env').and_return(result)
273
+ variables = subject.send(:read_env_file, '.env')
274
+ expect(variables).to eq([
275
+ 'APIKEY=12345',
276
+ 'MYSQL_ROOT_PASSWORD=secret',
277
+ 'WP_ADMIN_PASSWORD=verysecret'
278
+ ])
279
+ end
280
+
281
+ it 'merges variables' do
282
+ result = subject.execute[:services]
283
+ expect(result['wordpress']['environment']).to eq([
284
+ 'WORDPRESS_DB_PASSWORD=test_secret',
285
+ 'APIKEY=12345',
286
+ 'MYSQL_ROOT_PASSWORD=secret',
287
+ 'WP_ADMIN_PASSWORD=verysecret'
288
+ ])
289
+ end
290
+
291
+ end
235
292
  end
236
293
  it 'returns result hash' do
237
294
  outcome = subject.execute
@@ -246,4 +303,31 @@ describe Kontena::Cli::Apps::YAML::Reader do
246
303
  expect(outcome[:errors].size).to eq(1)
247
304
  end
248
305
  end
306
+
307
+ context 'when build option is string' do
308
+ it 'expands build option to absolute path' do
309
+ allow(File).to receive(:read)
310
+ .with(absolute_yaml_path('docker-compose.yml'))
311
+ .and_return(fixture('docker-compose.yml'))
312
+ allow(File).to receive(:read)
313
+ .with(absolute_yaml_path('kontena.yml'))
314
+ .and_return(fixture('kontena-build.yml'))
315
+ outcome = subject.execute
316
+
317
+ expect(outcome[:services]['wordpress']['build']).to eq(File.expand_path('.'))
318
+ end
319
+ end
320
+
321
+ context 'when build option is Hash' do
322
+ it 'expands build context to absolute path' do
323
+ allow(File).to receive(:read)
324
+ .with(absolute_yaml_path('docker-compose.yml'))
325
+ .and_return(fixture('docker-compose.yml'))
326
+ allow(File).to receive(:read)
327
+ .with(absolute_yaml_path('kontena.yml'))
328
+ .and_return(fixture('kontena_build_v2.yml'))
329
+ outcome = subject.execute
330
+ expect(outcome[:services]['webapp']['build']['context']).to eq(File.expand_path('.'))
331
+ end
332
+ end
249
333
  end
@@ -223,5 +223,41 @@ module Kontena::Cli::Services
223
223
  expect(subject.parse_build_args({'foo' => 'bar', 'baz' => nil})).to eq({'foo' => 'bar', 'baz' => 'baf'})
224
224
  end
225
225
  end
226
+
227
+ describe '#health_status' do
228
+ it 'returns :unknown by default' do
229
+ expect(subject.health_status({})).to eq(:unknown)
230
+ end
231
+
232
+ it 'returns :healthy if all instances are healthy' do
233
+ data = {
234
+ 'health_status' => {
235
+ 'healthy' => 3,
236
+ 'total' => 3
237
+ }
238
+ }
239
+ expect(subject.health_status(data)).to eq(:healthy)
240
+ end
241
+
242
+ it 'returns :partial if not all instances are healthy' do
243
+ data = {
244
+ 'health_status' => {
245
+ 'healthy' => 2,
246
+ 'total' => 3
247
+ }
248
+ }
249
+ expect(subject.health_status(data)).to eq(:partial)
250
+ end
251
+
252
+ it 'returns :unhealthy if all instances are down' do
253
+ data = {
254
+ 'health_status' => {
255
+ 'healthy' => 0,
256
+ 'total' => 3
257
+ }
258
+ }
259
+ expect(subject.health_status(data)).to eq(:unhealthy)
260
+ end
261
+ end
226
262
  end
227
263
  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.0.rc1
4
+ version: 0.15.0.rc2
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-08-09 00:00:00.000000000 Z
11
+ date: 2016-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -339,9 +339,10 @@ files:
339
339
  - spec/fixtures/health.yml
340
340
  - spec/fixtures/kontena-build.yml
341
341
  - spec/fixtures/kontena-invalid.yml
342
+ - spec/fixtures/kontena-with-env-file.yml
342
343
  - spec/fixtures/kontena-with-variables.yml
343
344
  - spec/fixtures/kontena.yml
344
- - spec/fixtures/kontena_build_v2.yaml
345
+ - spec/fixtures/kontena_build_v2.yml
345
346
  - spec/fixtures/kontena_v2.yml
346
347
  - spec/fixtures/mysql.yml
347
348
  - spec/fixtures/wordpress-scaled.yml
@@ -419,9 +420,10 @@ test_files:
419
420
  - spec/fixtures/health.yml
420
421
  - spec/fixtures/kontena-build.yml
421
422
  - spec/fixtures/kontena-invalid.yml
423
+ - spec/fixtures/kontena-with-env-file.yml
422
424
  - spec/fixtures/kontena-with-variables.yml
423
425
  - spec/fixtures/kontena.yml
424
- - spec/fixtures/kontena_build_v2.yaml
426
+ - spec/fixtures/kontena_build_v2.yml
425
427
  - spec/fixtures/kontena_v2.yml
426
428
  - spec/fixtures/mysql.yml
427
429
  - spec/fixtures/wordpress-scaled.yml