Skip to content

Commit

Permalink
Merge branch 'basecamp:main' into ssh-agent-support
Browse files Browse the repository at this point in the history
  • Loading branch information
rience authored Sep 6, 2023
2 parents 0551333 + adc7173 commit 05d94ef
Show file tree
Hide file tree
Showing 20 changed files with 112 additions and 30 deletions.
8 changes: 8 additions & 0 deletions lib/kamal/cli/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ def mutating

run_hook "pre-connect"

ensure_run_directory

acquire_lock

begin
Expand Down Expand Up @@ -167,5 +169,11 @@ def subcommand
def first_invocation
instance_variable_get("@_invocations").first
end

def ensure_run_directory
on(KAMAL.hosts) do
execute(*KAMAL.server.ensure_run_directory)
end
end
end
end
15 changes: 12 additions & 3 deletions lib/kamal/cli/lock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ class Kamal::Cli::Lock < Kamal::Cli::Base
desc "status", "Report lock status"
def status
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

Expand All @@ -11,15 +14,21 @@ def status
def acquire
message = options[:message]
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"
end
end

desc "release", "Release the deploy lock"
def release
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"
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kamal/cli/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ def bootstrap
end
end

on(KAMAL.hosts) do
execute(*KAMAL.server.ensure_run_directory)
end

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/"
end
Expand Down
4 changes: 4 additions & 0 deletions lib/kamal/commander.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ def registry
@registry ||= Kamal::Commands::Registry.new(config)
end

def server
@server ||= Kamal::Commands::Server.new(config)
end

def traefik
@traefik ||= Kamal::Commands::Traefik.new(config)
end
Expand Down
4 changes: 3 additions & 1 deletion lib/kamal/commands/auditor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def reveal

private
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

def audit_tags(**details)
Expand Down
9 changes: 6 additions & 3 deletions lib/kamal/commands/healthcheck.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
EXPOSED_PORT = 3999

def run
web = config.role(:web)

docker :run,
"--detach",
"--name", container_name_with_version,
"--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
"--publish", "#{exposed_port}:#{config.healthcheck["port"]}",
"--label", "service=#{container_name}",
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
*web.env_args,
Expand Down Expand Up @@ -52,6 +51,10 @@ def container_id
end

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
2 changes: 1 addition & 1 deletion lib/kamal/commands/lock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def stat_lock_dir
end

def lock_dir
"kamal_lock-#{config.service}"
"#{config.run_directory}/lock-#{config.service}"
end

def lock_details_file
Expand Down
5 changes: 5 additions & 0 deletions lib/kamal/commands/server.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Kamal::Commands::Server < Kamal::Commands::Base
def ensure_run_directory
[:mkdir, "-p", config.run_directory]
end
end
6 changes: 5 additions & 1 deletion lib/kamal/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ def abbreviated_version
Kamal::Utils.abbreviate_version(version)
end

def run_directory
raw_config.run_directory || ".kamal"
end


def roles
@roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
Expand Down Expand Up @@ -145,7 +149,7 @@ def sshkit


def healthcheck
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
{ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999 }.merge(raw_config.healthcheck || {})
end

def readiness_delay
Expand Down
6 changes: 3 additions & 3 deletions test/cli/build_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CliBuildTest < CliTestCase
end

test "push without builder" do
stub_locking
stub_setup
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with(:docker, "--version", "&&", :docker, :buildx, "version")

Expand All @@ -36,7 +36,7 @@ class CliBuildTest < CliTestCase
end

test "push with no buildx plugin" do
stub_locking
stub_setup
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with(:docker, "--version", "&&", :docker, :buildx, "version")
.raises(SSHKit::Command::Failed.new("no buildx"))
Expand Down Expand Up @@ -67,7 +67,7 @@ class CliBuildTest < CliTestCase
end

test "create with error" do
stub_locking
stub_setup
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with { |arg| arg == :docker }
.raises(SSHKit::Command::Failed.new("stderr=error"))
Expand Down
8 changes: 5 additions & 3 deletions test/cli/cli_test_case.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ def fail_hook(hook)
.raises(SSHKit::Command::Failed.new("failed"))
end

