Build and clean remote builders correctly

Check that the builder and context match what we expect, and if not
remove and re-create them.
This commit is contained in:
Donal McBreen
2024-09-02 15:10:39 +01:00
parent d7e785cd36
commit e557eea79c
12 changed files with 101 additions and 23 deletions

View File

@@ -32,10 +32,15 @@ class Kamal::Cli::Build < Kamal::Cli::Base
run_locally do run_locally do
begin begin
execute *KAMAL.builder.buildx_inspect execute *KAMAL.builder.inspect_builder
rescue SSHKit::Command::Failed => e rescue SSHKit::Command::Failed => e
if e.message =~ /(context not found|no builder|does not exist)/ if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
warn "Missing compatible builder, so creating a new one first" warn "Missing compatible builder, so creating a new one first"
begin
cli.remove
rescue SSHKit::Command::Failed
raise unless e.message =~ /(context not found|no builder|does not exist)/
end
cli.create cli.create
else else
raise raise

View File

@@ -81,6 +81,10 @@ module Kamal::Commands
[ :git, *([ "-C", path ] if path), *args.compact ] [ :git, *([ "-C", path ] if path), *args.compact ]
end end
def grep(*args)
args.compact.unshift :grep
end
def tags(**details) def tags(**details)
Kamal::Tags.from_config(config, **details) Kamal::Tags.from_config(config, **details)
end end

View File

@@ -1,7 +1,7 @@
require "active_support/core_ext/string/filters" require "active_support/core_ext/string/filters"
class Kamal::Commands::Builder < Kamal::Commands::Base class Kamal::Commands::Builder < Kamal::Commands::Base
delegate :create, :remove, :push, :clean, :pull, :info, :buildx_inspect, :validate_image, :first_mirror, to: :target delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
delegate :local?, :remote?, to: "config.builder" delegate :local?, :remote?, to: "config.builder"
include Clone include Clone

View File

@@ -32,7 +32,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
docker(:buildx, :ls) docker(:buildx, :ls)
end end
def buildx_inspect def inspect_builder
docker :buildx, :inspect, builder_name unless docker_driver? docker :buildx, :inspect, builder_name unless docker_driver?
end end
@@ -101,10 +101,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
config.builder config.builder
end end
def context_host(builder_name)
docker :context, :inspect, builder_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT
end
def platform_options(arches) def platform_options(arches)
argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any? argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
end end

View File

@@ -16,6 +16,6 @@ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
end end
def append_remote_buildx def append_remote_buildx
docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, builder_name docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, remote_context_name
end end
end end

View File

@@ -17,21 +17,44 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
docker(:buildx, :ls) docker(:buildx, :ls)
end end
def inspect_builder
combine \
combine inspect_buildx, inspect_remote_context,
[ "(echo no compatible builder && exit 1)" ],
by: "||"
end
private private
def builder_name def builder_name
"kamal-remote-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}" "kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
end
def remote_context_name
"#{builder_name}-context"
end
def inspect_buildx
pipe \
docker(:buildx, :inspect, builder_name),
grep("-q", "Endpoint:.*#{remote_context_name}")
end
def inspect_remote_context
pipe \
docker(:context, :inspect, remote_context_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT),
grep("-xq", remote)
end end
def create_remote_context def create_remote_context
docker :context, :create, builder_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'" docker :context, :create, remote_context_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
end end
def remove_remote_context def remove_remote_context
docker :context, :rm, builder_name docker :context, :rm, remote_context_name
end end
def create_buildx def create_buildx
docker :buildx, :create, "--name", builder_name, builder_name docker :buildx, :create, "--name", builder_name, remote_context_name
end end
def remove_buildx def remove_buildx

View File

@@ -9,7 +9,7 @@ class Kamal::Commands::Prune < Kamal::Commands::Base
def tagged_images def tagged_images
pipe \ pipe \
docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"), docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
"grep -v -w \"#{active_image_list}\"", grep("-v -w \"#{active_image_list}\""),
"while read image tag; do docker rmi $tag; done" "while read image tag; do docker rmi $tag; done"
end end

View File

