kontena-cli 1.4.0 → 1.4.1.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/VERSION +1 -1
  3. data/bin/kontena +2 -1
  4. data/kontena-cli.gemspec +1 -1
  5. data/lib/kontena/cli/certificate/common.rb +16 -0
  6. data/lib/kontena/cli/certificate/export_command.rb +28 -0
  7. data/lib/kontena/cli/certificate/import_command.rb +61 -0
  8. data/lib/kontena/cli/certificate/show_command.rb +5 -2
  9. data/lib/kontena/cli/certificate_command.rb +3 -1
  10. data/lib/kontena/cli/common.rb +5 -1
  11. data/lib/kontena/cli/config.rb +23 -5
  12. data/lib/kontena/cli/external_registries/add_command.rb +2 -0
  13. data/lib/kontena/cli/external_registries/list_command.rb +1 -1
  14. data/lib/kontena/cli/helpers/exec_helper.rb +12 -4
  15. data/lib/kontena/cli/master/config/import_command.rb +1 -1
  16. data/lib/kontena/cli/master/login_command.rb +1 -1
  17. data/lib/kontena/cli/stacks/common.rb +2 -1
  18. data/lib/kontena/cli/stacks/registry/show_command.rb +1 -1
  19. data/lib/kontena/cli/stacks/service_generator.rb +1 -0
  20. data/lib/kontena/cli/stacks/yaml/opto.rb +1 -0
  21. data/lib/kontena/cli/stacks/yaml/opto/certificates_resolver.rb +37 -0
  22. data/lib/kontena/cli/stacks/yaml/opto/vault_cert_prompt_resolver.rb +1 -1
  23. data/lib/kontena/cli/stacks/yaml/reader.rb +5 -2
  24. data/lib/kontena/cli/stacks/yaml/validations.rb +2 -1
  25. data/lib/kontena/cli/vault/import_command.rb +1 -1
  26. data/lib/kontena/client.rb +1 -1
  27. data/lib/kontena/command.rb +7 -5
  28. data/lib/kontena/plugin_manager.rb +1 -1
  29. data/lib/kontena/plugin_manager/cleaner.rb +1 -1
  30. data/lib/kontena/scripts/completer.rb +1 -1
  31. data/lib/kontena/stacks_cache.rb +2 -2
  32. data/lib/kontena_cli.rb +6 -2
  33. data/spec/fixtures/certificates/test/ca-key.pem +10 -0
  34. data/spec/fixtures/certificates/test/ca.pem +10 -0
  35. data/spec/fixtures/certificates/test/ca.srl +1 -0
  36. data/spec/fixtures/certificates/test/cert.pem +8 -0
  37. data/spec/fixtures/certificates/test/csr.pem +7 -0
  38. data/spec/fixtures/certificates/test/key.pem +10 -0
  39. data/spec/fixtures/certificates/test/real-cert.pem +30 -0
  40. data/spec/fixtures/docker-compose_v2.yml +1 -0
  41. data/spec/fixtures/docker-compose_v2_with_variables.yml +12 -0
  42. data/spec/fixtures/kontena_v3_with_compose_variables.yml +11 -0
  43. data/spec/fixtures/stack-with-anchors.yml +13 -0
  44. data/spec/kontena/cli/certificates/export_command_spec.rb +49 -0
  45. data/spec/kontena/cli/certificates/import_command_spec.rb +70 -0
  46. data/spec/kontena/cli/certificates/show_command_spec.rb +57 -0
  47. data/spec/kontena/cli/cloud/login_command_spec.rb +7 -14
  48. data/spec/kontena/cli/helpers/exec_helper_spec.rb +38 -0
  49. data/spec/kontena/cli/master/login_command_spec.rb +12 -24
  50. data/spec/kontena/cli/master/use_command_spec.rb +1 -1
  51. data/spec/kontena/cli/nodes/remove_command_spec.rb +1 -1
  52. data/spec/kontena/cli/registry/{create_spec.rb → create_command_spec.rb} +0 -0
  53. data/spec/kontena/cli/stacks/upgrade_command_spec.rb +1 -1
  54. data/spec/kontena/cli/stacks/yaml/opto/certificates_resolver_spec.rb +81 -0
  55. data/spec/kontena/cli/stacks/yaml/reader_spec.rb +22 -2
  56. data/spec/kontena/cli/stacks/yaml/validator_v3_spec.rb +17 -0
  57. data/spec/kontena/command_spec.rb +54 -0
  58. data/spec/kontena/config_spec.rb +17 -2
  59. data/spec/kontena/main_command_spec.rb +13 -0
  60. data/spec/spec_helper.rb +10 -17
  61. metadata +43 -11
  62. data/spec/kontena/cli/main_command_spec.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b07b1fcdc1957709dee6b0f6d99a4d4d7043c1bc
