Simplify the builders configuration

1. Add driver as an option, defaulting to `docker-container`. For a
   "native" build you can set it to `docker`
2. Set arch as a array of architectures to build for, defaulting to
   `[ "amd64", "arm64" ]` unless you are using the docker driver in
   which case we default to not setting a platform
3. Remote is now just a connection string for the remote builder
4. If remote is set, we only use it for non-local arches, if we are
   only building for the local arch, we'll ignore it.

Examples:

On arm64, build for arm64 locally, amd64 remotely or
On amd64, build for amd64 locally, arm64 remotely:

```yaml
builder:
  remote: ssh://docker@docker-builder
```

On arm64, build amd64 on remote,
On amd64 build locally:

```yaml
builder:
  arch:
    - amd64
  remote:
    host: ssh://docker@docker-builder
```

Build amd64 on local:

```yaml
builder:
  arch:
    - amd64
```

Use docker driver, building for local arch:

```yaml
builder:
  driver: docker
```
This commit is contained in:
Donal McBreen
2024-08-01 14:06:41 +01:00
committed by Donal McBreen
parent cffb6c3d7e
commit 56268d724d
19 changed files with 140 additions and 206 deletions

View File

@@ -66,7 +66,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
desc "create", "Create a build setup"
def create
if (remote_host = KAMAL.config.builder.remote_host)
if (remote_host = KAMAL.config.builder.remote)
connect_to_remote_host(remote_host)
end

View File

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

View File

@@ -5,8 +5,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
delegate :argumentize, to: Kamal::Utils
delegate \
:args, :secrets, :dockerfile, :target, :local_arch, :remote_arch, :remote_host,
:cache_from, :cache_to, :multiarch?, :ssh, :driver, :docker_driver?,
:args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
:cache_from, :cache_to, :ssh, :driver, :docker_driver?,
to: :builder_config
def clean
@@ -16,7 +16,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
def push
docker :build,
"--push",
*platform_options,
*platform_options(arches),
*([ "--builder", builder_name ] unless docker_driver?),
*build_options,
build_context
@@ -33,7 +33,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
end
def buildx_inspect
docker :buildx, :inspect, builder_name
docker :buildx, :inspect, builder_name unless docker_driver?
end
def build_options
@@ -104,4 +104,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
def context_host(builder_name)
docker :context, :inspect, builder_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT
end
def platform_options(arches)
argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
end
end

View File

@@ -8,18 +8,14 @@ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
private
def builder_name
"kamal-hybrid-#{driver}-#{local_arch}-#{remote_arch}-#{remote_host.gsub(/[^a-z0-9_-]/, "-")}"
"kamal-hybrid-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
end
def create_local_buildx
docker :buildx, :create, "--name", builder_name, "--platform", "linux/#{local_arch}", "--driver=#{driver}"
docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
end
def append_remote_buildx
docker :buildx, :create, "--append", "--name", builder_name, builder_name, "--platform", "linux/#{remote_arch}"
end
def platform
"linux/#{local_arch},linux/#{remote_arch}"
docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, builder_name
end
end

View File

@@ -11,14 +11,4 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
def builder_name
"kamal-local-#{driver}"
end
def platform_options
if multiarch?
if local_arch
[ "--platform", "linux/#{local_arch}" ]
else
[ "--platform", "linux/amd64,linux/arm64" ]
end
end
end
end

View File

@@ -17,22 +17,13 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
docker(:buildx, :ls)
end
def push
docker :build,
"--push",
*platform_options,
"--builder", builder_name,
*build_options,
build_context
end
private
def builder_name
"kamal-remote-#{driver}-#{remote_arch}-#{remote_host.gsub(/[^a-z0-9_-]/, "-")}"
"kamal-remote-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
end
def create_remote_context
docker :context, :create, builder_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote_host}'"
docker :context, :create, builder_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
end
def remove_remote_context
@@ -40,18 +31,10 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
end
def create_buildx
docker :buildx, :create, "--name", builder_name, builder_name, "--platform", platform
docker :buildx, :create, "--name", builder_name, builder_name
end
def remove_buildx
docker :buildx, :rm, builder_name
end
def platform_options
[ "--platform", platform ]
end
def platform
"linux/#{remote_arch}"
end
end

View File

