Lazily load secrets whenever needed
This commit is contained in:
committed by
Donal McBreen
parent
6a06efc9d9
commit
56754fe40c
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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}" ]
|
||||
|
||||
@@ -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("-")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
7
lib/kamal/configuration/env/tag.rb
vendored
7
lib/kamal/configuration/env/tag.rb
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
25
lib/kamal/configuration/secrets.rb
Normal file
25
lib/kamal/configuration/secrets.rb
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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, '\$')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
26
test/configuration/env/tags_test.rb
vendored
26
test/configuration/env/tags_test.rb
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user