4
- data.tar.gz: 914ea52f2f68ac31c30532f8c76892fee8e84ff4
2
+ SHA256:
3
+ metadata.gz: b214db2a1e9aa6a1bcdc8987786facfac926a094bc617ae1ed2786c90e206c2e
4
+ data.tar.gz: bd38e299c9bad45f7d54990e761036a33a3608ca067166fa2991583c1cc0ef5e
5
5
  SHA512:
6
- metadata.gz: '03778cee2db71334f25d7f46bb88058bc718d66468e36b94576de486092baf32fa077ccd238f2f4664474bf33a2cd47e7778b280760061fc764a647ec4e81233'
7
- data.tar.gz: 82b5e132ce92dae422b2b97fc81c355f64c3951f20331d1fd6c6094bf1209b6b98bb8568ea68aa6a67e230fc29aec6c047239f43b88d480da6cc734d9ef1def7
6
+ metadata.gz: 13ee888f1151a74792b424fc796b89d5d2ff2319635bc4801bd11017227698f861cd16c2ba087330689a3ebc107fa2432364f08948f6cd55947f77a5977634f2
7
+ data.tar.gz: 70471db05d252ea9e9bc01be0a07513f83ae840342a619f4fe69cea5292f1cac5fc7bbc6df9465577a66700d0dc97ce8ef3dc101bdff42d951bafaac5b0e1fce
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.4.1.pre1
data/bin/kontena CHANGED
@@ -12,7 +12,8 @@ if ARGV[0] == 'complete'
12
12
  ARGV.delete_at(0)
13
13
  require 'kontena/scripts/completer'
14
14
  else
15
- ENV['DEBUG'] ||= "true" if ARGV.any? { |arg| arg == '-D' || arg == '--debug'}
15
+ ENV['DEBUG'] = "true" if ARGV.any? { |arg| arg == '-D' || arg == '--debug'}
16
+ ENV['DEBUG'] = "false" if ARGV.any? { |arg| arg == '--no-debug' }
16
17
  require 'kontena_cli'
17
18
  Kontena::PluginManager.init unless ENV['NO_PLUGINS']
18
19
  Kontena::MainCommand.run
data/kontena-cli.gemspec CHANGED
@@ -32,6 +32,6 @@ Gem::Specification.new do |spec|
32
32
  spec.add_runtime_dependency "opto", "1.8.7"
33
33
  spec.add_runtime_dependency "semantic", "~> 1.5"
34
34
  spec.add_runtime_dependency "liquid", "~> 4.0.0"
35
- spec.add_runtime_dependency "tty-table", "~> 0.8.0"
35
+ spec.add_runtime_dependency "tty-table", "~> 0.9.0"
36
36
  spec.add_runtime_dependency "kontena-websocket-client", "~> 0.1.1"
37
37
  end