@@ -119,6 +119,9 @@ class CliBuildTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, "--version", "&&", :docker, :buildx, "version") .with(:docker, "--version", "&&", :docker, :buildx, "version")
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :rm, "kamal-local-docker-container")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :create, "--name", "kamal-local-docker-container", "--driver=docker-container") .with(:docker, :buildx, :create, "--name", "kamal-local-docker-container", "--driver=docker-container")
@@ -210,16 +213,25 @@ class CliBuildTest < CliTestCase
test "create remote" do test "create remote" do
run_command("create", fixture: :with_remote_builder).tap do |output| run_command("create", fixture: :with_remote_builder).tap do |output|
assert_match "Running /usr/bin/env true on 1.1.1.5", output assert_match "Running /usr/bin/env true on 1.1.1.5", output
assert_match "docker context create kamal-remote-docker-container-ssh---app-1-1-1-5 --description 'kamal-remote-docker-container-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output assert_match "docker context create kamal-remote-ssh---app-1-1-1-5-context --description 'kamal-remote-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output
assert_match "docker buildx create --name kamal-remote-docker-container-ssh---app-1-1-1-5 kamal-remote-docker-container-ssh---app-1-1-1-5", output assert_match "docker buildx create --name kamal-remote-ssh---app-1-1-1-5 kamal-remote-ssh---app-1-1-1-5-context", output
end end
end end
test "create remote with custom ports" do test "create remote with custom ports" do
run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output| run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output|
assert_match "Running /usr/bin/env true on 1.1.1.5", output assert_match "Running /usr/bin/env true on 1.1.1.5", output
assert_match "docker context create kamal-remote-docker-container-ssh---app-1-1-1-5-2122 --description 'kamal-remote-docker-container-ssh---app-1-1-1-5-2122 host' --docker 'host=ssh://app@1.1.1.5:2122'", output assert_match "docker context create kamal-remote-ssh---app-1-1-1-5-2122-context --description 'kamal-remote-ssh---app-1-1-1-5-2122 host' --docker 'host=ssh://app@1.1.1.5:2122'", output
assert_match "docker buildx create --name kamal-remote-docker-container-ssh---app-1-1-1-5-2122 kamal-remote-docker-container-ssh---app-1-1-1-5-2122", output assert_match "docker buildx create --name kamal-remote-ssh---app-1-1-1-5-2122 kamal-remote-ssh---app-1-1-1-5-2122-context", output
end
end
test "create hybrid" do
run_command("create", fixture: :with_hybrid_builder).tap do |output|
assert_match "Running /usr/bin/env true on 1.1.1.5", output
assert_match "docker buildx create --platform linux/#{Kamal::Utils.docker_arch} --name kamal-hybrid-docker-container-ssh---app-1-1-1-5 --driver=docker-container", output
assert_match "docker context create kamal-hybrid-docker-container-ssh---app-1-1-1-5-context --description 'kamal-hybrid-docker-container-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output
assert_match "docker buildx create --platform linux/#{Kamal::Utils.docker_arch == "amd64" ? "arm64" : "amd64"} --append --name kamal-hybrid-docker-container-ssh---app-1-1-1-5 kamal-hybrid-docker-container-ssh---app-1-1-1-5-context", output
end end
end end

View File

@@ -41,7 +41,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } }) builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
assert_equal "remote", builder.name assert_equal "remote", builder.name
assert_equal \ assert_equal \
"docker buildx build --push --platform linux/#{remote_arch} --builder kamal-remote-docker-container-ssh---app-host -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .", "docker buildx build --push --platform linux/#{remote_arch} --builder kamal-remote-ssh---app-host -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ") builder.push.join(" ")
end end

View File

@@ -0,0 +1,42 @@
service: app
image: dhh/app
servers:
web:
- "1.1.1.1"
- "1.1.1.2"
workers:
- "1.1.1.3"
- "1.1.1.4"
registry:
username: user
password: pw
accessories:
mysql:
image: mysql:5.7
host: 1.1.1.3
port: 3306
env:
clear:
MYSQL_ROOT_HOST: '%'
secret:
- MYSQL_ROOT_PASSWORD
files:
- test/fixtures/files/my.cnf:/etc/mysql/my.cnf
directories:
- data:/var/lib/mysql
redis:
image: redis:latest
roles:
- web
port: 6379
directories:
- data:/data
readiness_delay: 0
builder:
arch:
- arm64
- amd64
remote: ssh://app@1.1.1.5

View File

@@ -32,8 +32,6 @@ accessories:
port: 6379 port: 6379
directories: directories:
- data:/data - data:/data
builder:
arch: amd64
readiness_delay: 0 readiness_delay: 0

View File

@@ -32,8 +32,6 @@ accessories:
port: 6379 port: 6379
directories: directories:
- data:/data - data:/data
builder:
arch: amd64
readiness_delay: 0 readiness_delay: 0