def stub_locking
def stub_setup
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)
.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

def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: nil)
Expand Down
14 changes: 10 additions & 4 deletions test/cli/main_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,14 @@ class CliMainTest < CliTestCase
Thread.report_on_exception = false

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")

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
run_command("deploy")
Expand All @@ -78,7 +81,10 @@ class CliMainTest < CliTestCase
Thread.report_on_exception = false

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")

assert_raises(SSHKit::Runner::ExecuteError) do
Expand Down Expand Up @@ -230,7 +236,7 @@ class CliMainTest < CliTestCase

test "audit" do
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
end
end
Expand Down
3 changes: 3 additions & 0 deletions test/cli/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
class CliServerTest < CliTestCase
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(:mkdir, "-p", ".kamal").returns("").at_least_once

assert_equal "", run_command("bootstrap")
end

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('[ "${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
run_command("bootstrap")
Expand All @@ -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('[ "${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(:mkdir, "-p", ".kamal").returns("").at_least_once

run_command("bootstrap").tap do |output|
("1.1.1.1".."1.1.1.4").map do |host|
Expand Down
2 changes: 1 addition & 1 deletion test/cli/traefik_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CliTraefikTest < CliTestCase

test "reboot --rolling" do
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

Expand Down
8 changes: 4 additions & 4 deletions test/commands/auditor_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
:echo,
"[#{@recorded_at}] [#{@performer}]",
"app removed container",
">>", "kamal-app-audit.log"
">>", ".kamal/app-audit.log"
], @auditor.record("app removed container")
end

Expand All @@ -31,7 +31,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
:echo,
"[#{@recorded_at}] [#{@performer}] [staging]",
"app removed container",
">>", "kamal-app-staging-audit.log"
">>", ".kamal/app-staging-audit.log"
], auditor.record("app removed container")
end
end
Expand All @@ -42,7 +42,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
:echo,
"[#{@recorded_at}] [#{@performer}] [web]",
"app removed container",
">>", "kamal-app-audit.log"
">>", ".kamal/app-audit.log"
], auditor.record("app removed container")
end
end
Expand All @@ -52,7 +52,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
:echo,
"[#{@recorded_at}] [#{@performer}] [value]",
"app removed container",
">>", "kamal-app-audit.log"
">>", ".kamal/app-audit.log"
], @auditor.record("app removed container", detail: "value")
end

Expand Down
3 changes: 2 additions & 1 deletion test/commands/healthcheck_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase

test "run with custom options" do
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere" } } }
@config[:healthcheck] = { "exposed_port" => 4999 }
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\" --health-cmd \"curl -f http://localhost:3000/up || exit 1\" --health-interval \"1s\" --mount \"somewhere\" dhh/app:123",
new_command.run.join(" ")
end

Expand Down
6 changes: 3 additions & 3 deletions test/commands/lock_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ class CommandsLockTest < ActiveSupport::TestCase

test "status" do
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(" ")
end

test "acquire" do
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(" ")
end

test "release" do
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(" ")
end

Expand Down
23 changes: 23 additions & 0 deletions test/commands/server_test.rb
Original file line number Diff line number Diff line change
@@ -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
10 changes: 9 additions & 1 deletion test/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class ConfigurationTest < ActiveSupport::TestCase
:volume_args=>["--volume", "/local/path:/container/path"],
:builder=>{},
: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 }}

assert_equal expected_config, @config.to_h
end
Expand All @@ -283,4 +283,12 @@ class ConfigurationTest < ActiveSupport::TestCase
Kamal::Configuration.new(@deploy.tap { |c| c.merge!(minimum_version: "10000.0.0") })
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
end
2 changes: 1 addition & 1 deletion test/integration/main_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ class MainTest < IntegrationTest
assert_equal({ user: "root", auth_methods: [ "publickey" ], keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
assert_equal [ "--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, "cmd" => "wget -qO- http://localhost > /dev/null" }, config[:healthcheck])
end
end

0 comments on commit 05d94ef

Please sign in to comment.