Merge pull request #732 from basecamp/always-send-clear-env

Always send the clear env to the container
This commit is contained in:
Donal McBreen
2024-03-26 11:01:59 +00:00
committed by GitHub
19 changed files with 206 additions and 201 deletions

View File

@@ -9,20 +9,20 @@ class Kamal::Cli::Env < Kamal::Cli::Base
KAMAL.roles_on(host).each do |role| KAMAL.roles_on(host).each do |role|
execute *KAMAL.app(role: role).make_env_directory 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
end end
on(KAMAL.traefik_hosts) do on(KAMAL.traefik_hosts) do
execute *KAMAL.traefik.make_env_directory 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 end
on(KAMAL.accessory_hosts) do on(KAMAL.accessory_hosts) do
KAMAL.accessories_on(host).each do |accessory| KAMAL.accessories_on(host).each do |accessory|
accessory_config = KAMAL.config.accessory(accessory) accessory_config = KAMAL.config.accessory(accessory)
execute *KAMAL.accessory(accessory).make_env_directory 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 end
end end

View File

@@ -99,11 +99,11 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
end end
def make_env_directory def make_env_directory
make_directory accessory_config.host_env_directory make_directory accessory_config.env.secrets_directory
end end
def remove_env_file def remove_env_file
[ :rm, "-f", accessory_config.host_env_file_path ] [ :rm, "-f", accessory_config.env.secrets_file ]
end end
private private

View File

@@ -68,11 +68,11 @@ class Kamal::Commands::App < Kamal::Commands::Base
def make_env_directory def make_env_directory
make_directory role.host_env_directory make_directory role.env.secrets_directory
end end
def remove_env_file def remove_env_file
[ :rm, "-f", role.host_env_file_path ] [ :rm, "-f", role.env.secrets_file ]
end end

View File

@@ -71,20 +71,18 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
"#{host_port}:#{CONTAINER_PORT}" "#{host_port}:#{CONTAINER_PORT}"
end end
def env_file def env
Kamal::EnvFile.new(config.traefik.fetch("env", {})) Kamal::Configuration::Env.from_config \
end config: config.traefik.fetch("env", {}),
secrets_file: File.join(config.host_env_directory, "traefik", "traefik.env")
def host_env_file_path
File.join host_env_directory, "traefik.env"
end end
def make_env_directory def make_env_directory
make_directory(host_env_directory) make_directory(env.secrets_directory)
end end
def remove_env_file def remove_env_file
[ :rm, "-f", host_env_file_path ] [ :rm, "-f", env.secrets_file ]
end end
private private
@@ -97,11 +95,7 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
end end
def env_args def env_args
argumentize "--env-file", host_env_file_path env.args
end
def host_env_directory
File.join config.host_env_directory, "traefik"
end end
def labels def labels

View File

@@ -6,7 +6,7 @@ require "erb"
require "net/ssh/proxy/jump" require "net/ssh/proxy/jump"
class Kamal::Configuration 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 delegate :argumentize, :optionize, to: Kamal::Utils
attr_reader :destination, :raw_config attr_reader :destination, :raw_config
@@ -216,12 +216,17 @@ class Kamal::Configuration
raw_config.hooks_path || ".kamal/hooks" raw_config.hooks_path || ".kamal/hooks"
end end
def asset_path
raw_config.asset_path
end
def host_env_directory def host_env_directory
"#{run_directory}/env" "#{run_directory}/env"
end end
def asset_path def env
raw_config.asset_path raw_config.env || {}
end end

View File

