diff --git a/lib/kamal/cli/build.rb b/lib/kamal/cli/build.rb index eb431b332..ac73d11dc 100644 --- a/lib/kamal/cli/build.rb +++ b/lib/kamal/cli/build.rb @@ -66,7 +66,7 @@ def pull 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 diff --git a/lib/kamal/commands/builder.rb b/lib/kamal/commands/builder.rb index 1f105d536..e69e3c2ff 100644 --- a/lib/kamal/commands/builder.rb +++ b/lib/kamal/commands/builder.rb @@ -2,7 +2,7 @@ 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 diff --git a/lib/kamal/commands/builder/base.rb b/lib/kamal/commands/builder/base.rb index 8f68b334e..5c03881fa 100644 --- a/lib/kamal/commands/builder/base.rb +++ b/lib/kamal/commands/builder/base.rb @@ -5,8 +5,8 @@ class BuilderError < StandardError; end 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 @@ def clean def push docker :build, "--push", - *platform_options, + *platform_options(arches), *([ "--builder", builder_name ] unless docker_driver?), *build_options, build_context @@ -33,7 +33,7 @@ def info 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 @@ def builder_config 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 diff --git a/lib/kamal/commands/builder/hybrid.rb b/lib/kamal/commands/builder/hybrid.rb index f8d873dd9..5434169ab 100644 --- a/lib/kamal/commands/builder/hybrid.rb +++ b/lib/kamal/commands/builder/hybrid.rb @@ -8,18 +8,14 @@ def create 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 diff --git a/lib/kamal/commands/builder/local.rb b/lib/kamal/commands/builder/local.rb index 64b933aec..c4215e333 100644 --- a/lib/kamal/commands/builder/local.rb +++ b/lib/kamal/commands/builder/local.rb @@ -11,14 +11,4 @@ def remove 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 diff --git a/lib/kamal/commands/builder/remote.rb b/lib/kamal/commands/builder/remote.rb index 3196a6e99..b46440471 100644 --- a/lib/kamal/commands/builder/remote.rb +++ b/lib/kamal/commands/builder/remote.rb @@ -17,22 +17,13 @@ def info 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 @@ def remove_remote_context 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 diff --git a/lib/kamal/configuration/builder.rb b/lib/kamal/configuration/builder.rb index 6229a518f..4f51669a3 100644 --- a/lib/kamal/configuration/builder.rb +++ b/lib/kamal/configuration/builder.rb @@ -19,16 +19,38 @@ def to_h 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 @@ def driver 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 @@ def docker_driver? 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? + if docker_driver? + 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 @@ def repo_relative_pwd def pwd_sha Digest::SHA256.hexdigest(Dir.pwd)[0..12] end + + def default_arch + docker_driver? ? [] : [ "amd64", "arm64" ] + end end diff --git a/lib/kamal/configuration/docs/builder.yml b/lib/kamal/configuration/docs/builder.yml index 9695a4686..5455f1a2b 100644 --- a/lib/kamal/configuration/docs/builder.yml +++ b/lib/kamal/configuration/docs/builder.yml @@ -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--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 - # - # The build configuration for local builds, only used if multiarch is enabled (the default) + # Arch # - # 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 diff --git a/lib/kamal/configuration/validator.rb b/lib/kamal/configuration/validator.rb index c7ba0f727..d3ac08558 100644 --- a/lib/kamal/configuration/validator.rb +++ b/lib/kamal/configuration/validator.rb @@ -27,7 +27,11 @@ def validate_against_example!(validation_config, example) elsif key == "hosts" validate_servers! value elsif example_value.is_a?(Array) - validate_array_of! value, example_value.first.class + 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" @@ -69,6 +73,16 @@ def stringish?(value) 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 diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index a3b426cf7..a59680425 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -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 @@ -48,7 +48,7 @@ class CliBuildTest < CliTestCase SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx") 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) @@ -73,7 +73,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 @@ -119,10 +119,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") } @@ -136,7 +136,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 @@ -202,23 +202,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 diff --git a/test/cli/cli_test_case.rb b/test/cli/cli_test_case.rb index 1776500da..7dc06a2ce 100644 --- a/test/cli/cli_test_case.rb +++ b/test/cli/cli_test_case.rb @@ -37,7 +37,7 @@ def stub_setup 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) diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 620aa73d7..960c17fe5 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -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 + 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-local -t dhh/app:123 -t dhh/app:latest --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 native remote when only remote is set" do - builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "cache" => { "type" => "gha" } }) - assert_equal "remote", builder.name + 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/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/#{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 @@ def new_builder_command(additional_config = {}) 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 diff --git a/test/configuration/builder_test.rb b/test/configuration/builder_test.rb index 94acdacce..efcd613b5 100644 --- a/test/configuration/builder_test.rb +++ b/test/configuration/builder_test.rb @@ -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 diff --git a/test/configuration/validation_test.rb b/test/configuration/validation_test.rb index b7bd6b6aa..b39dbc369 100644 --- a/test/configuration/validation_test.rb +++ b/test/configuration/validation_test.rb @@ -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 diff --git a/test/fixtures/deploy_with_remote_builder.yml b/test/fixtures/deploy_with_remote_builder.yml index 441034994..a67963c2a 100644 --- a/test/fixtures/deploy_with_remote_builder.yml +++ b/test/fixtures/deploy_with_remote_builder.yml @@ -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 diff --git a/test/fixtures/deploy_with_remote_builder_and_custom_ports.yml b/test/fixtures/deploy_with_remote_builder_and_custom_ports.yml index d1e81836b..d76100ff6 100644 --- a/test/fixtures/deploy_with_remote_builder_and_custom_ports.yml +++ b/test/fixtures/deploy_with_remote_builder_and_custom_ports.yml @@ -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 diff --git a/test/integration/docker/deployer/app/config/deploy.yml b/test/integration/docker/deployer/app/config/deploy.yml index 72d09d917..99bfc2977 100644 --- a/test/integration/docker/deployer/app/config/deploy.yml +++ b/test/integration/docker/deployer/app/config/deploy.yml @@ -23,7 +23,6 @@ registry: username: root password: root builder: - multiarch: false driver: docker args: COMMIT_SHA: <%= `git rev-parse HEAD` %> diff --git a/test/integration/docker/deployer/app_with_roles/config/deploy.yml b/test/integration/docker/deployer/app_with_roles/config/deploy.yml index 818dc160e..24c9c0a2f 100644 --- a/test/integration/docker/deployer/app_with_roles/config/deploy.yml +++ b/test/integration/docker/deployer/app_with_roles/config/deploy.yml @@ -17,7 +17,6 @@ registry: username: root password: root builder: - multiarch: false driver: docker args: COMMIT_SHA: <%= `git rev-parse HEAD` %> diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index c4558c1d3..a36a7ab5c 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -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