From 56754fe40cff73aa62ce3c6d21b7b55ddd037986 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Mon, 5 Aug 2024 14:41:50 +0100 Subject: [PATCH] Lazily load secrets whenever needed --- lib/kamal.rb | 1 + lib/kamal/cli/app/boot.rb | 8 +- lib/kamal/cli/base.rb | 13 +- lib/kamal/cli/build.rb | 2 +- lib/kamal/cli/main.rb | 3 - lib/kamal/commander.rb | 4 + lib/kamal/commands/accessory.rb | 8 - lib/kamal/commands/app.rb | 10 - lib/kamal/commands/auditor.rb | 9 +- lib/kamal/commands/builder/base.rb | 2 +- lib/kamal/commands/traefik.rb | 8 - lib/kamal/configuration.rb | 8 +- lib/kamal/configuration/accessory.rb | 2 +- lib/kamal/configuration/builder.rb | 2 +- lib/kamal/configuration/env.rb | 36 ++- lib/kamal/configuration/env/tag.rb | 7 +- lib/kamal/configuration/registry.rb | 5 +- lib/kamal/configuration/role.rb | 2 +- lib/kamal/configuration/secrets.rb | 25 ++ lib/kamal/configuration/traefik.rb | 2 +- lib/kamal/env_file.rb | 38 --- lib/kamal/utils.rb | 6 + test/cli/accessory_test.rb | 24 +- test/cli/app_test.rb | 6 +- test/cli/build_test.rb | 4 +- test/cli/env_test.rb | 37 --- test/cli/main_test.rb | 21 -- test/cli/traefik_test.rb | 4 +- test/commands/accessory_test.rb | 26 +- test/commands/app_test.rb | 46 ++-- test/commands/auditor_test.rb | 4 + test/commands/builder_test.rb | 22 +- test/commands/registry_test.rb | 49 ++-- test/commands/traefik_test.rb | 52 ++-- test/configuration/accessory_test.rb | 23 +- test/configuration/builder_test.rb | 8 +- test/configuration/env/tags_test.rb | 26 +- test/configuration/env_test.rb | 64 ++--- test/configuration/role_test.rb | 248 +++++++++--------- .../deployer/app/.kamal/{env.erb => secrets} | 0 .../.kamal/{env.erb => secrets} | 0 test/integration/main_test.rb | 27 +- test/test_helper.rb | 28 ++ 43 files changed, 391 insertions(+), 529 deletions(-) create mode 100644 lib/kamal/configuration/secrets.rb delete mode 100644 lib/kamal/env_file.rb delete mode 100644 test/cli/env_test.rb rename test/integration/docker/deployer/app/.kamal/{env.erb => secrets} (100%) rename test/integration/docker/deployer/app_with_roles/.kamal/{env.erb => secrets} (100%) diff --git a/lib/kamal.rb b/lib/kamal.rb index 2da2bbf2..b197408e 100644 --- a/lib/kamal.rb +++ b/lib/kamal.rb @@ -5,6 +5,7 @@ end require "active_support" require "zeitwerk" require "yaml" +require "tmpdir" loader = Zeitwerk::Loader.for_gem loader.ignore(File.join(__dir__, "kamal", "sshkit_with_ext.rb")) diff --git a/lib/kamal/cli/app/boot.rb b/lib/kamal/cli/app/boot.rb index b78763ce..d5b76d4e 100644 --- a/lib/kamal/cli/app/boot.rb +++ b/lib/kamal/cli/app/boot.rb @@ -88,8 +88,12 @@ class Kamal::Cli::App::Boot def close_barrier if barrier.close info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles" - error capture_with_info(*app.logs(version: version)) - error capture_with_info(*app.container_health_log(version: version)) + begin + error capture_with_info(*app.logs(version: version)) + error capture_with_info(*app.container_health_log(version: version)) + rescue SSHKit::Command::Failed + error "Could not fetch logs for #{version}" + end end end diff --git a/lib/kamal/cli/base.rb b/lib/kamal/cli/base.rb index a583ebad..d815560e 100644 --- a/lib/kamal/cli/base.rb +++ b/lib/kamal/cli/base.rb @@ -31,24 +31,15 @@ module Kamal::Cli else super end - @original_env = ENV.to_h.dup - initialize_commander(options_with_subcommand_class_options) + initialize_commander unless KAMAL.configured? end private - def load_secrets - if destination = options[:destination] - Dotenv.parse(".kamal/secrets.#{destination}", ".kamal/secrets") - else - Dotenv.parse(".kamal/secrets") - end - end - def options_with_subcommand_class_options options.merge(@_initializer.last[:class_options] || {}) end - def initialize_commander(options) + def initialize_commander KAMAL.tap do |commander| if options[:verbose] ENV["VERBOSE"] = "1" # For backtraces via cli/start diff --git a/lib/kamal/cli/build.rb b/lib/kamal/cli/build.rb index 93e1efd9..0347d18c 100644 --- a/lib/kamal/cli/build.rb +++ b/lib/kamal/cli/build.rb @@ -51,7 +51,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base push = KAMAL.builder.push KAMAL.with_verbosity(:debug) do - Dir.chdir(KAMAL.config.builder.build_directory) { execute *push } + Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.config.builder.secrets } end end end diff --git a/lib/kamal/cli/main.rb b/lib/kamal/cli/main.rb index b72c1c8f..65222eb7 100644 --- a/lib/kamal/cli/main.rb +++ b/lib/kamal/cli/main.rb @@ -202,9 +202,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base desc "build", "Build application image" subcommand "build", Kamal::Cli::Build - desc "env", "Manage environment files" - subcommand "env", Kamal::Cli::Env - desc "lock", "Manage the deploy lock" subcommand "lock", Kamal::Cli::Lock diff --git a/lib/kamal/commander.rb b/lib/kamal/commander.rb index ae98e0f8..ffe140c4 100644 --- a/lib/kamal/commander.rb +++ b/lib/kamal/commander.rb @@ -23,6 +23,10 @@ class Kamal::Commander @config, @config_kwargs = nil, kwargs end + def configured? + @config || @config_kwargs + end + attr_reader :specific_roles, :specific_hosts def specific_primary! diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 23377ab5..d34377c7 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -98,14 +98,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base docker :image, :rm, "--force", image end - def make_env_directory - make_directory accessory_config.env.secrets_directory - end - - def remove_env_file - [ :rm, "-f", accessory_config.env.secrets_file ] - end - private def service_filter [ "--filter", "label=service=#{service_name}" ] diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 37fa86ab..4fe8ead7 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -69,16 +69,6 @@ class Kamal::Commands::App < Kamal::Commands::Base extract_version_from_name end - - def make_env_directory - make_directory role.env(host).secrets_directory - end - - def remove_env_file - [ :rm, "-f", role.env(host).secrets_file ] - end - - private def container_name(version = nil) [ role.container_prefix, version || config.version ].compact.join("-") diff --git a/lib/kamal/commands/auditor.rb b/lib/kamal/commands/auditor.rb index f0c0850d..9846d8e2 100644 --- a/lib/kamal/commands/auditor.rb +++ b/lib/kamal/commands/auditor.rb @@ -8,9 +8,12 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base # Runs remotely def record(line, **details) - append \ - [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ], - audit_log_file + combine \ + [ :mkdir, "-p", config.run_directory ], + append( + [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ], + audit_log_file + ) end def reveal diff --git a/lib/kamal/commands/builder/base.rb b/lib/kamal/commands/builder/base.rb index 1e6f5be3..636fe4f4 100644 --- a/lib/kamal/commands/builder/base.rb +++ b/lib/kamal/commands/builder/base.rb @@ -78,7 +78,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base end def build_secrets - argumentize "--secret", secrets.collect { |secret| [ "id", secret ] } + argumentize "--secret", secrets.keys.collect { |secret| [ "id", secret ] } end def build_dockerfile diff --git a/lib/kamal/commands/traefik.rb b/lib/kamal/commands/traefik.rb index 07e0e6ea..dd08ef50 100644 --- a/lib/kamal/commands/traefik.rb +++ b/lib/kamal/commands/traefik.rb @@ -54,14 +54,6 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik" end - def make_env_directory - make_directory(env.secrets_directory) - end - - def remove_env_file - [ :rm, "-f", env.secrets_file ] - end - private def publish_args argumentize "--publish", port if publish? diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index af3754eb..d19a7786 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -57,7 +57,7 @@ class Kamal::Configuration @aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {} @boot = Boot.new(config: self) @builder = Builder.new(config: self) - @env = Env.new(config: @raw_config.env || {}) + @env = Env.new(config: @raw_config.env || {}, secrets: secrets) @healthcheck = Healthcheck.new(healthcheck_config: @raw_config.healthcheck) @logging = Logging.new(logging_config: @raw_config.logging) @@ -224,7 +224,7 @@ class Kamal::Configuration def env_tags @env_tags ||= if (tags = raw_config.env["tags"]) - tags.collect { |name, config| Env::Tag.new(name, config: config) } + tags.collect { |name, config| Env::Tag.new(name, config: config, secrets: secrets) } else [] end @@ -254,6 +254,10 @@ class Kamal::Configuration }.compact end + def secrets + @secrets ||= Secrets.new(destination: destination) + end + private # Will raise ArgumentError if any required config keys are missing def ensure_destination_if_required diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 07f40b56..5d69af7a 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -16,7 +16,7 @@ class Kamal::Configuration::Accessory @env = Kamal::Configuration::Env.new \ config: accessory_config.fetch("env", {}), - secrets_file: File.join(config.host_env_directory, "accessories", "#{service_name}.env"), + secrets: config.secrets, context: "accessories/#{name}/env" end diff --git a/lib/kamal/configuration/builder.rb b/lib/kamal/configuration/builder.rb index bad3b386..a395e228 100644 --- a/lib/kamal/configuration/builder.rb +++ b/lib/kamal/configuration/builder.rb @@ -62,7 +62,7 @@ class Kamal::Configuration::Builder end def secrets - builder_config["secrets"] || [] + (builder_config["secrets"] || []).to_h { |key| [ key, config.secrets[key] ] } end def dockerfile diff --git a/lib/kamal/configuration/env.rb b/lib/kamal/configuration/env.rb index 1c0fb1e9..d8f27ece 100644 --- a/lib/kamal/configuration/env.rb +++ b/lib/kamal/configuration/env.rb @@ -1,36 +1,34 @@ class Kamal::Configuration::Env include Kamal::Configuration::Validation - attr_reader :secrets_keys, :clear, :secrets_file, :context + attr_reader :context, :secrets + attr_reader :clear, :secret_keys delegate :argumentize, to: Kamal::Utils - def initialize(config:, secrets_file: nil, context: "env") + def initialize(config:, secrets:, context: "env") @clear = config.fetch("clear", config.key?("secret") || config.key?("tags") ? {} : config) - @secrets_keys = config.fetch("secret", []) - @secrets_file = secrets_file + @secrets = secrets + @secret_keys = config.fetch("secret", []) @context = context validate! config, context: context, with: Kamal::Configuration::Validator::Env 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) + [ *clear_args, *secret_args ] end def merge(other) self.class.new \ - config: { "clear" => clear.merge(other.clear), "secret" => secrets_keys | other.secrets_keys }, - secrets_file: secrets_file || other.secrets_file + config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys }, + secrets: secrets end + + private + def clear_args + argumentize("--env", clear) + end + + def secret_args + argumentize("--env", secret_keys.to_h { |key| [ key, secrets[key] ] }, sensitive: true) + end end diff --git a/lib/kamal/configuration/env/tag.rb b/lib/kamal/configuration/env/tag.rb index c4151202..30160edc 100644 --- a/lib/kamal/configuration/env/tag.rb +++ b/lib/kamal/configuration/env/tag.rb @@ -1,12 +1,13 @@ class Kamal::Configuration::Env::Tag - attr_reader :name, :config + attr_reader :name, :config, :secrets - def initialize(name, config:) + def initialize(name, config:, secrets:) @name = name @config = config + @secrets = secrets end def env - Kamal::Configuration::Env.new(config: config) + Kamal::Configuration::Env.new(config: config, secrets: secrets) end end diff --git a/lib/kamal/configuration/registry.rb b/lib/kamal/configuration/registry.rb index fa0ba04a..763cf976 100644 --- a/lib/kamal/configuration/registry.rb +++ b/lib/kamal/configuration/registry.rb @@ -1,10 +1,11 @@ class Kamal::Configuration::Registry include Kamal::Configuration::Validation - attr_reader :registry_config + attr_reader :registry_config, :secrets def initialize(config:) @registry_config = config.raw_config.registry || {} + @secrets = config.secrets validate! registry_config, with: Kamal::Configuration::Validator::Registry end @@ -23,7 +24,7 @@ class Kamal::Configuration::Registry private def lookup(key) if registry_config[key].is_a?(Array) - ENV.fetch(registry_config[key].first).dup + secrets[registry_config[key].first] else registry_config[key] end diff --git a/lib/kamal/configuration/role.rb b/lib/kamal/configuration/role.rb index e9e520a7..60bee1a6 100644 --- a/lib/kamal/configuration/role.rb +++ b/lib/kamal/configuration/role.rb @@ -18,7 +18,7 @@ class Kamal::Configuration::Role @specialized_env = Kamal::Configuration::Env.new \ config: specializations.fetch("env", {}), - secrets_file: File.join(config.host_env_directory, "roles", "#{container_prefix}.env"), + secrets: config.secrets, context: "servers/#{name}/env" @specialized_logging = Kamal::Configuration::Logging.new \ diff --git a/lib/kamal/configuration/secrets.rb b/lib/kamal/configuration/secrets.rb new file mode 100644 index 00000000..215bcaf2 --- /dev/null +++ b/lib/kamal/configuration/secrets.rb @@ -0,0 +1,25 @@ +class Kamal::Configuration::Secrets + attr_reader :secret_files + + def initialize(destination: nil) + @secret_files = \ + (destination ? [ ".kamal/secrets", ".kamal/secrets.#{destination}" ] : [ ".kamal/secrets" ]) + .select { |file| File.exist?(file) } + end + + def [](key) + @secrets ||= load + @secrets.fetch(key) + rescue KeyError + if secret_files.any? + raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secret_files.join(', ')}" + else + raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files provided" + end + end + + private + def load + secret_files.any? ? Dotenv.parse(*secret_files) : {} + end +end diff --git a/lib/kamal/configuration/traefik.rb b/lib/kamal/configuration/traefik.rb index c958afdf..e046a1e3 100644 --- a/lib/kamal/configuration/traefik.rb +++ b/lib/kamal/configuration/traefik.rb @@ -34,7 +34,7 @@ class Kamal::Configuration::Traefik def env Kamal::Configuration::Env.new \ config: traefik_config.fetch("env", {}), - secrets_file: File.join(config.host_env_directory, "traefik", "traefik.env"), + secrets: config.secrets, context: "traefik/env" end diff --git a/lib/kamal/env_file.rb b/lib/kamal/env_file.rb deleted file mode 100644 index 2228be09..00000000 --- a/lib/kamal/env_file.rb +++ /dev/null @@ -1,38 +0,0 @@ -# Encode an env hash as a string where secret values have been looked up and all values escaped for Docker. -class Kamal::EnvFile - def initialize(env) - @env = env - end - - def to_s - env_file = StringIO.new.tap do |contents| - @env.each do |key, value| - contents << docker_env_file_line(key, value) - end - end.string - - # Ensure the file has some contents to avoid the SSHKIT empty file warning - env_file.presence || "\n" - end - - alias to_str to_s - - private - def docker_env_file_line(key, value) - "#{key}=#{escape_docker_env_file_value(value)}\n" - end - - # Escape a value to make it safe to dump in a docker file. - def escape_docker_env_file_value(value) - # keep non-ascii(UTF-8) characters as it is - value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/).map do |part| - part.ascii_only? ? escape_docker_env_file_ascii_value(part) : part - end.join - end - - def escape_docker_env_file_ascii_value(value) - # Doublequotes are treated literally in docker env files - # so remove leading and trailing ones and unescape any others - value.to_s.dump[1..-2].gsub(/\\"/, "\"") - end -end diff --git a/lib/kamal/utils.rb b/lib/kamal/utils.rb index 46736ba5..8c6c9321 100644 --- a/lib/kamal/utils.rb +++ b/lib/kamal/utils.rb @@ -54,6 +54,12 @@ module Kamal::Utils # Escape a value to make it safe for shell use. def escape_shell_value(value) + value.to_s.scan(/[\x00-\x7F]+|[^\x00-\x7F]+/) \ + .map { |part| part.ascii_only? ? escape_ascii_shell_value(part) : part } + .join + end + + def escape_ascii_shell_value(value) value.to_s.dump .gsub(/`/, '\\\\`') .gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$') diff --git a/test/cli/accessory_test.rb b/test/cli/accessory_test.rb index e56eef2d..9a130551 100644 --- a/test/cli/accessory_test.rb +++ b/test/cli/accessory_test.rb @@ -1,13 +1,21 @@ require_relative "cli_test_case" class CliAccessoryTest < CliTestCase + setup do + setup_test_secrets("secrets" => "MYSQL_ROOT_PASSWORD=secret") + end + + teardown do + teardown_test_secrets + end + test "boot" do Kamal::Cli::Accessory.any_instance.expects(:directories).with("mysql") Kamal::Cli::Accessory.any_instance.expects(:upload).with("mysql") 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 --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-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env [REDACTED] --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,9 +29,9 @@ 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 --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 + assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env [REDACTED] --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 --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 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output end end @@ -192,8 +200,8 @@ class CliAccessoryTest < CliTestCase run_command("boot", "redis", "--hosts", "1.1.1.1").tap do |output| assert_match /docker login.*on 1.1.1.1/, output assert_no_match /docker login.*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.1", output - assert_no_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 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output + assert_no_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output end end @@ -204,8 +212,8 @@ class CliAccessoryTest < CliTestCase run_command("boot", "redis", "--hosts", "1.1.1.1,1.1.1.3").tap do |output| assert_match /docker login.*on 1.1.1.1/, output assert_no_match /docker login.*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_no_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.3", output + assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output + assert_no_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.3", output end end diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 8218dba8..d1344b81 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -113,7 +113,7 @@ class CliAppTest < CliTestCase run_command("boot", config: :with_env_tags).tap do |output| assert_match "docker tag dhh/app:latest dhh/app:latest", output - assert_match %r{docker run --detach --restart unless-stopped --name app-web-latest --hostname 1.1.1.1-[0-9a-f]{12} -e KAMAL_CONTAINER_NAME="app-web-latest" -e KAMAL_VERSION="latest" --env-file .kamal/env/roles/app-web.env --env TEST="root" --env EXPERIMENT="disabled" --env SITE="site1"}, output + assert_match %r{docker run --detach --restart unless-stopped --name app-web-latest --hostname 1.1.1.1-[0-9a-f]{12} -e KAMAL_CONTAINER_NAME="app-web-latest" -e KAMAL_VERSION="latest" --env TEST="root" --env EXPERIMENT="disabled" --env SITE="site1"}, output assert_match "docker container ls --all --filter name=^app-web-123$ --quiet | xargs docker stop", output end end @@ -243,7 +243,7 @@ class CliAppTest < CliTestCase test "exec" do run_command("exec", "ruby -v").tap do |output| - assert_match "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:latest ruby -v", output + assert_match "docker run --rm dhh/app:latest ruby -v", output end end @@ -262,7 +262,7 @@ class CliAppTest < CliTestCase test "exec interactive" do SSHKit::Backend::Abstract.any_instance.expects(:exec) - .with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --env-file .kamal/env/roles/app-web.env dhh/app:latest ruby -v'") + .with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm dhh/app:latest ruby -v'") run_command("exec", "-i", "ruby -v").tap do |output| assert_match "Get most recent version available as an image...", output assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index 4259fa5b..2d5d1051 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -49,7 +49,7 @@ class CliBuildTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init") SSHKit::Backend::Abstract.any_instance.expects(:execute) - .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") + .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", env: {}) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:git, "-C", anything, :"rev-parse", :HEAD) @@ -140,7 +140,7 @@ class CliBuildTest < CliTestCase .returns("") SSHKit::Backend::Abstract.any_instance.expects(:execute) - .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") + .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", env: {}) run_command("push").tap do |output| assert_match /WARN Missing compatible builder, so creating a new one first/, output diff --git a/test/cli/env_test.rb b/test/cli/env_test.rb deleted file mode 100644 index 299b2aaf..00000000 --- a/test/cli/env_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require_relative "cli_test_case" - -class CliEnvTest < CliTestCase - test "push" do - run_command("push").tap do |output| - assert_match "Running /usr/bin/env mkdir -p .kamal/env/roles on 1.1.1.1", output - assert_match "Running /usr/bin/env mkdir -p .kamal/env/traefik on 1.1.1.1", output - assert_match "Running /usr/bin/env mkdir -p .kamal/env/accessories on 1.1.1.1", output - assert_match "Running /usr/bin/env mkdir -p .kamal/env/roles on 1.1.1.1", output - assert_match "Running /usr/bin/env mkdir -p .kamal/env/traefik on 1.1.1.2", output - assert_match "Running /usr/bin/env mkdir -p .kamal/env/accessories on 1.1.1.1", output - assert_match ".kamal/env/roles/app-web.env", output - assert_match ".kamal/env/roles/app-workers.env", output - assert_match ".kamal/env/traefik/traefik.env", output - assert_match ".kamal/env/accessories/app-redis.env", output - end - end - - test "delete" do - run_command("delete").tap do |output| - assert_match "Running /usr/bin/env rm -f .kamal/env/roles/app-web.env on 1.1.1.1", output - assert_match "Running /usr/bin/env rm -f .kamal/env/roles/app-web.env on 1.1.1.2", output - assert_match "Running /usr/bin/env rm -f .kamal/env/roles/app-workers.env on 1.1.1.3", output - assert_match "Running /usr/bin/env rm -f .kamal/env/roles/app-workers.env on 1.1.1.4", output - assert_match "Running /usr/bin/env rm -f .kamal/env/traefik/traefik.env on 1.1.1.1", output - assert_match "Running /usr/bin/env rm -f .kamal/env/traefik/traefik.env on 1.1.1.2", output - assert_match "Running /usr/bin/env rm -f .kamal/env/accessories/app-redis.env on 1.1.1.1", output - assert_match "Running /usr/bin/env rm -f .kamal/env/accessories/app-redis.env on 1.1.1.2", output - assert_match "Running /usr/bin/env rm -f .kamal/env/accessories/app-mysql.env on 1.1.1.3", output - end - end - - private - def run_command(*command) - stdouted { Kamal::Cli::Env.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) } - end -end diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index 8272f67b..8b9f129c 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -13,7 +13,6 @@ class CliMainTest < CliTestCase run_command("setup").tap do |output| assert_match /Ensure Docker is installed.../, output - assert_match /Evaluate and push env files.../, output end end @@ -21,7 +20,6 @@ class CliMainTest < CliTestCase invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false } Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options) - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options) # deploy Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true)) @@ -33,7 +31,6 @@ class CliMainTest < CliTestCase run_command("setup", "--skip_push").tap do |output| assert_match /Ensure Docker is installed.../, output - assert_match /Evaluate and push env files.../, output # deploy assert_match /Acquiring the deploy lock/, output assert_match /Log into image registry/, output @@ -524,22 +521,4 @@ class CliMainTest < CliTestCase stdouted { Kamal::Cli::Main.start } end end - - def with_test_env_files(**files) - Dir.mktmpdir do |dir| - fixtures_dup = File.join(dir, "test") - FileUtils.mkdir_p(fixtures_dup) - FileUtils.cp_r("test/fixtures/", fixtures_dup) - - Dir.chdir(dir) do - FileUtils.mkdir_p(".kamal") - Dir.chdir(".kamal") do - files.each do |filename, contents| - File.binwrite(filename.to_s, contents) - end - end - yield - end - end - end end diff --git a/test/cli/traefik_test.rb b/test/cli/traefik_test.rb index 29171150..41921f96 100644 --- a/test/cli/traefik_test.rb +++ b/test/cli/traefik_test.rb @@ -4,7 +4,7 @@ class CliTraefikTest < CliTestCase test "boot" do run_command("boot").tap do |output| assert_match "docker login", output - assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output + assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output end end @@ -14,7 +14,7 @@ class CliTraefikTest < CliTestCase run_command("reboot", "-y").tap do |output| assert_match "docker container stop traefik", output assert_match "docker container prune --force --filter label=org.opencontainers.image.title=Traefik", output - assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output + assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output end end diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 40002988..d63fcd76 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -2,6 +2,8 @@ require "test_helper" class CommandsAccessoryTest < ActiveSupport::TestCase setup do + setup_test_secrets("secrets" => "MYSQL_ROOT_PASSWORD=secret123") + @config = { service: "app", image: "dhh/app", registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], @@ -41,25 +43,23 @@ class CommandsAccessoryTest < ActiveSupport::TestCase } } } - - ENV["MYSQL_ROOT_PASSWORD"] = "secret123" end teardown do - ENV.delete("MYSQL_ROOT_PASSWORD") + teardown_test_secrets end 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 --env MYSQL_ROOT_HOST=\"%\" --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 MYSQL_ROOT_HOST=\"%\" --env MYSQL_ROOT_PASSWORD=\"secret123\" --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 --env SOMETHING=\"else\" --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 SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest", new_command(:redis).run.join(" ") assert_equal \ - "docker run --name custom-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --env-file .kamal/env/accessories/custom-busybox.env --label service=\"custom-busybox\" busybox:latest", + "docker run --name custom-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --label service=\"custom-busybox\" busybox:latest", new_command(:busybox).run.join(" ") end @@ -67,7 +67,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase @config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } } assert_equal \ - "docker run --name custom-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/env/accessories/custom-busybox.env --label service=\"custom-busybox\" busybox:latest", + "docker run --name custom-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"custom-busybox\" busybox:latest", new_command(:busybox).run.join(" ") end @@ -92,7 +92,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase test "execute in new container" do assert_equal \ - "docker run --rm --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root", + "docker run --rm --env MYSQL_ROOT_HOST=\"%\" --env MYSQL_ROOT_PASSWORD=\"secret123\" private.registry/mysql:8.0 mysql -u root", new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ") end @@ -104,7 +104,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 --env MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root}, + assert_match %r{docker run -it --rm --env MYSQL_ROOT_HOST=\"%\" --env MYSQL_ROOT_PASSWORD=\"secret123\" private.registry/mysql:8.0 mysql -u root}, new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root") end end @@ -150,14 +150,6 @@ class CommandsAccessoryTest < ActiveSupport::TestCase new_command(:mysql).remove_image.join(" ") end - test "make_env_directory" do - assert_equal "mkdir -p .kamal/env/accessories", new_command(:mysql).make_env_directory.join(" ") - end - - test "remove_env_file" do - assert_equal "rm -f .kamal/env/accessories/app-mysql.env", new_command(:mysql).remove_env_file.join(" ") - end - private def new_command(accessory) Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory) diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index bee7dc34..2ccb6033 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -2,25 +2,25 @@ require "test_helper" class CommandsAppTest < ActiveSupport::TestCase setup do - ENV["RAILS_MASTER_KEY"] = "456" + setup_test_secrets("secrets" => "RAILS_MASTER_KEY=456") Kamal::Configuration.any_instance.stubs(:run_id).returns("12345678901234567890123456789012") @config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] }, builder: { "arch" => "amd64" } } end teardown do - ENV.delete("RAILS_MASTER_KEY") + teardown_test_secrets end test "run" do assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end test "run with hostname" do assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 --hostname myhost -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 --hostname myhost -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run(hostname: "myhost").join(" ") end @@ -28,7 +28,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:volumes] = [ "/local/path:/container/path" ] assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -36,7 +36,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:healthcheck] = { "path" => "/healthz" } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/healthz || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/healthz || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -44,7 +44,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:healthcheck] = { "cmd" => "/bin/up" } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(/bin/up) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(/bin/up) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -52,14 +52,14 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "healthcheck" => { "cmd" => "/bin/healthy" } } } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(/bin/healthy) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(/bin/healthy) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end test "run with custom options" do @config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_equal \ - "docker run --detach --restart unless-stopped --name app-jobs-999 -e KAMAL_CONTAINER_NAME=\"app-jobs-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-jobs.env --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --label destination --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs", + "docker run --detach --restart unless-stopped --name app-jobs-999 -e KAMAL_CONTAINER_NAME=\"app-jobs-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --label destination --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs", new_command(role: "jobs", host: "1.1.1.2").run.join(" ") end @@ -67,7 +67,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -76,7 +76,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "logging" => { "driver" => "local", "options" => { "max-size" => "100m" } } } } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -85,7 +85,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --env ENV1=\"value1\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env ENV1=\"value1\" --env RAILS_MASTER_KEY=\"456\" --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -204,13 +204,13 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in new container" do assert_equal \ - "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails db:setup", + "docker run --rm --env RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end test "execute in new container with env" do assert_equal \ - "docker run --rm --env-file .kamal/env/roles/app-web.env --env foo=\"bar\" dhh/app:999 bin/rails db:setup", + "docker run --rm --env RAILS_MASTER_KEY=\"456\" --env foo=\"bar\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end @@ -219,14 +219,14 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } assert_equal \ - "docker run --rm --env-file .kamal/env/roles/app-web.env --env ENV1=\"value1\" dhh/app:999 bin/rails db:setup", + "docker run --rm --env ENV1=\"value1\" --env RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end test "execute in new container with custom options" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_equal \ - "docker run --rm --env-file .kamal/env/roles/app-web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup", + "docker run --rm --env RAILS_MASTER_KEY=\"456\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end @@ -243,7 +243,7 @@ class CommandsAppTest < ActiveSupport::TestCase end test "execute in new container over ssh" do - assert_match %r{docker run -it --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails c}, + assert_match %r{docker run -it --rm --env RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c}, new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end @@ -251,13 +251,13 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:servers] = [ { "1.1.1.1" => "tag1" } ] @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } - assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --env-file .kamal/env/roles/app-web.env --env ENV1=\"value1\" dhh/app:999 bin/rails c'", + assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --env ENV1=\"value1\" --env RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c'", new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end test "execute in new container with custom options over ssh" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } - assert_match %r{docker run -it --rm --env-file .kamal/env/roles/app-web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, + assert_match %r{docker run -it --rm --env RAILS_MASTER_KEY=\"456\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end @@ -412,14 +412,6 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.tag_latest_image.join(" ") end - test "make_env_directory" do - assert_equal "mkdir -p .kamal/env/roles", new_command.make_env_directory.join(" ") - end - - test "remove_env_file" do - assert_equal "rm -f .kamal/env/roles/app-web.env", new_command.remove_env_file.join(" ") - end - test "cord" do assert_equal "docker inspect -f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}' app-web-123 | awk '$2 == \"/tmp/kamal-cord\" {print $1}'", new_command.cord(version: 123).join(" ") end diff --git a/test/commands/auditor_test.rb b/test/commands/auditor_test.rb index 2aaafd67..2abc8d81 100644 --- a/test/commands/auditor_test.rb +++ b/test/commands/auditor_test.rb @@ -18,6 +18,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase test "record" do assert_equal [ + :mkdir, "-p", ".kamal", "&&", :echo, "[#{@recorded_at}] [#{@performer}]", "app removed container", @@ -28,6 +29,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase test "record with destination" do new_command(destination: "staging").tap do |auditor| assert_equal [ + :mkdir, "-p", ".kamal", "&&", :echo, "[#{@recorded_at}] [#{@performer}] [staging]", "app removed container", @@ -39,6 +41,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase test "record with command details" do new_command(role: "web").tap do |auditor| assert_equal [ + :mkdir, "-p", ".kamal", "&&", :echo, "[#{@recorded_at}] [#{@performer}] [web]", "app removed container", @@ -49,6 +52,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase test "record with arg details" do assert_equal [ + :mkdir, "-p", ".kamal", "&&", :echo, "[#{@recorded_at}] [#{@performer}] [value]", "app removed container", diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 45c40388..e5daddfd 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -69,10 +69,13 @@ class CommandsBuilderTest < ActiveSupport::TestCase end test "build secrets" do - builder = new_builder_command(builder: { "secrets" => [ "token_a", "token_b" ] }) - assert_equal \ - "-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile", - builder.target.build_options.join(" ") + with_test_secrets("secrets" => "token_a=foo\ntoken_b=bar") do + FileUtils.touch("Dockerfile") + builder = new_builder_command(builder: { "secrets" => [ "token_a", "token_b" ] }) + assert_equal \ + "-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile", + builder.target.build_options.join(" ") + end end test "build dockerfile" do @@ -113,10 +116,13 @@ class CommandsBuilderTest < ActiveSupport::TestCase end test "push with build secrets" do - builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] }) - assert_equal \ - "docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .", - builder.push.join(" ") + with_test_secrets("secrets" => "a=foo\nb=bar") do + FileUtils.touch("Dockerfile") + builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] }) + assert_equal \ + "docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .", + builder.push.join(" ") + end end test "build with ssh agent socket" do diff --git a/test/commands/registry_test.rb b/test/commands/registry_test.rb index 17376fef..cf2734b7 100755 --- a/test/commands/registry_test.rb +++ b/test/commands/registry_test.rb @@ -11,51 +11,52 @@ class CommandsRegistryTest < ActiveSupport::TestCase builder: { "arch" => "amd64" }, servers: [ "1.1.1.1" ] } - @registry = Kamal::Commands::Registry.new Kamal::Configuration.new(@config) end test "registry login" do assert_equal \ "docker login hub.docker.com -u \"dhh\" -p \"secret\"", - @registry.login.join(" ") + registry.login.join(" ") end test "registry login with ENV password" do - ENV["KAMAL_REGISTRY_PASSWORD"] = "more-secret" - @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] + with_test_secrets("secrets" => "KAMAL_REGISTRY_PASSWORD=more-secret") do + @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] - assert_equal \ - "docker login hub.docker.com -u \"dhh\" -p \"more-secret\"", - @registry.login.join(" ") - ensure - ENV.delete("KAMAL_REGISTRY_PASSWORD") + assert_equal \ + "docker login hub.docker.com -u \"dhh\" -p \"more-secret\"", + registry.login.join(" ") + end end test "registry login escape password" do - ENV["KAMAL_REGISTRY_PASSWORD"] = "more-secret'\"" - @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] + with_test_secrets("secrets" => "KAMAL_REGISTRY_PASSWORD=more-secret'\"") do + @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] - assert_equal \ - "docker login hub.docker.com -u \"dhh\" -p \"more-secret'\\\"\"", - @registry.login.join(" ") - ensure - ENV.delete("KAMAL_REGISTRY_PASSWORD") + assert_equal \ + "docker login hub.docker.com -u \"dhh\" -p \"more-secret'\\\"\"", + registry.login.join(" ") + end end test "registry login with ENV username" do - ENV["KAMAL_REGISTRY_USERNAME"] = "also-secret" - @config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ] + with_test_secrets("secrets" => "KAMAL_REGISTRY_USERNAME=also-secret") do + @config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ] - assert_equal \ - "docker login hub.docker.com -u \"also-secret\" -p \"secret\"", - @registry.login.join(" ") - ensure - ENV.delete("KAMAL_REGISTRY_USERNAME") + assert_equal \ + "docker login hub.docker.com -u \"also-secret\" -p \"secret\"", + registry.login.join(" ") + end end test "registry logout" do assert_equal \ "docker logout hub.docker.com", - @registry.logout.join(" ") + registry.logout.join(" ") end + + private + def registry + Kamal::Commands::Registry.new Kamal::Configuration.new(@config) + end end diff --git a/test/commands/traefik_test.rb b/test/commands/traefik_test.rb index 446c3077..3e90cd50 100644 --- a/test/commands/traefik_test.rb +++ b/test/commands/traefik_test.rb @@ -9,81 +9,81 @@ class CommandsTraefikTest < ActiveSupport::TestCase traefik: { "image" => @image, "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } } } - ENV["EXAMPLE_API_KEY"] = "456" + setup_test_secrets("secrets" => "EXAMPLE_API_KEY=456") end teardown do - ENV.delete("EXAMPLE_API_KEY") + teardown_test_secrets end test "run" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["host_port"] = "8080" assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["publish"] = false assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with ports configured" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["options"] = { "publish" => %w[9000:9000 9001:9001] } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --publish \"9000:9000\" --publish \"9001:9001\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --publish \"9000:9000\" --publish \"9001:9001\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with volumes configured" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["options"] = { "volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with several options configured" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["options"] = { "volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m" } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with labels configured" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["labels"] = { "traefik.http.routers.dashboard.service" => "api@internal", "traefik.http.routers.dashboard.middlewares" => "auth" } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --label traefik.http.routers.dashboard.service=\"api@internal\" --label traefik.http.routers.dashboard.middlewares=\"auth\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" --label traefik.http.routers.dashboard.service=\"api@internal\" --label traefik.http.routers.dashboard.middlewares=\"auth\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with env configured" do assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") @config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env EXAMPLE_API_KEY=\"456\" --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end @@ -91,7 +91,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase @config.delete(:traefik) assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{Kamal::Configuration::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", new_command.run.join(" ") end @@ -99,7 +99,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase @config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } } assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"DEBUG\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end @@ -107,13 +107,13 @@ class CommandsTraefikTest < ActiveSupport::TestCase @config[:traefik]["args"]["log.level"] = "ERROR" assert_equal \ - "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"ERROR\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", + "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" #{@image} --providers.docker --log.level=\"ERROR\" --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"", new_command.run.join(" ") end test "run with args array" do @config[:traefik]["args"] = { "entrypoints.web.forwardedheaders.trustedips" => %w[ 127.0.0.1 127.0.0.2 ] } - assert_equal "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" traefik:test --providers.docker --log.level=\"DEBUG\" --entrypoints.web.forwardedheaders.trustedips=\"127.0.0.1\" --entrypoints.web.forwardedheaders.trustedips=\"127.0.0.2\"", new_command.run.join(" ") + assert_equal "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" traefik:test --providers.docker --log.level=\"DEBUG\" --entrypoints.web.forwardedheaders.trustedips=\"127.0.0.1\" --entrypoints.web.forwardedheaders.trustedips=\"127.0.0.2\"", new_command.run.join(" ") end test "traefik start" do @@ -188,20 +188,6 @@ class CommandsTraefikTest < ActiveSupport::TestCase new_command.follow_logs(host: @config[:servers].first, grep: "hello!") end - test "secrets io" do - @config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] } - - assert_equal "EXAMPLE_API_KEY=456\n", new_command.env.secrets_io.string - end - - test "make_env_directory" do - assert_equal "mkdir -p .kamal/env/traefik", new_command.make_env_directory.join(" ") - end - - test "remove_env_file" do - assert_equal "rm -f .kamal/env/traefik/traefik.env", new_command.remove_env_file.join(" ") - end - private def new_command Kamal::Commands::Traefik.new(Kamal::Configuration.new(@config, version: "123")) diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index 3f939607..3497e6c1 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -116,25 +116,12 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase end test "env args" do - 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 + with_test_secrets("secrets" => "MYSQL_ROOT_PASSWORD=secret123") do + config = Kamal::Configuration.new(@deploy) - test "env with secrets" do - ENV["MYSQL_ROOT_PASSWORD"] = "secret123" - - expected_secrets_file = <<~ENV - MYSQL_ROOT_PASSWORD=secret123 - ENV - - 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 "env secrets path" do - assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).env.secrets_file + assert_equal [ "--env", "MYSQL_ROOT_HOST=\"%\"", "--env", "MYSQL_ROOT_PASSWORD=\"secret123\"" ], config.accessory(:mysql).env_args.map(&:to_s) + assert_equal [ "--env", "SOMETHING=\"else\"" ], @config.accessory(:redis).env_args + end end test "volume args" do diff --git a/test/configuration/builder_test.rb b/test/configuration/builder_test.rb index a4fa7fbb..53740ca8 100644 --- a/test/configuration/builder_test.rb +++ b/test/configuration/builder_test.rb @@ -93,13 +93,15 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase end test "secrets" do - assert_equal [], config.builder.secrets + assert_equal({}, config.builder.secrets) end test "setting secrets" do - @deploy[:builder]["secrets"] = [ "GITHUB_TOKEN" ] + with_test_secrets("secrets" => "GITHUB_TOKEN=secret123") do + @deploy[:builder]["secrets"] = [ "GITHUB_TOKEN" ] - assert_equal [ "GITHUB_TOKEN" ], config.builder.secrets + assert_equal({ "GITHUB_TOKEN" => "secret123" }, config.builder.secrets) + end end test "dockerfile" do diff --git a/test/configuration/env/tags_test.rb b/test/configuration/env/tags_test.rb index 0fb649d1..7db61777 100644 --- a/test/configuration/env/tags_test.rb +++ b/test/configuration/env/tags_test.rb @@ -79,23 +79,21 @@ class ConfigurationEnvTagsTest < ActiveSupport::TestCase end test "tag secret env" do - ENV["PASSWORD"] = "hello" - - deploy = { - service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, - servers: [ { "1.1.1.1" => "secrets" } ], - builder: { "arch" => "amd64" }, - env: { - "tags" => { - "secrets" => { "secret" => [ "PASSWORD" ] } + with_test_secrets("secrets" => "PASSWORD=hello") do + deploy = { + service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, + servers: [ { "1.1.1.1" => "secrets" } ], + builder: { "arch" => "amd64" }, + env: { + "tags" => { + "secrets" => { "secret" => [ "PASSWORD" ] } + } } } - } - config = Kamal::Configuration.new(deploy) - assert_equal "hello", config.role("web").env("1.1.1.1").secrets["PASSWORD"] - ensure - ENV.delete "PASSWORD" + config = Kamal::Configuration.new(deploy) + assert_equal "hello", config.role("web").env("1.1.1.1").secrets["PASSWORD"] + end end test "tag clear env" do diff --git a/test/configuration/env_test.rb b/test/configuration/env_test.rb index 49d800ef..c3f0b929 100644 --- a/test/configuration/env_test.rb +++ b/test/configuration/env_test.rb @@ -6,27 +6,21 @@ class ConfigurationEnvTest < ActiveSupport::TestCase test "simple" do assert_config \ config: { "foo" => "bar", "baz" => "haz" }, - clear: { "foo" => "bar", "baz" => "haz" }, - secrets: {} + results: { "foo" => "bar", "baz" => "haz" } end test "clear" do assert_config \ config: { "clear" => { "foo" => "bar", "baz" => "haz" } }, - clear: { "foo" => "bar", "baz" => "haz" }, - secrets: {} + results: { "foo" => "bar", "baz" => "haz" } end test "secret" do - ENV["PASSWORD"] = "hello" - env = Kamal::Configuration::Env.new config: { "secret" => [ "PASSWORD" ] } - - assert_config \ - config: { "secret" => [ "PASSWORD" ] }, - clear: {}, - secrets: { "PASSWORD" => "hello" } - ensure - ENV.delete "PASSWORD" + with_test_secrets("secrets" => "PASSWORD=hello") do + assert_config \ + config: { "secret" => [ "PASSWORD" ] }, + results: { "PASSWORD" => "hello" } + end end test "missing secret" do @@ -34,41 +28,29 @@ class ConfigurationEnvTest < ActiveSupport::TestCase "secret" => [ "PASSWORD" ] } - assert_raises(KeyError) { Kamal::Configuration::Env.new(config: { "secret" => [ "PASSWORD" ] }).secrets } + assert_raises(Kamal::ConfigurationError) { Kamal::Configuration::Env.new(config: { "secret" => [ "PASSWORD" ] }, secrets: Kamal::Configuration::Secrets.new).args } end test "secret and clear" do - ENV["PASSWORD"] = "hello" - config = { - "secret" => [ "PASSWORD" ], - "clear" => { - "foo" => "bar", - "baz" => "haz" + with_test_secrets("secrets" => "PASSWORD=hello") do + 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 + assert_config \ + config: config, + results: { "foo" => "bar", "baz" => "haz", "PASSWORD" => "hello" } + end end private - def assert_config(config:, clear:, secrets:) - env = Kamal::Configuration::Env.new config: config, secrets_file: "secrets.env" - assert_equal clear, env.clear - assert_equal secrets, env.secrets + def assert_config(config:, results:) + env = Kamal::Configuration::Env.new config: config, secrets: Kamal::Configuration::Secrets.new + expected_args = results.to_a.flat_map { |key, value| [ "--env", "#{key}=\"#{value}\"" ] } + assert_equal expected_args, env.args.map(&:to_s) #  to_s removes the redactions end end diff --git a/test/configuration/role_test.rb b/test/configuration/role_test.rb index 37c26fcd..d3b54ca6 100644 --- a/test/configuration/role_test.rb +++ b/test/configuration/role_test.rb @@ -9,8 +9,6 @@ class ConfigurationRoleTest < ActiveSupport::TestCase env: { "REDIS_URL" => "redis://x/y" } } - @config = Kamal::Configuration.new(@deploy) - @deploy_with_roles = @deploy.dup.merge({ servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], @@ -24,31 +22,29 @@ class ConfigurationRoleTest < ActiveSupport::TestCase } } }) - - @config_with_roles = Kamal::Configuration.new(@deploy_with_roles) end test "hosts" do - assert_equal [ "1.1.1.1", "1.1.1.2" ], @config.role(:web).hosts - assert_equal [ "1.1.1.3", "1.1.1.4" ], @config_with_roles.role(:workers).hosts + assert_equal [ "1.1.1.1", "1.1.1.2" ], config.role(:web).hosts + assert_equal [ "1.1.1.3", "1.1.1.4" ], config_with_roles.role(:workers).hosts end test "cmd" do - assert_nil @config.role(:web).cmd - assert_equal "bin/jobs", @config_with_roles.role(:workers).cmd + assert_nil config.role(:web).cmd + assert_equal "bin/jobs", config_with_roles.role(:workers).cmd end test "label args" do - assert_equal [ "--label", "service=\"app\"", "--label", "role=\"workers\"", "--label", "destination" ], @config_with_roles.role(:workers).label_args + assert_equal [ "--label", "service=\"app\"", "--label", "role=\"workers\"", "--label", "destination" ], config_with_roles.role(:workers).label_args end test "special label args for web" do - assert_equal [ "--label", "service=\"app\"", "--label", "role=\"web\"", "--label", "destination", "--label", "traefik.http.services.app-web.loadbalancer.server.scheme=\"http\"", "--label", "traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.routers.app-web.priority=\"2\"", "--label", "traefik.http.middlewares.app-web-retry.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\"" ], @config.role(:web).label_args + assert_equal [ "--label", "service=\"app\"", "--label", "role=\"web\"", "--label", "destination", "--label", "traefik.http.services.app-web.loadbalancer.server.scheme=\"http\"", "--label", "traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\"", "--label", "traefik.http.routers.app-web.priority=\"2\"", "--label", "traefik.http.middlewares.app-web-retry.retry.attempts=\"5\"", "--label", "traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\"", "--label", "traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\"" ], config.role(:web).label_args end test "custom labels" do @deploy[:labels] = { "my.custom.label" => "50" } - assert_equal "50", @config.role(:web).labels["my.custom.label"] + assert_equal "50", config.role(:web).labels["my.custom.label"] end test "custom labels via role specialization" do @@ -59,7 +55,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase test "overwriting default traefik label" do @deploy[:labels] = { "traefik.http.routers.app-web.rule" => "\"Host(\\`example.com\\`) || (Host(\\`example.org\\`) && Path(\\`/traefik\\`))\"" } - assert_equal "\"Host(\\`example.com\\`) || (Host(\\`example.org\\`) && Path(\\`/traefik\\`))\"", @config.role(:web).labels["traefik.http.routers.app-web.rule"] + assert_equal "\"Host(\\`example.com\\`) || (Host(\\`example.org\\`) && Path(\\`/traefik\\`))\"", config.role(:web).labels["traefik.http.routers.app-web.rule"] end test "default traefik label on non-web role" do @@ -71,166 +67,149 @@ class ConfigurationRoleTest < ActiveSupport::TestCase end test "env overwritten by role" do - assert_equal "redis://a/b", @config_with_roles.role(:workers).env("1.1.1.3").clear["REDIS_URL"] + assert_equal "redis://a/b", config_with_roles.role(:workers).env("1.1.1.3").clear["REDIS_URL"] - assert_equal "\n", @config_with_roles.role(:workers).env("1.1.1.3").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("1.1.1.3") + assert_equal [ + "--env", "REDIS_URL=\"redis://a/b\"", + "--env", "WEB_CONCURRENCY=\"4\"" ], + config_with_roles.role(:workers).env_args("1.1.1.3") end test "container name" do ENV["VERSION"] = "12345" - assert_equal "app-workers-12345", @config_with_roles.role(:workers).container_name - assert_equal "app-web-12345", @config_with_roles.role(:web).container_name + assert_equal "app-workers-12345", config_with_roles.role(:workers).container_name + assert_equal "app-web-12345", config_with_roles.role(:web).container_name ensure ENV.delete("VERSION") end test "env args" do - 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("1.1.1.3") + assert_equal [ "--env", "REDIS_URL=\"redis://a/b\"", "--env", "WEB_CONCURRENCY=\"4\"" ], config_with_roles.role(:workers).env_args("1.1.1.3") end test "env secret overwritten by role" do - @deploy_with_roles[:env] = { - "clear" => { - "REDIS_URL" => "redis://a/b" - }, - "secret" => [ - "REDIS_PASSWORD" - ] - } + with_test_secrets("secrets" => "REDIS_PASSWORD=secret456\nDB_PASSWORD=secret&\"123") do + @deploy_with_roles[:env] = { + "clear" => { + "REDIS_URL" => "redis://a/b" + }, + "secret" => [ + "REDIS_PASSWORD" + ] + } - @deploy_with_roles[:servers]["workers"]["env"] = { - "clear" => { - "REDIS_URL" => "redis://a/b", - "WEB_CONCURRENCY" => "4" - }, - "secret" => [ - "DB_PASSWORD" - ] - } + @deploy_with_roles[:servers]["workers"]["env"] = { + "clear" => { + "REDIS_URL" => "redis://a/b", + "WEB_CONCURRENCY" => "4" + }, + "secret" => [ + "DB_PASSWORD" + ] + } - ENV["REDIS_PASSWORD"] = "secret456" - ENV["DB_PASSWORD"] = "secret&\"123" - - expected_secrets_file = <<~ENV - REDIS_PASSWORD=secret456 - DB_PASSWORD=secret&\"123 - ENV - - assert_equal expected_secrets_file, Kamal::Configuration.new(@deploy_with_roles).role(:workers).env("1.1.1.3").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("1.1.1.3") - ensure - ENV["REDIS_PASSWORD"] = nil - ENV["DB_PASSWORD"] = nil + assert_equal [ + "--env", "REDIS_URL=\"redis://a/b\"", + "--env", "WEB_CONCURRENCY=\"4\"", + "--env", "REDIS_PASSWORD=\"secret456\"", + "--env", "DB_PASSWORD=\"secret&\\\"123\"" ], + config_with_roles.role(:workers).env_args("1.1.1.3").map(&:to_s) + end end test "env secrets only in role" do - @deploy_with_roles[:servers]["workers"]["env"] = { - "clear" => { - "REDIS_URL" => "redis://a/b", - "WEB_CONCURRENCY" => "4" - }, - "secret" => [ - "DB_PASSWORD" - ] - } + with_test_secrets("secrets" => "DB_PASSWORD=secret123") do + @deploy_with_roles[:servers]["workers"]["env"] = { + "clear" => { + "REDIS_URL" => "redis://a/b", + "WEB_CONCURRENCY" => "4" + }, + "secret" => [ + "DB_PASSWORD" + ] + } - ENV["DB_PASSWORD"] = "secret123" - - expected_secrets_file = <<~ENV - DB_PASSWORD=secret123 - ENV - - assert_equal expected_secrets_file, Kamal::Configuration.new(@deploy_with_roles).role(:workers).env("1.1.1.3").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("1.1.1.3") - ensure - ENV["DB_PASSWORD"] = nil + assert_equal [ + "--env", "REDIS_URL=\"redis://a/b\"", + "--env", "WEB_CONCURRENCY=\"4\"", + "--env", "DB_PASSWORD=\"secret123\"" ], + config_with_roles.role(:workers).env_args("1.1.1.3").map(&:to_s) + end end test "env secrets only at top level" do - @deploy_with_roles[:env] = { - "clear" => { - "REDIS_URL" => "redis://a/b" - }, - "secret" => [ - "REDIS_PASSWORD" - ] - } + with_test_secrets("secrets" => "REDIS_PASSWORD=secret456") do + @deploy_with_roles[:env] = { + "clear" => { + "REDIS_URL" => "redis://a/b" + }, + "secret" => [ + "REDIS_PASSWORD" + ] + } - ENV["REDIS_PASSWORD"] = "secret456" - - expected_secrets_file = <<~ENV - REDIS_PASSWORD=secret456 - ENV - - assert_equal expected_secrets_file, Kamal::Configuration.new(@deploy_with_roles).role(:workers).env("1.1.1.3").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("1.1.1.3") - ensure - ENV["REDIS_PASSWORD"] = nil + assert_equal [ + "--env", "REDIS_URL=\"redis://a/b\"", + "--env", "WEB_CONCURRENCY=\"4\"", + "--env", "REDIS_PASSWORD=\"secret456\"" ], + config_with_roles.role(:workers).env_args("1.1.1.3").map(&:to_s) + end end test "env overwritten by role with secrets" do - @deploy_with_roles[:env] = { - "clear" => { - "REDIS_URL" => "redis://a/b" - }, - "secret" => [ - "REDIS_PASSWORD" - ] - } - - @deploy_with_roles[:servers]["workers"]["env"] = { - "clear" => { - "REDIS_URL" => "redis://c/d" + with_test_secrets("secrets" => "REDIS_PASSWORD=secret456") do + @deploy_with_roles[:env] = { + "clear" => { + "REDIS_URL" => "redis://a/b" + }, + "secret" => [ + "REDIS_PASSWORD" + ] } - } - ENV["REDIS_PASSWORD"] = "secret456" + @deploy_with_roles[:servers]["workers"]["env"] = { + "clear" => { + "REDIS_URL" => "redis://c/d" + } + } - expected_secrets_file = <<~ENV - REDIS_PASSWORD=secret456 - ENV - - config = Kamal::Configuration.new(@deploy_with_roles) - assert_equal expected_secrets_file, config.role(:workers).env("1.1.1.3").secrets_io.string - assert_equal [ "--env-file", ".kamal/env/roles/app-workers.env", "--env", "REDIS_URL=\"redis://c/d\"" ], config.role(:workers).env_args("1.1.1.3") - ensure - ENV["REDIS_PASSWORD"] = nil - end - - test "env secrets_file" do - assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).env("1.1.1.3").secrets_file + config = config_with_roles + assert_equal [ + "--env", "REDIS_URL=\"redis://c/d\"", + "--env", "REDIS_PASSWORD=\"secret456\"" ], + config.role(:workers).env_args("1.1.1.3").map(&:to_s) + end end test "uses cord" do - assert @config_with_roles.role(:web).uses_cord? - assert_not @config_with_roles.role(:workers).uses_cord? + assert config_with_roles.role(:web).uses_cord? + assert_not config_with_roles.role(:workers).uses_cord? end test "cord host file" do - assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}/cord}, @config_with_roles.role(:web).cord_host_file + assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}/cord}, config_with_roles.role(:web).cord_host_file end test "cord volume" do - assert_equal "/tmp/kamal-cord", @config_with_roles.role(:web).cord_volume.container_path - assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}}, @config_with_roles.role(:web).cord_volume.host_path - assert_equal "--volume", @config_with_roles.role(:web).cord_volume.docker_args[0] - assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}:/tmp/kamal-cord}, @config_with_roles.role(:web).cord_volume.docker_args[1] + assert_equal "/tmp/kamal-cord", config_with_roles.role(:web).cord_volume.container_path + assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}}, config_with_roles.role(:web).cord_volume.host_path + assert_equal "--volume", config_with_roles.role(:web).cord_volume.docker_args[0] + assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}:/tmp/kamal-cord}, config_with_roles.role(:web).cord_volume.docker_args[1] end test "cord container file" do - assert_equal "/tmp/kamal-cord/cord", @config_with_roles.role(:web).cord_container_file + assert_equal "/tmp/kamal-cord/cord", config_with_roles.role(:web).cord_container_file end test "asset path and volume args" do ENV["VERSION"] = "12345" - assert_nil @config_with_roles.role(:web).asset_volume_args - assert_nil @config_with_roles.role(:workers).asset_volume_args - assert_nil @config_with_roles.role(:web).asset_path - assert_nil @config_with_roles.role(:workers).asset_path - assert_not @config_with_roles.role(:web).assets? - assert_not @config_with_roles.role(:workers).assets? + assert_nil config_with_roles.role(:web).asset_volume_args + assert_nil config_with_roles.role(:workers).asset_volume_args + assert_nil config_with_roles.role(:web).asset_path + assert_nil config_with_roles.role(:workers).asset_path + assert_not config_with_roles.role(:web).assets? + assert_not config_with_roles.role(:workers).assets? config_with_assets = Kamal::Configuration.new(@deploy_with_roles.dup.tap { |c| c[:asset_path] = "foo" @@ -258,17 +237,26 @@ class ConfigurationRoleTest < ActiveSupport::TestCase test "asset extracted path" do ENV["VERSION"] = "12345" - assert_equal ".kamal/assets/extracted/app-web-12345", @config_with_roles.role(:web).asset_extracted_path - assert_equal ".kamal/assets/extracted/app-workers-12345", @config_with_roles.role(:workers).asset_extracted_path + assert_equal ".kamal/assets/extracted/app-web-12345", config_with_roles.role(:web).asset_extracted_path + assert_equal ".kamal/assets/extracted/app-workers-12345", config_with_roles.role(:workers).asset_extracted_path ensure ENV.delete("VERSION") end test "asset volume path" do ENV["VERSION"] = "12345" - assert_equal ".kamal/assets/volumes/app-web-12345", @config_with_roles.role(:web).asset_volume_path - assert_equal ".kamal/assets/volumes/app-workers-12345", @config_with_roles.role(:workers).asset_volume_path + assert_equal ".kamal/assets/volumes/app-web-12345", config_with_roles.role(:web).asset_volume_path + assert_equal ".kamal/assets/volumes/app-workers-12345", config_with_roles.role(:workers).asset_volume_path ensure ENV.delete("VERSION") end + + private + def config + Kamal::Configuration.new(@deploy) + end + + def config_with_roles + Kamal::Configuration.new(@deploy_with_roles) + end end diff --git a/test/integration/docker/deployer/app/.kamal/env.erb b/test/integration/docker/deployer/app/.kamal/secrets similarity index 100% rename from test/integration/docker/deployer/app/.kamal/env.erb rename to test/integration/docker/deployer/app/.kamal/secrets diff --git a/test/integration/docker/deployer/app_with_roles/.kamal/env.erb b/test/integration/docker/deployer/app_with_roles/.kamal/secrets similarity index 100% rename from test/integration/docker/deployer/app_with_roles/.kamal/env.erb rename to test/integration/docker/deployer/app_with_roles/.kamal/secrets diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 79e39a6b..6e8d3bf1 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -2,9 +2,6 @@ require_relative "integration_test" class MainTest < IntegrationTest test "deploy, redeploy, rollback, details and audit" do - assert_env_files - remove_local_env_file - first_version = latest_app_version assert_app_is_down @@ -105,11 +102,7 @@ class MainTest < IntegrationTest end private - def assert_local_env_file(contents) - assert_equal contents, deployer_exec("cat .kamal/secrets", capture: true) - end - - def assert_envs(version:) + def assert_envs(version:) assert_env :CLEAR_TOKEN, "4321", version: version, vm: :vm1 assert_env :HOST_TOKEN, "abcd", version: version, vm: :vm1 assert_env :SECRET_TOKEN, "1234 with \"中文\"", version: version, vm: :vm1 @@ -129,24 +122,6 @@ class MainTest < IntegrationTest end end - def assert_env_files - assert_local_env_file "SECRET_TOKEN='1234 with \"中文\"'\nSECRET_TAG='TAGME'" - assert_remote_env_file "SECRET_TOKEN=1234 with \"中文\"", vm: :vm1 - assert_remote_env_file "SECRET_TOKEN=1234 with \"中文\"\nSECRET_TAG=TAGME", vm: :vm2 - end - - def remove_local_env_file - deployer_exec("rm .kamal/secrets") - end - - def assert_remote_env_file(contents, vm:) - assert_equal contents, docker_compose("exec #{vm} cat /root/.kamal/secrets/roles/app-web.env", capture: true) - end - - def assert_no_remote_env_file - assert_equal "nofile", docker_compose("exec vm1 stat /root/.kamal/secrets/roles/app-web.env 2> /dev/null || echo nofile", capture: true) - end - def assert_accumulated_assets(*versions) versions.each do |version| assert_equal "200", Net::HTTP.get_response(URI.parse("http://localhost:12345/versions/#{version}")).code diff --git a/test/test_helper.rb b/test/test_helper.rb index 5f7a25c4..ff1ad43a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -34,4 +34,32 @@ class ActiveSupport::TestCase def stderred capture(:stderr) { yield }.strip end + + def with_test_secrets(**files) + setup_test_secrets(**files) + yield + ensure + teardown_test_secrets + end + + def setup_test_secrets(**files) + @original_pwd = Dir.pwd + @secrets_tmpdir = Dir.mktmpdir + fixtures_dup = File.join(@secrets_tmpdir, "test") + FileUtils.mkdir_p(fixtures_dup) + FileUtils.cp_r("test/fixtures/", fixtures_dup) + + Dir.chdir(@secrets_tmpdir) + FileUtils.mkdir_p(".kamal") + Dir.chdir(".kamal") do + files.each do |filename, contents| + File.binwrite(filename.to_s, contents) + end + end + end + + def teardown_test_secrets + Dir.chdir(@original_pwd) + FileUtils.rm_rf(@secrets_tmpdir) + end end