@@ -0,0 +1,16 @@
1
+ module Kontena::Cli::Certificate
2
+ module Common
3
+ def show_certificate(cert)
4
+ puts "#{cert['id']}:"
5
+ puts " subject: #{cert['subject']}"
6
+ puts " valid until: #{Time.parse(cert['valid_until']).utc.strftime("%FT%TZ")}"
7
+ if cert['alt_names'] && !cert['alt_names'].empty?
8
+ puts " alt names:"
9
+ cert['alt_names'].each do |alt_name|
10
+ puts " - #{alt_name}"
11
+ end
12
+ end
13
+ puts " auto renewable: #{cert['auto_renewable']}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Kontena::Cli::Certificate
2
+ class ExportCommand < Kontena::Command
3
+ include Kontena::Cli::Common
4
+ include Kontena::Cli::GridOptions
5
+
6
+ parameter "SUBJECT", "Certificate subject"
7
+
8
+ requires_current_master
9
+ requires_current_master_token
10
+ requires_current_grid
11
+
12
+ option ['--certificate', '--cert'], :flag, "Output certificate"
13
+ option ['--chain'], :flag, "Output chain"
14
+ option ['--private-key', '--key'], :flag, "Output private key"
15
+
16
+ def bundle?
17
+ ![certificate?, chain?, private_key?].any?
18
+ end
19
+
20
+ def execute
21
+ certificate = client.get("certificates/#{current_grid}/#{self.subject}/export")
22
+
23
+ puts certificate['certificate'] if certificate? || bundle?
24
+ puts certificate['chain'] if chain? || bundle?
25
+ puts certificate['private_key'] if private_key? || bundle?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,61 @@
1
+ require 'openssl'
2
+ require_relative './common'
3
+
4
+ module Kontena::Cli::Certificate
5
+ class ImportCommand < Kontena::Command
6
+ include Kontena::Cli::Common
7
+ include Kontena::Cli::GridOptions
8
+ include Common
9
+
10
+ # @raise [ArgumentError]
11
+ def open_file(path)
12
+ File.open(path)
13
+ rescue Errno::ENOENT
14
+ raise ArgumentError, "File not found: #{path}"
15
+ end
16
+
17
+ parameter 'CERT_FILE', "Path to PEM-encoded X.509 certificate file" do |path|
18
+ open_file(path)
19
+ end
20
+ option '--subject', 'SUBJECT', "Import cert specific subject"
21
+ option ['--private-key', '--key'], 'KEY_FILE', "Path to private key file", :required => true, :attribute_name => :key_file do |path|
22
+ open_file(path)
23
+ end
24
+ option ['--chain'], 'CHAIN_FILE', "Path to CA cert chain file", :multivalued => true, :attribute_name => :chain_file_list do |path|
25
+ open_file(path)
26
+ end
27
+
28
+ requires_current_master
29
+ requires_current_master_token
30
+ requires_current_grid
31
+
32
+ def load_certificate
33
+ OpenSSL::X509::Certificate.new(self.cert_file)
34
+ rescue OpenSSL::OpenSSLError => exc
35
+ exit_with_error "Invalid certificate at #{self.cert_file.path}: #{exc.class}: #{exc.message}"
36
+ end
37
+
38
+ def certificate_subject(cert)
39
+ cert.subject.to_a.each do |name, data|
40
+ return data if name == 'CN'
41
+ end
42
+
43
+ exit_with_error "No CN in certificate subject: #{cert.subject}"
44
+ end
45
+
46
+ def execute
47
+ cert = load_certificate
48
+ subject = self.subject || self.certificate_subject(cert)
49
+
50
+ certificate = spinner "Importing certificate from #{cert_file.path}..." do
51
+ client.put("certificates/#{current_grid}/#{subject}",
52
+ certificate: cert.to_pem,
53
+ private_key: self.key_file.read(),
54
+ chain: chain_file_list.map{|chain_file| chain_file.read() },
55
+ )
56
+ end
57
+
58
+ show_certificate(certificate)
59
+ end
60
+ end
61
+ end
@@ -1,9 +1,11 @@
1
1
  require_relative '../services/services_helper'
2
+ require_relative './common'
2
3
 
3
4
  module Kontena::Cli::Certificate
4
5
  class ShowCommand < Kontena::Command
5
6
  include Kontena::Cli::Common
6
7
  include Kontena::Cli::GridOptions
8
+ include Common
7
9
 
8
10
  parameter "SUBJECT", "Certificate subject"
9
11
 
@@ -12,8 +14,9 @@ module Kontena::Cli::Certificate
12
14
  requires_current_grid
13
15
 
14
16
  def execute
15
- certificate = client.get("certificates/#{current_grid}/#{self.subject}")
16
- puts YAML.dump(certificate)
17
+ cert = client.get("certificates/#{current_grid}/#{self.subject}")
18
+
19
+ show_certificate(cert)
17
20
  end
18
21
  end
19
22
  end
@@ -3,13 +3,15 @@ class Kontena::Cli::CertificateCommand < Kontena::Command
3
3
 
4
4
  subcommand ["list", "ls"], "List certificates", load_subcommand('certificate/list_command')
5
5
  subcommand "show", "Show certificate details", load_subcommand('certificate/show_command')
6
+ subcommand "export", "Export certificate to file", load_subcommand('certificate/export_command')
6
7
  subcommand "register", "Register to LetsEncrypt", load_subcommand('certificate/register_command')
