Compare commits

...

9 Commits

Author SHA1 Message Date
Farah Schüller
eb2a9b5f72 Add test for custom per-role healthchecks
This adds the test for checking whether custom healthcheck options
in the role context are actually accepted into configuration.
2023-11-20 16:15:44 +01:00
Donal McBreen
635876bdb9 Merge pull request #523 from rmacklin/fix-error-message-in-pre-build-sample-hook
Fix duplicate error message in pre-build.sample
2023-11-16 08:51:22 +00:00
Donal McBreen
11521517fa Merge pull request #550 from dmitrytrager/feature-name-all-for-accessory-reboot
feature: add NAME=all option for accessory reboot
2023-11-16 08:50:51 +00:00
Donal McBreen
610d9de3fd Merge pull request #580 from happyscribe/feat/no-web
Allow Kamal to run without traefik
2023-11-16 08:44:45 +00:00
Yoel Cabo
7472e5dfa6 Merge remote-tracking branch 'origin/main' into feat/no-web 2023-11-14 12:11:18 +01:00
Yoel Cabo
887b7dd46d Do not invoke healthcheck on deploy when no web role 2023-11-14 11:34:32 +01:00
Yoel Cabo
87cb8c1f71 fix: allow configurations without web roles 2023-11-12 09:39:07 +01:00
dmitrytrager
2d22143a24 feature: add NAME=all option for accessory reboot 2023-10-31 00:13:45 +01:00
Richard Macklin
f96d071222 Fix copy-pasted error message in pre-build.sample
The "No git remote set" error message was appropriate for the previous
block (where it was presumably copy-pasted from), but in this line we
have failed the check that determines if we have a git branch checked
out, so we should output a corresponding error.
2023-10-08 15:14:40 -07:00
16 changed files with 119 additions and 56 deletions

View File

@@ -49,17 +49,21 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
end
end
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
def reboot(name)
mutating do
with_accessory(name) do |accessory|
on(accessory.hosts) do
execute *KAMAL.registry.login
end
if name == "all"
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
else
with_accessory(name) do |accessory|
on(accessory.hosts) do
execute *KAMAL.registry.login
end
stop(name)
remove_container(name)
boot(name, login: false)
stop(name)
remove_container(name)
boot(name, login: false)
end
end
end
end

View File

@@ -3,6 +3,7 @@ class Kamal::Cli::Healthcheck < Kamal::Cli::Base
desc "perform", "Health check current app version"
def perform
raise "The primary host is not configured to run Traefik" unless KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
on(KAMAL.primary_host) do
begin
execute *KAMAL.healthcheck.run

View File

@@ -38,8 +38,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
say "Ensure Traefik is running...", :magenta
invoke "kamal:cli:traefik:boot", [], invoke_options
say "Ensure app can pass healthcheck...", :magenta
invoke "kamal:cli:healthcheck:perform", [], invoke_options
if KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
say "Ensure app can pass healthcheck...", :magenta
invoke "kamal:cli:healthcheck:perform", [], invoke_options
end
say "Detect stale containers...", :magenta
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)

View File

@@ -84,13 +84,12 @@ registry:
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
# wait: 2
# Configure the role used to determine the primary_web_host. This host takes
# Configure the role used to determine the primary_host. This host takes
# deploy locks, runs health checks during the deploy, and follow logs, etc.
# This role should have traefik enabled.
#
# Caution: there's no support for role renaming yet, so be careful to cleanup
# the previous role on the deployed hosts.
# primary_web_role: web
# primary_role: web
# Controls if we abort when see a role with no hosts. Disabling this may be
# useful for more complex deploy configurations.

View File

@@ -32,7 +32,7 @@ fi
current_branch=$(git branch --show-current)
if [ -z "$current_branch" ]; then
echo "No git remote set, aborting..." >&2
echo "Not on a git branch, aborting..." >&2
exit 1
fi

View File

