Always send the clear env to the container
Secret and clear env variables have different lifecycles. The clear ones are part of the repo, so it makes sense to always deploy them with the rest of the repo. The secret ones are external so we can't be sure that they are up to date, therefore they require an explicit push via `envify` or `env push`. We'll keep the env file, but now it just contains secrets. The clear values are passed directly to `docker run`.
This commit is contained in:
@@ -9,20 +9,20 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
||||
|
||||
KAMAL.roles_on(host).each do |role|
|
||||
execute *KAMAL.app(role: role).make_env_directory
|
||||
upload! StringIO.new(role.env_file), role.host_env_file_path, mode: 400
|
||||
upload! role.env.secrets_io, role.env.secrets_file, mode: 400
|
||||
end
|
||||
end
|
||||
|
||||
on(KAMAL.traefik_hosts) do
|
||||
execute *KAMAL.traefik.make_env_directory
|
||||
upload! StringIO.new(KAMAL.traefik.env_file), KAMAL.traefik.host_env_file_path, mode: 400
|
||||
upload! KAMAL.traefik.env.secrets_io, KAMAL.traefik.env.secrets_file, mode: 400
|
||||
end
|
||||
|
||||
on(KAMAL.accessory_hosts) do
|
||||
KAMAL.accessories_on(host).each do |accessory|
|
||||
accessory_config = KAMAL.config.accessory(accessory)
|
||||
execute *KAMAL.accessory(accessory).make_env_directory
|
||||
upload! StringIO.new(accessory_config.env_file), accessory_config.host_env_file_path, mode: 400
|
||||
upload! accessory_config.env.secrets_io, accessory_config.env.secrets_file, mode: 400
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -99,11 +99,11 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def make_env_directory
|
||||
make_directory accessory_config.host_env_directory
|
||||
make_directory accessory_config.env.secrets_directory
|
||||
end
|
||||
|
||||
def remove_env_file
|
||||
[ :rm, "-f", accessory_config.host_env_file_path ]
|
||||
[ :rm, "-f", accessory_config.env.secrets_file ]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -68,11 +68,11 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
|
||||
|
||||
def make_env_directory
|
||||
make_directory role.host_env_directory
|
||||
make_directory role.env.secrets_directory
|
||||
end
|
||||
|
||||
def remove_env_file
|
||||
[ :rm, "-f", role.host_env_file_path ]
|
||||
[ :rm, "-f", role.env.secrets_file ]
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -71,20 +71,18 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
||||
"#{host_port}:#{CONTAINER_PORT}"
|
||||
end
|
||||
|
||||
def env_file
|
||||
Kamal::EnvFile.new(config.traefik.fetch("env", {}))
|
||||
end
|
||||
|
||||
def host_env_file_path
|
||||
File.join host_env_directory, "traefik.env"
|
||||
def env
|
||||
Kamal::Configuration::Env.from_config \
|
||||
config: config.traefik.fetch("env", {}),
|
||||
secrets_file: File.join(config.host_env_directory, "traefik", "traefik.env")
|
||||
end
|
||||
|
||||
def make_env_directory
|
||||
make_directory(host_env_directory)
|
||||
make_directory(env.secrets_directory)
|
||||
end
|
||||
|
||||
def remove_env_file
|
||||
[ :rm, "-f", host_env_file_path ]
|
||||
[ :rm, "-f", env.secrets_file ]
|
||||
end
|
||||
|
||||
private
|
||||
@@ -97,11 +95,7 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def env_args
|
||||
argumentize "--env-file", host_env_file_path
|
||||
end
|
||||
|
||||
def host_env_directory
|
||||
File.join config.host_env_directory, "traefik"
|
||||
env.args
|
||||
end
|
||||
|
||||
def labels
|
||||
|
||||
@@ -6,7 +6,7 @@ require "erb"
|
||||
require "net/ssh/proxy/jump"
|
||||
|
||||
class Kamal::Configuration
|
||||
delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
|
||||
delegate :service, :image, :servers, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
|
||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||
|
||||
attr_reader :destination, :raw_config
|
||||
@@ -216,12 +216,17 @@ class Kamal::Configuration
|
||||
raw_config.hooks_path || ".kamal/hooks"
|
||||
end
|
||||
|
||||
def asset_path
|
||||
raw_config.asset_path
|
||||
end
|
||||
|
||||
|
||||
def host_env_directory
|
||||
"#{run_directory}/env"
|
||||
end
|
||||
|
||||
def asset_path
|
||||
raw_config.asset_path
|
||||
def env
|
||||
raw_config.env || {}
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -42,23 +42,13 @@ class Kamal::Configuration::Accessory
|
||||
end
|
||||
|
||||
def env
|
||||
specifics["env"] || {}
|
||||
end
|
||||
|
||||
def env_file
|
||||
Kamal::EnvFile.new(env)
|
||||
end
|
||||
|
||||
def host_env_directory
|
||||
File.join config.host_env_directory, "accessories"
|
||||
end
|
||||
|
||||
def host_env_file_path
|
||||
File.join host_env_directory, "#{service_name}.env"
|
||||
Kamal::Configuration::Env.from_config \
|
||||
config: specifics.fetch("env", {}),
|
||||
secrets_file: File.join(config.host_env_directory, "accessories", "#{service_name}.env")
|
||||
end
|
||||
|
||||
def env_args
|
||||
argumentize "--env-file", host_env_file_path
|
||||
env.args
|
||||
end
|
||||
|
||||
def files
|
||||
@@ -111,10 +101,10 @@ class Kamal::Configuration::Accessory
|
||||
end
|
||||
|
||||
def with_clear_env_loaded
|
||||
(env["clear"] || env).each { |k, v| ENV[k] = v }
|
||||
env.clear.each { |k, v| ENV[k] = v }
|
||||
yield
|
||||
ensure
|
||||
(env["clear"] || env).each { |k, v| ENV.delete(k) }
|
||||
env.clear.each { |k, v| ENV.delete(k) }
|
||||
end
|
||||
|
||||
def read_dynamic_file(local_file)
|
||||
|
||||
40
lib/kamal/configuration/env.rb
Normal file
40
lib/kamal/configuration/env.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
class Kamal::Configuration::Env
|
||||
attr_reader :secrets_keys, :clear, :secrets_file
|
||||
delegate :argumentize, to: Kamal::Utils
|
||||
|
||||
def self.from_config(config:, secrets_file: nil)
|
||||
secrets_keys = config.fetch("secret", [])
|
||||
clear = config.fetch("clear", config.key?("secret") ? {} : config)
|
||||
|
||||
new clear: clear, secrets_keys: secrets_keys, secrets_file: secrets_file
|
||||
end
|
||||
|
||||
def initialize(clear:, secrets_keys:, secrets_file:)
|
||||
@clear = clear
|
||||
@secrets_keys = secrets_keys
|
||||
@secrets_file = secrets_file
|
||||
end
|
||||
|
||||
def args
|
||||
[ "--env-file", secrets_file, *argumentize("--env", clear) ]
|
||||
end
|
||||
|
||||
def secrets_io
|
||||
StringIO.new(Kamal::EnvFile.new(secrets).to_s)
|
||||
end
|
||||
|
||||
def secrets
|
||||
@secrets ||= secrets_keys.to_h { |key| [ key, ENV.fetch(key) ] }
|
||||
end
|
||||
|
||||
def secrets_directory
|
||||
File.dirname(secrets_file)
|
||||
end
|
||||
|
||||
def merge(other)
|
||||
self.class.new \
|
||||
clear: @clear.merge(other.clear),
|
||||
secrets_keys: @secrets_keys | other.secrets_keys,
|
||||
secrets_file: secrets_file
|
||||
end
|
||||
end
|
||||
@@ -51,27 +51,11 @@ class Kamal::Configuration::Role
|
||||
|
||||
|
||||
def env
|
||||
if config.env && config.env["secret"]
|
||||
merged_env_with_secrets
|
||||
else
|
||||
merged_env
|
||||
end
|
||||
end
|
||||
|
||||
def env_file
|
||||
Kamal::EnvFile.new(env)
|
||||
end
|
||||
|
||||
def host_env_directory
|
||||
File.join config.host_env_directory, "roles"
|
||||
end
|
||||
|
||||
def host_env_file_path
|
||||
File.join host_env_directory, "#{[ config.service, name, config.destination ].compact.join("-")}.env"
|
||||
@env ||= base_env.merge(specialized_env)
|
||||
end
|
||||
|
||||
def env_args
|
||||
argumentize "--env-file", host_env_file_path
|
||||
env.args
|
||||
end
|
||||
|
||||
def asset_volume_args
|
||||
@@ -217,7 +201,7 @@ class Kamal::Configuration::Role
|
||||
end
|
||||
|
||||
def traefik_service
|
||||
[ config.service, name, config.destination ].compact.join("-")
|
||||
container_prefix
|
||||
end
|
||||
|
||||
def custom_labels
|
||||
@@ -236,24 +220,14 @@ class Kamal::Configuration::Role
|
||||
end
|
||||
|
||||
def specialized_env
|
||||
specializations["env"] || {}
|
||||
end
|
||||
|
||||
def merged_env
|
||||
config.env&.merge(specialized_env) || {}
|
||||
Kamal::Configuration::Env.from_config config: specializations.fetch("env", {})
|
||||
end
|
||||
|
||||
# Secrets are stored in an array, which won't merge by default, so have to do it by hand.
|
||||
def merged_env_with_secrets
|
||||
merged_env.tap do |new_env|
|
||||
new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
|
||||
|
||||
# If there's no secret/clear split, everything is clear
|
||||
clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env)
|
||||
clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env)
|
||||
|
||||
new_env["clear"] = clear_app_env.to_h.merge(clear_role_env.to_h)
|
||||
end
|
||||
def base_env
|
||||
Kamal::Configuration::Env.from_config \
|
||||
config: config.env,
|
||||
secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env")
|
||||
end
|
||||
|
||||
def http_health_check(port:, path:)
|
||||
|
||||
@@ -6,19 +6,9 @@ class Kamal::EnvFile
|
||||
|
||||
def to_s
|
||||
env_file = StringIO.new.tap do |contents|
|
||||
if (secrets = @env["secret"]).present?
|
||||
@env.fetch("secret", @env)&.each do |key|
|
||||
contents << docker_env_file_line(key, ENV.fetch(key))
|
||||
end
|
||||
|
||||
@env["clear"]&.each do |key, value|
|
||||
@env.each do |key, value|
|
||||
contents << docker_env_file_line(key, value)
|
||||
end
|
||||
else
|
||||
@env.fetch("clear", @env)&.each do |key, value|
|
||||
contents << docker_env_file_line(key, value)
|
||||
end
|
||||
end
|
||||
end.string
|
||||
|
||||
# Ensure the file has some contents to avoid the SSHKIT empty file warning
|
||||
|
||||
@@ -7,7 +7,7 @@ class CliAccessoryTest < CliTestCase
|
||||
|
||||
run_command("boot", "mysql").tap do |output|
|
||||
assert_match /docker login.*on 1.1.1.3/, output
|
||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,7 +21,7 @@ class CliAccessoryTest < CliTestCase
|
||||
assert_match /docker login.*on 1.1.1.3/, output
|
||||
assert_match /docker login.*on 1.1.1.1/, output
|
||||
assert_match /docker login.*on 1.1.1.2/, output
|
||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
||||
end
|
||||
|
||||
@@ -50,11 +50,11 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "run" do
|
||||
assert_equal \
|
||||
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --label service=\"app-mysql\" private.registry/mysql:8.0",
|
||||
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" private.registry/mysql:8.0",
|
||||
new_command(:mysql).run.join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
||||
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --env SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
||||
new_command(:redis).run.join(" ")
|
||||
|
||||
assert_equal \
|
||||
@@ -91,7 +91,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container" do
|
||||
assert_equal \
|
||||
"docker run --rm --env-file .kamal/env/accessories/app-mysql.env private.registry/mysql:8.0 mysql -u root",
|
||||
"docker run --rm --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root",
|
||||
new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ")
|
||||
end
|
||||
|
||||
@@ -103,7 +103,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
|
||||
test "execute in new container over ssh" do
|
||||
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
||||
assert_match %r{docker run -it --rm --env-file .kamal/env/accessories/app-mysql.env private.registry/mysql:8.0 mysql -u root},
|
||||
assert_match %r{docker run -it --rm --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root},
|
||||
new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -177,14 +177,10 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
||||
new_command.follow_logs(host: @config[:servers].first, grep: "hello!")
|
||||
end
|
||||
|
||||
test "env_file" do
|
||||
test "secrets io" do
|
||||
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
||||
|
||||
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env_file.to_s
|
||||
end
|
||||
|
||||
test "host_env_file_path" do
|
||||
assert_equal ".kamal/env/traefik/traefik.env", new_command.host_env_file_path
|
||||
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env.secrets_io.string
|
||||
end
|
||||
|
||||
test "make_env_directory" do
|
||||
|
||||
@@ -113,29 +113,25 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "env args" do
|
||||
assert_equal [ "--env-file", ".kamal/env/accessories/app-mysql.env" ], @config.accessory(:mysql).env_args
|
||||
assert_equal [ "--env-file", ".kamal/env/accessories/app-redis.env" ], @config.accessory(:redis).env_args
|
||||
assert_equal [ "--env-file", ".kamal/env/accessories/app-mysql.env", "--env", "MYSQL_ROOT_HOST=\"%\"" ], @config.accessory(:mysql).env_args
|
||||
assert_equal [ "--env-file", ".kamal/env/accessories/app-redis.env", "--env", "SOMETHING=\"else\"" ], @config.accessory(:redis).env_args
|
||||
end
|
||||
|
||||
test "env file with secret" do
|
||||
test "env with secrets" do
|
||||
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
||||
|
||||
expected = <<~ENV
|
||||
expected_secrets_file = <<~ENV
|
||||
MYSQL_ROOT_PASSWORD=secret123
|
||||
MYSQL_ROOT_HOST=%
|
||||
ENV
|
||||
|
||||
assert_equal expected, @config.accessory(:mysql).env_file.to_s
|
||||
assert_equal expected_secrets_file, @config.accessory(:mysql).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/accessories/app-mysql.env", "--env", "MYSQL_ROOT_HOST=\"%\"" ], @config.accessory(:mysql).env_args
|
||||
ensure
|
||||
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
||||
end
|
||||
|
||||
test "host_env_directory" do
|
||||
assert_equal ".kamal/env/accessories", @config.accessory(:mysql).host_env_directory
|
||||
end
|
||||
|
||||
test "host_env_file_path" do
|
||||
assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).host_env_file_path
|
||||
test "env secrets path" do
|
||||
assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).env.secrets_file
|
||||
end
|
||||
|
||||
test "volume args" do
|
||||
|
||||
74
test/configuration/env_test.rb
Normal file
74
test/configuration/env_test.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
require "test_helper"
|
||||
|
||||
class ConfigurationEnvTest < ActiveSupport::TestCase
|
||||
require "test_helper"
|
||||
|
||||
test "simple" do
|
||||
assert_config \
|
||||
config: { "foo" => "bar", "baz" => "haz" },
|
||||
clear: { "foo" => "bar", "baz" => "haz" },
|
||||
secrets: {}
|
||||
end
|
||||
|
||||
test "clear" do
|
||||
assert_config \
|
||||
config: { "clear" => { "foo" => "bar", "baz" => "haz" } },
|
||||
clear: { "foo" => "bar", "baz" => "haz" },
|
||||
secrets: {}
|
||||
end
|
||||
|
||||
test "secret" do
|
||||
ENV["PASSWORD"] = "hello"
|
||||
env = Kamal::Configuration::Env.from_config config: { "secret" => [ "PASSWORD" ] }
|
||||
|
||||
assert_config \
|
||||
config: { "secret" => [ "PASSWORD" ] },
|
||||
clear: {},
|
||||
secrets: { "PASSWORD" => "hello" }
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "missing secret" do
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ]
|
||||
}
|
||||
|
||||
assert_raises(KeyError) { Kamal::Configuration::Env.from_config(config: { "secret" => [ "PASSWORD" ] }).secrets }
|
||||
end
|
||||
|
||||
test "secret and clear" do
|
||||
ENV["PASSWORD"] = "hello"
|
||||
config = {
|
||||
"secret" => [ "PASSWORD" ],
|
||||
"clear" => {
|
||||
"foo" => "bar",
|
||||
"baz" => "haz"
|
||||
}
|
||||
}
|
||||
|
||||
assert_config \
|
||||
config: config,
|
||||
clear: { "foo" => "bar", "baz" => "haz" },
|
||||
secrets: { "PASSWORD" => "hello" }
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "stringIO conversion" do
|
||||
env = {
|
||||
"foo" => "bar",
|
||||
"baz" => "haz"
|
||||
}
|
||||
|
||||
assert_equal "foo=bar\nbaz=haz\n", \
|
||||
StringIO.new(Kamal::EnvFile.new(env)).read
|
||||
end
|
||||
|
||||
private
|
||||
def assert_config(config:, clear:, secrets:)
|
||||
env = Kamal::Configuration::Env.from_config config: config
|
||||
assert_equal clear, env.clear
|
||||
assert_equal secrets, env.secrets
|
||||
end
|
||||
end
|
||||
@@ -70,14 +70,10 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "env overwritten by role" do
|
||||
assert_equal "redis://a/b", @config_with_roles.role(:workers).env["REDIS_URL"]
|
||||
assert_equal "redis://a/b", @config_with_roles.role(:workers).env.clear["REDIS_URL"]
|
||||
|
||||
expected_env = <<~ENV
|
||||
REDIS_URL=redis://a/b
|
||||
WEB_CONCURRENCY=4
|
||||
ENV
|
||||
|
||||
assert_equal expected_env, @config_with_roles.role(:workers).env_file.to_s
|
||||
assert_equal "\n", @config_with_roles.role(:workers).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], @config_with_roles.role(:workers).env_args
|
||||
end
|
||||
|
||||
test "container name" do
|
||||
@@ -90,7 +86,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test "env args" do
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env" ], @config_with_roles.role(:workers).env_args
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], @config_with_roles.role(:workers).env_args
|
||||
end
|
||||
|
||||
test "env secret overwritten by role" do
|
||||
@@ -116,14 +112,13 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
ENV["REDIS_PASSWORD"] = "secret456"
|
||||
ENV["DB_PASSWORD"] = "secret&\"123"
|
||||
|
||||
expected = <<~ENV
|
||||
expected_secrets_file = <<~ENV
|
||||
REDIS_PASSWORD=secret456
|
||||
DB_PASSWORD=secret&\"123
|
||||
REDIS_URL=redis://a/b
|
||||
WEB_CONCURRENCY=4
|
||||
ENV
|
||||
|
||||
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||
assert_equal expected_secrets_file, @config_with_roles.role(:workers).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["REDIS_PASSWORD"] = nil
|
||||
ENV["DB_PASSWORD"] = nil
|
||||
@@ -142,13 +137,12 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["DB_PASSWORD"] = "secret123"
|
||||
|
||||
expected = <<~ENV
|
||||
expected_secrets_file = <<~ENV
|
||||
DB_PASSWORD=secret123
|
||||
REDIS_URL=redis://a/b
|
||||
WEB_CONCURRENCY=4
|
||||
ENV
|
||||
|
||||
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||
assert_equal expected_secrets_file, @config_with_roles.role(:workers).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["DB_PASSWORD"] = nil
|
||||
end
|
||||
@@ -165,13 +159,12 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["REDIS_PASSWORD"] = "secret456"
|
||||
|
||||
expected = <<~ENV
|
||||
expected_secrets_file = <<~ENV
|
||||
REDIS_PASSWORD=secret456
|
||||
REDIS_URL=redis://a/b
|
||||
WEB_CONCURRENCY=4
|
||||
ENV
|
||||
|
||||
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||
assert_equal expected_secrets_file, @config_with_roles.role(:workers).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["REDIS_PASSWORD"] = nil
|
||||
end
|
||||
@@ -194,22 +187,18 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["REDIS_PASSWORD"] = "secret456"
|
||||
|
||||
expected = <<~ENV
|
||||
expected_secrets_file = <<~ENV
|
||||
REDIS_PASSWORD=secret456
|
||||
REDIS_URL=redis://c/d
|
||||
ENV
|
||||
|
||||
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||
assert_equal expected_secrets_file, @config_with_roles.role(:workers).env.secrets_io.string
|
||||
assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://c/d\"" ], @config_with_roles.role(:workers).env_args
|
||||
ensure
|
||||
ENV["REDIS_PASSWORD"] = nil
|
||||
end
|
||||
|
||||
test "host_env_directory" do
|
||||
assert_equal ".kamal/env/roles", @config_with_roles.role(:workers).host_env_directory
|
||||
end
|
||||
|
||||
test "host_env_file_path" do
|
||||
assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).host_env_file_path
|
||||
test "env secrets_file" do
|
||||
assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).env.secrets_file
|
||||
end
|
||||
|
||||
test "uses cord" do
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require "test_helper"
|
||||
|
||||
class EnvFileTest < ActiveSupport::TestCase
|
||||
test "env file simple" do
|
||||
test "to_s" do
|
||||
env = {
|
||||
"foo" => "bar",
|
||||
"baz" => "haz"
|
||||
@@ -11,80 +11,27 @@ class EnvFileTest < ActiveSupport::TestCase
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
end
|
||||
|
||||
test "env file clear" do
|
||||
env = {
|
||||
"clear" => {
|
||||
"foo" => "bar",
|
||||
"baz" => "haz"
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal "foo=bar\nbaz=haz\n", \
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
end
|
||||
|
||||
test "env file empty" do
|
||||
test "to_s empty" do
|
||||
assert_equal "\n", Kamal::EnvFile.new({}).to_s
|
||||
end
|
||||
|
||||
test "env file secret" do
|
||||
ENV["PASSWORD"] = "hello"
|
||||
test "to_s escaped newline" do
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ]
|
||||
"foo" => "hello\\nthere"
|
||||
}
|
||||
|
||||
assert_equal "PASSWORD=hello\n", \
|
||||
assert_equal "foo=hello\\\\nthere\n", \
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "env file secret escaped newline" do
|
||||
ENV["PASSWORD"] = "hello\\nthere"
|
||||
test "to_s newline" do
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ]
|
||||
"foo" => "hello\nthere"
|
||||
}
|
||||
|
||||
assert_equal "PASSWORD=hello\\\\nthere\n", \
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "env file secret newline" do
|
||||
ENV["PASSWORD"] = "hello\nthere"
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ]
|
||||
}
|
||||
|
||||
assert_equal "PASSWORD=hello\\nthere\n", \
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "env file missing secret" do
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ]
|
||||
}
|
||||
|
||||
assert_raises(KeyError) { Kamal::EnvFile.new(env).to_s }
|
||||
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
end
|
||||
|
||||
test "env file secret and clear" do
|
||||
ENV["PASSWORD"] = "hello"
|
||||
env = {
|
||||
"secret" => [ "PASSWORD" ],
|
||||
"clear" => {
|
||||
"foo" => "bar",
|
||||
"baz" => "haz"
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal "PASSWORD=hello\nfoo=bar\nbaz=haz\n", \
|
||||
assert_equal "foo=hello\\nthere\n", \
|
||||
Kamal::EnvFile.new(env).to_s
|
||||
ensure
|
||||
ENV.delete "PASSWORD"
|
||||
|
||||
@@ -4,7 +4,7 @@ class MainTest < IntegrationTest
|
||||
test "envify, deploy, redeploy, rollback, details and audit" do
|
||||
kamal :envify
|
||||
assert_local_env_file "SECRET_TOKEN=1234"
|
||||
assert_remote_env_file "SECRET_TOKEN=1234\nCLEAR_TOKEN=4321"
|
||||
assert_remote_env_file "SECRET_TOKEN=1234"
|
||||
remove_local_env_file
|
||||
|
||||
first_version = latest_app_version
|
||||
@@ -14,6 +14,8 @@ class MainTest < IntegrationTest
|
||||
kamal :deploy
|
||||
assert_app_is_up version: first_version
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy"
|
||||
assert_env :CLEAR_TOKEN, "4321", version: first_version
|
||||
assert_env :SECRET_TOKEN, "1234", version: first_version
|
||||
|
||||
second_version = update_app_rev
|
||||
|
||||
@@ -94,6 +96,10 @@ class MainTest < IntegrationTest
|
||||
assert_equal contents, deployer_exec("cat .env", capture: true)
|
||||
end
|
||||
|
||||
def assert_env(key, value, version:)
|
||||
assert_equal "#{key}=#{value}", docker_compose("exec vm1 docker exec app-web-#{version} env | grep #{key}", capture: true)
|
||||
end
|
||||
|
||||
def remove_local_env_file
|
||||
deployer_exec("rm .env")
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user