7
8
  subcommand "authorize", "Create DNS authorization for domain", load_subcommand('certificate/authorize_command')
8
9
  subcommand "request", "Request certificate for domain", load_subcommand('certificate/request_command')
9
10
  subcommand "get", "Get certificate for domain", load_subcommand('certificate/get_command')
11
+ subcommand "import", "Import certificate from file", load_subcommand('certificate/import_command')
10
12
  subcommand ["remove", "rm"], "Remove certificate for domain", load_subcommand('certificate/remove_command')
11
13
 
12
14
 
13
15
  def execute
14
16
  end
15
- end
17
+ end
@@ -40,6 +40,10 @@ module Kontena
40
40
  Kontena::Cli::Config.instance
41
41
  end
42
42
 
43
+ def debug?
44
+ Kontena.debug?
45
+ end
46
+
43
47
  # Read from STDIN. If stdin is a console, use prompt to ask.
44
48
  # @param [String] message
45
49
  # @param [Symbol] mode (prompt method: :ask, :multiline, etc)
@@ -100,7 +104,7 @@ module Kontena
100
104
  def vputs(msg = nil)
101
105
  if running_verbose?
102
106
  puts msg
103
- elsif ENV["DEBUG"] && msg
107
+ elsif debug? && msg
104
108
  logger.debug msg
105
109
  end
106
110
  end
@@ -53,21 +53,39 @@ module Kontena
53
53
 
54
54
  # Craft a regular looking configuration based on ENV variables
55
55
  def load_settings_from_env
56
+ load_cloud_settings_from_env
57
+ load_master_settings_from_env
58
+ end
59
+
60
+ def load_master_settings_from_env
56
61
  return nil unless ENV['KONTENA_URL']
57
- debug { 'Loading configuration from ENV' }
62
+
63
+ debug { 'Loading master configuration from ENV' }
58
64
  servers << Server.new(
59
65
  url: ENV['KONTENA_URL'],
60
66
  name: 'default',
61
- token: Token.new(access_token: ENV['KONTENA_TOKEN'], parent_type: :master, parent_name: 'default'),
67
+ token: Token.new(
68
+ access_token: ENV['KONTENA_TOKEN'],
69
+ parent_type: :master, parent_name: 'default'
70
+ ),
62
71
  grid: ENV['KONTENA_GRID'],
63
72
  parent_type: :master,
64
73
  parent_name: 'default'
65
74
  )
66
- accounts << Account.new(kontena_account_data.merge(
67
- token: Token.new(access_token: ENV['KONTENA_CLOUD_TOKEN'], parent_type: :account, parent_name: 'default')
68
- ))
69
75
 
70
76
  self.current_master = 'default'
77
+ end
78
+
79
+ def load_cloud_settings_from_env
80
+ return unless ENV['KONTENA_CLOUD_TOKEN']
81
+
82
+ debug { 'Loading cloud configuration from ENV' }
83
+ accounts << Account.new(kontena_account_data.merge(
84
+ token: Token.new(
85
+ access_token: ENV['KONTENA_CLOUD_TOKEN'],
86
+ parent_type: :account, parent_name: 'default'
87
+ )
88
+ ))
71
89
  self.current_account = 'kontena'
72
90
  end
73
91
 
@@ -14,6 +14,8 @@ module Kontena::Cli::ExternalRegistries
14
14
  require_current_grid
15
15
  token = require_token
16
16
 
17
+ self.url = "https://#{self.url}" unless self.url.start_with?('http')
18
+
17
19
  data = { username: username, password: password, email: email, url: url }
18
20
  spinner "Adding #{url.colorize(:cyan)} to external registries " do
19
21
  client(token).post("grids/#{current_grid}/external_registries", data)
@@ -9,7 +9,7 @@ module Kontena::Cli::ExternalRegistries
9
9
  requires_current_grid
10
10
 
11
11
  def fields
12
- quiet? ? %(name) : %w(name username email)
12
+ quiet? ? %w(name) : %w(name username email)
13
13
  end
14
14
 
15
15
  def external_registries
@@ -6,7 +6,7 @@ module Kontena::Cli::Helpers
6
6
 
7
7
  websocket_log_level = if ENV["DEBUG"] == 'websocket'
8
8
  Logger::DEBUG
