diff --git a/lib/kamal/cli/env.rb b/lib/kamal/cli/env.rb index 1a02aeb1..3cfb1741 100644 --- a/lib/kamal/cli/env.rb +++ b/lib/kamal/cli/env.rb @@ -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 diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index c4d4545f..661ab7ee 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -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 diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index c49c228b..1695b32a 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -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 diff --git a/lib/kamal/commands/traefik.rb b/lib/kamal/commands/traefik.rb index ca5b65bd..569c2c2c 100644 --- a/lib/kamal/commands/traefik.rb +++ b/lib/kamal/commands/traefik.rb @@ -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 diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 7ac5dc1b..bd5189d9 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -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 diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index f83b6a1e..1477031e 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -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) diff --git a/lib/kamal/configuration/env.rb b/lib/kamal/configuration/env.rb new file mode 100644 index 00000000..f9b8cd66 --- /dev/null +++ b/lib/kamal/configuration/env.rb @@ -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 diff --git a/lib/kamal/configuration/role.rb b/lib/kamal/configuration/role.rb index 4f0f1d67..d77d6ee0 100644 --- a/lib/kamal/configuration/role.rb +++ b/lib/kamal/configuration/role.rb @@ -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:) diff --git a/lib/kamal/env_file.rb b/lib/kamal/env_file.rb index 24c3353a..0c9f2341 100644 --- a/lib/kamal/env_file.rb +++ b/lib/kamal/env_file.rb @@ -6,18 +6,8 @@ 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| - 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 + @env.each do |key, value| + contents << docker_env_file_line(key, value) end end.string diff --git a/test/cli/accessory_test.rb b/test/cli/accessory_test.rb index f6e6045b..ecce810f 100644 --- a/test/cli/accessory_test.rb +++ b/test/cli/accessory_test.rb @@ -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 diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index f0e1fbe7..da8c3057 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -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 diff --git a/test/commands/traefik_test.rb b/test/commands/traefik_test.rb index 4ba98584..157670a3 100644 --- a/test/commands/traefik_test.rb +++ b/test/commands/traefik_test.rb @@ -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 diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index fea05f6c..d53ac9ba 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -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 diff --git a/test/configuration/env_test.rb b/test/configuration/env_test.rb new file mode 100644 index 00000000..d24c6211 --- /dev/null +++ b/test/configuration/env_test.rb @@ -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 diff --git a/test/configuration/role_test.rb b/test/configuration/role_test.rb index 8c4560cd..1c57a06a 100644 --- a/test/configuration/role_test.rb +++ b/test/configuration/role_test.rb @@ -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 diff --git a/test/env_file_test.rb b/test/env_file_test.rb index e4a1322c..6fcef6e3 100644 --- a/test/env_file_test.rb +++ b/test/env_file_test.rb @@ -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" diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 7c858a17..e4f8db61 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -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