@@ -42,23 +42,13 @@ class Kamal::Configuration::Accessory
end end
def env def env
specifics["env"] || {} Kamal::Configuration::Env.from_config \
end config: specifics.fetch("env", {}),
secrets_file: File.join(config.host_env_directory, "accessories", "#{service_name}.env")
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"
end end
def env_args def env_args
argumentize "--env-file", host_env_file_path env.args
end end
def files def files
@@ -111,10 +101,10 @@ class Kamal::Configuration::Accessory
end end
def with_clear_env_loaded def with_clear_env_loaded
(env["clear"] || env).each { |k, v| ENV[k] = v } env.clear.each { |k, v| ENV[k] = v }
yield yield
ensure ensure
(env["clear"] || env).each { |k, v| ENV.delete(k) } env.clear.each { |k, v| ENV.delete(k) }
end end
def read_dynamic_file(local_file) 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 def env
if config.env && config.env["secret"] @env ||= base_env.merge(specialized_env)
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"
end end
def env_args def env_args
argumentize "--env-file", host_env_file_path env.args
end end
def asset_volume_args def asset_volume_args
@@ -217,7 +201,7 @@ class Kamal::Configuration::Role
end end
def traefik_service def traefik_service
[ config.service, name, config.destination ].compact.join("-") container_prefix
end end
def custom_labels def custom_labels
@@ -236,24 +220,14 @@ class Kamal::Configuration::Role
end end
def specialized_env def specialized_env
specializations["env"] || {} Kamal::Configuration::Env.from_config config: specializations.fetch("env", {})
end
def merged_env
config.env&.merge(specialized_env) || {}
end end
# Secrets are stored in an array, which won't merge by default, so have to do it by hand. # Secrets are stored in an array, which won't merge by default, so have to do it by hand.
def merged_env_with_secrets def base_env
merged_env.tap do |new_env| Kamal::Configuration::Env.from_config \
new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"]) config: config.env,
secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env")
# 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
end end
def http_health_check(port:, path:) def http_health_check(port:, path:)

View File

@@ -6,18 +6,8 @@ class Kamal::EnvFile
def to_s def to_s
env_file = StringIO.new.tap do |contents| env_file = StringIO.new.tap do |contents|
if (secrets = @env["secret"]).present? @env.each do |key, value|
@env.fetch("secret", @env)&.each do |key| contents << docker_env_file_line(key, value)
contents << docker_env_file_line(key, ENV.fetch(key))
end
@env["clear"]&.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
end.string end.string

View File

@@ -7,7 +7,7 @@ class CliAccessoryTest < CliTestCase
run_command("boot", "mysql").tap do |output| run_command("boot", "mysql").tap do |output|
assert_match /docker login.*on 1.1.1.3/, 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
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.3/, output
assert_match /docker login.*on 1.1.1.1/, output assert_match /docker login.*on 1.1.1.1/, output
assert_match /docker login.*on 1.1.1.2/, 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.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 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 end

View File

@@ -50,11 +50,11 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
test "run" do test "run" do
assert_equal \ 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(" ") new_command(:mysql).run.join(" ")
assert_equal \ 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(" ") new_command(:redis).run.join(" ")
assert_equal \ assert_equal \
@@ -91,7 +91,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
test "execute in new container" do test "execute in new container" do
assert_equal \ 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(" ") new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ")
end end
@@ -103,7 +103,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
test "execute in new container over ssh" do test "execute in new container over ssh" do
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) 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") new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root")
end end
end end

View File

@@ -177,14 +177,10 @@ class CommandsTraefikTest < ActiveSupport::TestCase
new_command.follow_logs(host: @config[:servers].first, grep: "hello!") new_command.follow_logs(host: @config[:servers].first, grep: "hello!")
end end
test "env_file" do test "secrets io" do
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] } @config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env_file.to_s assert_equal "EXAMPLE_API_KEY=456\n", new_command.env.secrets_io.string
end
test "host_env_file_path" do
assert_equal ".kamal/env/traefik/traefik.env", new_command.host_env_file_path
end end
test "make_env_directory" do test "make_env_directory" do

View File