9
- elsif ENV["DEBUG"]
9
+ elsif Kontena.debug?
10
10
  Logger::INFO
11
11
  else
12
12
  Logger::WARN
@@ -27,7 +27,7 @@ module Kontena::Cli::Helpers
27
27
  # @param tty [Boolean] read stdin in raw mode, sending tty escapes for remote pty
28
28
  # @raise [ArgumentError] not a tty
29
29
  # @yield [data]
30
- # @yieldparam data [String] data from stdin
30
+ # @yieldparam data [String] unicode data from stdin
31
31
  # @raise [ArgumentError] not a tty
32
32
  # @return EOF on stdin (!tty)
33
33
  def read_stdin(tty: nil)
@@ -38,11 +38,18 @@ module Kontena::Cli::Helpers
38
38
  # we do not expect EOF on a TTY, ^D sends a tty escape to close the pty instead
39
39
  loop do
40
40
  # raises EOFError, SyscallError or IOError
41
- yield io.readpartial(1024)
41
+ chunk = io.readpartial(1024)
42
+
43
+ # STDIN.raw does not use the ruby external_encoding, it returns binary strings (ASCII-8BIT encoding)
44
+ # however, we use websocket text frames with JSON, which expects unicode strings encodable as UTF-8, and does not handle arbitrary binary data
45
+ # assume all stdin input is using ruby's external_encoding... the JSON.dump will fail if not.
46
+ chunk.force_encoding(Encoding.default_external)
47
+
48
+ yield chunk
42
49
  end
43
50
  }
44
51
  else
45
- # line-buffered
52
+ # line-buffered, using the default external_encoding (probably UTF-8)
46
53
  while line = STDIN.gets
47
54
  yield line
48
55
  end
@@ -106,6 +113,7 @@ module Kontena::Cli::Helpers
106
113
  })
107
114
  end
108
115
  read_stdin(tty: tty) do |stdin|
116
+ logger.debug "websocket exec stdin with encoding=#{stdin.encoding}: #{stdin.inspect}"
109
117
  websocket_exec_write(ws, 'stdin' => stdin)
110
118
  end
111
119
  websocket_exec_write(ws, 'stdin' => nil) # EOF
@@ -40,7 +40,7 @@ module Kontena::Cli::Master::Config
40
40
  require 'json'
41
41
  JSON.parse(data)
42
42
  when 'yaml', 'yml'
43
- YAML.safe_load(data)
43
+ ::YAML.safe_load(data, [], [], true)
44
44
  else
45
45
  exit_with_error "Unknown input format '#{self.format}'"
46
46
  end
@@ -159,7 +159,7 @@ module Kontena::Cli::Master
159
159
  elsif response.kind_of?(String) && response.length > 1
160
160
  exit_with_error response
161
161
  else
162
- exit_with_error "Invalid response to authentication request : HTTP#{client.last_response.status} #{client.last_response.body if ENV["DEBUG"]}"
162
+ exit_with_error "Invalid response to authentication request : HTTP#{client.last_response.status} #{client.last_response.body if debug?}"
163
163
  end
164
164
  end
165
165
  end
@@ -95,7 +95,7 @@ module Kontena::Cli::Stacks
95
95
  where.prepend InstanceMethods
96
96
 
97
97
  where.option '--values-from', '[FILE]', 'Read variable values from a YAML file', multivalued: true do |filename|
98
- values_from_file.merge!(::YAML.safe_load(File.read(filename)))
98
+ values_from_file.merge!(::YAML.safe_load(File.read(filename), [], [], true, filename))
99
99
  filename
100
100
  end
101
101
 
@@ -110,6 +110,7 @@ module Kontena::Cli::Stacks
110
110
  where.option '-v', "VARIABLE=VALUE", "Set stack variable values, example: -v domain=example.com. Can be used multiple times.", multivalued: true, attribute_name: :var_option do |var_pair|
111
111
  var_name, var_value = var_pair.split('=', 2)
112
112
  values_from_value_options.merge!(::YAML.safe_load(::YAML.dump(var_name => var_value)))
113
+ var_pair
113
114
  end
114
115
  end
115
116
 
@@ -15,7 +15,7 @@ module Kontena::Cli::Stacks::Registry
15
15
  def execute
16
16
  require 'semantic'
17
17
  unless versions?
