Compare commits
44 Commits
v1.7.3
...
simplify-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4435fdf6fc | ||
|
|
2dd0ccc276 | ||
|
|
efb2a5d7c6 | ||
|
|
e7431f9832 | ||
|
|
cf80932216 | ||
|
|
cf81837737 | ||
|
|
8567ce9bf9 | ||
|
|
5d8e4dee13 | ||
|
|
10a7645ab8 | ||
|
|
c93f0f3048 | ||
|
|
6cedec68e3 | ||
|
|
2a4a8ac859 | ||
|
|
f48987aa03 | ||
|
|
ef051eca1b | ||
|
|
173d44ee0a | ||
|
|
4e811372f8 | ||
|
|
ec4aa45852 | ||
|
|
5e11a64181 | ||
|
|
57d9ce177a | ||
|
|
8a98949634 | ||
|
|
0eb9f48082 | ||
|
|
9db6fc0704 | ||
|
|
27fede3caa | ||
|
|
29c723f7ec | ||
|
|
2755582c47 | ||
|
|
fa73d722ea | ||
|
|
c535e4e44f | ||
|
|
0ea07b1760 | ||
|
|
03b531f179 | ||
|
|
d8570d1c2c | ||
|
|
3fe70b458d | ||
|
|
ade8b43599 | ||
|
|
d24fc3ca4e | ||
|
|
7c244bbb98 | ||
|
|
1369c46a83 | ||
|
|
deccf1cfaf | ||
|
|
1573cebadf | ||
|
|
85a2926cde | ||
|
|
58a51b079e | ||
|
|
f1f3fc566f | ||
|
|
44726ff65a | ||
|
|
fd0d4af21f | ||
|
|
13409ada5a | ||
|
|
e160852e4d |
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -24,25 +24,12 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
ruby-version:
|
||||
- "2.7"
|
||||
- "3.1"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
gemfile:
|
||||
- Gemfile
|
||||
- gemfiles/ruby_2.7.gemfile
|
||||
- gemfiles/rails_edge.gemfile
|
||||
exclude:
|
||||
- ruby-version: "2.7"
|
||||
gemfile: Gemfile
|
||||
- ruby-version: "2.7"
|
||||
gemfile: gemfiles/rails_edge.gemfile
|
||||
- ruby-version: "3.1"
|
||||
gemfile: gemfiles/ruby_2.7.gemfile
|
||||
- ruby-version: "3.2"
|
||||
gemfile: gemfiles/ruby_2.7.gemfile
|
||||
- ruby-version: "3.3"
|
||||
gemfile: gemfiles/ruby_2.7.gemfile
|
||||
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Use the official Ruby 3.2.0 Alpine image as the base image
|
||||
FROM ruby:3.2.0-alpine
|
||||
|
||||
# Install docker/buildx-bin
|
||||
# Install docker/buildx-bin
|
||||
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||
|
||||
# Set the working directory to /kamal
|
||||
@@ -14,7 +14,7 @@ COPY Gemfile Gemfile.lock kamal.gemspec ./
|
||||
COPY lib/kamal/version.rb /kamal/lib/kamal/version.rb
|
||||
|
||||
# Install system dependencies
|
||||
RUN apk add --no-cache --update build-base git docker openrc openssh-client-default \
|
||||
RUN apk add --no-cache build-base git docker openrc openssh-client-default \
|
||||
&& rc-update add docker boot \
|
||||
&& gem install bundler --version=2.4.3 \
|
||||
&& bundle install
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
kamal (1.7.3)
|
||||
kamal (1.8.1)
|
||||
activesupport (>= 7.0)
|
||||
base64 (~> 0.2)
|
||||
bcrypt_pbkdf (~> 1.0)
|
||||
@@ -9,9 +9,8 @@ PATH
|
||||
dotenv (~> 2.8)
|
||||
ed25519 (~> 1.2)
|
||||
net-ssh (~> 7.0)
|
||||
sshkit (>= 1.22.2, < 2.0)
|
||||
sshkit (>= 1.23.0, < 2.0)
|
||||
thor (~> 1.2)
|
||||
x25519 (~> 1.0, >= 1.0.10)
|
||||
zeitwerk (~> 2.5)
|
||||
|
||||
GEM
|
||||
@@ -154,9 +153,8 @@ GEM
|
||||
rubocop-rails
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sshkit (1.22.2)
|
||||
sshkit (1.23.0)
|
||||
base64
|
||||
mutex_m
|
||||
net-scp (>= 1.1.2)
|
||||
net-sftp (>= 2.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
@@ -166,7 +164,6 @@ GEM
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
webrick (1.8.1)
|
||||
x25519 (1.0.10)
|
||||
zeitwerk (2.6.12)
|
||||
|
||||
PLATFORMS
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
gemspec path: "../"
|
||||
|
||||
gem "nokogiri", "~> 1.15.0"
|
||||
@@ -12,13 +12,12 @@ Gem::Specification.new do |spec|
|
||||
spec.executables = %w[ kamal ]
|
||||
|
||||
spec.add_dependency "activesupport", ">= 7.0"
|
||||
spec.add_dependency "sshkit", ">= 1.22.2", "< 2.0"
|
||||
spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0"
|
||||
spec.add_dependency "net-ssh", "~> 7.0"
|
||||
spec.add_dependency "thor", "~> 1.2"
|
||||
spec.add_dependency "dotenv", "~> 2.8"
|
||||
spec.add_dependency "zeitwerk", "~> 2.5"
|
||||
spec.add_dependency "ed25519", "~> 1.2"
|
||||
spec.add_dependency "x25519", "~> 1.0", ">= 1.0.10"
|
||||
spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
|
||||
spec.add_dependency "concurrent-ruby", "~> 1.2"
|
||||
spec.add_dependency "base64", "~> 0.2"
|
||||
|
||||
@@ -25,12 +25,17 @@ module Kamal::Cli
|
||||
def initialize(*)
|
||||
super
|
||||
@original_env = ENV.to_h.dup
|
||||
load_envs
|
||||
load_env
|
||||
initialize_commander(options_with_subcommand_class_options)
|
||||
end
|
||||
|
||||
private
|
||||
def load_envs
|
||||
def reload_env
|
||||
reset_env
|
||||
load_env
|
||||
end
|
||||
|
||||
def load_env
|
||||
if destination = options[:destination]
|
||||
Dotenv.load(".env.#{destination}", ".env")
|
||||
else
|
||||
@@ -38,10 +43,27 @@ module Kamal::Cli
|
||||
end
|
||||
end
|
||||
|
||||
def reload_envs
|
||||
def reset_env
|
||||
replace_env @original_env
|
||||
end
|
||||
|
||||
def replace_env(env)
|
||||
ENV.clear
|
||||
ENV.update(@original_env)
|
||||
load_envs
|
||||
ENV.update(env)
|
||||
end
|
||||
|
||||
def with_original_env
|
||||
keeping_current_env do
|
||||
reset_env
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def keeping_current_env
|
||||
current_env = ENV.to_h.dup
|
||||
yield
|
||||
ensure
|
||||
replace_env(current_env)
|
||||
end
|
||||
|
||||
def options_with_subcommand_class_options
|
||||
|
||||
@@ -30,18 +30,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
||||
end
|
||||
|
||||
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
||||
push = KAMAL.builder.push
|
||||
|
||||
run_locally do
|
||||
begin
|
||||
context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
|
||||
|
||||
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
|
||||
execute *KAMAL.builder.buildx_inspect
|
||||
rescue SSHKit::Command::Failed => e
|
||||
if e.message =~ /(context not found|no builder|does not exist)/
|
||||
warn "Missing compatible builder, so creating a new one first"
|
||||
@@ -51,6 +42,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
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
|
||||
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
||||
end
|
||||
@@ -59,11 +53,14 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
|
||||
desc "pull", "Pull app image from registry onto servers"
|
||||
def pull
|
||||
on(KAMAL.hosts) do
|
||||
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
||||
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
||||
execute *KAMAL.builder.pull
|
||||
execute *KAMAL.builder.validate_image
|
||||
if (first_hosts = mirror_hosts).any?
|
||||
# Pull on a single host per mirror first to seed them
|
||||
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
|
||||
pull_on_hosts(first_hosts)
|
||||
say "Pulling image on remaining hosts...", :magenta
|
||||
pull_on_hosts(KAMAL.hosts - first_hosts)
|
||||
else
|
||||
pull_on_hosts(KAMAL.hosts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,4 +128,28 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mirror_hosts
|
||||
if KAMAL.hosts.many?
|
||||
mirror_hosts = Concurrent::Hash.new
|
||||
on(KAMAL.hosts) do |host|
|
||||
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
|
||||
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
|
||||
rescue SSHKit::Command::Failed => e
|
||||
raise unless e.message =~ /error calling index: reflect: slice index out of range/
|
||||
end
|
||||
mirror_hosts.values
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def pull_on_hosts(hosts)
|
||||
on(hosts) do
|
||||
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
||||
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
||||
execute *KAMAL.builder.pull
|
||||
execute *KAMAL.builder.validate_image
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -191,10 +191,12 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
||||
end
|
||||
|
||||
if Pathname.new(File.expand_path(env_template_path)).exist?
|
||||
File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
|
||||
# Ensure existing env doesn't pollute template evaluation
|
||||
content = with_original_env { ERB.new(File.read(env_template_path), trim_mode: "-").result }
|
||||
File.write(env_path, content, perm: 0600)
|
||||
|
||||
unless options[:skip_push]
|
||||
reload_envs
|
||||
reload_env
|
||||
invoke "kamal:cli:env:push", options
|
||||
end
|
||||
else
|
||||
|
||||
@@ -9,7 +9,7 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
|
||||
# Runs remotely
|
||||
def record(line, **details)
|
||||
append \
|
||||
[ :echo, audit_tags(**details).except(:version, :service_version).to_s, line ],
|
||||
[ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ],
|
||||
audit_log_file
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
require "active_support/core_ext/string/filters"
|
||||
|
||||
class Kamal::Commands::Builder < Kamal::Commands::Base
|
||||
delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
|
||||
to: :target
|
||||
delegate :create, :remove, :push, :clean, :pull, :info, :buildx_inspect, :validate_image, :first_mirror, to: :target
|
||||
delegate :multiarch?, :local?, :remote?, to: "config.builder"
|
||||
|
||||
include Clone
|
||||
|
||||
@@ -11,43 +11,27 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def target
|
||||
if config.builder.multiarch?
|
||||
if config.builder.remote?
|
||||
if config.builder.local?
|
||||
multiarch_remote
|
||||
else
|
||||
native_remote
|
||||
end
|
||||
if remote?
|
||||
if local?
|
||||
hybrid
|
||||
else
|
||||
multiarch
|
||||
remote
|
||||
end
|
||||
else
|
||||
if config.builder.cached?
|
||||
native_cached
|
||||
else
|
||||
native
|
||||
end
|
||||
local
|
||||
end
|
||||
end
|
||||
|
||||
def native
|
||||
@native ||= Kamal::Commands::Builder::Native.new(config)
|
||||
def remote
|
||||
@remote ||= Kamal::Commands::Builder::Remote.new(config)
|
||||
end
|
||||
|
||||
def native_cached
|
||||
@native ||= Kamal::Commands::Builder::Native::Cached.new(config)
|
||||
def local
|
||||
@local ||= Kamal::Commands::Builder::Local.new(config)
|
||||
end
|
||||
|
||||
def native_remote
|
||||
@native ||= Kamal::Commands::Builder::Native::Remote.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)
|
||||
def hybrid
|
||||
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
|
||||
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
class BuilderError < StandardError; end
|
||||
|
||||
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
||||
|
||||
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
|
||||
docker :image, :rm, "--force", config.absolute_image
|
||||
end
|
||||
|
||||
def push
|
||||
docker :build,
|
||||
"--push",
|
||||
*platform_options,
|
||||
*([ "--builder", builder_name ] unless docker_driver?),
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
|
||||
def pull
|
||||
docker :pull, config.absolute_image
|
||||
end
|
||||
|
||||
def info
|
||||
combine \
|
||||
docker(:context, :ls),
|
||||
docker(:buildx, :ls)
|
||||
end
|
||||
|
||||
def buildx_inspect
|
||||
docker :buildx, :inspect, builder_name
|
||||
end
|
||||
|
||||
def build_options
|
||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
||||
end
|
||||
@@ -32,12 +53,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
)
|
||||
end
|
||||
|
||||
def context_hosts
|
||||
:true
|
||||
end
|
||||
|
||||
def config_context_hosts
|
||||
[]
|
||||
def first_mirror
|
||||
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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,61 +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
|
||||
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
|
||||
@@ -47,7 +47,7 @@ class Kamal::Configuration
|
||||
@destination = destination
|
||||
@declared_version = version
|
||||
|
||||
validate! raw_config, example: validation_yml.symbolize_keys, context: ""
|
||||
validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration
|
||||
|
||||
# Eager load config to validate it, these are first as they have dependencies later on
|
||||
@servers = Servers.new(config: self)
|
||||
|
||||
@@ -55,12 +55,12 @@ class Kamal::Configuration::Builder
|
||||
builder_config["context"] || "."
|
||||
end
|
||||
|
||||
def local_arch
|
||||
builder_config["local"]["arch"] if local?
|
||||
def driver
|
||||
builder_config.fetch("driver", "docker-container")
|
||||
end
|
||||
|
||||
def local_host
|
||||
builder_config["local"]["host"] if local?
|
||||
def local_arch
|
||||
builder_config["local"]["arch"] if local?
|
||||
end
|
||||
|
||||
def remote_arch
|
||||
@@ -114,7 +114,36 @@ class Kamal::Configuration::Builder
|
||||
end
|
||||
end
|
||||
|
||||
def docker_driver?
|
||||
driver == "docker"
|
||||
end
|
||||
|
||||
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
|
||||
builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
|
||||
end
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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:
|
||||
# 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
|
||||
|
||||
@@ -18,6 +18,11 @@ builder:
|
||||
# Enables multiarch builds, defaults to `true`
|
||||
multiarch: false
|
||||
|
||||
# Driver
|
||||
#
|
||||
# The build driver to use, defaults to `docker-container`
|
||||
driver: docker
|
||||
|
||||
# Local configuration
|
||||
#
|
||||
# The build configuration for local builds, only used if multiarch is enabled (the default)
|
||||
|
||||
@@ -2,13 +2,24 @@
|
||||
#
|
||||
# Configuration is read from the `config/deploy.yml`
|
||||
#
|
||||
|
||||
# Destinations
|
||||
#
|
||||
# When running commands, you can specify a destination with the `-d` flag,
|
||||
# e.g. `kamal deploy -d staging`
|
||||
#
|
||||
# In this case the configuration will also be read from `config/deploy.staging.yml`
|
||||
# and merged with the base configuration.
|
||||
|
||||
# Extensions
|
||||
#
|
||||
# The available configuration options are explained below.
|
||||
# Kamal will not accept unrecognized keys in the configuration file.
|
||||
#
|
||||
# However, you might want to declare a configuration block using YAML anchors
|
||||
# and aliases to avoid repetition.
|
||||
#
|
||||
# You can use prefix a configuration section with `x-` to indicate that it is an
|
||||
# extension. Kamal will ignore the extension and not raise an error.
|
||||
|
||||
# The service name
|
||||
# This is a required value. It is used as the container name prefix.
|
||||
|
||||
@@ -29,7 +29,7 @@ env:
|
||||
# To pass the secrets you should list them under the `secret` key. When you do this the
|
||||
# other variables need to be moved under the `clear` key.
|
||||
#
|
||||
# Unlike clear valies, secrets are not passed directly to the container,
|
||||
# Unlike clear values, secrets are not passed directly to the container,
|
||||
# but are stored in an env file on the host
|
||||
# The file is not updated when deploying, only when running `kamal envify` or `kamal env push`.
|
||||
env:
|
||||
|
||||
@@ -44,3 +44,23 @@ ssh:
|
||||
# Defaults to `fatal`. Set this to debug if you are having
|
||||
# SSH connection issues.
|
||||
log_level: debug
|
||||
|
||||
# Keys Only
|
||||
#
|
||||
# Set to true to use only private keys from keys and key_data parameters,
|
||||
# even if ssh-agent offers more identities. This option is intended for
|
||||
# situations where ssh-agent offers many different identites or you have
|
||||
# a need to overwrite all identites and force a single one.
|
||||
keys_only: false
|
||||
|
||||
# Keys
|
||||
#
|
||||
# An array of file names of private keys to use for publickey
|
||||
# and hostbased authentication
|
||||
keys: [ "~/.ssh/id.pem" ]
|
||||
|
||||
# Key Data
|
||||
#
|
||||
# An array of strings, with each element of the array being
|
||||
# a raw private key in PEM format.
|
||||
key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]
|
||||
|
||||
@@ -26,8 +26,20 @@ class Kamal::Configuration::Ssh
|
||||
end
|
||||
end
|
||||
|
||||
def keys_only
|
||||
ssh_config["keys_only"]
|
||||
end
|
||||
|
||||
def keys
|
||||
ssh_config["keys"]
|
||||
end
|
||||
|
||||
def key_data
|
||||
ssh_config["key_data"]
|
||||
end
|
||||
|
||||
def options
|
||||
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
|
||||
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
|
||||
end
|
||||
|
||||
def to_h
|
||||
|
||||
@@ -15,11 +15,10 @@ class Kamal::Configuration::Validator
|
||||
def validate_against_example!(validation_config, example)
|
||||
validate_type! validation_config, Hash
|
||||
|
||||
if (unknown_keys = validation_config.keys - example.keys).any?
|
||||
unknown_keys_error unknown_keys
|
||||
end
|
||||
check_unknown_keys! validation_config, example
|
||||
|
||||
validation_config.each do |key, value|
|
||||
next if extension?(key)
|
||||
with_context(key) do
|
||||
example_value = example[key]
|
||||
|
||||
@@ -137,4 +136,18 @@ class Kamal::Configuration::Validator
|
||||
ensure
|
||||
@context = old_context
|
||||
end
|
||||
|
||||
def allow_extensions?
|
||||
false
|
||||
end
|
||||
|
||||
def extension?(key)
|
||||
key.to_s.start_with?("x-")
|
||||
end
|
||||
|
||||
def check_unknown_keys!(config, example)
|
||||
unknown_keys = config.keys - example.keys
|
||||
unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
|
||||
unknown_keys_error unknown_keys if unknown_keys.present?
|
||||
end
|
||||
end
|
||||
|
||||
6
lib/kamal/configuration/validator/configuration.rb
Normal file
6
lib/kamal/configuration/validator/configuration.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Kamal::Configuration::Validator::Configuration < Kamal::Configuration::Validator
|
||||
private
|
||||
def allow_extensions?
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -9,6 +9,10 @@ module Kamal::Git
|
||||
`git config user.name`.strip
|
||||
end
|
||||
|
||||
def email
|
||||
`git config user.email`.strip
|
||||
end
|
||||
|
||||
def revision
|
||||
`git rev-parse HEAD`.strip
|
||||
end
|
||||
|
||||
@@ -10,10 +10,11 @@ class Kamal::Tags
|
||||
|
||||
def default_tags(config)
|
||||
{ recorded_at: Time.now.utc.iso8601,
|
||||
performer: `whoami`.chomp,
|
||||
performer: Kamal::Git.email.presence || `whoami`.chomp,
|
||||
destination: config.destination,
|
||||
version: config.version,
|
||||
service_version: service_version(config) }
|
||||
service_version: service_version(config),
|
||||
service: config.service }
|
||||
end
|
||||
|
||||
def service_version(config)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Kamal
|
||||
VERSION = "1.7.3"
|
||||
VERSION = "1.8.1"
|
||||
end
|
||||
|
||||
@@ -21,16 +21,12 @@ class CliBuildTest < CliTestCase
|
||||
.with(:git, "-C", anything, :status, "--porcelain")
|
||||
.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|
|
||||
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
|
||||
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
|
||||
@@ -52,7 +48,7 @@ class CliBuildTest < CliTestCase
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx")
|
||||
|
||||
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)
|
||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||
@@ -77,7 +73,7 @@ class CliBuildTest < CliTestCase
|
||||
assert_no_match /Cloning repo into build directory/, output
|
||||
assert_hook_ran "pre-build", output, **hook_variables
|
||||
assert_match /docker --version && docker buildx version/, output
|
||||
assert_match /docker buildx build --push --platform linux\/amd64,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
|
||||
|
||||
@@ -123,10 +119,10 @@ class CliBuildTest < CliTestCase
|
||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||
|
||||
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)
|
||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||
.with(:docker, :buildx, :inspect, "kamal-local")
|
||||
.raises(SSHKit::Command::Failed.new("no builder"))
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") }
|
||||
@@ -140,7 +136,7 @@ class CliBuildTest < CliTestCase
|
||||
.returns("")
|
||||
|
||||
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|
|
||||
assert_match /WARN Missing compatible builder, so creating a new one first/, output
|
||||
@@ -164,36 +160,65 @@ class CliBuildTest < CliTestCase
|
||||
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 ] }
|
||||
assert @executions.none? { |args| args[0..2] == [ :docker, :build ] }
|
||||
end
|
||||
|
||||
test "pull" do
|
||||
run_command("pull").tap do |output|
|
||||
assert_match /docker info --format '{{index .RegistryConfig.Mirrors 0}}'/, 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 "pull with mirror" do
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||
.returns("registry-mirror.example.com")
|
||||
.at_least_once
|
||||
|
||||
run_command("pull").tap do |output|
|
||||
assert_match /Pulling image on 1\.1\.1\.\d to seed the mirror\.\.\./, output
|
||||
assert_match "Pulling image on remaining hosts...", output
|
||||
assert_equal 4, output.scan(/docker pull dhh\/app:999/).size, 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 "pull with mirrors" do
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||
.returns("registry-mirror.example.com", "registry-mirror2.example.com")
|
||||
.at_least_once
|
||||
|
||||
run_command("pull").tap do |output|
|
||||
assert_match /Pulling image on 1\.1\.1\.\d, 1\.1\.1\.\d to seed the mirrors\.\.\./, output
|
||||
assert_match "Pulling image on remaining hosts...", output
|
||||
assert_equal 4, output.scan(/docker pull dhh\/app:999/).size, 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
|
||||
assert_match /docker buildx create --name kamal-local --driver=docker-container/, 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
|
||||
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-remote-amd64-ssh---app-1-1-1-5 kamal-remote-amd64-ssh---app-1-1-1-5 --platform linux/amd64", output
|
||||
end
|
||||
end
|
||||
|
||||
test "create remote with custom ports" do
|
||||
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 "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 buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", 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-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
|
||||
|
||||
@@ -210,7 +235,7 @@ class CliBuildTest < CliTestCase
|
||||
|
||||
test "remove" do
|
||||
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
|
||||
|
||||
@@ -220,7 +245,7 @@ class CliBuildTest < CliTestCase
|
||||
.returns("docker builder info")
|
||||
|
||||
run_command("details").tap do |output|
|
||||
assert_match /Builder: multiarch/, output
|
||||
assert_match /Builder: local/, output
|
||||
assert_match /docker builder info/, output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,22 +36,24 @@ class CliTestCase < ActiveSupport::TestCase
|
||||
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
||||
.with { |*args| args[0..2] == [ :docker, :buildx, :inspect ] }
|
||||
.returns("")
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with(:docker, :buildx, :inspect, "kamal-local")
|
||||
end
|
||||
|
||||
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false)
|
||||
performer = `whoami`.strip
|
||||
whoami = `whoami`.chomp
|
||||
performer = Kamal::Git.email.presence || whoami
|
||||
service = service_version.split("@").first
|
||||
|
||||
assert_match "Running the #{hook} hook...\n", output
|
||||
|
||||
expected = %r{Running\s/usr/bin/env\s\.kamal/hooks/#{hook}\sas\s#{performer}@localhost\n\s
|
||||
expected = %r{Running\s/usr/bin/env\s\.kamal/hooks/#{hook}\sas\s#{whoami}@localhost\n\s
|
||||
DEBUG\s\[[0-9a-f]*\]\sCommand:\s\(\sexport\s
|
||||
KAMAL_RECORDED_AT=\"\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\"\s
|
||||
KAMAL_PERFORMER=\"#{performer}\"\s
|
||||
KAMAL_VERSION=\"#{version}\"\s
|
||||
KAMAL_SERVICE_VERSION=\"#{service_version}\"\s
|
||||
KAMAL_SERVICE=\"#{service}\"\s
|
||||
KAMAL_HOSTS=\"#{hosts}\"\s
|
||||
KAMAL_COMMAND=\"#{command}\"\s
|
||||
#{"KAMAL_SUBCOMMAND=\\\"#{subcommand}\\\"\\s" if subcommand}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
require_relative "cli_test_case"
|
||||
|
||||
class CliMainTest < CliTestCase
|
||||
setup { @original_env = ENV.to_h.dup }
|
||||
teardown { ENV.clear; ENV.update @original_env }
|
||||
|
||||
test "setup" do
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||
|
||||
@@ -119,8 +122,9 @@ class CliMainTest < CliTestCase
|
||||
.returns("")
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||
.returns("")
|
||||
.at_least_once
|
||||
|
||||
assert_raises(Kamal::Cli::LockError) do
|
||||
run_command("deploy")
|
||||
@@ -152,8 +156,9 @@ class CliMainTest < CliTestCase
|
||||
.returns("")
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||
.returns("")
|
||||
.at_least_once
|
||||
|
||||
assert_raises(SSHKit::Runner::ExecuteError) do
|
||||
run_command("deploy")
|
||||
@@ -434,7 +439,7 @@ class CliMainTest < CliTestCase
|
||||
end
|
||||
|
||||
test "envify" do
|
||||
with_test_dot_env_erb(contents: "HELLO=<%= 'world' %>") do
|
||||
with_test_dotenv(".env.erb": "HELLO=<%= 'world' %>") do
|
||||
run_command("envify")
|
||||
assert_equal("HELLO=world", File.read(".env"))
|
||||
end
|
||||
@@ -448,14 +453,14 @@ class CliMainTest < CliTestCase
|
||||
<% end -%>
|
||||
EOF
|
||||
|
||||
with_test_dot_env_erb(contents: file) do
|
||||
with_test_dotenv(".env.erb": file) do
|
||||
run_command("envify")
|
||||
assert_equal("HELLO=world\nKEY=value\n", File.read(".env"))
|
||||
end
|
||||
end
|
||||
|
||||
test "envify with destination" do
|
||||
with_test_dot_env_erb(contents: "HELLO=<%= 'world' %>", file: ".env.world.erb") do
|
||||
with_test_dotenv(".env.world.erb": "HELLO=<%= 'world' %>") do
|
||||
run_command("envify", "-d", "world", config_file: "deploy_for_dest")
|
||||
assert_equal "HELLO=world", File.read(".env.world")
|
||||
end
|
||||
@@ -470,6 +475,13 @@ class CliMainTest < CliTestCase
|
||||
run_command("envify", "--skip-push")
|
||||
end
|
||||
|
||||
test "envify with clean env" do
|
||||
with_test_dotenv(".env": "HELLO=already", ".env.erb": "HELLO=<%= ENV.fetch 'HELLO', 'never' %>") do
|
||||
run_command("envify", "--skip-push")
|
||||
assert_equal "HELLO=never", File.read(".env")
|
||||
end
|
||||
end
|
||||
|
||||
test "remove with confirmation" do
|
||||
run_command("remove", "-y", config_file: "deploy_with_accessories").tap do |output|
|
||||
assert_match /docker container stop traefik/, output
|
||||
@@ -522,14 +534,16 @@ class CliMainTest < CliTestCase
|
||||
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) }
|
||||
end
|
||||
|
||||
def with_test_dot_env_erb(contents:, file: ".env.erb")
|
||||
def with_test_dotenv(**files)
|
||||
Dir.mktmpdir do |dir|
|
||||
fixtures_dup = File.join(dir, "test")
|
||||
FileUtils.mkdir_p(fixtures_dup)
|
||||
FileUtils.cp_r("test/fixtures/", fixtures_dup)
|
||||
|
||||
Dir.chdir(dir) do
|
||||
File.write(file, contents)
|
||||
files.each do |filename, contents|
|
||||
File.binwrite(filename.to_s, contents)
|
||||
end
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
||||
}
|
||||
|
||||
@auditor = new_command
|
||||
@performer = `whoami`.strip
|
||||
@performer = Kamal::Git.email.presence || `whoami`.chomp
|
||||
@recorded_at = Time.now.utc.iso8601
|
||||
end
|
||||
|
||||
|
||||
@@ -7,49 +7,49 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
|
||||
test "target multiarch by default" do
|
||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||
assert_equal "multiarch", builder.name
|
||||
assert_equal "local", builder.name
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "target native when multiarch is off" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false })
|
||||
assert_equal "native", builder.name
|
||||
assert_equal "local", builder.name
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "target native cached when multiarch is off and cache is set" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "gha" } })
|
||||
assert_equal "native/cached", builder.name
|
||||
assert_equal "local", builder.name
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "target multiarch remote when local and remote is set" do
|
||||
builder = new_builder_command(builder: { "local" => {}, "remote" => {}, "cache" => { "type" => "gha" } })
|
||||
assert_equal "multiarch/remote", builder.name
|
||||
builder = new_builder_command(builder: { "local" => { "arch" => "arm64" }, "remote" => { "arch" => "amd64", "host" => "ssh://app@127.0.0.1" }, "cache" => { "type" => "gha" } })
|
||||
assert_equal "hybrid", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --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(" ")
|
||||
end
|
||||
|
||||
test "target multiarch local when arch is set" do
|
||||
builder = new_builder_command(builder: { "local" => { "arch" => "amd64" } })
|
||||
assert_equal "multiarch", builder.name
|
||||
assert_equal "local", builder.name
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "target native remote when only remote is set" do
|
||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "cache" => { "type" => "gha" } })
|
||||
assert_equal "native/remote", builder.name
|
||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64", "host" => "ssh://app@host" }, "cache" => { "type" => "gha" } })
|
||||
assert_equal "remote", builder.name
|
||||
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(" ")
|
||||
end
|
||||
|
||||
@@ -93,28 +93,28 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
test "build context" do
|
||||
builder = new_builder_command(builder: { "context" => ".." })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "native push with build args" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "multiarch push with build args" do
|
||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "native push with build secrets" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
@@ -133,71 +133,34 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
test "multiarch context build" do
|
||||
builder = new_builder_command(builder: { "context" => "./foo" })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "native context build" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo" })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
test "cached context build" do
|
||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo", "cache" => { "type" => "gha" } })
|
||||
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(" ")
|
||||
end
|
||||
|
||||
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 \
|
||||
"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(" ")
|
||||
end
|
||||
|
||||
test "multiarch context hosts" do
|
||||
test "mirror count" 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
|
||||
assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -11,7 +11,7 @@ class CommandsHookTest < ActiveSupport::TestCase
|
||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||
}
|
||||
|
||||
@performer = `whoami`.strip
|
||||
@performer = Kamal::Git.email.presence || `whoami`.chomp
|
||||
@recorded_at = Time.now.utc.iso8601
|
||||
end
|
||||
|
||||
@@ -22,7 +22,8 @@ class CommandsHookTest < ActiveSupport::TestCase
|
||||
"KAMAL_RECORDED_AT" => @recorded_at,
|
||||
"KAMAL_PERFORMER" => @performer,
|
||||
"KAMAL_VERSION" => "123",
|
||||
"KAMAL_SERVICE_VERSION" => "app@123" } }
|
||||
"KAMAL_SERVICE_VERSION" => "app@123",
|
||||
"KAMAL_SERVICE" => "app" } }
|
||||
], new_command.run("foo")
|
||||
end
|
||||
|
||||
@@ -33,7 +34,8 @@ class CommandsHookTest < ActiveSupport::TestCase
|
||||
"KAMAL_RECORDED_AT" => @recorded_at,
|
||||
"KAMAL_PERFORMER" => @performer,
|
||||
"KAMAL_VERSION" => "123",
|
||||
"KAMAL_SERVICE_VERSION" => "app@123" } }
|
||||
"KAMAL_SERVICE_VERSION" => "app@123",
|
||||
"KAMAL_SERVICE" => "app" } }
|
||||
], new_command(hooks_path: "custom/hooks/path").run("foo")
|
||||
end
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
||||
|
||||
test "setting both local and remote configs" do
|
||||
@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" }
|
||||
}
|
||||
|
||||
@@ -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 "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
|
||||
|
||||
test "cached?" do
|
||||
|
||||
@@ -344,4 +344,12 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
|
||||
assert_raises(Kamal::ConfigurationError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) }
|
||||
end
|
||||
|
||||
test "extensions" do
|
||||
dest_config_file = Pathname.new(File.expand_path("fixtures/deploy_with_extensions.yml", __dir__))
|
||||
|
||||
config = Kamal::Configuration.create_from config_file: dest_config_file
|
||||
assert_equal config.role(:web_tokyo).running_traefik?, true
|
||||
assert_equal config.role(:web_chicago).running_traefik?, true
|
||||
end
|
||||
end
|
||||
|
||||
24
test/fixtures/deploy_with_extensions.yml
vendored
Normal file
24
test/fixtures/deploy_with_extensions.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
x-web: &web
|
||||
traefik: true
|
||||
|
||||
service: app
|
||||
image: dhh/app
|
||||
servers:
|
||||
web_chicago:
|
||||
<<: *web
|
||||
hosts:
|
||||
- 1.1.1.1
|
||||
- 1.1.1.2
|
||||
web_tokyo:
|
||||
<<: *web
|
||||
hosts:
|
||||
- 1.1.1.3
|
||||
- 1.1.1.4
|
||||
env:
|
||||
REDIS_URL: redis://x/y
|
||||
registry:
|
||||
server: registry.digitalocean.com
|
||||
username: user
|
||||
password: pw
|
||||
primary_role: web_tokyo
|
||||
@@ -1,4 +1,3 @@
|
||||
version: "3.7"
|
||||
name: "kamal-test"
|
||||
|
||||
volumes:
|
||||
@@ -30,8 +29,6 @@ services:
|
||||
context: docker/registry
|
||||
environment:
|
||||
- REGISTRY_HTTP_ADDR=0.0.0.0:4443
|
||||
- REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt
|
||||
- REGISTRY_HTTP_TLS_KEY=/certs/domain.key
|
||||
volumes:
|
||||
- shared:/shared
|
||||
- registry:/var/lib/registry/
|
||||
|
||||
@@ -22,7 +22,6 @@ COPY app_with_roles/ app_with_roles/
|
||||
|
||||
RUN rm -rf /root/.ssh
|
||||
RUN ln -s /shared/ssh /root/.ssh
|
||||
RUN mkdir -p /etc/docker/certs.d/registry:4443 && ln -s /shared/certs/domain.crt /etc/docker/certs.d/registry:4443/ca.crt
|
||||
|
||||
RUN git config --global user.email "deployer@example.com"
|
||||
RUN git config --global user.name "Deployer"
|
||||
|
||||
@@ -24,6 +24,7 @@ registry:
|
||||
password: root
|
||||
builder:
|
||||
multiarch: false
|
||||
driver: docker
|
||||
args:
|
||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||
healthcheck:
|
||||
|
||||
@@ -18,6 +18,7 @@ registry:
|
||||
password: root
|
||||
builder:
|
||||
multiarch: false
|
||||
driver: docker
|
||||
args:
|
||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||
healthcheck:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
dockerd --max-concurrent-downloads 1 &
|
||||
dockerd --max-concurrent-downloads 1 --insecure-registry registry:4443 &
|
||||
|
||||
exec sleep infinity
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
while [ ! -f /certs/domain.crt ]; do sleep 1; done
|
||||
|
||||
exec /entrypoint.sh /etc/docker/registry/config.yml
|
||||
|
||||
@@ -10,8 +10,6 @@ RUN mkdir ssh && \
|
||||
COPY registry-dns.conf .
|
||||
COPY boot.sh .
|
||||
|
||||
RUN mkdir certs && openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key -x509 -days 365 -out certs/domain.crt -subj '/CN=registry' -extensions EXT -config registry-dns.conf
|
||||
|
||||
HEALTHCHECK --interval=1s CMD pgrep sleep
|
||||
|
||||
CMD ["./boot.sh"]
|
||||
|
||||
@@ -5,7 +5,6 @@ WORKDIR /work
|
||||
RUN apt-get update --fix-missing && apt-get -y install openssh-client openssh-server docker.io
|
||||
|
||||
RUN mkdir /root/.ssh && ln -s /shared/ssh/id_rsa.pub /root/.ssh/authorized_keys
|
||||
RUN mkdir -p /etc/docker/certs.d/registry:4443 && ln -s /shared/certs/domain.crt /etc/docker/certs.d/registry:4443/ca.crt
|
||||
|
||||
RUN echo "HOST_TOKEN=abcd" >> /etc/environment
|
||||
|
||||
|
||||
@@ -4,6 +4,6 @@ while [ ! -f /root/.ssh/authorized_keys ]; do echo "Waiting for ssh keys"; sleep
|
||||
|
||||
service ssh restart
|
||||
|
||||
dockerd --max-concurrent-downloads 1 &
|
||||
dockerd --max-concurrent-downloads 1 --insecure-registry registry:4443 &
|
||||
|
||||
exec sleep infinity
|
||||
|
||||
Reference in New Issue
Block a user