@@ -113,29 +113,25 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
end end
test "env args" do 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-mysql.env", "--env", "MYSQL_ROOT_HOST=\"%\"" ], @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-redis.env", "--env", "SOMETHING=\"else\"" ], @config.accessory(:redis).env_args
end end
test "env file with secret" do test "env with secrets" do
ENV["MYSQL_ROOT_PASSWORD"] = "secret123" ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
expected = <<~ENV expected_secrets_file = <<~ENV
MYSQL_ROOT_PASSWORD=secret123 MYSQL_ROOT_PASSWORD=secret123
MYSQL_ROOT_HOST=%
ENV 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 ensure
ENV["MYSQL_ROOT_PASSWORD"] = nil ENV["MYSQL_ROOT_PASSWORD"] = nil
end end
test "host_env_directory" do test "env secrets path" do
assert_equal ".kamal/env/accessories", @config.accessory(:mysql).host_env_directory assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).env.secrets_file
end
test "host_env_file_path" do
assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).host_env_file_path
end end
test "volume args" do 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 end
test "env overwritten by role" do 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 assert_equal "\n", @config_with_roles.role(:workers).env.secrets_io.string
REDIS_URL=redis://a/b 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
WEB_CONCURRENCY=4
ENV
assert_equal expected_env, @config_with_roles.role(:workers).env_file.to_s
end end
test "container name" do test "container name" do
@@ -90,7 +86,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
end end
test "env args" do 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 end
test "env secret overwritten by role" do test "env secret overwritten by role" do
@@ -116,14 +112,13 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
ENV["REDIS_PASSWORD"] = "secret456" ENV["REDIS_PASSWORD"] = "secret456"
ENV["DB_PASSWORD"] = "secret&\"123" ENV["DB_PASSWORD"] = "secret&\"123"
expected = <<~ENV expected_secrets_file = <<~ENV
REDIS_PASSWORD=secret456 REDIS_PASSWORD=secret456
DB_PASSWORD=secret&\"123 DB_PASSWORD=secret&\"123
REDIS_URL=redis://a/b
WEB_CONCURRENCY=4
ENV 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 ensure
ENV["REDIS_PASSWORD"] = nil ENV["REDIS_PASSWORD"] = nil
ENV["DB_PASSWORD"] = nil ENV["DB_PASSWORD"] = nil
@@ -142,13 +137,12 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
ENV["DB_PASSWORD"] = "secret123" ENV["DB_PASSWORD"] = "secret123"
expected = <<~ENV expected_secrets_file = <<~ENV
DB_PASSWORD=secret123 DB_PASSWORD=secret123
REDIS_URL=redis://a/b
WEB_CONCURRENCY=4
ENV 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 ensure
ENV["DB_PASSWORD"] = nil ENV["DB_PASSWORD"] = nil
end end
@@ -165,13 +159,12 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
ENV["REDIS_PASSWORD"] = "secret456" ENV["REDIS_PASSWORD"] = "secret456"
expected = <<~ENV expected_secrets_file = <<~ENV
REDIS_PASSWORD=secret456 REDIS_PASSWORD=secret456
REDIS_URL=redis://a/b
WEB_CONCURRENCY=4
ENV 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 ensure
ENV["REDIS_PASSWORD"] = nil ENV["REDIS_PASSWORD"] = nil
end end
@@ -194,22 +187,18 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
ENV["REDIS_PASSWORD"] = "secret456" ENV["REDIS_PASSWORD"] = "secret456"
expected = <<~ENV expected_secrets_file = <<~ENV
REDIS_PASSWORD=secret456 REDIS_PASSWORD=secret456
REDIS_URL=redis://c/d
ENV 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 ensure
ENV["REDIS_PASSWORD"] = nil ENV["REDIS_PASSWORD"] = nil
end end
test "host_env_directory" do test "env secrets_file" do
assert_equal ".kamal/env/roles", @config_with_roles.role(:workers).host_env_directory assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).env.secrets_file
end
test "host_env_file_path" do
assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).host_env_file_path
end end
test "uses cord" do test "uses cord" do

View File

