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:
Donal McBreen
2024-03-20 16:37:09 +00:00
parent 5f58575b62
commit 49afdbb09a
17 changed files with 201 additions and 200 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@@ -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:)

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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