Merge branch 'main' into fix/ssh-auth-methods
This commit is contained in:
@@ -18,8 +18,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
app = KAMAL.app(role: role)
|
app = KAMAL.app(role: role)
|
||||||
auditor = KAMAL.auditor(role: role)
|
auditor = KAMAL.auditor(role: role)
|
||||||
|
role_config = KAMAL.config.role(role)
|
||||||
|
|
||||||
if capture_with_info(*app.container_id_for_version(version, only_running: true), raise_on_non_zero_exit: false).present?
|
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
||||||
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
||||||
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
||||||
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
||||||
@@ -29,11 +30,25 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||||
|
|
||||||
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
||||||
execute *app.start_or_run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
|
||||||
|
if role_config.uses_cord?
|
||||||
|
execute *app.tie_cord(role_config.cord_host_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
||||||
|
|
||||||
Kamal::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
Kamal::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
||||||
|
|
||||||
execute *app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
|
if old_version.present?
|
||||||
|
if role_config.uses_cord?
|
||||||
|
cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
|
||||||
|
if cord.present?
|
||||||
|
execute *app.cut_cord(cord)
|
||||||
|
Kamal::Utils::HealthcheckPoller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: old_version)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ module Kamal::Cli
|
|||||||
|
|
||||||
run_hook "pre-connect"
|
run_hook "pre-connect"
|
||||||
|
|
||||||
|
ensure_run_directory
|
||||||
|
|
||||||
acquire_lock
|
acquire_lock
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -167,5 +169,11 @@ module Kamal::Cli
|
|||||||
def first_invocation
|
def first_invocation
|
||||||
instance_variable_get("@_invocations").first
|
instance_variable_get("@_invocations").first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_run_directory
|
||||||
|
on(KAMAL.hosts) do
|
||||||
|
execute(*KAMAL.server.ensure_run_directory)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
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
|
||||||
@@ -2,7 +2,10 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|||||||
desc "status", "Report lock status"
|
desc "status", "Report lock status"
|
||||||
def status
|
def status
|
||||||
handle_missing_lock do
|
handle_missing_lock do
|
||||||
on(KAMAL.primary_host) { puts capture_with_debug(*KAMAL.lock.status) }
|
on(KAMAL.primary_host) do
|
||||||
|
execute *KAMAL.server.ensure_run_directory
|
||||||
|
puts capture_with_debug(*KAMAL.lock.status)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -11,7 +14,10 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|||||||
def acquire
|
def acquire
|
||||||
message = options[:message]
|
message = options[:message]
|
||||||
raise_if_locked do
|
raise_if_locked do
|
||||||
on(KAMAL.primary_host) { execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug }
|
on(KAMAL.primary_host) do
|
||||||
|
execute *KAMAL.server.ensure_run_directory
|
||||||
|
execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
|
||||||
|
end
|
||||||
say "Acquired the deploy lock"
|
say "Acquired the deploy lock"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -19,7 +25,10 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
|
|||||||
desc "release", "Release the deploy lock"
|
desc "release", "Release the deploy lock"
|
||||||
def release
|
def release
|
||||||
handle_missing_lock do
|
handle_missing_lock do
|
||||||
on(KAMAL.primary_host) { execute *KAMAL.lock.release, verbosity: :debug }
|
on(KAMAL.primary_host) do
|
||||||
|
execute *KAMAL.server.ensure_run_directory
|
||||||
|
execute *KAMAL.lock.release, verbosity: :debug
|
||||||
|
end
|
||||||
say "Released the deploy lock"
|
say "Released the deploy lock"
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
on(KAMAL.hosts) do
|
||||||
|
execute(*KAMAL.server.ensure_run_directory)
|
||||||
|
end
|
||||||
|
|
||||||
if missing.any?
|
if missing.any?
|
||||||
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/"
|
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -116,6 +120,10 @@ class Kamal::Commander
|
|||||||
@registry ||= Kamal::Commands::Registry.new(config)
|
@registry ||= Kamal::Commands::Registry.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def server
|
||||||
|
@server ||= Kamal::Commands::Server.new(config)
|
||||||
|
end
|
||||||
|
|
||||||
def traefik
|
def traefik
|
||||||
@traefik ||= Kamal::Commands::Traefik.new(config)
|
@traefik ||= Kamal::Commands::Traefik.new(config)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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}" ]
|
||||||
|
|||||||
@@ -1,34 +1,29 @@
|
|||||||
class Kamal::Commands::App < Kamal::Commands::Base
|
class Kamal::Commands::App < Kamal::Commands::Base
|
||||||
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
||||||
|
|
||||||
attr_reader :role
|
attr_reader :role, :role_config
|
||||||
|
|
||||||
def initialize(config, role: nil)
|
def initialize(config, role: nil)
|
||||||
super(config)
|
super(config)
|
||||||
@role = role
|
@role = role
|
||||||
end
|
@role_config = config.role(self.role)
|
||||||
|
|
||||||
def start_or_run(hostname: nil)
|
|
||||||
combine start, run(hostname: hostname), by: "||"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(hostname: nil)
|
def run(hostname: nil)
|
||||||
role = config.role(self.role)
|
|
||||||
|
|
||||||
docker :run,
|
docker :run,
|
||||||
"--detach",
|
"--detach",
|
||||||
"--restart unless-stopped",
|
"--restart unless-stopped",
|
||||||
"--name", container_name,
|
"--name", container_name,
|
||||||
*(["--hostname", hostname] if hostname),
|
*(["--hostname", hostname] if hostname),
|
||||||
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
||||||
*role.env_args,
|
*role_config.env_args,
|
||||||
*role.health_check_args,
|
*role_config.health_check_args,
|
||||||
*config.logging_args,
|
*config.logging_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role.label_args,
|
*role_config.label_args,
|
||||||
*role.option_args,
|
*role_config.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
role.cmd
|
role_config.cmd
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
@@ -76,14 +71,12 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def execute_in_new_container(*command, interactive: false)
|
def execute_in_new_container(*command, interactive: false)
|
||||||
role = config.role(self.role)
|
|
||||||
|
|
||||||
docker :run,
|
docker :run,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
"--rm",
|
"--rm",
|
||||||
*config.env_args,
|
*role_config&.env_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role&.option_args,
|
*role_config&.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
*command
|
*command
|
||||||
end
|
end
|
||||||
@@ -112,7 +105,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
def list_versions(*docker_args, statuses: nil)
|
def list_versions(*docker_args, statuses: nil)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||||
%(while read line; do echo ${line##{service_role_dest}-}; done) # Extract SHA from "service-role-dest-SHA"
|
%(while read line; do echo ${line##{role_config.full_name}-}; done) # Extract SHA from "service-role-dest-SHA"
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_containers
|
def list_containers
|
||||||
@@ -149,10 +142,31 @@ 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 role_config.host_env_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_env_file
|
||||||
|
[:rm, "-f", role_config.host_env_file_path]
|
||||||
|
end
|
||||||
|
|
||||||
|
def cord(version:)
|
||||||
|
pipe \
|
||||||
|
docker(:inspect, "-f '{{ range .Mounts }}{{ .Source }} {{ .Destination }} {{ end }}'", container_name(version)),
|
||||||
|
[:awk, "'$2 == \"#{role_config.cord_container_directory}\" {print $1}'"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def tie_cord(cord)
|
||||||
|
create_empty_file(cord)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cut_cord(cord)
|
||||||
|
remove_directory(cord)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def container_name(version = nil)
|
def container_name(version = nil)
|
||||||
[ config.service, role, config.destination, version || config.version ].compact.join("-")
|
[ role_config.full_name, version || config.version ].compact.join("-")
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_args(statuses: nil)
|
def filter_args(statuses: nil)
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
def audit_log_file
|
def audit_log_file
|
||||||
[ "kamal", config.service, config.destination, "audit.log" ].compact.join("-")
|
file = [ config.service, config.destination, "audit.log" ].compact.join("-")
|
||||||
|
|
||||||
|
"#{config.run_directory}/#{file}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def audit_tags(**details)
|
def audit_tags(**details)
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ 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
|
||||||
|
|
||||||
|
def remove_directory(path)
|
||||||
|
[ :rm, "-r", path ]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def combine(*commands, by: "&&")
|
def combine(*commands, by: "&&")
|
||||||
commands
|
commands
|
||||||
@@ -61,5 +73,11 @@ module Kamal::Commands
|
|||||||
def tags(**details)
|
def tags(**details)
|
||||||
Kamal::Tags.from_config(config, **details)
|
Kamal::Tags.from_config(config, **details)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_empty_file(file)
|
||||||
|
chain \
|
||||||
|
make_directory_for(file),
|
||||||
|
[:touch, file]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
||||||
EXPOSED_PORT = 3999
|
|
||||||
|
|
||||||
def run
|
def run
|
||||||
web = config.role(:web)
|
web = config.role(:web)
|
||||||
@@ -7,11 +6,11 @@ class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
|||||||
docker :run,
|
docker :run,
|
||||||
"--detach",
|
"--detach",
|
||||||
"--name", container_name_with_version,
|
"--name", container_name_with_version,
|
||||||
"--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
|
"--publish", "#{exposed_port}:#{config.healthcheck["port"]}",
|
||||||
"--label", "service=#{container_name}",
|
"--label", "service=#{container_name}",
|
||||||
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
||||||
*web.env_args,
|
*web.env_args,
|
||||||
*web.health_check_args,
|
*web.health_check_args(cord: false),
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*web.option_args,
|
*web.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
@@ -52,6 +51,10 @@ class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def health_url
|
def health_url
|
||||||
"http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
|
"http://localhost:#{exposed_port}#{config.healthcheck["path"]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def exposed_port
|
||||||
|
config.healthcheck["exposed_port"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Kamal::Commands::Lock < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def lock_dir
|
def lock_dir
|
||||||
"kamal_lock-#{config.service}"
|
"#{config.run_directory}/lock-#{config.service}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock_details_file
|
def lock_details_file
|
||||||
|
|||||||
5
lib/kamal/commands/server.rb
Normal file
5
lib/kamal/commands/server.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class Kamal::Commands::Server < Kamal::Commands::Base
|
||||||
|
def ensure_run_directory
|
||||||
|
[:mkdir, "-p", config.run_directory]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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
|
||||||
@@ -57,6 +57,18 @@ class Kamal::Configuration
|
|||||||
Kamal::Utils.abbreviate_version(version)
|
Kamal::Utils.abbreviate_version(version)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def run_directory
|
||||||
|
raw_config.run_directory || ".kamal"
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_directory_as_docker_volume
|
||||||
|
if Pathname.new(run_directory).absolute?
|
||||||
|
run_directory
|
||||||
|
else
|
||||||
|
File.join "$(pwd)", run_directory
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def roles
|
def roles
|
||||||
@roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
|
@roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
|
||||||
@@ -109,14 +121,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
|
||||||
@@ -145,7 +149,7 @@ class Kamal::Configuration
|
|||||||
|
|
||||||
|
|
||||||
def healthcheck
|
def healthcheck
|
||||||
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
|
{ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord" => "/tmp/kamal-cord" }.merge(raw_config.healthcheck || {})
|
||||||
end
|
end
|
||||||
|
|
||||||
def readiness_delay
|
def readiness_delay
|
||||||
@@ -170,7 +174,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,
|
||||||
@@ -195,12 +198,19 @@ 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
|
||||||
|
|
||||||
|
def run_id
|
||||||
|
@run_id ||= SecureRandom.hex(16)
|
||||||
|
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,6 @@
|
|||||||
class Kamal::Configuration::Role
|
class Kamal::Configuration::Role
|
||||||
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
|
CORD_FILE = "cord"
|
||||||
|
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name
|
attr_accessor :name
|
||||||
|
|
||||||
@@ -31,32 +32,68 @@ class Kamal::Configuration::Role
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def env_args
|
def env_file
|
||||||
argumentize_env_with_secrets env
|
env_file_with_secrets env
|
||||||
end
|
end
|
||||||
|
|
||||||
def health_check_args
|
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
|
||||||
|
argumentize "--env-file", host_env_file_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def health_check_args(cord: true)
|
||||||
if health_check_cmd.present?
|
if health_check_cmd.present?
|
||||||
optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
|
if cord && uses_cord?
|
||||||
|
optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" => health_check_interval })
|
||||||
|
.concat(["--volume", "#{cord_host_directory}:#{cord_container_directory}"])
|
||||||
|
else
|
||||||
|
optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
|
||||||
|
end
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def health_check_cmd
|
def health_check_cmd
|
||||||
options = specializations["healthcheck"] || {}
|
health_check_options["cmd"] || http_health_check(port: health_check_options["port"], path: health_check_options["path"])
|
||||||
options = config.healthcheck.merge(options) if running_traefik?
|
end
|
||||||
|
|
||||||
options["cmd"] || http_health_check(port: options["port"], path: options["path"])
|
def health_check_cmd_with_cord
|
||||||
|
"(#{health_check_cmd}) && (stat #{cord_container_file} > /dev/null || exit 1)"
|
||||||
end
|
end
|
||||||
|
|
||||||
def health_check_interval
|
def health_check_interval
|
||||||
options = specializations["healthcheck"] || {}
|
health_check_options["interval"] || "1s"
|
||||||
options = config.healthcheck.merge(options) if running_traefik?
|
|
||||||
|
|
||||||
options["interval"] || "1s"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def uses_cord?
|
||||||
|
running_traefik? && cord_container_directory.present? && health_check_cmd.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def cord_host_directory
|
||||||
|
File.join config.run_directory_as_docker_volume, "cords", [full_name, config.run_id].join("-")
|
||||||
|
end
|
||||||
|
|
||||||
|
def cord_host_file
|
||||||
|
File.join cord_host_directory, CORD_FILE
|
||||||
|
end
|
||||||
|
|
||||||
|
def cord_container_directory
|
||||||
|
health_check_options.fetch("cord", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cord_container_file
|
||||||
|
File.join cord_container_directory, CORD_FILE
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def cmd
|
def cmd
|
||||||
specializations["cmd"]
|
specializations["cmd"]
|
||||||
end
|
end
|
||||||
@@ -73,6 +110,10 @@ class Kamal::Configuration::Role
|
|||||||
name.web? || specializations["traefik"]
|
name.web? || specializations["traefik"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def full_name
|
||||||
|
[ config.service, name, config.destination ].compact.join("-")
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_accessor :config
|
attr_accessor :config
|
||||||
|
|
||||||
@@ -152,4 +193,12 @@ class Kamal::Configuration::Role
|
|||||||
def http_health_check(port:, path:)
|
def http_health_check(port:, path:)
|
||||||
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def health_check_options
|
||||||
|
@health_check_options ||= begin
|
||||||
|
options = specializations["healthcheck"] || {}
|
||||||
|
options = config.healthcheck.merge(options) if running_traefik?
|
||||||
|
options
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Utils::HealthcheckPoller
|
class Kamal::Utils::HealthcheckPoller
|
||||||
TRAEFIK_HEALTHY_DELAY = 2
|
TRAEFIK_UPDATE_DELAY = 2
|
||||||
|
|
||||||
class HealthcheckError < StandardError; end
|
class HealthcheckError < StandardError; end
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ class Kamal::Utils::HealthcheckPoller
|
|||||||
begin
|
begin
|
||||||
case status = block.call
|
case status = block.call
|
||||||
when "healthy"
|
when "healthy"
|
||||||
sleep TRAEFIK_HEALTHY_DELAY if pause_after_ready
|
sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
|
||||||
when "running" # No health check configured
|
when "running" # No health check configured
|
||||||
sleep KAMAL.config.readiness_delay if pause_after_ready
|
sleep KAMAL.config.readiness_delay if pause_after_ready
|
||||||
else
|
else
|
||||||
@@ -31,6 +31,31 @@ class Kamal::Utils::HealthcheckPoller
|
|||||||
info "Container is healthy!"
|
info "Container is healthy!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def wait_for_unhealthy(pause_after_ready: false, &block)
|
||||||
|
attempt = 1
|
||||||
|
max_attempts = KAMAL.config.healthcheck["max_attempts"]
|
||||||
|
|
||||||
|
begin
|
||||||
|
case status = block.call
|
||||||
|
when "unhealthy"
|
||||||
|
sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
|
||||||
|
else
|
||||||
|
raise HealthcheckError, "container not unhealthy (#{status})"
|
||||||
|
end
|
||||||
|
rescue HealthcheckError => e
|
||||||
|
if attempt <= max_attempts
|
||||||
|
info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
|
||||||
|
sleep attempt
|
||||||
|
attempt += 1
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
info "Container is unhealthy!"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def info(message)
|
def info(message)
|
||||||
SSHKit.config.output.info(message)
|
SSHKit.config.output.info(message)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "boot will rename if same version is already running" do
|
test "boot will rename if same version is already running" do
|
||||||
|
Object.any_instance.stubs(:sleep)
|
||||||
run_command("details") # Preheat Kamal const
|
run_command("details") # Preheat Kamal const
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--filter", "name=^app-web-latest$", "--quiet", raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", raise_on_non_zero_exit: false)
|
||||||
.returns("12345678") # running version
|
.returns("12345678") # running version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
@@ -25,6 +26,14 @@ class CliAppTest < CliTestCase
|
|||||||
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
||||||
.returns("123") # old version
|
.returns("123") # old version
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :inspect, "-f '{{ range .Mounts }}{{ .Source }} {{ .Destination }} {{ end }}'", "app-web-123", "|", :awk, "'$2 == \"/tmp/kamal-cord\" {print $1}'", :raise_on_non_zero_exit => false)
|
||||||
|
.returns("cordfile") # old version
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
|
.returns("unhealthy") # old version unhealthy
|
||||||
|
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match /Renaming container .* to .* as already deployed on 1.1.1.1/, output # Rename
|
assert_match /Renaming container .* to .* as already deployed on 1.1.1.1/, output # Rename
|
||||||
assert_match /docker rename app-web-latest app-web-latest_replaced_[0-9a-f]{16}/, output
|
assert_match /docker rename app-web-latest app-web-latest_replaced_[0-9a-f]{16}/, output
|
||||||
@@ -180,10 +189,16 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def stub_running
|
def stub_running
|
||||||
|
Object.any_instance.stubs(:sleep)
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("123") # old version
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("123") # old version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
.returns("running") # health check
|
.returns("running") # health check
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
|
.returns("unhealthy") # health check
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "push without builder" do
|
test "push without builder" do
|
||||||
stub_locking
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "push with no buildx plugin" do
|
test "push with no buildx plugin" do
|
||||||
stub_locking
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
.raises(SSHKit::Command::Failed.new("no buildx"))
|
.raises(SSHKit::Command::Failed.new("no buildx"))
|
||||||
@@ -67,7 +67,7 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "create with error" do
|
test "create with error" do
|
||||||
stub_locking
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg| arg == :docker }
|
.with { |arg| arg == :docker }
|
||||||
.raises(SSHKit::Command::Failed.new("stderr=error"))
|
.raises(SSHKit::Command::Failed.new("stderr=error"))
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
.raises(SSHKit::Command::Failed.new("failed"))
|
.raises(SSHKit::Command::Failed.new("failed"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_locking
|
def stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg1, arg2| arg1 == :mkdir && arg2 == "kamal_lock-app" }
|
.with { |*args| args == [ :mkdir, "-p", ".kamal" ] }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg1, arg2| arg1 == :rm && arg2 == "kamal_lock-app/details" }
|
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/lock-app" }
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
|
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/lock-app/details" }
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: nil)
|
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: nil)
|
||||||
|
|||||||
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
|
||||||
@@ -6,11 +6,12 @@ class CliHealthcheckTest < CliTestCase
|
|||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
Kamal::Utils::HealthcheckPoller.stubs(:sleep) # No sleeping when retrying
|
Kamal::Utils::HealthcheckPoller.stubs(:sleep) # No sleeping when retrying
|
||||||
|
Kamal::Configuration.any_instance.stubs(:run_id).returns("12345678901234567890123456789012")
|
||||||
|
|
||||||
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 +40,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
|
||||||
|
|||||||
@@ -63,11 +63,14 @@ class CliMainTest < CliTestCase
|
|||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |*arg| arg[0..1] == [:mkdir, 'kamal_lock-app'] }
|
.with { |*args| args == [ :mkdir, "-p", ".kamal" ] }
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
|
.with { |*arg| arg[0..1] == [:mkdir, ".kamal/lock-app"] }
|
||||||
.raises(RuntimeError, "mkdir: cannot create directory ‘kamal_lock-app’: File exists")
|
.raises(RuntimeError, "mkdir: cannot create directory ‘kamal_lock-app’: File exists")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_debug)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_debug)
|
||||||
.with(:stat, 'kamal_lock-app', ">", "/dev/null", "&&", :cat, "kamal_lock-app/details", "|", :base64, "-d")
|
.with(:stat, ".kamal/lock-app", ">", "/dev/null", "&&", :cat, ".kamal/lock-app/details", "|", :base64, "-d")
|
||||||
|
|
||||||
assert_raises(Kamal::Cli::LockError) do
|
assert_raises(Kamal::Cli::LockError) do
|
||||||
run_command("deploy")
|
run_command("deploy")
|
||||||
@@ -78,7 +81,10 @@ class CliMainTest < CliTestCase
|
|||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |*arg| arg[0..1] == [:mkdir, 'kamal_lock-app'] }
|
.with { |*args| args == [ :mkdir, "-p", ".kamal" ] }
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
|
.with { |*arg| arg[0..1] == [:mkdir, ".kamal/lock-app"] }
|
||||||
.raises(SocketError, "getaddrinfo: nodename nor servname provided, or not known")
|
.raises(SocketError, "getaddrinfo: nodename nor servname provided, or not known")
|
||||||
|
|
||||||
assert_raises(SSHKit::Runner::ExecuteError) do
|
assert_raises(SSHKit::Runner::ExecuteError) do
|
||||||
@@ -170,9 +176,10 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "rollback good version" do
|
test "rollback good version" do
|
||||||
|
Object.any_instance.stubs(:sleep)
|
||||||
[ "web", "workers" ].each do |role|
|
[ "web", "workers" ].each do |role|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--filter", "name=^app-#{role}-123$", "--quiet", raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet", raise_on_non_zero_exit: false)
|
||||||
.returns("").at_least_once
|
.returns("").at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet")
|
||||||
@@ -185,14 +192,21 @@ class CliMainTest < CliTestCase
|
|||||||
.returns("running").at_least_once # health check
|
.returns("running").at_least_once # health check
|
||||||
end
|
end
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :inspect, "-f '{{ range .Mounts }}{{ .Source }} {{ .Destination }} {{ end }}'", "app-web-version-to-rollback", "|", :awk, "'$2 == \"/tmp/kamal-cord\" {print $1}'", :raise_on_non_zero_exit => false)
|
||||||
|
.returns("corddirectory").at_least_once # health check
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-version-to-rollback$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
|
.returns("unhealthy").at_least_once # health check
|
||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
hook_variables = { version: 123, service_version: "app@123", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "rollback" }
|
hook_variables = { version: 123, service_version: "app@123", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "rollback" }
|
||||||
|
|
||||||
run_command("rollback", "123", config_file: "deploy_with_accessories").tap do |output|
|
run_command("rollback", "123", config_file: "deploy_with_accessories").tap do |output|
|
||||||
assert_match "Start container with version 123", output
|
|
||||||
assert_hook_ran "pre-deploy", output, **hook_variables
|
assert_hook_ran "pre-deploy", output, **hook_variables
|
||||||
assert_match "docker tag dhh/app:123 dhh/app:latest", output
|
assert_match "docker tag dhh/app:123 dhh/app:latest", output
|
||||||
assert_match "docker start app-web-123", output
|
assert_match "docker run --detach --restart unless-stopped --name app-web-123", output
|
||||||
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
||||||
assert_hook_ran "post-deploy", output, **hook_variables, runtime: "0"
|
assert_hook_ran "post-deploy", output, **hook_variables, runtime: "0"
|
||||||
end
|
end
|
||||||
@@ -204,7 +218,7 @@ class CliMainTest < CliTestCase
|
|||||||
Kamal::Utils::HealthcheckPoller.stubs(:sleep)
|
Kamal::Utils::HealthcheckPoller.stubs(:sleep)
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--filter", "name=^app-web-123$", "--quiet", raise_on_non_zero_exit: false)
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet", raise_on_non_zero_exit: false)
|
||||||
.returns("").at_least_once
|
.returns("").at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
||||||
@@ -214,8 +228,7 @@ class CliMainTest < CliTestCase
|
|||||||
.returns("running").at_least_once # health check
|
.returns("running").at_least_once # health check
|
||||||
|
|
||||||
run_command("rollback", "123").tap do |output|
|
run_command("rollback", "123").tap do |output|
|
||||||
assert_match "Start container with version 123", output
|
assert_match "docker run --detach --restart unless-stopped --name app-web-123", output
|
||||||
assert_match "docker start app-web-123 || docker run --detach --restart unless-stopped --name app-web-123", output
|
|
||||||
assert_no_match "docker stop", output
|
assert_no_match "docker stop", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -230,7 +243,7 @@ class CliMainTest < CliTestCase
|
|||||||
|
|
||||||
test "audit" do
|
test "audit" do
|
||||||
run_command("audit").tap do |output|
|
run_command("audit").tap do |output|
|
||||||
assert_match /tail -n 50 kamal-app-audit.log on 1.1.1.1/, output
|
assert_match %r{tail -n 50 \.kamal/app-audit.log on 1.1.1.1}, output
|
||||||
assert_match /App Host: 1.1.1.1/, output
|
assert_match /App Host: 1.1.1.1/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -333,10 +346,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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ require_relative "cli_test_case"
|
|||||||
class CliServerTest < CliTestCase
|
class CliServerTest < CliTestCase
|
||||||
test "bootstrap already installed" do
|
test "bootstrap already installed" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
|
||||||
|
|
||||||
assert_equal "", run_command("bootstrap")
|
assert_equal "", run_command("bootstrap")
|
||||||
end
|
end
|
||||||
@@ -10,6 +11,7 @@ class CliServerTest < CliTestCase
|
|||||||
test "bootstrap install as non-root user" do
|
test "bootstrap install as non-root user" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(false).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(false).at_least_once
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
|
||||||
|
|
||||||
assert_raise RuntimeError, "Docker is not installed on 1.1.1.1, 1.1.1.3, 1.1.1.4, 1.1.1.2 and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/" do
|
assert_raise RuntimeError, "Docker is not installed on 1.1.1.1, 1.1.1.3, 1.1.1.4, 1.1.1.2 and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/" do
|
||||||
run_command("bootstrap")
|
run_command("bootstrap")
|
||||||
@@ -20,6 +22,7 @@ class CliServerTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(true).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(true).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:curl, "-fsSL", "https://get.docker.com", "|", :sh).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:curl, "-fsSL", "https://get.docker.com", "|", :sh).at_least_once
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
|
||||||
|
|
||||||
run_command("bootstrap").tap do |output|
|
run_command("bootstrap").tap do |output|
|
||||||
("1.1.1.1".."1.1.1.4").map do |host|
|
("1.1.1.1".."1.1.1.4").map do |host|
|
||||||
|
|||||||
@@ -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,13 +14,15 @@ 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
|
||||||
|
|
||||||
test "reboot --rolling" do
|
test "reboot --rolling" do
|
||||||
|
Object.any_instance.stubs(:sleep)
|
||||||
|
|
||||||
run_command("reboot", "--rolling").tap do |output|
|
run_command("reboot", "--rolling").tap do |output|
|
||||||
assert_match "Running docker container prune --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.1", output.lines[3]
|
assert_match "Running docker container prune --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.1", 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)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ require "test_helper"
|
|||||||
class CommandsAppTest < ActiveSupport::TestCase
|
class CommandsAppTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
ENV["RAILS_MASTER_KEY"] = "456"
|
ENV["RAILS_MASTER_KEY"] = "456"
|
||||||
|
Kamal::Configuration.any_instance.stubs(:run_id).returns("12345678901234567890123456789012")
|
||||||
|
|
||||||
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } }
|
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } }
|
||||||
end
|
end
|
||||||
@@ -13,13 +14,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label 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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label 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 +28,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label 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 +36,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label 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 +44,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label 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 +52,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label 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 +67,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) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label 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
|
||||||
|
|
||||||
@@ -83,18 +84,6 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
new_command.start.join(" ")
|
new_command.start.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "start_or_run" do
|
|
||||||
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",
|
|
||||||
new_command.start_or_run.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "start_or_run with hostname" do
|
|
||||||
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",
|
|
||||||
new_command.start_or_run(hostname: "myhost").join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "stop" do
|
test "stop" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop",
|
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop",
|
||||||
@@ -167,14 +156,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 +174,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 +323,28 @@ 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
|
||||||
|
|
||||||
|
test "cord" do
|
||||||
|
assert_equal "docker inspect -f '{{ range .Mounts }}{{ .Source }} {{ .Destination }} {{ end }}' app-web-123 | awk '$2 == \"/tmp/kamal-cord\" {print $1}'", new_command.cord(version: 123).join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "tie cord" do
|
||||||
|
assert_equal "mkdir -p . ; touch cordfile", new_command.tie_cord("cordfile").join(" ")
|
||||||
|
assert_equal "mkdir -p corddir ; touch corddir/cordfile", new_command.tie_cord("corddir/cordfile").join(" ")
|
||||||
|
assert_equal "mkdir -p /corddir ; touch /corddir/cordfile", new_command.tie_cord("/corddir/cordfile").join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cut cord" do
|
||||||
|
assert_equal "rm -r corddir", new_command.cut_cord("corddir").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)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
:echo,
|
:echo,
|
||||||
"[#{@recorded_at}] [#{@performer}]",
|
"[#{@recorded_at}] [#{@performer}]",
|
||||||
"app removed container",
|
"app removed container",
|
||||||
">>", "kamal-app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], @auditor.record("app removed container")
|
], @auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
:echo,
|
:echo,
|
||||||
"[#{@recorded_at}] [#{@performer}] [staging]",
|
"[#{@recorded_at}] [#{@performer}] [staging]",
|
||||||
"app removed container",
|
"app removed container",
|
||||||
">>", "kamal-app-staging-audit.log"
|
">>", ".kamal/app-staging-audit.log"
|
||||||
], auditor.record("app removed container")
|
], auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -42,7 +42,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
:echo,
|
:echo,
|
||||||
"[#{@recorded_at}] [#{@performer}] [web]",
|
"[#{@recorded_at}] [#{@performer}] [web]",
|
||||||
"app removed container",
|
"app removed container",
|
||||||
">>", "kamal-app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], auditor.record("app removed container")
|
], auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -52,7 +52,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
:echo,
|
:echo,
|
||||||
"[#{@recorded_at}] [#{@performer}] [value]",
|
"[#{@recorded_at}] [#{@performer}] [value]",
|
||||||
"app removed container",
|
"app removed container",
|
||||||
">>", "kamal-app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], @auditor.record("app removed container", detail: "value")
|
], @auditor.record("app removed container", detail: "value")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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,15 @@ 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" } } }
|
||||||
|
@config[:healthcheck] = { "exposed_port" => 4999 }
|
||||||
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 4999: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
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ class CommandsLockTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "status" do
|
test "status" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"stat kamal_lock-app > /dev/null && cat kamal_lock-app/details | base64 -d",
|
"stat .kamal/lock-app > /dev/null && cat .kamal/lock-app/details | base64 -d",
|
||||||
new_command.status.join(" ")
|
new_command.status.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "acquire" do
|
test "acquire" do
|
||||||
assert_match \
|
assert_match \
|
||||||
/mkdir kamal_lock-app && echo ".*" > kamal_lock-app\/details/m,
|
%r{mkdir \.kamal/lock-app && echo ".*" > \.kamal/lock-app/details}m,
|
||||||
new_command.acquire("Hello", "123").join(" ")
|
new_command.acquire("Hello", "123").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "release" do
|
test "release" do
|
||||||
assert_match \
|
assert_match \
|
||||||
"rm kamal_lock-app/details && rm -r kamal_lock-app",
|
"rm .kamal/lock-app/details && rm -r .kamal/lock-app",
|
||||||
new_command.release.join(" ")
|
new_command.release.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
23
test/commands/server_test.rb
Normal file
23
test/commands/server_test.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class CommandsServerTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
@config = {
|
||||||
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
|
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ensure run directory" do
|
||||||
|
assert_equal "mkdir -p .kamal", new_command.ensure_run_directory.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ensure non default run directory" do
|
||||||
|
assert_equal "mkdir -p /var/run/kamal", new_command(run_directory: "/var/run/kamal").ensure_run_directory.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def new_command(extra_config = {})
|
||||||
|
Kamal::Commands::Server.new(Kamal::Configuration.new(@config.merge(extra_config)))
|
||||||
|
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,43 @@ 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
|
||||||
|
|
||||||
|
test "uses cord" do
|
||||||
|
assert @config_with_roles.role(:web).uses_cord?
|
||||||
|
assert !@config_with_roles.role(:workers).uses_cord?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cord host directory" do
|
||||||
|
assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}}, @config_with_roles.role(:web).cord_host_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cord host file" do
|
||||||
|
assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}/cord}, @config_with_roles.role(:web).cord_host_file
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cord container directory" do
|
||||||
|
assert_equal "/tmp/kamal-cord", @config_with_roles.role(:web).cord_container_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cord container file" do
|
||||||
|
assert_equal "/tmp/kamal-cord/cord", @config_with_roles.role(:web).cord_container_file
|
||||||
|
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,13 +219,12 @@ 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", log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
:ssh_options=>{ :user=>"root", log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
||||||
:sshkit=>{},
|
:sshkit=>{},
|
||||||
:volume_args=>["--volume", "/local/path:/container/path"],
|
:volume_args=>["--volume", "/local/path:/container/path"],
|
||||||
:builder=>{},
|
:builder=>{},
|
||||||
:logging=>["--log-opt", "max-size=\"10m\""],
|
:logging=>["--log-opt", "max-size=\"10m\""],
|
||||||
:healthcheck=>{ "path"=>"/up", "port"=>3000, "max_attempts" => 7 }}
|
:healthcheck=>{ "path"=>"/up", "port"=>3000, "max_attempts" => 7, "exposed_port" => 3999, "cord" => "/tmp/kamal-cord" }}
|
||||||
|
|
||||||
assert_equal expected_config, @config.to_h
|
assert_equal expected_config, @config.to_h
|
||||||
end
|
end
|
||||||
@@ -283,4 +244,25 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
Kamal::Configuration.new(@deploy.tap { |c| c.merge!(minimum_version: "10000.0.0") })
|
Kamal::Configuration.new(@deploy.tap { |c| c.merge!(minimum_version: "10000.0.0") })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "run directory" do
|
||||||
|
config = Kamal::Configuration.new(@deploy)
|
||||||
|
assert_equal ".kamal", config.run_directory
|
||||||
|
|
||||||
|
config = Kamal::Configuration.new(@deploy.merge!(run_directory: "/root/kamal"))
|
||||||
|
assert_equal "/root/kamal", config.run_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run directory as docker volume" do
|
||||||
|
config = Kamal::Configuration.new(@deploy)
|
||||||
|
assert_equal "$(pwd)/.kamal", config.run_directory_as_docker_volume
|
||||||
|
|
||||||
|
config = Kamal::Configuration.new(@deploy.merge!(run_directory: "/root/kamal"))
|
||||||
|
assert_equal "/root/kamal", config.run_directory_as_docker_volume
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run id" do
|
||||||
|
SecureRandom.expects(:hex).with(16).returns("09876543211234567890098765432112")
|
||||||
|
assert_equal "09876543211234567890098765432112", @config.run_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ FROM ruby:3.2
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV VERBOSE=true
|
||||||
|
|
||||||
RUN apt-get update --fix-missing && apt-get install -y ca-certificates openssh-client curl gnupg docker.io
|
RUN apt-get update --fix-missing && apt-get install -y ca-certificates openssh-client curl gnupg docker.io
|
||||||
|
|
||||||
RUN install -m 0755 -d /etc/apt/keyrings
|
RUN install -m 0755 -d /etc/apt/keyrings
|
||||||
@@ -23,7 +25,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
|
||||||
@@ -12,7 +18,7 @@ builder:
|
|||||||
args:
|
args:
|
||||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||||
healthcheck:
|
healthcheck:
|
||||||
cmd: wget -qO- http://localhost > /dev/null
|
cmd: wget -qO- http://localhost > /dev/null || exit 1
|
||||||
traefik:
|
traefik:
|
||||||
args:
|
args:
|
||||||
accesslog: true
|
accesslog: true
|
||||||
|
|||||||
@@ -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", keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
assert_equal({ user: "root", 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, "exposed_port" => 3999, "cord"=>"/tmp/kamal-cord", "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, 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