Copy env files to remote hosts
Setting env variables in the docker arguments requires having them on the deploy host. Instead we'll add two new commands `kamal env push` and `kamal env delete` which will manage copying the environment as .env files to the remote host. Docker will pick up the file with `--env-file <path-to-file>`. Env files will be stored under `<kamal run directory>/env`. Running `kamal env push` will create env files for each role and accessory, and traefik if required. `kamal envify` has been updated to also push the env files. By avoiding using `kamal envify` and creating the local and remote secrets manually, you can now avoid accessing secrets needed for the docker runtime environment locally. You will still need build secrets. One thing to note - the Docker doesn't parse the environment variables in the env file, one result of this is that you can't specify multi-line values - see https://github.com/moby/moby/issues/12997. We maybe need to look docker config or docker secrets longer term to get around this. Hattip to @kevinmcconnell - this was all his idea.
This commit is contained in:
52
lib/kamal/cli/env.rb
Normal file
52
lib/kamal/cli/env.rb
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
require "tempfile"
|
||||||
|
|
||||||
|
class Kamal::Cli::Env < Kamal::Cli::Base
|
||||||
|
desc "push", "Push the env file to the remote hosts"
|
||||||
|
def push
|
||||||
|
mutating do
|
||||||
|
on(KAMAL.hosts) do
|
||||||
|
KAMAL.roles_on(host).each do |role|
|
||||||
|
role_config = KAMAL.config.role(role)
|
||||||
|
execute *KAMAL.app(role: role).make_env_directory
|
||||||
|
upload! StringIO.new(role_config.env_file), role_config.host_env_file_path, mode: 400
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on(KAMAL.traefik_hosts) do
|
||||||
|
execute *KAMAL.traefik.make_env_directory
|
||||||
|
upload! StringIO.new(KAMAL.traefik.env_file), KAMAL.traefik.host_env_file_path, mode: 400
|
||||||
|
end
|
||||||
|
|
||||||
|
on(KAMAL.accessory_hosts) do
|
||||||
|
KAMAL.accessories_on(host).each do |accessory|
|
||||||
|
accessory_config = KAMAL.config.accessory(accessory)
|
||||||
|
execute *KAMAL.accessory(accessory).make_env_directory
|
||||||
|
upload! StringIO.new(accessory_config.env_file), accessory_config.host_env_file_path, mode: 400
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "delete", "Delete the env file from the remote hosts"
|
||||||
|
def delete
|
||||||
|
mutating do
|
||||||
|
on(KAMAL.hosts) do
|
||||||
|
KAMAL.roles_on(host).each do |role|
|
||||||
|
role_config = KAMAL.config.role(role)
|
||||||
|
execute *KAMAL.app(role: role).remove_env_file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
on(KAMAL.traefik_hosts) do
|
||||||
|
execute *KAMAL.traefik.remove_env_file
|
||||||
|
end
|
||||||
|
|
||||||
|
on(KAMAL.accessory_hosts) do
|
||||||
|
KAMAL.accessories_on(host).each do |accessory|
|
||||||
|
accessory_config = KAMAL.config.accessory(accessory)
|
||||||
|
execute *KAMAL.accessory(accessory).remove_env_file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -175,6 +175,9 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
File.write(env_path, ERB.new(File.read(env_template_path)).result, perm: 0600)
|
File.write(env_path, ERB.new(File.read(env_template_path)).result, perm: 0600)
|
||||||
|
|
||||||
|
load_envs # reload new file
|
||||||
|
invoke "kamal:cli:env:push", options
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
|
desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
|
||||||
@@ -204,6 +207,9 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
desc "build", "Build application image"
|
desc "build", "Build application image"
|
||||||
subcommand "build", Kamal::Cli::Build
|
subcommand "build", Kamal::Cli::Build
|
||||||
|
|
||||||
|
desc "env", "Manage environment files"
|
||||||
|
subcommand "env", Kamal::Cli::Env
|
||||||
|
|
||||||
desc "healthcheck", "Healthcheck application"
|
desc "healthcheck", "Healthcheck application"
|
||||||
subcommand "healthcheck", Kamal::Cli::Healthcheck
|
subcommand "healthcheck", Kamal::Cli::Healthcheck
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,10 @@ class Kamal::Commander
|
|||||||
config.accessories&.collect(&:name) || []
|
config.accessories&.collect(&:name) || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessories_on(host)
|
||||||
|
config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def app(role: nil)
|
def app(role: nil)
|
||||||
Kamal::Commands::App.new(config, role: role)
|
Kamal::Commands::App.new(config, role: role)
|
||||||
|
|||||||
@@ -86,14 +86,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_directory_for(remote_file)
|
|
||||||
make_directory Pathname.new(remote_file).dirname.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def make_directory(path)
|
|
||||||
[ :mkdir, "-p", path ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_service_directory
|
def remove_service_directory
|
||||||
[ :rm, "-rf", service_name ]
|
[ :rm, "-rf", service_name ]
|
||||||
end
|
end
|
||||||
@@ -106,6 +98,14 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
docker :image, :rm, "--force", image
|
docker :image, :rm, "--force", image
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_env_directory
|
||||||
|
make_directory accessory_config.host_env_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_env_file
|
||||||
|
[:rm, "-f", accessory_config.host_env_file_path]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def service_filter
|
def service_filter
|
||||||
[ "--filter", "label=service=#{service_name}" ]
|
[ "--filter", "label=service=#{service_name}" ]
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
docker :run,
|
docker :run,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
"--rm",
|
"--rm",
|
||||||
*config.env_args,
|
*role&.env_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role&.option_args,
|
*role&.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
@@ -149,6 +149,13 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
docker :tag, config.absolute_image, config.latest_image
|
docker :tag, config.absolute_image, config.latest_image
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_env_directory
|
||||||
|
make_directory config.role(role).host_env_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_env_file
|
||||||
|
[:rm, "-f", config.role(role).host_env_file_path]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def container_name(version = nil)
|
def container_name(version = nil)
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ module Kamal::Commands
|
|||||||
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def make_directory_for(remote_file)
|
||||||
|
make_directory Pathname.new(remote_file).dirname.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_directory(path)
|
||||||
|
[ :mkdir, "-p", path ]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def combine(*commands, by: "&&")
|
def combine(*commands, by: "&&")
|
||||||
commands
|
commands
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
||||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
DEFAULT_IMAGE = "traefik:v2.9"
|
DEFAULT_IMAGE = "traefik:v2.9"
|
||||||
CONTAINER_PORT = 80
|
CONTAINER_PORT = 80
|
||||||
@@ -63,6 +63,22 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|||||||
"#{host_port}:#{CONTAINER_PORT}"
|
"#{host_port}:#{CONTAINER_PORT}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def env_file
|
||||||
|
env_file_with_secrets config.traefik.fetch("env", {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_env_file_path
|
||||||
|
File.join host_env_directory, "traefik.env"
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_env_directory
|
||||||
|
make_directory(host_env_directory)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_env_file
|
||||||
|
[:rm, "-f", host_env_file_path]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def publish_args
|
def publish_args
|
||||||
argumentize "--publish", port unless config.traefik["publish"] == false
|
argumentize "--publish", port unless config.traefik["publish"] == false
|
||||||
@@ -73,13 +89,11 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def env_args
|
def env_args
|
||||||
env_config = config.traefik["env"] || {}
|
argumentize "--env-file", host_env_file_path
|
||||||
|
end
|
||||||
|
|
||||||
if env_config.present?
|
def host_env_directory
|
||||||
argumentize_env_with_secrets(env_config)
|
File.join config.host_env_directory, "traefik"
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def labels
|
def labels
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ require "net/ssh/proxy/jump"
|
|||||||
|
|
||||||
class Kamal::Configuration
|
class Kamal::Configuration
|
||||||
delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
||||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :destination
|
attr_accessor :destination
|
||||||
attr_accessor :raw_config
|
attr_accessor :raw_config
|
||||||
@@ -113,14 +113,6 @@ class Kamal::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def env_args
|
|
||||||
if raw_config.env.present?
|
|
||||||
argumentize_env_with_secrets(raw_config.env)
|
|
||||||
else
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def volume_args
|
def volume_args
|
||||||
if raw_config.volumes.present?
|
if raw_config.volumes.present?
|
||||||
argumentize "--volume", raw_config.volumes
|
argumentize "--volume", raw_config.volumes
|
||||||
@@ -174,7 +166,6 @@ class Kamal::Configuration
|
|||||||
repository: repository,
|
repository: repository,
|
||||||
absolute_image: absolute_image,
|
absolute_image: absolute_image,
|
||||||
service_with_version: service_with_version,
|
service_with_version: service_with_version,
|
||||||
env_args: env_args,
|
|
||||||
volume_args: volume_args,
|
volume_args: volume_args,
|
||||||
ssh_options: ssh.to_h,
|
ssh_options: ssh.to_h,
|
||||||
sshkit: sshkit.to_h,
|
sshkit: sshkit.to_h,
|
||||||
@@ -199,12 +190,15 @@ class Kamal::Configuration
|
|||||||
|
|
||||||
# Will raise KeyError if any secret ENVs are missing
|
# Will raise KeyError if any secret ENVs are missing
|
||||||
def ensure_env_available
|
def ensure_env_available
|
||||||
env_args
|
roles.each(&:env_file)
|
||||||
roles.each(&:env_args)
|
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def host_env_directory
|
||||||
|
"#{run_directory}/env"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Will raise ArgumentError if any required config keys are missing
|
# Will raise ArgumentError if any required config keys are missing
|
||||||
def ensure_required_keys_present
|
def ensure_required_keys_present
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Configuration::Accessory
|
class Kamal::Configuration::Accessory
|
||||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name, :specifics
|
attr_accessor :name, :specifics
|
||||||
|
|
||||||
@@ -45,8 +45,20 @@ class Kamal::Configuration::Accessory
|
|||||||
specifics["env"] || {}
|
specifics["env"] || {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def env_file
|
||||||
|
env_file_with_secrets env
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_env_directory
|
||||||
|
File.join config.host_env_directory, "accessories"
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_env_file_path
|
||||||
|
File.join host_env_directory, "#{service_name}.env"
|
||||||
|
end
|
||||||
|
|
||||||
def env_args
|
def env_args
|
||||||
argumentize_env_with_secrets env
|
argumentize "--env-file", host_env_file_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def files
|
def files
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Configuration::Role
|
class Kamal::Configuration::Role
|
||||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name
|
attr_accessor :name
|
||||||
|
|
||||||
@@ -31,8 +31,20 @@ class Kamal::Configuration::Role
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def env_file
|
||||||
|
env_file_with_secrets env
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_env_directory
|
||||||
|
File.join config.host_env_directory, "roles"
|
||||||
|
end
|
||||||
|
|
||||||
|
def host_env_file_path
|
||||||
|
File.join host_env_directory, "#{[config.service, name, config.destination].compact.join("-")}.env"
|
||||||
|
end
|
||||||
|
|
||||||
def env_args
|
def env_args
|
||||||
argumentize_env_with_secrets env
|
argumentize "--env-file", host_env_file_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def health_check_args
|
def health_check_args
|
||||||
|
|||||||
@@ -16,14 +16,24 @@ module Kamal::Utils
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a list of shell arguments using the same named argument against the passed attributes,
|
def env_file_with_secrets(env)
|
||||||
# but redacts and expands secrets.
|
env_file = StringIO.new.tap do |contents|
|
||||||
def argumentize_env_with_secrets(env)
|
if (secrets = env["secret"]).present?
|
||||||
if (secrets = env["secret"]).present?
|
env.fetch("secret", env)&.each do |key|
|
||||||
argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, sensitive: true) + argumentize("-e", env["clear"])
|
contents << docker_env_file_line(key, ENV.fetch(key))
|
||||||
else
|
end
|
||||||
argumentize "-e", env.fetch("clear", env)
|
env["clear"]&.each do |key, value|
|
||||||
end
|
contents << docker_env_file_line(key, value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
env.fetch("clear", env)&.each do |key, value|
|
||||||
|
contents << docker_env_file_line(key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end.string
|
||||||
|
|
||||||
|
# Ensure the file has some contents to avoid the SSHKIT empty file warning
|
||||||
|
env_file || "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
|
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
|
||||||
@@ -97,4 +107,12 @@ module Kamal::Utils
|
|||||||
def uncommitted_changes
|
def uncommitted_changes
|
||||||
`git status --porcelain`.strip
|
`git status --porcelain`.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def docker_env_file_line(key, value)
|
||||||
|
if key.include?("\n") || value.to_s.include?("\n")
|
||||||
|
raise ArgumentError, "docker env file format does not support newlines in keys or values, key: #{key}"
|
||||||
|
end
|
||||||
|
|
||||||
|
"#{key.to_s}=#{value.to_s}\n"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class CliAccessoryTest < CliTestCase
|
|||||||
|
|
||||||
run_command("boot", "mysql").tap do |output|
|
run_command("boot", "mysql").tap do |output|
|
||||||
assert_match /docker login.*on 1.1.1.3/, output
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e [REDACTED] -e 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-file .kamal/env/accessories/app-mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ class CliAccessoryTest < CliTestCase
|
|||||||
assert_match /docker login.*on 1.1.1.3/, output
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match /docker login.*on 1.1.1.1/, output
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
assert_match /docker login.*on 1.1.1.2/, output
|
assert_match /docker login.*on 1.1.1.2/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e [REDACTED] -e 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-file .kamal/env/accessories/app-mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
assert_match "docker run --name app-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 --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 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
38
test/cli/env_test.rb
Normal file
38
test/cli/env_test.rb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
@@ -10,7 +10,7 @@ class CliHealthcheckTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :stop, raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :stop, raise_on_non_zero_exit: false)
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :run, "--detach", "--name", "healthcheck-app-999", "--publish", "3999:3000", "--label", "service=healthcheck-app", "-e", "KAMAL_CONTAINER_NAME=\"healthcheck-app\"", "--health-cmd", "\"curl -f http://localhost:3000/up || exit 1\"", "--health-interval", "\"1s\"", "dhh/app:999")
|
.with(:docker, :run, "--detach", "--name", "healthcheck-app-999", "--publish", "3999:3000", "--label", "service=healthcheck-app", "-e", "KAMAL_CONTAINER_NAME=\"healthcheck-app\"", "--env-file", ".kamal/env/roles/app-web.env", "--health-cmd", "\"curl -f http://localhost:3000/up || exit 1\"", "--health-interval", "\"1s\"", "dhh/app:999")
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :container, :rm, raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :container, :rm, raise_on_non_zero_exit: false)
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class CliHealthcheckTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :stop, raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :stop, raise_on_non_zero_exit: false)
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :run, "--detach", "--name", "healthcheck-app-999", "--publish", "3999:3000", "--label", "service=healthcheck-app", "-e", "KAMAL_CONTAINER_NAME=\"healthcheck-app\"", "--health-cmd", "\"curl -f http://localhost:3000/up || exit 1\"", "--health-interval", "\"1s\"", "dhh/app:999")
|
.with(:docker, :run, "--detach", "--name", "healthcheck-app-999", "--publish", "3999:3000", "--label", "service=healthcheck-app", "-e", "KAMAL_CONTAINER_NAME=\"healthcheck-app\"", "--env-file", ".kamal/env/roles/app-web.env", "--health-cmd", "\"curl -f http://localhost:3000/up || exit 1\"", "--health-interval", "\"1s\"", "dhh/app:999")
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :container, :rm, raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^healthcheck-app-999$", "--quiet", "|", :xargs, :docker, :container, :rm, raise_on_non_zero_exit: false)
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ require_relative "cli_test_case"
|
|||||||
|
|
||||||
class CliLockTest < CliTestCase
|
class CliLockTest < CliTestCase
|
||||||
test "status" do
|
test "status" do
|
||||||
run_command("status") do |output|
|
run_command("status").tap do |output|
|
||||||
assert_match "stat lock", output
|
assert_match "Running /usr/bin/env stat .kamal/lock-app > /dev/null && cat .kamal/lock-app/details | base64 -d on 1.1.1.1", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "release" do
|
test "release" do
|
||||||
run_command("release") do |output|
|
run_command("release").tap do |output|
|
||||||
assert_match "rm -rf lock", output
|
assert_match "Released the deploy lock", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Lock.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
stdouted { Kamal::Cli::Lock.start([*command, "-v", "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -339,10 +339,10 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "envify with destination" do
|
test "envify with destination" do
|
||||||
File.expects(:read).with(".env.staging.erb").returns("HELLO=<%= 'world' %>")
|
File.expects(:read).with(".env.world.erb").returns("HELLO=<%= 'world' %>")
|
||||||
File.expects(:write).with(".env.staging", "HELLO=world", perm: 0600)
|
File.expects(:write).with(".env.world", "HELLO=world", perm: 0600)
|
||||||
|
|
||||||
run_command("envify", "-d", "staging")
|
run_command("envify", "-d", "world", config_file: "deploy_for_dest")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove with confirmation" do
|
test "remove with confirmation" do
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class CliTraefikTest < CliTestCase
|
|||||||
test "boot" do
|
test "boot" do
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match "docker login", 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 --log-opt max-size=\"10m\" #{Kamal::Commands::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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{Kamal::Commands::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class CliTraefikTest < CliTestCase
|
|||||||
run_command("reboot").tap do |output|
|
run_command("reboot").tap do |output|
|
||||||
assert_match "docker container stop traefik", 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 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 --log-opt max-size=\"10m\" #{Kamal::Commands::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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{Kamal::Commands::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -49,15 +49,15 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e 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-file .kamal/env/accessories/app-mysql.env --label service=\"app-mysql\" private.registry/mysql:8.0",
|
||||||
new_command(:mysql).run.join(" ")
|
new_command(:mysql).run.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 -e 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-file .kamal/env/accessories/app-redis.env --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
||||||
new_command(:redis).run.join(" ")
|
new_command(:redis).run.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --label service=\"app-busybox\" busybox:latest",
|
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --env-file .kamal/env/accessories/app-busybox.env --label service=\"app-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app-busybox\" busybox:latest",
|
"docker run --name app-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/env/accessories/app-busybox.env --label service=\"app-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root",
|
"docker run --rm --env-file .kamal/env/accessories/app-mysql.env private.registry/mysql:8.0 mysql -u root",
|
||||||
new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ")
|
new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "execute in new container over ssh" do
|
test "execute in new container over ssh" do
|
||||||
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
||||||
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root|,
|
assert_match %r|docker run -it --rm --env-file .kamal/env/accessories/app-mysql.env private.registry/mysql:8.0 mysql -u root|,
|
||||||
new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root")
|
new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -144,6 +144,14 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
new_command(:mysql).remove_image.join(" ")
|
new_command(:mysql).remove_image.join(" ")
|
||||||
end
|
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
|
private
|
||||||
def new_command(accessory)
|
def new_command(accessory)
|
||||||
Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory)
|
Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory)
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with hostname" do
|
test "run with hostname" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 --hostname myhost -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run(hostname: "myhost").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:volumes] = ["/local/path:/container/path" ]
|
@config[:volumes] = ["/local/path:/container/path" ]
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "path" => "/healthz" }
|
@config[:healthcheck] = { "path" => "/healthz" }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/healthz || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/healthz || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "cmd" => "/bin/up" }
|
@config[:healthcheck] = { "cmd" => "/bin/up" }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"/bin/up\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"/bin/up\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -51,14 +51,14 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "healthcheck" => { "cmd" => "/bin/healthy" } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "healthcheck" => { "cmd" => "/bin/healthy" } } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"/bin/healthy\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"/bin/healthy\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with custom options" do
|
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 } } }
|
@config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-jobs-999 -e KAMAL_CONTAINER_NAME=\"app-jobs-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --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\" --env-file .kamal/env/roles/app-jobs.env --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs",
|
||||||
new_command(role: "jobs").run.join(" ")
|
new_command(role: "jobs").run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -85,13 +85,13 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "start_or_run" do
|
test "start_or_run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker start app-web-999 || docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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 start app-web-999 || docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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.start_or_run.join(" ")
|
new_command.start_or_run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "start_or_run with hostname" do
|
test "start_or_run with hostname" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker start app-web-999 || docker run --detach --restart unless-stopped --name app-web-999 --hostname myhost -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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 start app-web-999 || docker run --detach --restart unless-stopped --name app-web-999 --hostname myhost -e KAMAL_CONTAINER_NAME=\"app-web-999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --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.start_or_run(hostname: "myhost").join(" ")
|
new_command.start_or_run(hostname: "myhost").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -167,14 +167,14 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup",
|
"docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container with custom options" do
|
test "execute in new container with custom options" do
|
||||||
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm -e RAILS_MASTER_KEY=\"456\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup",
|
"docker run --rm --env-file .kamal/env/roles/app-web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -185,13 +185,13 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container over ssh" do
|
test "execute in new container over ssh" do
|
||||||
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c|,
|
assert_match %r|docker run -it --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails c|,
|
||||||
new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container with custom options over ssh" do
|
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 } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=\"456\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c|,
|
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|,
|
||||||
new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -334,6 +334,14 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
new_command.tag_current_as_latest.join(" ")
|
new_command.tag_current_as_latest.join(" ")
|
||||||
end
|
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
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_command(role: "web")
|
def new_command(role: "web")
|
||||||
Kamal::Commands::App.new(Kamal::Configuration.new(@config, destination: @destination, version: "999"), role: role)
|
Kamal::Commands::App.new(Kamal::Configuration.new(@config, destination: @destination, version: "999"), role: role)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "port" => 3001 }
|
@config[:healthcheck] = { "port" => 3001 }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3001 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --health-cmd \"curl -f http://localhost:3001/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3001 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3001/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
@destination = "staging"
|
@destination = "staging"
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-staging-123 --publish 3999:3000 --label service=healthcheck-app-staging -e KAMAL_CONTAINER_NAME=\"healthcheck-app-staging\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
"docker run --detach --name healthcheck-app-staging-123 --publish 3999:3000 --label service=healthcheck-app-staging -e KAMAL_CONTAINER_NAME=\"healthcheck-app-staging\" --env-file .kamal/env/roles/app-web-staging.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -34,14 +34,14 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "cmd" => "/bin/up" }
|
@config[:healthcheck] = { "cmd" => "/bin/up" }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --health-cmd \"/bin/up\" --health-interval \"1s\" dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --env-file .kamal/env/roles/app-web.env --health-cmd \"/bin/up\" --health-interval \"1s\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with custom options" do
|
test "run with custom options" do
|
||||||
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere" } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere" } } }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --mount \"somewhere\" dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e KAMAL_CONTAINER_NAME=\"healthcheck-app\" --env-file .kamal/env/roles/app-web.env --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --mount \"somewhere\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -18,72 +18,72 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["host_port"] = "8080"
|
@config[:traefik]["host_port"] = "8080"
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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\" #{@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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["publish"] = false
|
@config[:traefik]["publish"] = false
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" #{@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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with ports configured" do
|
test "run with ports configured" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
|
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
|
||||||
assert_equal \
|
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\" --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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with volumes configured" do
|
test "run with volumes configured" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
|
||||||
assert_equal \
|
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\" --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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with several options configured" do
|
test "run with several options configured" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
|
||||||
assert_equal \
|
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\" --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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with labels configured" do
|
test "run with labels configured" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["labels"] = { "traefik.http.routers.dashboard.service" => "api@internal", "traefik.http.routers.dashboard.middlewares" => "auth" }
|
@config[:traefik]["labels"] = { "traefik.http.routers.dashboard.service" => "api@internal", "traefik.http.routers.dashboard.middlewares" => "auth" }
|
||||||
assert_equal \
|
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.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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with env configured" do
|
test "run with env configured" do
|
||||||
assert_equal \
|
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\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock -e EXAMPLE_API_KEY=\"456\" --log-opt max-size=\"10m\" #{@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-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@config.delete(:traefik)
|
@config.delete(:traefik)
|
||||||
|
|
||||||
assert_equal \
|
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\" #{Kamal::Commands::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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{Kamal::Commands::Traefik::DEFAULT_IMAGE} --providers.docker --log.level=\"DEBUG\"",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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\" #{@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-file .kamal/env/traefik/traefik.env --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@config[:traefik]["args"]["log.level"] = "ERROR"
|
@config[:traefik]["args"]["log.level"] = "ERROR"
|
||||||
|
|
||||||
assert_equal \
|
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\" #{@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 --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" #{@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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -177,6 +177,24 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
new_command.follow_logs(host: @config[:servers].first, grep: 'hello!')
|
new_command.follow_logs(host: @config[:servers].first, grep: 'hello!')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "env_file" do
|
||||||
|
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
||||||
|
|
||||||
|
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env_file
|
||||||
|
end
|
||||||
|
|
||||||
|
test "host_env_file_path" do
|
||||||
|
assert_equal ".kamal/env/traefik/traefik.env", new_command.host_env_file_path
|
||||||
|
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
|
private
|
||||||
def new_command
|
def new_command
|
||||||
Kamal::Commands::Traefik.new(Kamal::Configuration.new(@config, version: "123"))
|
Kamal::Commands::Traefik.new(Kamal::Configuration.new(@config, version: "123"))
|
||||||
|
|||||||
@@ -110,19 +110,30 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
|||||||
assert_equal ["--label", "service=\"app-redis\"", "--label", "cache=\"true\""], @config.accessory(:redis).label_args
|
assert_equal ["--label", "service=\"app-redis\"", "--label", "cache=\"true\""], @config.accessory(:redis).label_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env args with secret" do
|
test "env args" do
|
||||||
|
assert_equal ["--env-file", ".kamal/env/accessories/app-mysql.env"], @config.accessory(:mysql).env_args
|
||||||
|
assert_equal ["--env-file", ".kamal/env/accessories/app-redis.env"], @config.accessory(:redis).env_args
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file with secret" do
|
||||||
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
||||||
|
|
||||||
@config.accessory(:mysql).env_args.tap do |env_args|
|
expected = <<~ENV
|
||||||
assert_equal ["-e", "MYSQL_ROOT_PASSWORD=\"secret123\"", "-e", "MYSQL_ROOT_HOST=\"%\""], Kamal::Utils.unredacted(env_args)
|
MYSQL_ROOT_PASSWORD=secret123
|
||||||
assert_equal ["-e", "MYSQL_ROOT_PASSWORD=[REDACTED]", "-e", "MYSQL_ROOT_HOST=\"%\""], Kamal::Utils.redacted(env_args)
|
MYSQL_ROOT_HOST=%
|
||||||
end
|
ENV
|
||||||
|
|
||||||
|
assert_equal expected, @config.accessory(:mysql).env_file
|
||||||
ensure
|
ensure
|
||||||
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env args without secret" do
|
test "host_env_directory" do
|
||||||
assert_equal ["-e", "SOMETHING=\"else\""], @config.accessory(:redis).env_args
|
assert_equal ".kamal/env/accessories", @config.accessory(:mysql).host_env_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
test "host_env_file_path" do
|
||||||
|
assert_equal ".kamal/env/accessories/app-mysql.env", @config.accessory(:mysql).host_env_file_path
|
||||||
end
|
end
|
||||||
|
|
||||||
test "volume args" do
|
test "volume args" do
|
||||||
|
|||||||
@@ -71,7 +71,17 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "env overwritten by role" do
|
test "env overwritten by role" do
|
||||||
assert_equal "redis://a/b", @config_with_roles.role(:workers).env["REDIS_URL"]
|
assert_equal "redis://a/b", @config_with_roles.role(:workers).env["REDIS_URL"]
|
||||||
assert_equal ["-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], @config_with_roles.role(:workers).env_args
|
|
||||||
|
expected_env = <<~ENV
|
||||||
|
REDIS_URL=redis://a/b
|
||||||
|
WEB_CONCURRENCY=4
|
||||||
|
ENV
|
||||||
|
|
||||||
|
assert_equal expected_env, @config_with_roles.role(:workers).env_file
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env args" do
|
||||||
|
assert_equal ["--env-file", ".kamal/env/roles/app-workers.env"], @config_with_roles.role(:workers).env_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env secret overwritten by role" do
|
test "env secret overwritten by role" do
|
||||||
@@ -97,10 +107,14 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
ENV["REDIS_PASSWORD"] = "secret456"
|
ENV["REDIS_PASSWORD"] = "secret456"
|
||||||
ENV["DB_PASSWORD"] = "secret&\"123"
|
ENV["DB_PASSWORD"] = "secret&\"123"
|
||||||
|
|
||||||
@config_with_roles.role(:workers).env_args.tap do |env_args|
|
expected = <<~ENV
|
||||||
assert_equal ["-e", "REDIS_PASSWORD=\"secret456\"", "-e", "DB_PASSWORD=\"secret&\\\"123\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.unredacted(env_args)
|
REDIS_PASSWORD=secret456
|
||||||
assert_equal ["-e", "REDIS_PASSWORD=[REDACTED]", "-e", "DB_PASSWORD=[REDACTED]", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.redacted(env_args)
|
DB_PASSWORD=secret&\"123
|
||||||
end
|
REDIS_URL=redis://a/b
|
||||||
|
WEB_CONCURRENCY=4
|
||||||
|
ENV
|
||||||
|
|
||||||
|
assert_equal expected, @config_with_roles.role(:workers).env_file
|
||||||
ensure
|
ensure
|
||||||
ENV["REDIS_PASSWORD"] = nil
|
ENV["REDIS_PASSWORD"] = nil
|
||||||
ENV["DB_PASSWORD"] = nil
|
ENV["DB_PASSWORD"] = nil
|
||||||
@@ -119,10 +133,13 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
ENV["DB_PASSWORD"] = "secret123"
|
ENV["DB_PASSWORD"] = "secret123"
|
||||||
|
|
||||||
@config_with_roles.role(:workers).env_args.tap do |env_args|
|
expected = <<~ENV
|
||||||
assert_equal ["-e", "DB_PASSWORD=\"secret123\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.unredacted(env_args)
|
DB_PASSWORD=secret123
|
||||||
assert_equal ["-e", "DB_PASSWORD=[REDACTED]", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.redacted(env_args)
|
REDIS_URL=redis://a/b
|
||||||
end
|
WEB_CONCURRENCY=4
|
||||||
|
ENV
|
||||||
|
|
||||||
|
assert_equal expected, @config_with_roles.role(:workers).env_file
|
||||||
ensure
|
ensure
|
||||||
ENV["DB_PASSWORD"] = nil
|
ENV["DB_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
@@ -139,11 +156,23 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
ENV["REDIS_PASSWORD"] = "secret456"
|
ENV["REDIS_PASSWORD"] = "secret456"
|
||||||
|
|
||||||
@config_with_roles.role(:workers).env_args.tap do |env_args|
|
expected = <<~ENV
|
||||||
assert_equal ["-e", "REDIS_PASSWORD=\"secret456\"", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.unredacted(env_args)
|
REDIS_PASSWORD=secret456
|
||||||
assert_equal ["-e", "REDIS_PASSWORD=[REDACTED]", "-e", "REDIS_URL=\"redis://a/b\"", "-e", "WEB_CONCURRENCY=\"4\""], Kamal::Utils.redacted(env_args)
|
REDIS_URL=redis://a/b
|
||||||
end
|
WEB_CONCURRENCY=4
|
||||||
|
ENV
|
||||||
|
|
||||||
|
assert_equal expected, @config_with_roles.role(:workers).env_file
|
||||||
ensure
|
ensure
|
||||||
ENV["REDIS_PASSWORD"] = nil
|
ENV["REDIS_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "host_env_directory" do
|
||||||
|
assert_equal ".kamal/env/roles", @config_with_roles.role(:workers).host_env_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
test "host_env_file_path" do
|
||||||
|
assert_equal ".kamal/env/roles/app-workers.env", @config_with_roles.role(:workers).host_env_file_path
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -124,45 +124,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
assert_equal "app-missing", @config.service_with_version
|
assert_equal "app-missing", @config.service_with_version
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env args" do
|
test "env with missing secret" do
|
||||||
assert_equal [ "-e", "REDIS_URL=\"redis://x/y\"" ], @config.env_args
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env args with clear and secrets" do
|
|
||||||
ENV["PASSWORD"] = "secret123"
|
|
||||||
|
|
||||||
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!({
|
|
||||||
env: { "clear" => { "PORT" => "3000" }, "secret" => [ "PASSWORD" ] }
|
|
||||||
}) })
|
|
||||||
|
|
||||||
assert_equal [ "-e", "PASSWORD=\"secret123\"", "-e", "PORT=\"3000\"" ], Kamal::Utils.unredacted(config.env_args)
|
|
||||||
assert_equal [ "-e", "PASSWORD=[REDACTED]", "-e", "PORT=\"3000\"" ], Kamal::Utils.redacted(config.env_args)
|
|
||||||
ensure
|
|
||||||
ENV["PASSWORD"] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env args with only clear" do
|
|
||||||
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!({
|
|
||||||
env: { "clear" => { "PORT" => "3000" } }
|
|
||||||
}) })
|
|
||||||
|
|
||||||
assert_equal [ "-e", "PORT=\"3000\"" ], config.env_args
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env args with only secrets" do
|
|
||||||
ENV["PASSWORD"] = "secret123"
|
|
||||||
|
|
||||||
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!({
|
|
||||||
env: { "secret" => [ "PASSWORD" ] }
|
|
||||||
}) })
|
|
||||||
|
|
||||||
assert_equal [ "-e", "PASSWORD=\"secret123\"" ], Kamal::Utils.unredacted(config.env_args)
|
|
||||||
assert_equal [ "-e", "PASSWORD=[REDACTED]" ], Kamal::Utils.redacted(config.env_args)
|
|
||||||
ensure
|
|
||||||
ENV["PASSWORD"] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env args with missing secret" do
|
|
||||||
assert_raises(KeyError) do
|
assert_raises(KeyError) do
|
||||||
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!({
|
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!({
|
||||||
env: { "secret" => [ "PASSWORD" ] }
|
env: { "secret" => [ "PASSWORD" ] }
|
||||||
@@ -257,7 +219,6 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
:repository=>"dhh/app",
|
:repository=>"dhh/app",
|
||||||
:absolute_image=>"dhh/app:missing",
|
:absolute_image=>"dhh/app:missing",
|
||||||
:service_with_version=>"app-missing",
|
:service_with_version=>"app-missing",
|
||||||
:env_args=>["-e", "REDIS_URL=\"redis://x/y\""],
|
|
||||||
:ssh_options=>{ :user=>"root", :auth_methods=>["publickey"], log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
:ssh_options=>{ :user=>"root", :auth_methods=>["publickey"], log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
||||||
:sshkit=>{},
|
:sshkit=>{},
|
||||||
:volume_args=>["--volume", "/local/path:/container/path"],
|
:volume_args=>["--volume", "/local/path:/container/path"],
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ require_relative "integration_test"
|
|||||||
|
|
||||||
class AccessoryTest < IntegrationTest
|
class AccessoryTest < IntegrationTest
|
||||||
test "boot, stop, start, restart, logs, remove" do
|
test "boot, stop, start, restart, logs, remove" do
|
||||||
|
kamal :envify
|
||||||
|
|
||||||
kamal :accessory, :boot, :busybox
|
kamal :accessory, :boot, :busybox
|
||||||
assert_accessory_running :busybox
|
assert_accessory_running :busybox
|
||||||
|
|
||||||
@@ -19,6 +21,8 @@ class AccessoryTest < IntegrationTest
|
|||||||
|
|
||||||
kamal :accessory, :remove, :busybox, "-y"
|
kamal :accessory, :remove, :busybox, "-y"
|
||||||
assert_accessory_not_running :busybox
|
assert_accessory_not_running :busybox
|
||||||
|
|
||||||
|
kamal :env, :delete
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ require_relative "integration_test"
|
|||||||
|
|
||||||
class AppTest < IntegrationTest
|
class AppTest < IntegrationTest
|
||||||
test "stop, start, boot, logs, images, containers, exec, remove" do
|
test "stop, start, boot, logs, images, containers, exec, remove" do
|
||||||
|
kamal :envify
|
||||||
|
|
||||||
kamal :deploy
|
kamal :deploy
|
||||||
|
|
||||||
assert_app_is_up
|
assert_app_is_up
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ RUN mkdir -p /etc/docker/certs.d/registry:4443 && ln -s /shared/certs/domain.crt
|
|||||||
|
|
||||||
RUN git config --global user.email "deployer@example.com"
|
RUN git config --global user.email "deployer@example.com"
|
||||||
RUN git config --global user.name "Deployer"
|
RUN git config --global user.name "Deployer"
|
||||||
RUN git init && git add . && git commit -am "Initial version"
|
RUN git init && echo ".env" >> .gitignore && git add . && git commit -am "Initial version"
|
||||||
|
|
||||||
HEALTHCHECK --interval=1s CMD pgrep sleep
|
HEALTHCHECK --interval=1s CMD pgrep sleep
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ image: app
|
|||||||
servers:
|
servers:
|
||||||
- vm1
|
- vm1
|
||||||
- vm2
|
- vm2
|
||||||
|
env:
|
||||||
|
clear:
|
||||||
|
CLEAR_TOKEN: '4321'
|
||||||
|
secret:
|
||||||
|
- SECRET_TOKEN
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
server: registry:4443
|
server: registry:4443
|
||||||
username: root
|
username: root
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ require_relative "integration_test"
|
|||||||
|
|
||||||
class LockTest < IntegrationTest
|
class LockTest < IntegrationTest
|
||||||
test "acquire, release, status" do
|
test "acquire, release, status" do
|
||||||
|
kamal :envify
|
||||||
|
|
||||||
kamal :lock, :acquire, "-m 'Integration Tests'"
|
kamal :lock, :acquire, "-m 'Integration Tests'"
|
||||||
|
|
||||||
status = kamal :lock, :status, capture: true
|
status = kamal :lock, :status, capture: true
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
require_relative "integration_test"
|
require_relative "integration_test"
|
||||||
|
|
||||||
class MainTest < IntegrationTest
|
class MainTest < IntegrationTest
|
||||||
test "deploy, redeploy, rollback, details and audit" do
|
test "envify, deploy, redeploy, rollback, details and audit" do
|
||||||
|
kamal :envify
|
||||||
|
assert_local_env_file "SECRET_TOKEN=1234"
|
||||||
|
assert_remote_env_file "SECRET_TOKEN=1234\nCLEAR_TOKEN=4321"
|
||||||
|
|
||||||
first_version = latest_app_version
|
first_version = latest_app_version
|
||||||
|
|
||||||
assert_app_is_down
|
assert_app_is_down
|
||||||
@@ -30,12 +34,9 @@ class MainTest < IntegrationTest
|
|||||||
|
|
||||||
audit = kamal :audit, capture: true
|
audit = kamal :audit, capture: true
|
||||||
assert_match /Booted app version #{first_version}.*Booted app version #{second_version}.*Booted app version #{first_version}.*/m, audit
|
assert_match /Booted app version #{first_version}.*Booted app version #{second_version}.*Booted app version #{first_version}.*/m, audit
|
||||||
end
|
|
||||||
|
|
||||||
test "envify" do
|
kamal :env, :delete
|
||||||
kamal :envify
|
assert_no_remote_env_file
|
||||||
|
|
||||||
assert_equal "SECRET_TOKEN=1234", deployer_exec("cat .env", capture: true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "config" do
|
test "config" do
|
||||||
@@ -49,11 +50,23 @@ class MainTest < IntegrationTest
|
|||||||
assert_equal "registry:4443/app", config[:repository]
|
assert_equal "registry:4443/app", config[:repository]
|
||||||
assert_equal "registry:4443/app:#{version}", config[:absolute_image]
|
assert_equal "registry:4443/app:#{version}", config[:absolute_image]
|
||||||
assert_equal "app-#{version}", config[:service_with_version]
|
assert_equal "app-#{version}", config[:service_with_version]
|
||||||
assert_equal [], config[:env_args]
|
|
||||||
assert_equal [], config[:volume_args]
|
assert_equal [], config[:volume_args]
|
||||||
assert_equal({ user: "root", auth_methods: [ "publickey" ], keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
assert_equal({ user: "root", auth_methods: [ "publickey" ], keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
||||||
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
|
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
|
||||||
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
|
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
|
||||||
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "cmd" => "wget -qO- http://localhost > /dev/null" }, config[:healthcheck])
|
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "cmd" => "wget -qO- http://localhost > /dev/null" }, config[:healthcheck])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def assert_local_env_file(contents)
|
||||||
|
assert_equal contents, deployer_exec("cat .env", capture: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_remote_env_file(contents)
|
||||||
|
assert_equal contents, docker_compose("exec vm1 cat /root/.kamal/env/roles/app-web.env", capture: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_no_remote_env_file
|
||||||
|
assert_equal "nofile", docker_compose("exec vm1 stat /root/.kamal/env/roles/app-web.env 2> /dev/null || echo nofile", capture: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ require_relative "integration_test"
|
|||||||
|
|
||||||
class TraefikTest < IntegrationTest
|
class TraefikTest < IntegrationTest
|
||||||
test "boot, reboot, stop, start, restart, logs, remove" do
|
test "boot, reboot, stop, start, restart, logs, remove" do
|
||||||
|
kamal :envify
|
||||||
|
|
||||||
kamal :traefik, :boot
|
kamal :traefik, :boot
|
||||||
assert_traefik_running
|
assert_traefik_running
|
||||||
|
|
||||||
@@ -33,6 +35,8 @@ class TraefikTest < IntegrationTest
|
|||||||
|
|
||||||
kamal :traefik, :remove
|
kamal :traefik, :remove
|
||||||
assert_traefik_not_running
|
assert_traefik_not_running
|
||||||
|
|
||||||
|
kamal :env, :delete
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -11,13 +11,65 @@ class UtilsTest < ActiveSupport::TestCase
|
|||||||
Kamal::Utils.argumentize("--label", { foo: "bar" }, sensitive: true).last
|
Kamal::Utils.argumentize("--label", { foo: "bar" }, sensitive: true).last
|
||||||
end
|
end
|
||||||
|
|
||||||
test "argumentize_env_with_secrets" do
|
test "env file simple" do
|
||||||
ENV.expects(:fetch).with("FOO").returns("secret")
|
env = {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
|
||||||
args = Kamal::Utils.argumentize_env_with_secrets({ "secret" => [ "FOO" ], "clear" => { BAZ: "qux" } })
|
assert_equal "foo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::Utils.env_file_with_secrets(env)
|
||||||
|
end
|
||||||
|
|
||||||
assert_equal [ "-e", "FOO=[REDACTED]", "-e", "BAZ=\"qux\"" ], Kamal::Utils.redacted(args)
|
test "env file clear" do
|
||||||
assert_equal [ "-e", "FOO=\"secret\"", "-e", "BAZ=\"qux\"" ], Kamal::Utils.unredacted(args)
|
env = {
|
||||||
|
"clear" => {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "foo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::Utils.env_file_with_secrets(env)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret" do
|
||||||
|
ENV["PASSWORD"] = "hello"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\n", \
|
||||||
|
Kamal::Utils.env_file_with_secrets(env)
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file missing secret" do
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_raises(KeyError) { Kamal::Utils.env_file_with_secrets(env) }
|
||||||
|
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret and clear" do
|
||||||
|
ENV["PASSWORD"] = "hello"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ],
|
||||||
|
"clear" => {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\nfoo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::Utils.env_file_with_secrets(env)
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "optionize" do
|
test "optionize" do
|
||||||
|
|||||||
Reference in New Issue
Block a user