From 91259720b20cc299e56ccc1a76d1e01ae7275c87 Mon Sep 17 00:00:00 2001 From: Michael Weiner Date: Fri, 7 Feb 2025 20:13:18 -0600 Subject: [PATCH 1/6] Fail kamal app exec without a CMD --- lib/kamal/cli/app.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 553ccbb2..5d81c9a4 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -108,6 +108,10 @@ class Kamal::Cli::App < Kamal::Cli::Base raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}" end + if cmd.empty? + raise ArgumentError, "No command provided. You must specify a command to execute." + end + cmd = Kamal::Utils.join_commands(cmd) env = options[:env] detach = options[:detach] From 53778712787446262ca85ac875ef20f272430e48 Mon Sep 17 00:00:00 2001 From: Michael Weiner Date: Sat, 8 Feb 2025 13:09:57 -0600 Subject: [PATCH 2/6] Add UT for missing command --- test/cli/app_test.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 19190032..21a6c3f0 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -274,6 +274,13 @@ class CliAppTest < CliTestCase end end + test "exec without command fails" do + error = assert_raises(ArgumentError, "Exec requires a command to be specified") do + run_command("exec") + end + assert_equal 'No command provided. You must specify a command to execute.', error.message + end + test "exec separate arguments" do run_command("exec", "ruby", " -v").tap do |output| assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output From 5e87b6d58e4cc859e6889ce82b3730377ef463ef Mon Sep 17 00:00:00 2001 From: Michael Weiner Date: Tue, 4 Mar 2025 06:32:08 -0600 Subject: [PATCH 3/6] Use double-quotes on UT --- test/cli/app_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 21a6c3f0..dab45f1b 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -278,7 +278,7 @@ class CliAppTest < CliTestCase error = assert_raises(ArgumentError, "Exec requires a command to be specified") do run_command("exec") end - assert_equal 'No command provided. You must specify a command to execute.', error.message + assert_equal "No command provided. You must specify a command to execute.", error.message end test "exec separate arguments" do From d7dbef1c9e4a3dd4f16a7bcee795828b4801e8f6 Mon Sep 17 00:00:00 2001 From: Daniel Perrett Date: Tue, 25 Mar 2025 14:02:21 +0000 Subject: [PATCH 4/6] chore: guard GithubStatusChecks.new, which makes calls on initialize --- lib/kamal/cli/templates/sample_hooks/pre-deploy.sample | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample b/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample index 1b280c71..06b86aa1 100755 --- a/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +++ b/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample @@ -82,11 +82,12 @@ end $stdout.sync = true -puts "Checking build status..." -attempts = 0 -checks = GithubStatusChecks.new - begin + puts "Checking build status..." + + attempts = 0 + checks = GithubStatusChecks.new + loop do case checks.state when "success" From f768fab481612f3170672668c66b5f2313af435c Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Mon, 21 Apr 2025 09:45:23 +0100 Subject: [PATCH 5/6] Add KAMAL_ROLES to hook env variables And add an integration test to check the env vars are set. --- lib/kamal/cli/base.rb | 2 +- lib/kamal/cli/proxy.rb | 1 + .../docker/deployer/app/.kamal/hooks/pre-connect | 1 + test/integration/main_test.rb | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/kamal/cli/base.rb b/lib/kamal/cli/base.rb index 45b1411e..7e5dd77b 100644 --- a/lib/kamal/cli/base.rb +++ b/lib/kamal/cli/base.rb @@ -133,7 +133,7 @@ module Kamal::Cli def run_hook(hook, **extra_details) if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook) - details = { hosts: KAMAL.hosts.join(","), command: command, subcommand: subcommand } + details = { hosts: KAMAL.hosts.join(","), roles: KAMAL.specific_roles&.join(","), command: command, subcommand: subcommand }.compact say "Running the #{hook} hook...", :magenta with_env KAMAL.hook.env(**details, **extra_details) do diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index ad189b8c..1ed17740 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -69,6 +69,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base end end when "get" + on(KAMAL.proxy_hosts) do |host| puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}" end diff --git a/test/integration/docker/deployer/app/.kamal/hooks/pre-connect b/test/integration/docker/deployer/app/.kamal/hooks/pre-connect index e17784a5..0fe4e378 100755 --- a/test/integration/docker/deployer/app/.kamal/hooks/pre-connect +++ b/test/integration/docker/deployer/app/.kamal/hooks/pre-connect @@ -1,4 +1,5 @@ #!/bin/sh echo "About to lock..." +env mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 0a918a78..3b56fbb3 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -9,8 +9,12 @@ class MainTest < IntegrationTest kamal :deploy assert_app_is_up version: first_version assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "pre-app-boot", "post-app-boot", "post-deploy" + assert_envs version: first_version + output = kamal :app, :exec, "--verbose", "ls", "-r", "web", capture: true + assert_hook_env_variables output, version: first_version + second_version = update_app_rev kamal :redeploy @@ -191,4 +195,15 @@ class MainTest < IntegrationTest assert container_ids(vm: vm).any? end end + + def assert_hook_env_variables(output, version:) + assert_match "KAMAL_VERSION=#{version}", output + assert_match "KAMAL_SERVICE=app", output + assert_match "KAMAL_SERVICE_VERSION=app@#{version[0..6]}", output + assert_match "KAMAL_COMMAND=app", output + assert_match "KAMAL_PERFORMER=deployer@example.com", output + assert_match /KAMAL_RECORDED_AT=\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ/, output + assert_match "KAMAL_HOSTS=vm1,vm2", output + assert_match "KAMAL_ROLES=web", output + end end From 93d1bd1369e13c8a0f0307c5ee3edd35fd52f712 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Mon, 21 Apr 2025 10:19:36 +0100 Subject: [PATCH 6/6] Redirect buildx build output to stdout Docker buildx build outputs the build logs to stderr by default. SSHKit displays stderr logs in red, which can suggest that an error has occurred. Redirect the output to stdout, so it shows in green. If there is an error, the output will be repeated in red anyway. Fixes: https://github.com/basecamp/kamal/issues/1356 --- lib/kamal/commands/builder/base.rb | 3 ++- test/cli/build_test.rb | 14 ++++++------- test/commands/builder_test.rb | 32 +++++++++++++++--------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/lib/kamal/commands/builder/base.rb b/lib/kamal/commands/builder/base.rb index 0bd613cb..bb2b3516 100644 --- a/lib/kamal/commands/builder/base.rb +++ b/lib/kamal/commands/builder/base.rb @@ -20,7 +20,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base *([ "--builder", builder_name ] unless docker_driver?), *build_tag_options(tag_as_dirty: tag_as_dirty), *build_options, - build_context + build_context, + "2>&1" end def pull diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index f2dba886..c0a236ad 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -25,7 +25,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 buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output + assert_match /docker buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output end end end @@ -47,7 +47,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 buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output + assert_match /docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output end end end @@ -71,7 +71,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, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") + .with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", "2>&1") SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:git, "-C", anything, :"rev-parse", :HEAD) @@ -95,7 +95,7 @@ class CliBuildTest < CliTestCase assert_no_match /Cloning repo into build directory/, output assert_hook_ran "pre-build", output assert_match /docker --version && docker buildx version/, output - assert_match /docker buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output + assert_match /docker buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . 2>&1 as .*@localhost/, output end end @@ -165,7 +165,7 @@ class CliBuildTest < CliTestCase .returns("") SSHKit::Backend::Abstract.any_instance.expects(:execute) - .with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") + .with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", "2>&1") run_command("push").tap do |output| assert_match /WARN Missing compatible builder, so creating a new one first/, output @@ -307,7 +307,7 @@ class CliBuildTest < CliTestCase run_command("dev", "--verbose").tap do |output| assert_no_match(/Cloning repo into build directory/, output) assert_match(/docker --version && docker buildx version/, output) - assert_match(/docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. as .*@localhost/, output) + assert_match(/docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output) end end end @@ -319,7 +319,7 @@ class CliBuildTest < CliTestCase run_command("dev", "--output=local", "--verbose").tap do |output| assert_no_match(/Cloning repo into build directory/, output) assert_match(/docker --version && docker buildx version/, output) - assert_match(/docker buildx build --output=type=local --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. as .*@localhost/, output) + assert_match(/docker buildx build --output=type=local --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output) end end end diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 8ac8382e..2edc59ad 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -9,7 +9,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "cache" => { "type" => "gha" } }) assert_equal "local", builder.name assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --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 .", + "docker buildx build --output=type=registry --platform linux/amd64 --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 . 2>&1", builder.push.join(" ") end @@ -17,7 +17,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "amd64" ] }) assert_equal "local", builder.name assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . 2>&1", builder.push.join(" ") end @@ -25,7 +25,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "cache" => { "type" => "gha" } }) assert_equal "local", builder.name assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --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 .", + "docker buildx build --output=type=registry --platform linux/amd64 --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 . 2>&1", builder.push.join(" ") end @@ -33,7 +33,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" } }) assert_equal "hybrid", builder.name assert_equal \ - "docker buildx build --output=type=registry --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 .", + "docker buildx build --output=type=registry --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 . 2>&1", builder.push.join(" ") end @@ -41,7 +41,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" }, "local" => false }) assert_equal "remote", builder.name assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-remote-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 buildx build --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-remote-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 . 2>&1", builder.push.join(" ") end @@ -49,7 +49,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } }) assert_equal "remote", builder.name assert_equal \ - "docker buildx build --output=type=registry --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 .", + "docker buildx build --output=type=registry --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 . 2>&1", builder.push.join(" ") end @@ -57,7 +57,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } }) assert_equal "local", builder.name assert_equal \ - "docker buildx build --output=type=registry --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 .", + "docker buildx build --output=type=registry --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 . 2>&1", builder.push.join(" ") end @@ -65,7 +65,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "driver" => "cloud docker-org-name/builder-name" }) assert_equal "cloud", builder.name assert_equal \ - "docker buildx build --output=type=registry --platform linux/#{local_arch} --builder cloud-docker-org-name-builder-name -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .", + "docker buildx build --output=type=registry --platform linux/#{local_arch} --builder cloud-docker-org-name-builder-name -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . 2>&1", builder.push.join(" ") end @@ -112,14 +112,14 @@ class CommandsBuilderTest < ActiveSupport::TestCase test "build context" do builder = new_builder_command(builder: { "context" => ".." }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .. 2>&1", builder.push.join(" ") end test "push with build args" do builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --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 .", + "docker buildx build --output=type=registry --platform linux/amd64 --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 . 2>&1", builder.push.join(" ") end @@ -128,7 +128,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase FileUtils.touch("Dockerfile") builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile . 2>&1", builder.push.join(" ") end end @@ -148,35 +148,35 @@ class CommandsBuilderTest < ActiveSupport::TestCase test "context build" do builder = new_builder_command(builder: { "context" => "./foo" }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo 2>&1", builder.push.join(" ") end test "push with provenance" do builder = new_builder_command(builder: { "provenance" => "mode=max" }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max . 2>&1", builder.push.join(" ") end test "push with provenance false" do builder = new_builder_command(builder: { "provenance" => false }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false . 2>&1", builder.push.join(" ") end test "push with sbom" do builder = new_builder_command(builder: { "sbom" => true }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom true .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom true . 2>&1", builder.push.join(" ") end test "push with sbom false" do builder = new_builder_command(builder: { "sbom" => false }) assert_equal \ - "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom false .", + "docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom false . 2>&1", builder.push.join(" ") end