Compare commits
12 Commits
v1.9.2
...
simplify-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4435fdf6fc | ||
|
|
2dd0ccc276 | ||
|
|
efb2a5d7c6 | ||
|
|
e7431f9832 | ||
|
|
cf80932216 | ||
|
|
cf81837737 | ||
|
|
8567ce9bf9 | ||
|
|
5d8e4dee13 | ||
|
|
10a7645ab8 | ||
|
|
c93f0f3048 | ||
|
|
6cedec68e3 | ||
|
|
2a4a8ac859 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -3,7 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- 1-9-stable
|
|
||||||
pull_request:
|
pull_request:
|
||||||
jobs:
|
jobs:
|
||||||
rubocop:
|
rubocop:
|
||||||
@@ -31,9 +30,6 @@ jobs:
|
|||||||
gemfile:
|
gemfile:
|
||||||
- Gemfile
|
- Gemfile
|
||||||
- gemfiles/rails_edge.gemfile
|
- gemfiles/rails_edge.gemfile
|
||||||
exclude:
|
|
||||||
- ruby-version: "3.1"
|
|
||||||
gemfile: gemfiles/rails_edge.gemfile
|
|
||||||
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|||||||
1
.github/workflows/docker-publish.yml
vendored
1
.github/workflows/docker-publish.yml
vendored
@@ -51,4 +51,5 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
|
ghcr.io/basecamp/kamal:latest
|
||||||
ghcr.io/basecamp/kamal:${{ steps.version-tag.outputs.value }}
|
ghcr.io/basecamp/kamal:${{ steps.version-tag.outputs.value }}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ WORKDIR /workdir
|
|||||||
|
|
||||||
# Tell git it's safe to access /workdir/.git even if
|
# Tell git it's safe to access /workdir/.git even if
|
||||||
# the directory is owned by a different user
|
# the directory is owned by a different user
|
||||||
RUN git config --global --add safe.directory '*'
|
RUN git config --global --add safe.directory /workdir
|
||||||
|
|
||||||
# Set the entrypoint to run the installed binary in /workdir
|
# Set the entrypoint to run the installed binary in /workdir
|
||||||
# Example: docker run -it -v "$PWD:/workdir" kamal init
|
# Example: docker run -it -v "$PWD:/workdir" kamal init
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
kamal (1.9.2)
|
kamal (1.8.1)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
|
|||||||
@@ -222,25 +222,6 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "downgrade", "Downgrade accessories from Kamal 2 to 1.9"
|
|
||||||
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
|
|
||||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
||||||
def downgrade(name)
|
|
||||||
confirming "This will restart all accessories" do
|
|
||||||
with_lock do
|
|
||||||
host_groups = options[:rolling] ? KAMAL.accessory_hosts : [ KAMAL.accessory_hosts ]
|
|
||||||
host_groups.each do |hosts|
|
|
||||||
host_list = Array(hosts).join(",")
|
|
||||||
KAMAL.with_specific_hosts(hosts) do
|
|
||||||
say "Downgrading #{name} accessories on #{host_list}...", :magenta
|
|
||||||
reboot name
|
|
||||||
say "Downgraded #{name} accessories on #{host_list}...", :magenta
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def with_accessory(name)
|
def with_accessory(name)
|
||||||
if KAMAL.config.accessory(name)
|
if KAMAL.config.accessory(name)
|
||||||
|
|||||||
@@ -206,10 +206,6 @@ module Kamal::Cli
|
|||||||
instance_variable_get("@_invocations").first
|
instance_variable_get("@_invocations").first
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_invocation(cli_class)
|
|
||||||
instance_variable_get("@_invocations")[cli_class].pop
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_run_and_locks_directory
|
def ensure_run_and_locks_directory
|
||||||
on(KAMAL.hosts) do
|
on(KAMAL.hosts) do
|
||||||
execute(*KAMAL.server.ensure_run_directory)
|
execute(*KAMAL.server.ensure_run_directory)
|
||||||
|
|||||||
@@ -30,18 +30,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
|
||||||
push = KAMAL.builder.push
|
|
||||||
|
|
||||||
run_locally do
|
run_locally do
|
||||||
begin
|
begin
|
||||||
context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
|
execute *KAMAL.builder.buildx_inspect
|
||||||
|
|
||||||
if context_hosts != KAMAL.builder.config_context_hosts
|
|
||||||
warn "Context hosts have changed, so re-creating builder, was: #{context_hosts.join(", ")}], now: #{KAMAL.builder.config_context_hosts.join(", ")}"
|
|
||||||
cli.remove
|
|
||||||
cli.create
|
|
||||||
end
|
|
||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
if e.message =~ /(context not found|no builder|does not exist)/
|
if e.message =~ /(context not found|no builder|does not exist)/
|
||||||
warn "Missing compatible builder, so creating a new one first"
|
warn "Missing compatible builder, so creating a new one first"
|
||||||
@@ -51,6 +42,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
||||||
|
push = KAMAL.builder.push
|
||||||
|
|
||||||
KAMAL.with_verbosity(:debug) do
|
KAMAL.with_verbosity(:debug) do
|
||||||
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -217,37 +217,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "downgrade", "Downgrade from Kamal 2 to 1.9"
|
|
||||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
||||||
option :rolling, type: :boolean, default: false, desc: "Downgrade one host at a time"
|
|
||||||
def downgrade
|
|
||||||
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
|
|
||||||
with_lock do
|
|
||||||
if options[:rolling]
|
|
||||||
(KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
|
|
||||||
KAMAL.with_specific_hosts(host) do
|
|
||||||
say "Downgrading #{host}...", :magenta
|
|
||||||
if KAMAL.hosts.include?(host)
|
|
||||||
invoke "kamal:cli:traefik:downgrade", [], options.merge(confirmed: true, rolling: false)
|
|
||||||
reset_invocation(Kamal::Cli::Traefik)
|
|
||||||
end
|
|
||||||
if KAMAL.accessory_hosts.include?(host)
|
|
||||||
invoke "kamal:cli:accessory:downgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
|
|
||||||
reset_invocation(Kamal::Cli::Accessory)
|
|
||||||
end
|
|
||||||
say "Downgraded #{host}", :magenta
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
say "Downgrading all hosts...", :magenta
|
|
||||||
invoke "kamal:cli:traefik:downgrade", [], options.merge(confirmed: true)
|
|
||||||
invoke "kamal:cli:accessory:downgrade", [ "all" ], options.merge(confirmed: true)
|
|
||||||
say "Downgraded all hosts", :magenta
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "version", "Show Kamal version"
|
desc "version", "Show Kamal version"
|
||||||
def version
|
def version
|
||||||
puts Kamal::VERSION
|
puts Kamal::VERSION
|
||||||
|
|||||||
@@ -119,44 +119,4 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "downgrade", "Downgrade to Traefik on servers (stop container, remove container, start new container, reboot app)"
|
|
||||||
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
|
||||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
|
||||||
def downgrade
|
|
||||||
invoke_options = { "version" => KAMAL.config.latest_tag }.merge(options)
|
|
||||||
|
|
||||||
confirming "This will cause a brief outage on each host. Are you sure?" do
|
|
||||||
host_groups = options[:rolling] ? KAMAL.hosts : [ KAMAL.hosts ]
|
|
||||||
host_groups.each do |hosts|
|
|
||||||
host_list = Array(hosts).join(",")
|
|
||||||
say "Downgrading to Traefik on #{host_list}...", :magenta
|
|
||||||
run_hook "pre-traefik-reboot", hosts: host_list
|
|
||||||
on(hosts) do |host|
|
|
||||||
execute *KAMAL.auditor.record("Rebooted Traefik"), verbosity: :debug
|
|
||||||
execute *KAMAL.registry.login
|
|
||||||
|
|
||||||
"Stopping and removing kamal-proxy on #{host}, if running..."
|
|
||||||
execute *KAMAL.traefik.cleanup_kamal_proxy
|
|
||||||
|
|
||||||
"Stopping and removing Traefik on #{host}, if running..."
|
|
||||||
execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
|
|
||||||
execute *KAMAL.traefik.remove_container
|
|
||||||
execute *KAMAL.traefik.remove_image
|
|
||||||
end
|
|
||||||
|
|
||||||
KAMAL.with_specific_hosts(hosts) do
|
|
||||||
invoke "kamal:cli:traefik:boot", [], invoke_options
|
|
||||||
reset_invocation(Kamal::Cli::Traefik)
|
|
||||||
invoke "kamal:cli:app:boot", [], invoke_options
|
|
||||||
reset_invocation(Kamal::Cli::App)
|
|
||||||
invoke "kamal:cli:prune:all", [], invoke_options
|
|
||||||
reset_invocation(Kamal::Cli::Prune)
|
|
||||||
end
|
|
||||||
|
|
||||||
run_hook "post-traefik-reboot", hosts: host_list
|
|
||||||
say "Downgraded to Traefik on #{host_list}", :magenta
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -56,13 +56,6 @@ class Kamal::Commander
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_specific_hosts(hosts)
|
|
||||||
original_hosts, self.specific_hosts = specific_hosts, hosts
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
self.specific_hosts = original_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
def accessory_names
|
def accessory_names
|
||||||
config.accessories&.collect(&:name) || []
|
config.accessories&.collect(&:name) || []
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Kamal::Commander::Specifics
|
|||||||
end
|
end
|
||||||
|
|
||||||
def accessory_hosts
|
def accessory_hosts
|
||||||
config.accessories.flat_map(&:hosts) & specified_hosts
|
specific_hosts || config.accessories.flat_map(&:hosts)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
require "active_support/core_ext/string/filters"
|
require "active_support/core_ext/string/filters"
|
||||||
|
|
||||||
class Kamal::Commands::Builder < Kamal::Commands::Base
|
class Kamal::Commands::Builder < Kamal::Commands::Base
|
||||||
delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
|
delegate :create, :remove, :push, :clean, :pull, :info, :buildx_inspect, :validate_image, :first_mirror, to: :target
|
||||||
:first_mirror, to: :target
|
delegate :multiarch?, :local?, :remote?, to: "config.builder"
|
||||||
|
|
||||||
include Clone
|
include Clone
|
||||||
|
|
||||||
@@ -11,43 +11,27 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def target
|
def target
|
||||||
if config.builder.multiarch?
|
if remote?
|
||||||
if config.builder.remote?
|
if local?
|
||||||
if config.builder.local?
|
hybrid
|
||||||
multiarch_remote
|
|
||||||
else
|
|
||||||
native_remote
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
multiarch
|
remote
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if config.builder.cached?
|
local
|
||||||
native_cached
|
|
||||||
else
|
|
||||||
native
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def native
|
def remote
|
||||||
@native ||= Kamal::Commands::Builder::Native.new(config)
|
@remote ||= Kamal::Commands::Builder::Remote.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_cached
|
def local
|
||||||
@native ||= Kamal::Commands::Builder::Native::Cached.new(config)
|
@local ||= Kamal::Commands::Builder::Local.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_remote
|
def hybrid
|
||||||
@native ||= Kamal::Commands::Builder::Native::Remote.new(config)
|
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
||||||
end
|
|
||||||
|
|
||||||
def multiarch
|
|
||||||
@multiarch ||= Kamal::Commands::Builder::Multiarch.new(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def multiarch_remote
|
|
||||||
@multiarch_remote ||= Kamal::Commands::Builder::Multiarch::Remote.new(config)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
|
|
||||||
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||||
class BuilderError < StandardError; end
|
class BuilderError < StandardError; end
|
||||||
|
|
||||||
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
||||||
|
|
||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
delegate \
|
||||||
|
:args, :secrets, :dockerfile, :target, :local_arch, :remote_arch, :remote_host,
|
||||||
|
:cache_from, :cache_to, :multiarch?, :ssh, :driver, :docker_driver?,
|
||||||
|
to: :builder_config
|
||||||
|
|
||||||
def clean
|
def clean
|
||||||
docker :image, :rm, "--force", config.absolute_image
|
docker :image, :rm, "--force", config.absolute_image
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def push
|
||||||
|
docker :build,
|
||||||
|
"--push",
|
||||||
|
*platform_options,
|
||||||
|
*([ "--builder", builder_name ] unless docker_driver?),
|
||||||
|
*build_options,
|
||||||
|
build_context
|
||||||
|
end
|
||||||
|
|
||||||
def pull
|
def pull
|
||||||
docker :pull, config.absolute_image
|
docker :pull, config.absolute_image
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def info
|
||||||
|
combine \
|
||||||
|
docker(:context, :ls),
|
||||||
|
docker(:buildx, :ls)
|
||||||
|
end
|
||||||
|
|
||||||
|
def buildx_inspect
|
||||||
|
docker :buildx, :inspect, builder_name
|
||||||
|
end
|
||||||
|
|
||||||
def build_options
|
def build_options
|
||||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
||||||
end
|
end
|
||||||
@@ -32,14 +53,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
:true
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
def first_mirror
|
def first_mirror
|
||||||
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ module Kamal::Commands::Builder::Clone
|
|||||||
end
|
end
|
||||||
|
|
||||||
def clone
|
def clone
|
||||||
git :clone, Kamal::Git.root, "--recurse-submodules", path: clone_directory
|
git :clone, Kamal::Git.root, path: clone_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone_reset_steps
|
def clone_reset_steps
|
||||||
@@ -14,8 +14,7 @@ module Kamal::Commands::Builder::Clone
|
|||||||
git(:remote, "set-url", :origin, Kamal::Git.root, path: build_directory),
|
git(:remote, "set-url", :origin, Kamal::Git.root, path: build_directory),
|
||||||
git(:fetch, :origin, path: build_directory),
|
git(:fetch, :origin, path: build_directory),
|
||||||
git(:reset, "--hard", Kamal::Git.revision, path: build_directory),
|
git(:reset, "--hard", Kamal::Git.revision, path: build_directory),
|
||||||
git(:clean, "-fdx", path: build_directory),
|
git(:clean, "-fdx", path: build_directory)
|
||||||
git(:submodule, :update, "--init", path: build_directory)
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
25
lib/kamal/commands/builder/hybrid.rb
Normal file
25
lib/kamal/commands/builder/hybrid.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
|
||||||
|
def create
|
||||||
|
combine \
|
||||||
|
create_local_buildx,
|
||||||
|
create_remote_context,
|
||||||
|
append_remote_buildx
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def builder_name
|
||||||
|
"kamal-hybrid-#{driver}-#{local_arch}-#{remote_arch}-#{remote_host.gsub(/[^a-z0-9_-]/, "-")}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_local_buildx
|
||||||
|
docker :buildx, :create, "--name", builder_name, "--platform", "linux/#{local_arch}", "--driver=#{driver}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def append_remote_buildx
|
||||||
|
docker :buildx, :create, "--append", "--name", builder_name, builder_name, "--platform", "linux/#{remote_arch}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def platform
|
||||||
|
"linux/#{local_arch},linux/#{remote_arch}"
|
||||||
|
end
|
||||||
|
end
|
||||||
24
lib/kamal/commands/builder/local.rb
Normal file
24
lib/kamal/commands/builder/local.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
|
||||||
|
def create
|
||||||
|
docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove
|
||||||
|
docker :buildx, :rm, builder_name unless docker_driver?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def builder_name
|
||||||
|
"kamal-local-#{driver}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def platform_options
|
||||||
|
if multiarch?
|
||||||
|
if local_arch
|
||||||
|
[ "--platform", "linux/#{local_arch}" ]
|
||||||
|
else
|
||||||
|
[ "--platform", "linux/amd64,linux/arm64" ]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
|
||||||
def create
|
|
||||||
docker :buildx, :create, "--use", "--name", builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
combine \
|
|
||||||
docker(:context, :ls),
|
|
||||||
docker(:buildx, :ls)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
"--platform", platform_names,
|
|
||||||
"--builder", builder_name,
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
docker :buildx, :inspect, builder_name, "> /dev/null"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-multiarch"
|
|
||||||
end
|
|
||||||
|
|
||||||
def platform_names
|
|
||||||
if local_arch
|
|
||||||
"linux/#{local_arch}"
|
|
||||||
else
|
|
||||||
"linux/amd64,linux/arm64"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Multiarch::Remote < Kamal::Commands::Builder::Multiarch
|
|
||||||
def create
|
|
||||||
combine \
|
|
||||||
create_contexts,
|
|
||||||
create_local_buildx,
|
|
||||||
append_remote_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
combine \
|
|
||||||
remove_contexts,
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
chain \
|
|
||||||
context_host(builder_name_with_arch(local_arch)),
|
|
||||||
context_host(builder_name_with_arch(remote_arch))
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[ local_host, remote_host ].compact
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
super + "-remote"
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_name_with_arch(arch)
|
|
||||||
"#{builder_name}-#{arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_local_buildx
|
|
||||||
docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local_arch), "--platform", "linux/#{local_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def append_remote_buildx
|
|
||||||
docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote_arch), "--platform", "linux/#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_contexts
|
|
||||||
combine \
|
|
||||||
create_context(local_arch, local_host),
|
|
||||||
create_context(remote_arch, remote_host)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_context(arch, host)
|
|
||||||
docker :context, :create, builder_name_with_arch(arch), "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_contexts
|
|
||||||
combine \
|
|
||||||
remove_context(local_arch),
|
|
||||||
remove_context(remote_arch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_context(arch)
|
|
||||||
docker :context, :rm, builder_name_with_arch(arch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def platform_names
|
|
||||||
"linux/#{local_arch},linux/#{remote_arch}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
|
|
||||||
def create
|
|
||||||
# No-op on native without cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
# No-op on native without cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
# No-op on native
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
combine \
|
|
||||||
docker(:build, *build_options, build_context),
|
|
||||||
docker(:push, config.absolute_image),
|
|
||||||
docker(:push, config.latest_image)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
|
|
||||||
def create
|
|
||||||
docker :buildx, :create, "--name", builder_name, "--use", "--driver=docker-container"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
docker :buildx, :inspect, builder_name, "> /dev/null"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-native-cached"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Native
|
|
||||||
def create
|
|
||||||
chain \
|
|
||||||
create_context,
|
|
||||||
create_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
chain \
|
|
||||||
remove_context,
|
|
||||||
remove_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
chain \
|
|
||||||
docker(:context, :ls),
|
|
||||||
docker(:buildx, :ls)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
"--platform", platform,
|
|
||||||
"--builder", builder_name,
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
context_host(builder_name_with_arch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[ remote_host ]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-native-remote"
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_name_with_arch
|
|
||||||
"#{builder_name}-#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def platform
|
|
||||||
"linux/#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_context
|
|
||||||
docker :context, :create,
|
|
||||||
builder_name_with_arch, "--description", "'#{builder_name} #{remote_arch} native host'", "--docker", "'host=#{remote_host}'"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_context
|
|
||||||
docker :context, :rm, builder_name_with_arch
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_buildx
|
|
||||||
docker :buildx, :create, "--name", builder_name, builder_name_with_arch, "--platform", platform
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_buildx
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
57
lib/kamal/commands/builder/remote.rb
Normal file
57
lib/kamal/commands/builder/remote.rb
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
|
||||||
|
def create
|
||||||
|
chain \
|
||||||
|
create_remote_context,
|
||||||
|
create_buildx
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove
|
||||||
|
chain \
|
||||||
|
remove_remote_context,
|
||||||
|
remove_buildx
|
||||||
|
end
|
||||||
|
|
||||||
|
def info
|
||||||
|
chain \
|
||||||
|
docker(:context, :ls),
|
||||||
|
docker(:buildx, :ls)
|
||||||
|
end
|
||||||
|
|
||||||
|
def push
|
||||||
|
docker :build,
|
||||||
|
"--push",
|
||||||
|
*platform_options,
|
||||||
|
"--builder", builder_name,
|
||||||
|
*build_options,
|
||||||
|
build_context
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def builder_name
|
||||||
|
"kamal-remote-#{driver}-#{remote_arch}-#{remote_host.gsub(/[^a-z0-9_-]/, "-")}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_remote_context
|
||||||
|
docker :context, :create, builder_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote_host}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_remote_context
|
||||||
|
docker :context, :rm, builder_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_buildx
|
||||||
|
docker :buildx, :create, "--name", builder_name, builder_name, "--platform", platform
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_buildx
|
||||||
|
docker :buildx, :rm, builder_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def platform_options
|
||||||
|
[ "--platform", platform ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def platform
|
||||||
|
"linux/#{remote_arch}"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -62,15 +62,6 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|||||||
[ :rm, "-f", env.secrets_file ]
|
[ :rm, "-f", env.secrets_file ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def cleanup_kamal_proxy
|
|
||||||
chain \
|
|
||||||
docker(:container, :stop, "kamal-proxy"),
|
|
||||||
combine(
|
|
||||||
docker(:container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"),
|
|
||||||
docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy")
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def publish_args
|
def publish_args
|
||||||
argumentize "--publish", port if publish?
|
argumentize "--publish", port if publish?
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ class Kamal::Configuration::Builder
|
|||||||
builder_config["context"] || "."
|
builder_config["context"] || "."
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_arch
|
def driver
|
||||||
builder_config["local"]["arch"] if local?
|
builder_config.fetch("driver", "docker-container")
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_host
|
def local_arch
|
||||||
builder_config["local"]["host"] if local?
|
builder_config["local"]["arch"] if local?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_arch
|
def remote_arch
|
||||||
@@ -114,7 +114,36 @@ class Kamal::Configuration::Builder
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def docker_driver?
|
||||||
|
driver == "docker"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
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?
|
||||||
|
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
|
||||||
|
|
||||||
def cache_image
|
def cache_image
|
||||||
builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
|
builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Builder
|
# Builder
|
||||||
#
|
#
|
||||||
# The builder configuration controls how the application is built with `docker build` or `docker buildx build`
|
# The builder configuration controls how the application is built with `docker build`
|
||||||
#
|
#
|
||||||
# If no configuration is specified, Kamal will:
|
# If no configuration is specified, Kamal will:
|
||||||
# 1. Create a buildx context called `kamal-<service>-multiarch`
|
# 1. Create a buildx context called `kamal-<service>-multiarch`
|
||||||
# 2. Use `docker buildx build` to build a multiarch image for linux/amd64,linux/arm64 with that context
|
# 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
|
# See https://kamal-deploy.org/docs/configuration/builder-examples/ for more information
|
||||||
|
|
||||||
@@ -18,6 +18,11 @@ builder:
|
|||||||
# Enables multiarch builds, defaults to `true`
|
# Enables multiarch builds, defaults to `true`
|
||||||
multiarch: false
|
multiarch: false
|
||||||
|
|
||||||
|
# Driver
|
||||||
|
#
|
||||||
|
# The build driver to use, defaults to `docker-container`
|
||||||
|
driver: docker
|
||||||
|
|
||||||
# Local configuration
|
# Local configuration
|
||||||
#
|
#
|
||||||
# The build configuration for local builds, only used if multiarch is enabled (the default)
|
# The build configuration for local builds, only used if multiarch is enabled (the default)
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ traefik:
|
|||||||
|
|
||||||
# Image
|
# Image
|
||||||
#
|
#
|
||||||
# The Traefik image to use, defaults to `traefik:v2.11`
|
# The Traefik image to use, defaults to `traefik:v2.10`
|
||||||
image: traefik:v2.11
|
image: traefik:v2.9
|
||||||
|
|
||||||
# Host port
|
# Host port
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Configuration::Traefik
|
class Kamal::Configuration::Traefik
|
||||||
DEFAULT_IMAGE = "traefik:v2.11"
|
DEFAULT_IMAGE = "traefik:v2.10"
|
||||||
CONTAINER_PORT = 80
|
CONTAINER_PORT = 80
|
||||||
DEFAULT_ARGS = {
|
DEFAULT_ARGS = {
|
||||||
"log.level" => "DEBUG"
|
"log.level" => "DEBUG"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Kamal
|
module Kamal
|
||||||
VERSION = "1.9.2"
|
VERSION = "1.8.1"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -209,24 +209,6 @@ class CliAccessoryTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "downgrade" do
|
|
||||||
run_command("downgrade", "-y", "all").tap do |output|
|
|
||||||
assert_match "Downgrading all accessories on 1.1.1.3,1.1.1.1,1.1.1.2...", output
|
|
||||||
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
|
||||||
assert_match "Downgraded all accessories on 1.1.1.3,1.1.1.1,1.1.1.2", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "downgrade rolling" do
|
|
||||||
run_command("downgrade", "--rolling", "-y", "all").tap do |output|
|
|
||||||
assert_match "Downgrading all accessories on 1.1.1.3...", output
|
|
||||||
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 --env-file .kamal/env/accessories/app-mysql.env --env MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
|
||||||
assert_match "Downgraded all accessories on 1.1.1.3", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Accessory.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
stdouted { Kamal::Cli::Accessory.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
||||||
|
|||||||
@@ -21,16 +21,12 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
run_command("push", "--verbose").tap do |output|
|
run_command("push", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-build", output, **hook_variables
|
assert_hook_ran "pre-build", output, **hook_variables
|
||||||
assert_match /Cloning repo into build directory/, output
|
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 /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output
|
||||||
assert_match /docker --version && docker buildx version/, 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
|
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -42,7 +38,7 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
|
.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"))
|
.raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory"))
|
||||||
.then
|
.then
|
||||||
.returns(true)
|
.returns(true)
|
||||||
@@ -50,10 +46,9 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :fetch, :origin)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :fetch, :origin)
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :reset, "--hard", Kamal::Git.revision)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :reset, "--hard", Kamal::Git.revision)
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx")
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx")
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(: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", ".")
|
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||||
@@ -78,7 +73,7 @@ class CliBuildTest < CliTestCase
|
|||||||
assert_no_match /Cloning repo into build directory/, output
|
assert_no_match /Cloning repo into build directory/, output
|
||||||
assert_hook_ran "pre-build", output, **hook_variables
|
assert_hook_ran "pre-build", output, **hook_variables
|
||||||
assert_match /docker --version && docker buildx version/, 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
|
assert_match /docker build --push --platform linux\/amd64,linux\/arm64 --builder kamal-local -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -89,7 +84,7 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
|
.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"))
|
.raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory"))
|
||||||
.then
|
.then
|
||||||
.returns(true)
|
.returns(true)
|
||||||
@@ -124,10 +119,10 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
.with(:docker, :buildx, :create, "--name", "kamal-local", "--driver=docker-container")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
.with(:docker, :buildx, :inspect, "kamal-local")
|
||||||
.raises(SSHKit::Command::Failed.new("no builder"))
|
.raises(SSHKit::Command::Failed.new("no builder"))
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
|
||||||
@@ -141,7 +136,7 @@ class CliBuildTest < CliTestCase
|
|||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(: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", ".")
|
.with(:docker, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-local", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
||||||
|
|
||||||
run_command("push").tap do |output|
|
run_command("push").tap do |output|
|
||||||
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
||||||
@@ -165,7 +160,7 @@ class CliBuildTest < CliTestCase
|
|||||||
error = assert_raises(Kamal::Cli::HookError) { run_command("push") }
|
error = assert_raises(Kamal::Cli::HookError) { run_command("push") }
|
||||||
assert_equal "Hook `pre-build` failed:\nfailed", error.message
|
assert_equal "Hook `pre-build` failed:\nfailed", error.message
|
||||||
|
|
||||||
assert @executions.none? { |args| args[0..2] == [ :docker, :buildx, :build ] }
|
assert @executions.none? { |args| args[0..2] == [ :docker, :build ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
test "pull" do
|
test "pull" do
|
||||||
@@ -207,23 +202,23 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "create" do
|
test "create" do
|
||||||
run_command("create").tap do |output|
|
run_command("create").tap do |output|
|
||||||
assert_match /docker buildx create --use --name kamal-app-multiarch/, output
|
assert_match /docker buildx create --name kamal-local --driver=docker-container/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create remote" do
|
test "create remote" do
|
||||||
run_command("create", fixture: :with_remote_builder).tap do |output|
|
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 "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 context create kamal-remote-amd64-ssh---app-1-1-1-5 --description 'kamal-remote-amd64-ssh---app-1-1-1-5 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
|
assert_match "docker buildx create --name kamal-remote-amd64-ssh---app-1-1-1-5 kamal-remote-amd64-ssh---app-1-1-1-5 --platform linux/amd64", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create remote with custom ports" do
|
test "create remote with custom ports" do
|
||||||
run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output|
|
run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output|
|
||||||
assert_match "Running /usr/bin/env true on 1.1.1.5", 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:2122'", output
|
assert_match "docker context create kamal-remote-amd64-ssh---app-1-1-1-5-2122 --description 'kamal-remote-amd64-ssh---app-1-1-1-5-2122 host' --docker 'host=ssh://app@1.1.1.5:2122'", output
|
||||||
assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", output
|
assert_match "docker buildx create --name kamal-remote-amd64-ssh---app-1-1-1-5-2122 kamal-remote-amd64-ssh---app-1-1-1-5-2122 --platform linux/amd64", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -240,7 +235,7 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "remove" do
|
test "remove" do
|
||||||
run_command("remove").tap do |output|
|
run_command("remove").tap do |output|
|
||||||
assert_match /docker buildx rm kamal-app-multiarch/, output
|
assert_match /docker buildx rm kamal-local/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -250,7 +245,7 @@ class CliBuildTest < CliTestCase
|
|||||||
.returns("docker builder info")
|
.returns("docker builder info")
|
||||||
|
|
||||||
run_command("details").tap do |output|
|
run_command("details").tap do |output|
|
||||||
assert_match /Builder: multiarch/, output
|
assert_match /Builder: local/, output
|
||||||
assert_match /docker builder info/, output
|
assert_match /docker builder info/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |*args| args[0..2] == [ :docker, :buildx, :inspect ] }
|
.with(:docker, :buildx, :inspect, "kamal-local")
|
||||||
.returns("")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)
|
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)
|
||||||
|
|||||||
@@ -121,10 +121,6 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
.returns("")
|
.returns("")
|
||||||
@@ -159,10 +155,6 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
.returns("")
|
.returns("")
|
||||||
@@ -537,34 +529,6 @@ class CliMainTest < CliTestCase
|
|||||||
assert_equal Kamal::VERSION, version
|
assert_equal Kamal::VERSION, version
|
||||||
end
|
end
|
||||||
|
|
||||||
test "downgrade" do
|
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_hooks" => false, "confirmed" => true, "rolling" => false }
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:downgrade", [], invoke_options)
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:downgrade", [ "all" ], invoke_options)
|
|
||||||
|
|
||||||
run_command("downgrade", "-y", config_file: "deploy_with_accessories").tap do |output|
|
|
||||||
assert_match "Downgrading all hosts...", output
|
|
||||||
assert_match "Downgraded all hosts", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "downgrade rolling" do
|
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_hooks" => false, "confirmed" => true, "rolling" => false }
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:downgrade", [], invoke_options).times(4)
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:downgrade", [ "all" ], invoke_options).times(3)
|
|
||||||
|
|
||||||
run_command("downgrade", "--rolling", "-y", config_file: "deploy_with_accessories").tap do |output|
|
|
||||||
assert_match "Downgrading 1.1.1.1...", output
|
|
||||||
assert_match "Downgraded 1.1.1.1", output
|
|
||||||
assert_match "Downgrading 1.1.1.2...", output
|
|
||||||
assert_match "Downgraded 1.1.1.2", output
|
|
||||||
assert_match "Downgrading 1.1.1.3...", output
|
|
||||||
assert_match "Downgraded 1.1.1.3", output
|
|
||||||
assert_match "Downgrading 1.1.1.4...", output
|
|
||||||
assert_match "Downgraded 1.1.1.4", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command, config_file: "deploy_simple")
|
def run_command(*command, config_file: "deploy_simple")
|
||||||
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) }
|
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) }
|
||||||
|
|||||||
@@ -103,90 +103,6 @@ class CliTraefikTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "downgrade" do
|
|
||||||
Object.any_instance.stubs(:sleep)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", raise_on_non_zero_exit: false)
|
|
||||||
.returns("12345678")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", raise_on_non_zero_exit: false)
|
|
||||||
.returns("12345678")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with { |*args| args[0..1] == [ :sh, "-c" ] }
|
|
||||||
.returns("123") # old version
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
|
||||||
.returns("running") # health check
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
|
||||||
.returns("running").at_least_once # workers health check
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", "app-web-123", "|", :awk, "'$2 == \"/tmp/kamal-cord\" {print $1}'", raise_on_non_zero_exit: false)
|
|
||||||
.returns("") # old version
|
|
||||||
|
|
||||||
run_command("downgrade", "-y").tap do |output|
|
|
||||||
assert_match "Downgrading to Traefik on 1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4...", output
|
|
||||||
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
|
||||||
assert_match "docker container stop kamal-proxy ; docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy && docker image prune --all --force --filter label=org.opencontainers.image.title=kamal-proxy", output
|
|
||||||
assert_match "docker container stop traefik", output
|
|
||||||
assert_match "docker container prune --force --filter label=org.opencontainers.image.title=Traefik", output
|
|
||||||
assert_match "docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik", output
|
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal", output
|
|
||||||
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
|
||||||
assert_match "docker container start traefik || docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --env-file .kamal/env/traefik/traefik.env --log-opt max-size=\"10m\" --label traefik.http.routers.catchall.entryPoints=\"http\" --label traefik.http.routers.catchall.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.catchall.service=\"unavailable\" --label traefik.http.routers.catchall.priority=\"1\" --label traefik.http.services.unavailable.loadbalancer.server.port=\"0\" traefik:v2.11 --providers.docker --log.level=\"DEBUG\"", output
|
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal", output
|
|
||||||
assert_match %r{docker rename app-web-latest app-web-latest_replaced_.*}, output
|
|
||||||
assert_match %r{docker run --detach --restart unless-stopped --name app-web-latest --hostname 1.1.1.1-.* -e KAMAL_CONTAINER_NAME="app-web-latest" -e KAMAL_VERSION="latest" --env-file .kamal/env/roles/app-web.env --health-cmd}, output
|
|
||||||
assert_match "docker tag dhh/app:latest dhh/app:latest", output
|
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal", output
|
|
||||||
assert_match "docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +6 | while read container_id; do docker rm $container_id; done", output
|
|
||||||
assert_match "docker image prune --force --filter label=service=app", output
|
|
||||||
assert_match "Downgraded to Traefik on 1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "downgrade rolling" do
|
|
||||||
Object.any_instance.stubs(:sleep)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", raise_on_non_zero_exit: false)
|
|
||||||
.returns("12345678")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", raise_on_non_zero_exit: false)
|
|
||||||
.returns("12345678")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with { |*args| args[0..1] == [ :sh, "-c" ] }
|
|
||||||
.returns("123") # old version
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
|
||||||
.returns("running") # health check
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
|
||||||
.returns("running").at_least_once # workers health check
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
|
||||||
.with(:docker, :inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", "app-web-123", "|", :awk, "'$2 == \"/tmp/kamal-cord\" {print $1}'", raise_on_non_zero_exit: false)
|
|
||||||
.returns("") # old version
|
|
||||||
|
|
||||||
run_command("downgrade", "--rolling", "-y",).tap do |output|
|
|
||||||
%w[1.1.1.1 1.1.1.2 1.1.1.3 1.1.1.4].each do |host|
|
|
||||||
assert_match "Downgrading to Traefik on #{host}...", output
|
|
||||||
assert_match "docker container stop kamal-proxy ; docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy && docker image prune --all --force --filter label=org.opencontainers.image.title=kamal-proxy", output
|
|
||||||
assert_match "Downgraded to Traefik on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Traefik.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
stdouted { Kamal::Cli::Traefik.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
||||||
|
|||||||
@@ -7,49 +7,49 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "target multiarch by default" do
|
test "target multiarch by default" do
|
||||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||||
assert_equal "multiarch", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native when multiarch is off" do
|
test "target native when multiarch is off" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false })
|
builder = new_builder_command(builder: { "multiarch" => false })
|
||||||
assert_equal "native", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native cached when multiarch is off and cache is set" do
|
test "target native cached when multiarch is off and cache is set" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "gha" } })
|
||||||
assert_equal "native/cached", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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 .",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target multiarch remote when local and remote is set" do
|
test "target multiarch remote when local and remote is set" do
|
||||||
builder = new_builder_command(builder: { "local" => { "arch" => "arm64" }, "remote" => { "arch" => "amd64" }, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "local" => { "arch" => "arm64" }, "remote" => { "arch" => "amd64", "host" => "ssh://app@127.0.0.1" }, "cache" => { "type" => "gha" } })
|
||||||
assert_equal "multiarch/remote", builder.name
|
assert_equal "hybrid", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/arm64,linux/amd64 --builder kamal-app-multiarch-remote -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker build --push --platform linux/arm64,linux/amd64 --builder kamal-hybrid-arm64-amd64-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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target multiarch local when arch is set" do
|
test "target multiarch local when arch is set" do
|
||||||
builder = new_builder_command(builder: { "local" => { "arch" => "amd64" } })
|
builder = new_builder_command(builder: { "local" => { "arch" => "amd64" } })
|
||||||
assert_equal "multiarch", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
"docker build --push --platform linux/amd64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native remote when only remote is set" do
|
test "target native remote when only remote is set" do
|
||||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "cache" => { "type" => "gha" } })
|
||||||
assert_equal "native/remote", builder.name
|
assert_equal "remote", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64 --builder kamal-app-native-remote -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker build --push --platform linux/amd64 --builder kamal-remote-amd64-ssh---app-host -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -93,28 +93,28 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
test "build context" do
|
test "build context" do
|
||||||
builder = new_builder_command(builder: { "context" => ".." })
|
builder = new_builder_command(builder: { "context" => ".." })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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 ..",
|
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "native push with build args" do
|
test "native push with build args" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
|
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiarch push with build args" do
|
test "multiarch push with build args" do
|
||||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
|
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "native push with build secrets" do
|
test "native push with build secrets" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
|
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,73 +133,31 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
test "multiarch context build" do
|
test "multiarch context build" do
|
||||||
builder = new_builder_command(builder: { "context" => "./foo" })
|
builder = new_builder_command(builder: { "context" => "./foo" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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 ./foo",
|
"docker build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "native context build" do
|
test "native context build" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo" })
|
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo && docker push dhh/app:123 && docker push dhh/app:latest",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "cached context build" do
|
test "cached context build" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo", "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo", "cache" => { "type" => "gha" } })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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 ./foo",
|
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile ./foo",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remote context build" do
|
test "remote context build" do
|
||||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "context" => "./foo" })
|
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "context" => "./foo" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"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 ./foo",
|
"docker build --push --platform linux/amd64 --builder kamal-remote-amd64-ssh---app-host -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiarch context hosts" do
|
|
||||||
command = new_builder_command
|
|
||||||
assert_equal "docker buildx inspect kamal-app-multiarch > /dev/null", command.context_hosts.join(" ")
|
|
||||||
assert_equal "", command.config_context_hosts.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "native context hosts" do
|
|
||||||
command = new_builder_command(builder: { "multiarch" => false })
|
|
||||||
assert_equal :true, command.context_hosts
|
|
||||||
assert_equal "", command.config_context_hosts.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "native cached context hosts" do
|
|
||||||
command = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "registry" } })
|
|
||||||
assert_equal "docker buildx inspect kamal-app-native-cached > /dev/null", command.context_hosts.join(" ")
|
|
||||||
assert_equal "", command.config_context_hosts.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "native remote context hosts" do
|
|
||||||
command = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://host" } })
|
|
||||||
assert_equal "docker context inspect kamal-app-native-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
|
||||||
assert_equal [ "ssh://host" ], command.config_context_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
test "multiarch remote context hosts" do
|
|
||||||
command = new_builder_command(builder: {
|
|
||||||
"remote" => { "arch" => "amd64", "host" => "ssh://host" },
|
|
||||||
"local" => { "arch" => "arm64" }
|
|
||||||
})
|
|
||||||
assert_equal "docker context inspect kamal-app-multiarch-remote-arm64 --format '{{.Endpoints.docker.Host}}' ; docker context inspect kamal-app-multiarch-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
|
||||||
assert_equal [ "ssh://host" ], command.config_context_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
test "multiarch remote context hosts with local host" do
|
|
||||||
command = new_builder_command(builder: {
|
|
||||||
"remote" => { "arch" => "amd64", "host" => "ssh://host" },
|
|
||||||
"local" => { "arch" => "arm64", "host" => "unix:///var/run/docker.sock" }
|
|
||||||
})
|
|
||||||
assert_equal "docker context inspect kamal-app-multiarch-remote-arm64 --format '{{.Endpoints.docker.Host}}' ; docker context inspect kamal-app-multiarch-remote-amd64 --format '{{.Endpoints.docker.Host}}'", command.context_hosts.join(" ")
|
|
||||||
assert_equal [ "unix:///var/run/docker.sock", "ssh://host" ], command.config_context_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
test "mirror count" do
|
test "mirror count" do
|
||||||
command = new_builder_command
|
command = new_builder_command
|
||||||
assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ")
|
assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ")
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "setting both local and remote configs" do
|
test "setting both local and remote configs" do
|
||||||
@deploy_with_builder_option[:builder] = {
|
@deploy_with_builder_option[:builder] = {
|
||||||
"local" => { "arch" => "arm64", "host" => "unix:///Users/<%= `whoami`.strip %>/.docker/run/docker.sock" },
|
"local" => { "arch" => "arm64" },
|
||||||
"remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" }
|
"remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,6 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_equal "ssh://root@192.168.0.1", config_with_builder_option.builder.remote_host
|
assert_equal "ssh://root@192.168.0.1", config_with_builder_option.builder.remote_host
|
||||||
|
|
||||||
assert_equal "arm64", config_with_builder_option.builder.local_arch
|
assert_equal "arm64", config_with_builder_option.builder.local_arch
|
||||||
assert_equal "unix:///Users/<%= `whoami`.strip %>/.docker/run/docker.sock", config_with_builder_option.builder.local_host
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "cached?" do
|
test "cached?" do
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ registry:
|
|||||||
password: root
|
password: root
|
||||||
builder:
|
builder:
|
||||||
multiarch: false
|
multiarch: false
|
||||||
|
driver: docker
|
||||||
args:
|
args:
|
||||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -33,7 +34,7 @@ traefik:
|
|||||||
args:
|
args:
|
||||||
accesslog: true
|
accesslog: true
|
||||||
accesslog.format: json
|
accesslog.format: json
|
||||||
image: registry:4443/traefik:v2.11
|
image: registry:4443/traefik:v2.10
|
||||||
accessories:
|
accessories:
|
||||||
busybox:
|
busybox:
|
||||||
service: custom-busybox
|
service: custom-busybox
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ registry:
|
|||||||
password: root
|
password: root
|
||||||
builder:
|
builder:
|
||||||
multiarch: false
|
multiarch: false
|
||||||
|
driver: docker
|
||||||
args:
|
args:
|
||||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -27,7 +28,7 @@ traefik:
|
|||||||
args:
|
args:
|
||||||
accesslog: true
|
accesslog: true
|
||||||
accesslog.format: json
|
accesslog.format: json
|
||||||
image: registry:4443/traefik:v2.11
|
image: registry:4443/traefik:v2.10
|
||||||
accessories:
|
accessories:
|
||||||
busybox:
|
busybox:
|
||||||
service: custom-busybox
|
service: custom-busybox
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ push_image_to_registry_4443() {
|
|||||||
|
|
||||||
install_kamal
|
install_kamal
|
||||||
push_image_to_registry_4443 nginx 1-alpine-slim
|
push_image_to_registry_4443 nginx 1-alpine-slim
|
||||||
push_image_to_registry_4443 traefik v2.11
|
push_image_to_registry_4443 traefik v2.10
|
||||||
push_image_to_registry_4443 busybox 1.36.0
|
push_image_to_registry_4443 busybox 1.36.0
|
||||||
|
|
||||||
# .ssh is on a shared volume that persists between runs. Clean it up as the
|
# .ssh is on a shared volume that persists between runs. Clean it up as the
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class MainTest < IntegrationTest
|
|||||||
assert_match /Traefik Host: vm2/, details
|
assert_match /Traefik Host: vm2/, details
|
||||||
assert_match /App Host: vm1/, details
|
assert_match /App Host: vm1/, details
|
||||||
assert_match /App Host: vm2/, details
|
assert_match /App Host: vm2/, details
|
||||||
assert_match /traefik:v2.11/, details
|
assert_match /traefik:v2.10/, details
|
||||||
assert_match /registry:4443\/app:#{first_version}/, details
|
assert_match /registry:4443\/app:#{first_version}/, details
|
||||||
|
|
||||||
audit = kamal :audit, capture: true
|
audit = kamal :audit, capture: true
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ class TraefikTest < IntegrationTest
|
|||||||
|
|
||||||
private
|
private
|
||||||
def assert_traefik_running
|
def assert_traefik_running
|
||||||
assert_match /traefik:v2.11 "\/entrypoint.sh/, traefik_details
|
assert_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_traefik_not_running
|
def assert_traefik_not_running
|
||||||
assert_no_match /traefik:v2.11 "\/entrypoint.sh/, traefik_details
|
assert_no_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||||
end
|
end
|
||||||
|
|
||||||
def traefik_details
|
def traefik_details
|
||||||
|
|||||||
Reference in New Issue
Block a user