kontena-cli 1.4.0 → 1.4.1.pre1

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