Simplify the builders configuration

1. Add driver as an option, defaulting to `docker-container`. For a
   "native" build you can set it to `docker`
2. Set arch as a array of architectures to build for, defaulting to
   `[ "amd64", "arm64" ]` unless you are using the docker driver in
   which case we default to not setting a platform
3. Remote is now just a connection string for the remote builder
4. If remote is set, we only use it for non-local arches, if we are
   only building for the local arch, we'll ignore it.

Examples:

On arm64, build for arm64 locally, amd64 remotely or
On amd64, build for amd64 locally, arm64 remotely:

```yaml
builder:
  remote: ssh://docker@docker-builder
```

On arm64, build amd64 on remote,
On amd64 build locally:

```yaml
builder:
  arch:
    - amd64
  remote:
    host: ssh://docker@docker-builder
```

Build amd64 on local:

```yaml
builder:
  arch:
    - amd64
```

Use docker driver, building for local arch:

```yaml
builder:
  driver: docker
```
This commit is contained in:
Donal McBreen
2024-08-01 14:06:41 +01:00
committed by Donal McBreen
parent cffb6c3d7e
commit 56268d724d
19 changed files with 140 additions and 206 deletions

View File

@@ -19,16 +19,38 @@ class Kamal::Configuration::Builder
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 @@ class Kamal::Configuration::Builder
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 @@ class Kamal::Configuration::Builder
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 @@ class Kamal::Configuration::Builder
def pwd_sha
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
end
def default_arch
docker_driver? ? [] : [ "amd64", "arm64" ]
end
end

View File

@@ -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-<service>-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
# Arch
#
# The build configuration for local builds, only used if multiarch is enabled (the default)
#
# 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

View File

@@ -28,7 +28,11 @@ class Kamal::Configuration::Validator
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"
@@ -71,6 +75,16 @@ class Kamal::Configuration::Validator
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