Check that we have valid contexts before building
Load the hosts from the contexts before trying to build. If there is no context, we'll create one. If there is one but the hosts don't match we'll re-create. Where we just have a local context, there won't be any hosts but we still inspect the builder to check that it exists.
This commit is contained in:
@@ -35,22 +35,25 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
|
|
||||||
run_locally do
|
run_locally do
|
||||||
begin
|
begin
|
||||||
KAMAL.with_verbosity(:debug) do
|
context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
|
||||||
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
|
||||||
|
if context_hosts != KAMAL.builder.config_context_hosts
|
||||||
|
warn "Context hosts have changed, so re-creating builder, was: #{context_hosts.join(", ")}], now: #{KAMAL.builder.config_context_hosts.join(", ")}"
|
||||||
|
cli.remove
|
||||||
|
cli.create
|
||||||
end
|
end
|
||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
if e.message =~ /(no builder)|(no such file or directory)/
|
warn "Missing compatible builder, so creating a new one first"
|
||||||
warn "Missing compatible builder, so creating a new one first"
|
if e.message =~ /(context not found|no builder)/
|
||||||
|
cli.create
|
||||||
if cli.create
|
|
||||||
KAMAL.with_verbosity(:debug) do
|
|
||||||
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
KAMAL.with_verbosity(:debug) do
|
||||||
|
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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, :validate_image, to: :target
|
delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
|
||||||
|
to: :target
|
||||||
|
|
||||||
include Clone
|
include Clone
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||||
class BuilderError < StandardError; end
|
class BuilderError < StandardError; end
|
||||||
|
|
||||||
|
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
||||||
|
|
||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
||||||
|
|
||||||
@@ -30,6 +32,13 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_hosts
|
||||||
|
:true
|
||||||
|
end
|
||||||
|
|
||||||
|
def config_context_hosts
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def build_tags
|
def build_tags
|
||||||
@@ -74,4 +83,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
def builder_config
|
def builder_config
|
||||||
config.builder
|
config.builder
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_host(builder_name)
|
||||||
|
docker :context, :inspect, builder_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
|||||||
build_context
|
build_context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_hosts
|
||||||
|
docker :buildx, :inspect, builder_name, "> /dev/null"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def builder_name
|
def builder_name
|
||||||
"kamal-#{config.service}-multiarch"
|
"kamal-#{config.service}-multiarch"
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ class Kamal::Commands::Builder::Multiarch::Remote < Kamal::Commands::Builder::Mu
|
|||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_hosts
|
||||||
|
chain \
|
||||||
|
context_host(builder_name_with_arch(local_arch)),
|
||||||
|
context_host(builder_name_with_arch(remote_arch))
|
||||||
|
end
|
||||||
|
|
||||||
|
def config_context_hosts
|
||||||
|
[ local_host, remote_host ].compact
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def builder_name
|
def builder_name
|
||||||
super + "-remote"
|
super + "-remote"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
|
class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
|
||||||
def create
|
def create
|
||||||
docker :buildx, :create, "--use", "--driver=docker-container"
|
docker :buildx, :create, "--name", builder_name, "--use", "--driver=docker-container"
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove
|
def remove
|
||||||
@@ -13,4 +13,13 @@ class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Nativ
|
|||||||
*build_options,
|
*build_options,
|
||||||
build_context
|
build_context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_hosts
|
||||||
|
docker :buildx, :inspect, builder_name, "> /dev/null"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def builder_name
|
||||||
|
"kamal-#{config.service}-native-cached"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Nativ
|
|||||||
build_context
|
build_context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def context_hosts
|
||||||
|
context_host(builder_name_with_arch)
|
||||||
|
end
|
||||||
|
|
||||||
|
def config_context_hosts
|
||||||
|
[ remote_host ]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def builder_name
|
def builder_name
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||||
|
.returns("")
|
||||||
|
|
||||||
run_command("push", "--verbose").tap do |output|
|
run_command("push", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-build", output, **hook_variables
|
assert_hook_ran "pre-build", output, **hook_variables
|
||||||
assert_match /Cloning repo into build directory/, output
|
assert_match /Cloning repo into build directory/, output
|
||||||
@@ -121,11 +125,9 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
.with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with { |*args| args[0..1] == [ :docker, :buildx ] }
|
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||||
.raises(SSHKit::Command::Failed.new("no builder"))
|
.raises(SSHKit::Command::Failed.new("no builder"))
|
||||||
.then
|
|
||||||
.returns(true)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
|
||||||
|
|
||||||
@@ -137,6 +139,9 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
|
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-app-multiarch", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
||||||
|
|
||||||
run_command("push").tap do |output|
|
run_command("push").tap do |output|
|
||||||
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
||||||
|
.with { |*args| args[0..2] == [ :docker, :buildx, :inspect ] }
|
||||||
|
.returns("")
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)
|
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||||
|
.returns("")
|
||||||
|
|
||||||
assert_raises(Kamal::Cli::LockError) do
|
assert_raises(Kamal::Cli::LockError) do
|
||||||
run_command("deploy")
|
run_command("deploy")
|
||||||
end
|
end
|
||||||
@@ -145,6 +149,10 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
|
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||||
|
.returns("")
|
||||||
|
|
||||||
assert_raises(SSHKit::Runner::ExecuteError) do
|
assert_raises(SSHKit::Runner::ExecuteError) do
|
||||||
run_command("deploy")
|
run_command("deploy")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -158,6 +158,48 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "multiarch context hosts" do
|
||||||
|
command = new_builder_command
|
||||||
|
assert_equal "docker buildx inspect kamal-app-multiarch > /dev/null", command.context_hosts.join(" ")
|
||||||
|
assert_equal "", command.config_context_hosts.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "native context hosts" do
|
||||||
|
command = new_builder_command(builder: { "multiarch" => false })
|
||||||
|
assert_equal :true, command.context_hosts
|
||||||
|
assert_equal "", command.config_context_hosts.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "native cached context hosts" do
|
||||||
|
command = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "registry" } })
|
||||||
|
assert_equal "docker buildx inspect kamal-app-native-cached > /dev/null", command.context_hosts.join(" ")
|
||||||
|
assert_equal "", command.config_context_hosts.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "native remote context hosts" do
|
||||||
|
command = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://host" } })
|
||||||
|
assert_equal "docker context inspect kamal-app-native-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
||||||
|
assert_equal [ "ssh://host" ], command.config_context_hosts
|
||||||
|
end
|
||||||
|
|
||||||
|
test "multiarch remote context hosts" do
|
||||||
|
command = new_builder_command(builder: {
|
||||||
|
"remote" => { "arch" => "amd64", "host" => "ssh://host" },
|
||||||
|
"local" => { "arch" => "arm64" }
|
||||||
|
})
|
||||||
|
assert_equal "docker context inspect kamal-app-multiarch-remote-arm64 --format '{{.Endpoints.docker.Host}}' ; docker context inspect kamal-app-multiarch-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
||||||
|
assert_equal [ "ssh://host" ], command.config_context_hosts
|
||||||
|
end
|
||||||
|
|
||||||
|
test "multiarch remote context hosts with local host" do
|
||||||
|
command = new_builder_command(builder: {
|
||||||
|
"remote" => { "arch" => "amd64", "host" => "ssh://host" },
|
||||||
|
"local" => { "arch" => "arm64", "host" => "unix:///var/run/docker.sock" }
|
||||||
|
})
|
||||||
|
assert_equal "docker context inspect kamal-app-multiarch-remote-arm64 --format '{{.Endpoints.docker.Host}}' ; docker context inspect kamal-app-multiarch-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
||||||
|
assert_equal [ "unix:///var/run/docker.sock", "ssh://host" ], command.config_context_hosts
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_builder_command(additional_config = {})
|
def new_builder_command(additional_config = {})
|
||||||
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.merge(additional_config), version: "123"))
|
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.merge(additional_config), version: "123"))
|
||||||
|
|||||||
Reference in New Issue
Block a user