@@ -19,16 +19,38 @@ class Kamal::Configuration::Builder
builder_config
end
def multiarch?
builder_config["multiarch"] != false
def remote
builder_config["remote"]
end
def local?
!!builder_config["local"]
def arches
Array(builder_config.fetch("arch", default_arch))
end
def local_arches
@local_arches ||= if remote
uname_m = `uname -m`.strip
local_arch = uname_m == "x86_64" ? "amd64" : uname_m
arches & [ local_arch ]
else
arches
end
end
def remote_arches
@remote_arches ||= if remote
arches - local_arches
else
[]
end
end
def remote?
!!builder_config["remote"]
remote_arches.any?
end
def local?
arches.empty? || local_arches.any?
end
def cached?
@@ -59,18 +81,6 @@ class Kamal::Configuration::Builder
builder_config.fetch("driver", "docker-container")
end
def local_arch
builder_config["local"]["arch"] if local?
end
def remote_arch
builder_config["remote"]["arch"] if remote?
end
def remote_host
builder_config["remote"]["host"] if remote?
end
def cache_from
if cached?
case builder_config["cache"]["type"]
@@ -120,27 +130,14 @@ class Kamal::Configuration::Builder
private
def valid?
if multiarch?
if local?
raise ArgumentError, "Invalid builder configuration: local configuration, arch required" unless local_arch
end
if remote?
raise ArgumentError, "Invalid builder configuration: remote configuration, arch required" unless remote_arch
raise ArgumentError, "Invalid builder configuration: remote configuration, arch required" unless remote_host
end
if docker_driver?
raise ArgumentError, "Invalid builder configuration: the docker driver does not support multiarch builds"
end
else
raise ArgumentError, "Invalid builder configuration: multiarch must be enabled for local configuration" if local?
raise ArgumentError, "Invalid builder configuration: multiarch must be enabled for remote configuration" if remote?
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support remote builders" if remote
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support caching" if cached?
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support multiple arches" if arches.many?
end
if @options["cache"] && @options["cache"]["type"]
raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
raise ArgumentError, "The docker driver does not support caching" if docker_driver?
end
end
@@ -179,4 +176,8 @@ class Kamal::Configuration::Builder
def pwd_sha
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
end
def default_arch
docker_driver? ? [] : [ "amd64", "arm64" ]
end
end

View File

@@ -3,7 +3,7 @@
# The builder configuration controls how the application is built with `docker build`
#
# If no configuration is specified, Kamal will:
# 1. Create a buildx context called `kamal-<service>-multiarch`
# 1. Create a buildx context called `kamal-local-docker-container`, using the docker-container driver
# 2. Use `docker build` to build a multiarch image for linux/amd64,linux/arm64 with that context
#
# See https://kamal-deploy.org/docs/configuration/builder-examples/ for more information
@@ -12,41 +12,29 @@
#
# Options go under the builder key in the root configuration.
builder:
# Multiarch
#
# Enables multiarch builds, defaults to `true`
multiarch: false
# Driver
#
# The build driver to use, defaults to `docker-container`
driver: docker
# Local configuration
# Arch
#
# The build configuration for local builds, only used if multiarch is enabled (the default)
#
# If there is no remote configuration, by default we build for amd64 and arm64.
# If you only want to build for one architecture, you can specify it here.
# The docker socket is optional and uses the default docker host socket when not specified
local:
arch: amd64
host: /var/run/docker.sock
# The architectures to build for, defaults to `[ amd64, arm64 ]`
# Unless you are using the docker driver, when it defaults to the local architecture
# You can set an array or just a single value
arch:
- amd64
# Remote configuration
#
# The build configuration for remote builds, also only used if multiarch is enabled.
# The arch is required and can be either amd64 or arm64.
remote:
arch: arm64
host: ssh://docker@docker-builder
# If you have a remote builder, you can configure it here
remote: ssh://docker@docker-builder
# Builder cache
#
# The type must be either 'gha' or 'registry'
#
# The image is only used for registry cache
# The image is only used for registry cache. Not compatible with the docker driver
cache:
type: registry
options: mode=max

View File

@@ -28,7 +28,11 @@ class Kamal::Configuration::Validator
elsif key == "hosts"
validate_servers! value
elsif example_value.is_a?(Array)
if key == "arch"
validate_array_of_or_type! value, example_value.first.class
else
validate_array_of! value, example_value.first.class
end
elsif example_value.is_a?(Hash)
case key.to_s
when "options", "args"
@@ -71,6 +75,16 @@ class Kamal::Configuration::Validator
value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
end
def validate_array_of_or_type!(value, type)
if value.is_a?(Array)
validate_array_of! value, type
else
validate_type! value, type
end
rescue Kamal::ConfigurationError
type_error(Array, type)
end
def validate_array_of!(array, type)
validate_type! array, Array

View File

