Skip to content

Commit

Permalink
feat: Introduce a build push --output option
Browse files Browse the repository at this point in the history
which controls where the build result is exported.

The default value is "registry" to reflect the current behavior of
`build push`.

Any value provided to this option will be passed to the `buildx build`
command as a `--output=type=<VALUE>` flag.

For example, the following command will push to the local docker image
store:

    kamal build push --output=docker
  • Loading branch information
flavorjones committed Jan 20, 2025
1 parent 5f04e42 commit 9f59e0e
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 7 deletions.
3 changes: 2 additions & 1 deletion lib/kamal/cli/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ def deliver
end

desc "push", "Build and push app image to registry"
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
def push
cli = self

Expand Down Expand Up @@ -49,7 +50,7 @@ def push
end

# Get the command here to ensure the Dir.chdir doesn't interfere with it
push = KAMAL.builder.push
push = KAMAL.builder.push(cli.options[:output])

KAMAL.with_verbosity(:debug) do
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
Expand Down
4 changes: 2 additions & 2 deletions lib/kamal/commands/builder/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def clean
docker :image, :rm, "--force", config.absolute_image
end

def push
def push(export_action)
docker :buildx, :build,
"--push",
"--output=type=#{export_action}",
*platform_options(arches),
*([ "--builder", builder_name ] unless docker_driver?),
*build_options,
Expand Down
31 changes: 27 additions & 4 deletions test/cli/build_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,30 @@ 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 --push --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 \. as .*@localhost/, output
end
end
end

test "push --output=docker" do
with_build_directory do |build_directory|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }

SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:git, "-C", anything, :"rev-parse", :HEAD)
.returns(Kamal::Git.revision)

SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:git, "-C", anything, :status, "--porcelain")
.returns("")

run_command("push", "--output=docker", "--verbose").tap do |output|
assert_hook_ran "pre-build", output, **hook_variables
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
end
end
end
Expand All @@ -49,7 +72,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, "--push", "--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", ".")

SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:git, "-C", anything, :"rev-parse", :HEAD)
Expand All @@ -74,7 +97,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 buildx build --push --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 . as .*@localhost/, output
end
end

Expand Down Expand Up @@ -140,7 +163,7 @@ class CliBuildTest < CliTestCase
.returns("")

SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :build, "--push", "--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", ".")

run_command("push").tap do |output|
assert_match /WARN Missing compatible builder, so creating a new one first/, output
Expand Down

0 comments on commit 9f59e0e

Please sign in to comment.