@@ -24,7 +24,7 @@ class Kamal::Commander
attr_reader :specific_roles, :specific_hosts
def specific_primary!
self.specific_hosts = [ config.primary_web_host ]
self.specific_hosts = [ config.primary_host ]
end
def specific_roles=(role_names)
@@ -36,7 +36,7 @@ class Kamal::Commander
end
def primary_host
specific_hosts&.first || specific_roles&.first&.primary_host || config.primary_web_host
specific_hosts&.first || specific_roles&.first&.primary_host || config.primary_host
end
def primary_role

View File

@@ -1,7 +1,7 @@
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
def run
web = config.role(config.primary_web_role)
primary = config.role(config.primary_role)
docker :run,
"--detach",
@@ -9,12 +9,12 @@ class Kamal::Commands::Healthcheck < Kamal::Commands::Base
"--publish", "#{exposed_port}:#{config.healthcheck["port"]}",
"--label", "service=#{config.healthcheck_service}",
"-e", "KAMAL_CONTAINER_NAME=\"#{config.healthcheck_service}\"",
*web.env_args,
*web.health_check_args(cord: false),
*primary.env_args,
*primary.health_check_args(cord: false),
*config.volume_args,
*web.option_args,
*primary.option_args,
config.absolute_image,
web.cmd
primary.cmd
end
def status

View File

@@ -91,8 +91,8 @@ class Kamal::Configuration
roles.flat_map(&:hosts).uniq
end
def primary_web_host
role(primary_web_role)&.primary_host
def primary_host
role(primary_role)&.primary_host
end
def traefik_roles
@@ -208,8 +208,8 @@ class Kamal::Configuration
raw_config.asset_path
end
def primary_web_role
raw_config.primary_web_role || "web"
def primary_role
raw_config.primary_role || "web"
end
def allow_empty_roles?
@@ -225,7 +225,7 @@ class Kamal::Configuration
{
roles: role_names,
hosts: all_hosts,
primary_host: primary_web_host,
primary_host: primary_host,
version: version,
repository: repository,
absolute_image: absolute_image,
@@ -264,16 +264,12 @@ class Kamal::Configuration
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
end
unless role_names.include?(primary_web_role)
raise ArgumentError, "The primary_web_role #{primary_web_role} isn't defined"
unless role_names.include?(primary_role)
raise ArgumentError, "The primary_role #{primary_role} isn't defined"
end
unless traefik_role_names.include?(primary_web_role)
raise ArgumentError, "Role #{primary_web_role} needs to have traefik enabled"
end
if role(primary_web_role).hosts.empty?
raise ArgumentError, "No servers specified for the #{primary_web_role} primary_web_role"
if role(primary_role).hosts.empty?
raise ArgumentError, "No servers specified for the #{primary_role} primary_role"
end
unless allow_empty_roles?

View File

@@ -93,7 +93,15 @@ class Kamal::Configuration::Role
def running_traefik?
name.web? || specializations["traefik"]
if specializations["traefik"].nil?
primary?
else
specializations["traefik"]
end
end
def primary?
@config.primary_role == name
end

View File

@@ -48,6 +48,18 @@ class CliAccessoryTest < CliTestCase
run_command("reboot", "mysql")
end
test "reboot all" do
Kamal::Commands::Registry.any_instance.expects(:login).times(3)
Kamal::Cli::Accessory.any_instance.expects(:stop).with("mysql")
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("mysql")
Kamal::Cli::Accessory.any_instance.expects(:boot).with("mysql", login: false)
Kamal::Cli::Accessory.any_instance.expects(:stop).with("redis")
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("redis")
Kamal::Cli::Accessory.any_instance.expects(:boot).with("redis", login: false)
run_command("reboot", "all")
end
test "start" do
assert_match "docker container start app-mysql", run_command("start", "mysql")
end

View File

@@ -64,9 +64,19 @@ class CliHealthcheckTest < CliTestCase
end
assert_match "container not ready (unhealthy)", exception.message
end
test "raises an exception if primary does not have traefik" do
SSHKit::Backend::Abstract.any_instance.expects(:execute).never
exception = assert_raises do
run_command("perform", config_file: "test/fixtures/deploy_workers_only.yml")
end
assert_equal "The primary host is not configured to run Traefik", exception.message
end
private
def run_command(*command)
stdouted { Kamal::Cli::Healthcheck.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
def run_command(*command, config_file: "test/fixtures/deploy_with_accessories.yml")
stdouted { Kamal::Cli::Healthcheck.start([*command, "-c", config_file]) }
end
end

View File

@@ -122,6 +122,21 @@ class CliMainTest < CliTestCase
refute_match /Running the post-deploy hook.../, output
end
end
test "deploy without healthcheck if primary host doesn't have traefik" do
invoke_options = { "config_file" => "test/fixtures/deploy_workers_only.yml", "version" => "999", "skip_hooks" => false }
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:healthcheck:perform", [], invoke_options).never
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:boot", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
run_command("deploy", config_file: "deploy_workers_only")
end
test "deploy with missing secrets" do
invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false }

View File

@@ -46,6 +46,14 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
new_command.run.join(" ")
end
test "run with custom per-role healthcheck options" do
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ],
"healthcheck" => { "log_lines" => 150 } } }
assert_equal \
"docker container ls --all --filter name=^healthcheck-app-123$ --quiet | xargs docker logs --tail 150 2>&1",
new_command.logs.join(" ")
end
test "status" do
assert_equal \
"docker container ls --all --filter name=^healthcheck-app-123$ --quiet | xargs docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'",

