Docker does not respect the .dockerignore file when building from a tar. Instead by default we'll make a local clone into a tmp directory and build from there. Subsequent builds will reset the clone to match the checkout. Compared to building directly in the repo, we'll have reproducible builds. Compared to using a git archive: 1. .dockerignore is respected 2. We'll have faster builds - docker can be smarter about caching the build context on subsequent builds from a directory To build from the repo directly, set the build context to "." in the config. If there are uncommitted changes, we'll warn about them either being included or ignored depending on whether we build from the clone.
187 lines
7.8 KiB
Ruby
187 lines
7.8 KiB
Ruby
require_relative "cli_test_case"
|
|
|
|
class CliBuildTest < CliTestCase
|
|
test "deliver" do
|
|
Kamal::Cli::Build.any_instance.expects(:push)
|
|
Kamal::Cli::Build.any_instance.expects(:pull)
|
|
|
|
run_command("deliver")
|
|
end
|
|
|
|
test "push" do
|
|
with_build_directory do
|
|
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" }
|
|
|
|
run_command("push", "--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 --push --platform linux\/amd64,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
|
end
|
|
end
|
|
end
|
|
|
|
test "push reseting clone" do
|
|
with_build_directory do
|
|
stub_setup
|
|
build_dir = "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}/kamal/"
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:mkdir, "-p", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}")
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd)
|
|
.raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory"))
|
|
.then
|
|
.returns(true)
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:git, "-C", build_dir, :remote, "set-url", :origin, Dir.pwd)
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:git, "-C", build_dir, :fetch, :origin)
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:git, "-C", build_dir, :reset, "--hard", Kamal::Git.revision)
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:git, "-C", build_dir, :clean, "-fdx")
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-app-multiarch", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
|
|
|
run_command("push", "--verbose").tap do |output|
|
|
assert_match /Cloning repo into build directory/, output
|
|
assert_match /Resetting local clone/, output
|
|
end
|
|
end
|
|
end
|
|
|
|
test "push without clone" do
|
|
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" }
|
|
|
|
run_command("push", "--verbose", fixture: :without_clone).tap do |output|
|
|
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,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
|
|
end
|
|
end
|
|
|
|
test "push without builder" do
|
|
with_build_directory do
|
|
stub_setup
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with { |*args| args[0..1] == [ :docker, :buildx ] }
|
|
.raises(SSHKit::Command::Failed.new("no builder"))
|
|
.then
|
|
.returns(true)
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with { |*args| args.first.start_with?("git") }
|
|
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute).with(:mkdir, "-p", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}")
|
|
|
|
run_command("push").tap do |output|
|
|
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
|
end
|
|
end
|
|
end
|
|
|
|
test "push with no buildx plugin" do
|
|
stub_setup
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
.raises(SSHKit::Command::Failed.new("no buildx"))
|
|
|
|
Kamal::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false)
|
|
assert_raises(Kamal::Cli::Build::BuildError) { run_command("push") }
|
|
end
|
|
|
|
test "push pre-build hook failure" do
|
|
fail_hook("pre-build")
|
|
|
|
error = assert_raises(Kamal::Cli::HookError) { run_command("push") }
|
|
assert_equal "Hook `pre-build` failed:\nfailed", error.message
|
|
|
|
assert @executions.none? { |args| args[0..2] == [ :docker, :buildx, :build ] }
|
|
end
|
|
|
|
test "pull" do
|
|
run_command("pull").tap do |output|
|
|
assert_match /docker image rm --force dhh\/app:999/, output
|
|
assert_match /docker pull dhh\/app:999/, output
|
|
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output
|
|
end
|
|
end
|
|
|
|
test "create" do
|
|
run_command("create").tap do |output|
|
|
assert_match /docker buildx create --use --name kamal-app-multiarch/, 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-app-native-remote-amd64 --description 'kamal-app-native-remote amd64 native host' --docker 'host=ssh://app@1.1.1.5'", output
|
|
assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", output
|
|
end
|
|
end
|
|
|
|
test "create with error" do
|
|
stub_setup
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with { |arg| arg == :docker }
|
|
.raises(SSHKit::Command::Failed.new("stderr=error"))
|
|
|
|
run_command("create").tap do |output|
|
|
assert_match /Couldn't create remote builder: error/, output
|
|
end
|
|
end
|
|
|
|
test "remove" do
|
|
run_command("remove").tap do |output|
|
|
assert_match /docker buildx rm kamal-app-multiarch/, output
|
|
end
|
|
end
|
|
|
|
test "details" do
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
|
.with(:docker, :context, :ls, "&&", :docker, :buildx, :ls)
|
|
.returns("docker builder info")
|
|
|
|
run_command("details").tap do |output|
|
|
assert_match /Builder: multiarch/, output
|
|
assert_match /docker builder info/, output
|
|
end
|
|
end
|
|
|
|
private
|
|
def run_command(*command, fixture: :with_accessories)
|
|
stdouted { Kamal::Cli::Build.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) }
|
|
end
|
|
|
|
def stub_dependency_checks
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
.with { |*args| args[0..1] == [ :docker, :buildx ] }
|
|
end
|
|
|
|
def with_build_directory
|
|
build_directory = File.join Dir.tmpdir, "kamal-clones", "app-#{pwd_sha}", "kamal"
|
|
FileUtils.mkdir_p build_directory
|
|
FileUtils.touch File.join build_directory, "Dockerfile"
|
|
yield
|
|
ensure
|
|
FileUtils.rm_rf build_directory
|
|
end
|
|
|
|
def pwd_sha
|
|
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
|
end
|
|
end
|