18
- stack = ::YAML.safe_load(stacks_client.show(stack_name.stack_name, stack_name.version))
18
+ stack = ::YAML.safe_load(stacks_client.show(stack_name.stack_name, stack_name.version), [], [], true)
19
19
  puts "#{stack['stack']}:"
20
20
  puts " #{"latest_" unless stack_name.version}version: #{stack['version']}"
21
21
  puts " expose: #{stack['expose'] || '-'}"
@@ -69,6 +69,7 @@ module Kontena::Cli::Stacks
69
69
  data['stop_signal'] = options['stop_signal'] if options['stop_signal']
70
70
  data['stop_grace_period'] = options['stop_grace_period'] if options['stop_grace_period']
71
71
  data['read_only'] = options['read_only'] || false
72
+ data['entrypoint'] = options['entrypoint'] if options['entrypoint']
72
73
  data
73
74
  end
74
75
 
@@ -12,4 +12,5 @@ require_relative 'opto/vault_resolver'
12
12
  require_relative 'opto/prompt_resolver'
13
13
  require_relative 'opto/service_instances_resolver'
14
14
  require_relative 'opto/vault_cert_prompt_resolver'
15
+ require_relative 'opto/certificates_resolver'
15
16
  require_relative 'opto/service_link_resolver'
@@ -0,0 +1,37 @@
1
+ module Kontena::Cli::Stacks::YAML::Opto::Resolvers
2
+ class Certificates < ::Opto::Resolver
3
+ include Kontena::Cli::Common
4
+
5
+ def resolve
6
+ return nil unless current_master && current_grid
7
+ message = hint || 'Select SSL certificates'
8
+ certificates = get_certificates
9
+ if certificates.size > 0
10
+ prompt.multi_select(message) do |menu|
11
+ menu.default(*default_indexes(certificates)) if option.default
12
+ certificates.each do |s|
13
+ menu.choice s['subject']
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # @return [Array<Hash>] certificates
20
+ def get_certificates
21
+ client.get("grids/#{current_grid}/certificates")['certificates']
22
+ rescue
23
+ []
24
+ end
25
+
26
+ # @param certificates [Array<Hash>]
27
+ # @return [Array<Integer>]
28
+ def default_indexes(certificates)
29
+ indexes = []
30
+ option.default.to_a.each do |subject|
31
+ index = certificates.index { |s| s['subject'] == subject }
32
+ indexes << index.to_i + 1 if index
33
+ end
34
+ indexes
35
+ end
36
+ end
37
+ end
@@ -9,7 +9,7 @@ module Kontena::Cli::Stacks::YAML::Opto::Resolvers
9
9
  s['name'].match(/(ssl|cert)/i)
10
10
  }
11
11
  if secrets.size > 0
12
- prompt.multi_select(hint) do |menu|
12
+ prompt.multi_select(message) do |menu|
13
13
  menu.default(*default_indexes(secrets)) if option.default
14
14
  secrets.each do |s|
15
15
  menu.choice s['name']
@@ -83,7 +83,7 @@ module Kontena::Cli::Stacks
83
83
  substitutions: default_envs,
84
84
  warnings: false
85
85
  )
86
- )
86
+ ), [], [], true, file
87
87
  )
88
88
  rescue Psych::SyntaxError => ex
89
89
  raise ex, "Error while parsing #{file} : #{ex.message}"
@@ -104,7 +104,7 @@ module Kontena::Cli::Stacks
104
104
  use_opto: true,
105
105
  raise_on_unknown: true
106
106
  )
107
- )
107
+ ), [], [], true, file
108
108
  )
109
109
  rescue Psych::SyntaxError => ex
110
110
  raise ex, "Error while parsing #{file} : #{ex.message}"
@@ -350,6 +350,9 @@ module Kontena::Cli::Stacks
350
350
 
351
351
  def from_external_file(filename, service_name)
352
352
  external_reader = FileLoader.new(filename, loader).reader
353
+ variables.to_a(with_value: true).each do |var|
354
+ external_reader.variables.build_option(var)
355
+ end
353
356
  outcome = external_reader.execute(service_name)
354
357
  errors.concat external_reader.errors unless external_reader.errors.empty? || errors.include?(external_reader.errors)
355
358
  notifications.concat external_reader.notifications unless external_reader.notifications.empty? || notifications.include?(external_reader.notifications)