View File

@@ -58,9 +58,9 @@ class ConfigurationTest < ActiveSupport::TestCase
assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3" ], @config_with_roles.all_hosts
end
test "primary web host" do
assert_equal "1.1.1.1", @config.primary_web_host
assert_equal "1.1.1.1", @config_with_roles.primary_web_host
test "primary host" do
assert_equal "1.1.1.1", @config.primary_host
assert_equal "1.1.1.1", @config_with_roles.primary_host
end
test "traefik hosts" do
@@ -289,27 +289,23 @@ class ConfigurationTest < ActiveSupport::TestCase
assert_equal "foo", Kamal::Configuration.new(@deploy.merge!(asset_path: "foo")).asset_path
end
test "primary web role" do
assert_equal "web", @config.primary_web_role
test "primary role" do
assert_equal "web", @config.primary_role
config = Kamal::Configuration.new(@deploy_with_roles.deep_merge({
servers: { "alternate_web" => { "hosts" => [ "1.1.1.4", "1.1.1.5" ] , "traefik" => true } },
primary_web_role: "alternate_web" } ))
servers: { "alternate_web" => { "hosts" => [ "1.1.1.4", "1.1.1.5" ] } },
primary_role: "alternate_web" } ))
assert_equal "alternate_web", config.primary_web_role
assert_equal "1.1.1.4", config.primary_web_host
assert_equal "alternate_web", config.primary_role
assert_equal "1.1.1.4", config.primary_host
assert config.role(:alternate_web).primary?
assert config.role(:alternate_web).running_traefik?
end
test "primary web role no traefik" do
test "primary role missing" do
error = assert_raises(ArgumentError) do
Kamal::Configuration.new(@deploy_with_roles.merge(primary_web_role: "workers"))
end
assert_match /workers needs to have traefik enabled/, error.message
end
test "primary web role missing" do
error = assert_raises(ArgumentError) do
Kamal::Configuration.new(@deploy.merge(primary_web_role: "bar"))
Kamal::Configuration.new(@deploy.merge(primary_role: "bar"))
end
assert_match /bar isn't defined/, error.message
end

View File

@@ -17,4 +17,4 @@ registry:
server: registry.digitalocean.com
username: user
password: pw
primary_web_role: web_tokyo
primary_role: web_tokyo

12
test/fixtures/deploy_workers_only.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
service: app
image: dhh/app
servers:
workers:
traefik: false
hosts:
- 1.1.1.1
- 1.1.1.2
primary_role: workers
registry:
username: user
password: pw