@@ -1,7 +1,7 @@
require "test_helper" require "test_helper"
class EnvFileTest < ActiveSupport::TestCase class EnvFileTest < ActiveSupport::TestCase
test "env file simple" do test "to_s" do
env = { env = {
"foo" => "bar", "foo" => "bar",
"baz" => "haz" "baz" => "haz"
@@ -11,80 +11,27 @@ class EnvFileTest < ActiveSupport::TestCase
Kamal::EnvFile.new(env).to_s Kamal::EnvFile.new(env).to_s
end end
test "env file clear" do test "to_s empty" 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
assert_equal "\n", Kamal::EnvFile.new({}).to_s assert_equal "\n", Kamal::EnvFile.new({}).to_s
end end
test "env file secret" do test "to_s escaped newline" do
ENV["PASSWORD"] = "hello"
env = { env = {
"secret" => [ "PASSWORD" ] "foo" => "hello\\nthere"
} }
assert_equal "PASSWORD=hello\n", \ assert_equal "foo=hello\\\\nthere\n", \
Kamal::EnvFile.new(env).to_s Kamal::EnvFile.new(env).to_s
ensure ensure
ENV.delete "PASSWORD" ENV.delete "PASSWORD"
end end
test "env file secret escaped newline" do test "to_s newline" do
ENV["PASSWORD"] = "hello\\nthere"
env = { env = {
"secret" => [ "PASSWORD" ] "foo" => "hello\nthere"
} }
assert_equal "PASSWORD=hello\\\\nthere\n", \ assert_equal "foo=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", \
Kamal::EnvFile.new(env).to_s Kamal::EnvFile.new(env).to_s
ensure ensure
ENV.delete "PASSWORD" ENV.delete "PASSWORD"

View File

@@ -5,7 +5,8 @@ servers:
- vm2 - vm2
env: env:
clear: clear:
CLEAR_TOKEN: '4321' CLEAR_TOKEN: 4321
HOST_TOKEN: "${HOST_TOKEN}"
secret: secret:
- SECRET_TOKEN - SECRET_TOKEN
asset_path: /usr/share/nginx/html/versions asset_path: /usr/share/nginx/html/versions

View File

@@ -7,6 +7,8 @@ RUN apt-get update --fix-missing && apt-get -y install openssh-client openssh-se
RUN mkdir /root/.ssh && ln -s /shared/ssh/id_rsa.pub /root/.ssh/authorized_keys RUN mkdir /root/.ssh && ln -s /shared/ssh/id_rsa.pub /root/.ssh/authorized_keys
RUN mkdir -p /etc/docker/certs.d/registry:4443 && ln -s /shared/certs/domain.crt /etc/docker/certs.d/registry:4443/ca.crt RUN mkdir -p /etc/docker/certs.d/registry:4443 && ln -s /shared/certs/domain.crt /etc/docker/certs.d/registry:4443/ca.crt
RUN echo "HOST_TOKEN=abcd" >> /etc/environment
COPY boot.sh . COPY boot.sh .
HEALTHCHECK --interval=1s CMD pgrep dockerd HEALTHCHECK --interval=1s CMD pgrep dockerd

View File

@@ -4,7 +4,7 @@ class MainTest < IntegrationTest
test "envify, deploy, redeploy, rollback, details and audit" do test "envify, deploy, redeploy, rollback, details and audit" do
kamal :envify kamal :envify
assert_local_env_file "SECRET_TOKEN=1234" 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 remove_local_env_file
first_version = latest_app_version first_version = latest_app_version
@@ -14,6 +14,9 @@ class MainTest < IntegrationTest
kamal :deploy kamal :deploy
assert_app_is_up version: first_version assert_app_is_up version: first_version
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy" assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy"
assert_env :CLEAR_TOKEN, "4321", version: first_version
assert_env :HOST_TOKEN, "abcd", version: first_version
assert_env :SECRET_TOKEN, "1234", version: first_version
second_version = update_app_rev second_version = update_app_rev
@@ -94,6 +97,10 @@ class MainTest < IntegrationTest
assert_equal contents, deployer_exec("cat .env", capture: true) assert_equal contents, deployer_exec("cat .env", capture: true)
end 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 def remove_local_env_file
deployer_exec("rm .env") deployer_exec("rm .env")
end end