Build from a git archive
Building directly from a checkout will pull in uncommitted files to or more sneakily files that are git ignored, but not docker ignored. To avoid this, we'll add an option to build from a git archive of HEAD instead. Docker doesn't provide a way to build directly from a git repo, so instead we create a tarball of the current HEAD with git archive and pipe it into the build command. When building from a git archive, we'll still display the warning about uncommitted changes, but we won't add the `_uncommitted_...` suffix to the container name as they won't be included in the build. Perhaps this should be the default, but we'll leave that decision for now.
This commit is contained in:
@@ -31,6 +31,7 @@ registry:
|
||||
# user: app
|
||||
|
||||
# Configure builder setup.
|
||||
# Set git_archive: true to build from a git archive of HEAD
|
||||
# builder:
|
||||
# args:
|
||||
# RUBY_VERSION: 3.2.0
|
||||
@@ -39,6 +40,7 @@ registry:
|
||||
# remote:
|
||||
# arch: amd64
|
||||
# host: ssh://app@192.168.0.1
|
||||
# git_archive: false
|
||||
|
||||
# Use accessory services (secrets come from .env).
|
||||
# accessories:
|
||||
|
||||
@@ -78,6 +78,10 @@ module Kamal::Commands
|
||||
args.compact.unshift :docker
|
||||
end
|
||||
|
||||
def git(*args)
|
||||
args.compact.unshift :git
|
||||
end
|
||||
|
||||
def tags(**details)
|
||||
Kamal::Tags.from_config(config, **details)
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
class BuilderError < StandardError; end
|
||||
|
||||
delegate :argumentize, to: Kamal::Utils
|
||||
delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
||||
delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, :git_archive?, to: :builder_config
|
||||
|
||||
def clean
|
||||
docker :image, :rm, "--force", config.absolute_image
|
||||
@@ -13,6 +13,16 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
docker :pull, config.absolute_image
|
||||
end
|
||||
|
||||
def push
|
||||
if git_archive?
|
||||
pipe \
|
||||
git(:archive, "--format=tar", :HEAD),
|
||||
build_and_push
|
||||
else
|
||||
build_and_push
|
||||
end
|
||||
end
|
||||
|
||||
def build_options
|
||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_ssh ]
|
||||
end
|
||||
|
||||
@@ -7,15 +7,6 @@ class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
||||
docker :buildx, :rm, builder_name
|
||||
end
|
||||
|
||||
def push
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
"--platform", platform_names,
|
||||
"--builder", builder_name,
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
|
||||
def info
|
||||
combine \
|
||||
docker(:context, :ls),
|
||||
@@ -34,4 +25,13 @@ class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
||||
"linux/amd64,linux/arm64"
|
||||
end
|
||||
end
|
||||
|
||||
def build_and_push
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
"--platform", platform_names,
|
||||
"--builder", builder_name,
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,14 +7,15 @@ class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
|
||||
# No-op on native without cache
|
||||
end
|
||||
|
||||
def push
|
||||
def info
|
||||
# No-op on native
|
||||
end
|
||||
|
||||
private
|
||||
def build_and_push
|
||||
combine \
|
||||
docker(:build, *build_options, build_context),
|
||||
docker(:push, config.absolute_image),
|
||||
docker(:push, config.latest_image)
|
||||
end
|
||||
|
||||
def info
|
||||
# No-op on native
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,8 @@ class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Nativ
|
||||
docker :buildx, :rm, builder_name
|
||||
end
|
||||
|
||||
def push
|
||||
private
|
||||
def build_and_push
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
*build_options,
|
||||
|
||||
@@ -11,15 +11,6 @@ class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Nativ
|
||||
remove_buildx
|
||||
end
|
||||
|
||||
def push
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
"--platform", platform,
|
||||
"--builder", builder_name,
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
|
||||
def info
|
||||
chain \
|
||||
docker(:context, :ls),
|
||||
@@ -56,4 +47,13 @@ class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Nativ
|
||||
def remove_buildx
|
||||
docker :buildx, :rm, builder_name
|
||||
end
|
||||
|
||||
def build_and_push
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
"--platform", platform,
|
||||
"--builder", builder_name,
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
end
|
||||
|
||||
@@ -324,7 +324,10 @@ class Kamal::Configuration
|
||||
def git_version
|
||||
@git_version ||=
|
||||
if Kamal::Git.used?
|
||||
[ Kamal::Git.revision, Kamal::Git.uncommitted_changes.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : "" ].join
|
||||
if Kamal::Git.uncommitted_changes.present? && !builder.git_archive?
|
||||
uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
|
||||
end
|
||||
[ Kamal::Git.revision, uncommitted_suffix ].compact.join
|
||||
else
|
||||
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
||||
end
|
||||
|
||||
@@ -40,7 +40,7 @@ class Kamal::Configuration::Builder
|
||||
end
|
||||
|
||||
def context
|
||||
@options["context"] || "."
|
||||
@options["context"] || (git_archive? ? "-" : ".")
|
||||
end
|
||||
|
||||
def local_arch
|
||||
@@ -85,11 +85,18 @@ class Kamal::Configuration::Builder
|
||||
@options["ssh"]
|
||||
end
|
||||
|
||||
def git_archive?
|
||||
@options["git_archive"]
|
||||
end
|
||||
|
||||
private
|
||||
def valid?
|
||||
if @options["cache"] && @options["cache"]["type"]
|
||||
raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
|
||||
end
|
||||
if @options["context"] && @options["git_archive"]
|
||||
raise ArgumentError, "Cannot set a builder context when building from a git archive"
|
||||
end
|
||||
end
|
||||
|
||||
def cache_image
|
||||
|
||||
@@ -123,6 +123,34 @@ 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 git archive build" do
|
||||
builder = new_builder_command(builder: { "git_archive" => true })
|
||||
assert_equal \
|
||||
"git archive --format=tar HEAD | docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile -",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "native git archive build" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "git_archive" => true })
|
||||
assert_equal \
|
||||
"git archive --format=tar HEAD | docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile - && docker push dhh/app:123 && docker push dhh/app:latest",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "cached git archive build" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "git_archive" => true, "cache" => { "type" => "gha" } })
|
||||
assert_equal \
|
||||
"git archive --format=tar HEAD | docker buildx build --push -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 "remote git archive build" do
|
||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "git_archive" => true })
|
||||
assert_equal \
|
||||
"git archive --format=tar HEAD | docker buildx build --push --platform linux/amd64 --builder kamal-app-native-remote -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile -",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
def new_builder_command(additional_config = {})
|
||||
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.merge(additional_config), version: "123"))
|
||||
|
||||
@@ -106,6 +106,16 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
assert_match /^git-version_uncommitted_[0-9a-f]{16}$/, @config.version
|
||||
end
|
||||
|
||||
test "version from git archive uncommitted" do
|
||||
ENV.delete("VERSION")
|
||||
|
||||
config = Kamal::Configuration.new(@deploy.tap { |c| c[:builder] = { "git_archive" => true } })
|
||||
|
||||
Kamal::Git.expects(:revision).returns("git-version")
|
||||
Kamal::Git.expects(:uncommitted_changes).returns("M file\n")
|
||||
assert_equal "git-version", config.version
|
||||
end
|
||||
|
||||
test "version from env" do
|
||||
ENV["VERSION"] = "env-version"
|
||||
assert_equal "env-version", @config.version
|
||||
|
||||
Reference in New Issue
Block a user