@@ -26,7 +26,7 @@ class CliBuildTest < CliTestCase
assert_match /Cloning repo into build directory/, output
assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output
assert_match /docker --version && docker buildx version/, output
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
end
end
end
@@ -49,7 +49,7 @@ class CliBuildTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init")
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:git, "-C", anything, :"rev-parse", :HEAD)
@@ -74,7 +74,7 @@ class CliBuildTest < CliTestCase
assert_no_match /Cloning repo into build directory/, output
assert_hook_ran "pre-build", output, **hook_variables
assert_match /docker --version && docker buildx version/, output
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
end
end
@@ -120,10 +120,10 @@ class CliBuildTest < CliTestCase
.with(:docker, "--version", "&&", :docker, :buildx, "version")
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :create, "--name", "kamal-local", "--driver=docker-container")
.with(:docker, :buildx, :create, "--name", "kamal-local-docker-container", "--driver=docker-container")
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :inspect, "kamal-local")
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
.raises(SSHKit::Command::Failed.new("no builder"))
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
@@ -137,7 +137,7 @@ class CliBuildTest < CliTestCase
.returns("")
SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
run_command("push").tap do |output|
assert_match /WARN Missing compatible builder, so creating a new one first/, output
@@ -203,23 +203,23 @@ class CliBuildTest < CliTestCase
test "create" do
run_command("create").tap do |output|
assert_match /docker buildx create --name kamal-local --driver=docker-container/, output
assert_match /docker buildx create --name kamal-local-docker-container --driver=docker-container/, output
end
end
test "create remote" do
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 "docker context create kamal-remote-amd64-ssh---app-1-1-1-5 --description 'kamal-remote-amd64-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output
assert_match "docker buildx create --name kamal-remote-amd64-ssh---app-1-1-1-5 kamal-remote-amd64-ssh---app-1-1-1-5 --platform linux/amd64", 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 buildx create --name kamal-remote-docker-container-ssh---app-1-1-1-5 kamal-remote-docker-container-ssh---app-1-1-1-5", output
end
end
test "create remote with custom ports" do
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 "docker context create kamal-remote-amd64-ssh---app-1-1-1-5-2122 --description 'kamal-remote-amd64-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-amd64-ssh---app-1-1-1-5-2122 kamal-remote-amd64-ssh---app-1-1-1-5-2122 --platform linux/amd64", 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 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
end
end

View File

@@ -37,7 +37,7 @@ class CliTestCase < ActiveSupport::TestCase
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with(:docker, :buildx, :inspect, "kamal-local")
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
end
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)

View File

@@ -5,51 +5,51 @@ class CommandsBuilderTest < ActiveSupport::TestCase
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ] }
end
test "target multiarch by default" do
test "target linux/amd64,linux/arm64 locally by default" do
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
assert_equal "local", builder.name
assert_equal \
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
test "target native when multiarch is off" do
builder = new_builder_command(builder: { "multiarch" => false })
test "target specified arch locally by default" do
builder = new_builder_command(builder: { "arch" => [ "amd64" ] })
assert_equal "local", builder.name
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
"docker build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
test "target native cached when multiarch is off and cache is set" do
builder = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "gha" } })
test "build with caching" do
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
assert_equal "local", builder.name
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
test "target multiarch remote when local and remote is set" do
builder = new_builder_command(builder: { "local" => { "arch" => "arm64" }, "remote" => { "arch" => "amd64", "host" => "ssh://app@127.0.0.1" }, "cache" => { "type" => "gha" } })
test "hybrid build if remote is set" do
builder = new_builder_command(builder: { "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" } })
assert_equal "hybrid", builder.name
assert_equal \
"docker build --push --platform linux/arm64,linux/amd64 --builder kamal-hybrid-arm64-amd64-ssh---app-127-0-0-1 -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-hybrid-docker-container-ssh---app-127-0-0-1 -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
test "target multiarch local when arch is set" do
builder = new_builder_command(builder: { "local" => { "arch" => "amd64" } })
assert_equal "local", builder.name
assert_equal \
"docker build --push --platform linux/amd64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
test "target native remote when only remote is set" do
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "cache" => { "type" => "gha" } })
test "target remote when remote set and arch is non local" do
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
assert_equal "remote", builder.name
assert_equal \
"docker build --push --platform linux/amd64 --builder kamal-remote-amd64-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 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 .",
builder.push.join(" ")
end
test "target local when remote set and arch is local" do
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
assert_equal "local", builder.name
assert_equal \
"docker build --push --platform linux/#{local_arch} --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ")
end
@@ -93,28 +93,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
test "build context" do
builder = new_builder_command(builder: { "context" => ".." })
assert_equal \
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
builder.push.join(" ")
end
test "native push with build args" do
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
builder.push.join(" ")
end
test "multiarch push with build args" do
test "push with build args" do
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
assert_equal \
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
builder.push.join(" ")
end
test "native push with build secrets" do
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
test "push with build secrets" do
builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] })
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .",
builder.push.join(" ")
end
@@ -130,31 +123,10 @@ class CommandsBuilderTest < ActiveSupport::TestCase
assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the 'service' label\" && exit 1)", new_builder_command.validate_image.join(" ")
end
test "multiarch context build" do
test "context build" do
builder = new_builder_command(builder: { "context" => "./foo" })
assert_equal \
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
builder.push.join(" ")
end
test "native context build" do
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo" })
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
builder.push.join(" ")
end
test "cached context build" do
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo", "cache" => { "type" => "gha" } })
assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile ./foo",
builder.push.join(" ")
end
test "remote context build" do
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "context" => "./foo" })
assert_equal \
"docker build --push --platform linux/amd64 --builder kamal-remote-amd64-ssh---app-host -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
builder.push.join(" ")
end
@@ -171,4 +143,12 @@ class CommandsBuilderTest < ActiveSupport::TestCase
def build_directory
"#{Dir.tmpdir}/kamal-clones/app/kamal/"
end
def local_arch
`uname -m`.strip == "x86_64" ? "amd64" : "arm64"
end
def remote_arch
`uname -m`.strip == "x86_64" ? "arm64" : "amd64"
end
end

View File

@@ -14,45 +14,29 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
}
end
test "multiarch?" do
assert_equal true, config.builder.multiarch?
end
test "setting multiarch to false" do
@deploy_with_builder_option[:builder] = { "multiarch" => false }
assert_equal false, config_with_builder_option.builder.multiarch?
end
test "local?" do
assert_equal false, config.builder.local?
assert_equal true, config.builder.local?
end
test "remote?" do
assert_equal false, config.builder.remote?
end
test "remote_arch" do
assert_nil config.builder.remote_arch
end
test "remote_host" do
assert_nil config.builder.remote_host
test "remote" do
assert_nil config.builder.remote
end
test "setting both local and remote configs" do
@deploy_with_builder_option[:builder] = {
"local" => { "arch" => "arm64" },
"remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" }
"arch" => [ "amd64", "arm64" ],
"remote" => "ssh://root@192.168.0.1"
}
assert_equal true, config_with_builder_option.builder.local?
assert_equal true, config_with_builder_option.builder.remote?
assert_equal "amd64", config_with_builder_option.builder.remote_arch
assert_equal "ssh://root@192.168.0.1", config_with_builder_option.builder.remote_host
assert_equal "arm64", config_with_builder_option.builder.local_arch
assert_equal [ "amd64", "arm64" ], config_with_builder_option.builder.arches
assert_equal "ssh://root@192.168.0.1", config_with_builder_option.builder.remote
end
test "cached?" do

View File

@@ -90,10 +90,8 @@ class ConfigurationValidationTest < ActiveSupport::TestCase
test "builder" do
assert_error "builder: unknown key: foo", builder: { "foo" => "bar" }
assert_error "builder/remote: should be a hash", builder: { "remote" => true }
assert_error "builder/remote: unknown key: foo", builder: { "remote" => { "foo" => "bar" } }
assert_error "builder/local: unknown key: foo", builder: { "local" => { "foo" => "bar" } }
assert_error "builder/remote/arch: should be a string", builder: { "remote" => { "arch" => [] } }
assert_error "builder/remote: should be a string", builder: { "remote" => { "foo" => "bar" } }
assert_error "builder/arch: should be an array or a string", builder: { "arch" => {} }
assert_error "builder/args: should be a hash", builder: { "args" => [ "foo" ] }
assert_error "builder/cache/options: should be a string", builder: { "cache" => { "options" => [] } }
end

View File

@@ -36,6 +36,5 @@ accessories:
readiness_delay: 0
builder:
remote:
arch: amd64
host: ssh://app@1.1.1.5
arch: <%= `uname -m`.strip == "x86_64" ? "arm64" : "amd64" %>
remote: ssh://app@1.1.1.5

View File

@@ -40,6 +40,5 @@ ssh:
port: 22
builder:
remote:
arch: amd64
host: ssh://app@1.1.1.5:2122
arch: <%= `uname -m`.strip == "x86_64" ? "arm64" : "amd64" %>
remote: ssh://app@1.1.1.5:2122

View File

@@ -23,7 +23,6 @@ registry:
username: root
password: root
builder:
multiarch: false
driver: docker
args:
COMMIT_SHA: <%= `git rev-parse HEAD` %>

View File

@@ -17,7 +17,6 @@ registry:
username: root
password: root
builder:
multiarch: false
driver: docker
args:
COMMIT_SHA: <%= `git rev-parse HEAD` %>

View File

@@ -77,7 +77,7 @@ class MainTest < IntegrationTest
assert_equal "app-#{version}", config[:service_with_version]
assert_equal [], config[:volume_args]
assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
assert_equal({ "driver" => "docker", "args" => { "COMMIT_SHA" => version } }, config[:builder])
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
assert_equal({ "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1", "interval"=>"1s", "max_attempts"=>3, "port"=>3000, "path"=>"/up", "cord"=>"/tmp/kamal-cord", "log_lines"=>50 }, config[:healthcheck])
end