Compare commits

..

5 Commits

Author SHA1 Message Date
Donal McBreen
90212129d5 Fix typo 2024-07-31 08:36:45 +01:00
Donal McBreen
a13adbf0dd Remove redundant env replacement 2024-07-31 08:35:50 +01:00
Donal McBreen
d2f57b1889 Remove the envify command
Instead of using `kamal envify` to generate the .env file, we now assume
that it will be in place for us.

Options in place of `kamal envify`:
1. Pre-generate the .env file
2. Create the env file in the `.pre-init` hook
3. Log into a secret store/check you are logged in in the pre-init hook
   Then use .dotenv command and variable substitution to interpolate the
   secrets.
2024-07-30 17:26:45 +01:00
Donal McBreen
cbb4c87035 Add a pre-init hook
The hook is run before the environment is loaded or the config is
parsed.

This makes it a bit of a special case - it doesn't have the usual
KAMAL_XYZ environment variables, as we haven't parsed the config.

The use case for this is to do auth checking or setup. So for example
we can confirm you are logged in to a secret manager, and then you
can directly call it to load your secrets in the .kamal/.env file
using .dotenv's
[command substitution](https://github.com/bkeepers/dotenv?tab=readme-ov-file#command-substitution).
2024-07-30 16:49:22 +01:00
Donal McBreen
a8837d453c Read from .kamal/.env
To avoid conflicts with other tools that use .env files, read the files
from .kamal/ instead.

If there are no matching env files in .kamal/, we'll read from the
project root for now and emit a warning.
2024-07-30 12:29:44 +01:00
45 changed files with 465 additions and 393 deletions

View File

@@ -22,48 +22,46 @@ module Kamal::Cli
class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks" class_option :skip_hooks, aliases: "-H", type: :boolean, default: false, desc: "Don't run hooks"
@@ran_pre_init_hook = false
class << self
def ran_pre_init_hook
@@ran_pre_init_hook
end
def ran_pre_init_hook=(value)
@@ran_pre_init_hook = value
end
end
def initialize(*) def initialize(*)
super super
@original_env = ENV.to_h.dup @original_env = ENV.to_h.dup
run_pre_init_hook
load_env load_env
initialize_commander(options_with_subcommand_class_options) initialize_commander(options_with_subcommand_class_options)
end end
private private
def reload_env
reset_env
load_env
end
def load_env def load_env
if destination = options[:destination] if destination = options[:destination]
Dotenv.load(".env.#{destination}", ".env") if File.exist?(".kamal/.env.#{destination}") || File.exist?(".kamal/.env")
Dotenv.load(".kamal/.env.#{destination}", ".kamal/.env")
else else
loading_files = [ (".env" if File.exist?(".env")), (".env.#{destination}" if File.exist?(".env.#{destination}")) ].compact
if loading_files.any?
warn "Loading #{loading_files.join(" and ")} from the project root, in future they will be loaded from .kamal/"
Dotenv.load(".env.#{destination}", ".env")
end
end
else
if File.exist?(".kamal/.env")
Dotenv.load(".kamal/.env")
elsif File.exist?(".env")
$stderr.puts caller
warn "Loading .env from the project root, in future it will be loaded then from .kamal/.env"
Dotenv.load(".env") Dotenv.load(".env")
end end
end end
def reset_env
replace_env @original_env
end
def replace_env(env)
ENV.clear
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 end
def options_with_subcommand_class_options def options_with_subcommand_class_options
@@ -162,8 +160,23 @@ module Kamal::Cli
end end
end end
def run_pre_init_hook
unless self.class.ran_pre_init_hook
hook = "pre-init"
if run_hook?(hook)
say "Running the #{hook} hook...", :magenta
run_locally do
execute *Kamal::Hooks.file(hook), verbosity: :debug
rescue SSHKit::Command::Failed => e
raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
end
end
self.class.ran_pre_init_hook = true
end
end
def run_hook(hook, **extra_details) def run_hook(hook, **extra_details)
if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook) if run_hook?(hook)
details = { hosts: KAMAL.hosts.join(","), command: command, subcommand: subcommand } details = { hosts: KAMAL.hosts.join(","), command: command, subcommand: subcommand }
say "Running the #{hook} hook...", :magenta say "Running the #{hook} hook...", :magenta
@@ -175,6 +188,10 @@ module Kamal::Cli
end end
end end
def run_hook?(hook)
!options[:skip_hooks] && Kamal::Hooks.exists?(hook)
end
def on(*args, &block) def on(*args, &block)
if !KAMAL.connected? if !KAMAL.connected?
run_hook "pre-connect" run_hook "pre-connect"

View File

@@ -30,9 +30,18 @@ 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
execute *KAMAL.builder.buildx_inspect 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
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"
@@ -42,9 +51,6 @@ 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

View File

@@ -10,7 +10,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
invoke "kamal:cli:server:bootstrap", [], invoke_options invoke "kamal:cli:server:bootstrap", [], invoke_options
say "Evaluate and push env files...", :magenta say "Evaluate and push env files...", :magenta
invoke "kamal:cli:main:envify", [], invoke_options
invoke "kamal:cli:env:push", [], invoke_options invoke "kamal:cli:env:push", [], invoke_options
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
@@ -179,31 +178,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
end end
desc "envify", "Create .env by evaluating .env.erb (or .env.staging.erb -> .env.staging when using -d staging)"
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip .env file push"
def envify
if destination = options[:destination]
env_template_path = ".env.#{destination}.erb"
env_path = ".env.#{destination}"
else
env_template_path = ".env.erb"
env_path = ".env"
end
if Pathname.new(File.expand_path(env_template_path)).exist?
# 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_env
invoke "kamal:cli:env:push", options
end
else
puts "Skipping envify (no #{env_template_path} exist)"
end
end
desc "remove", "Remove Traefik, app, accessories, and registry session from servers" desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question" option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
def remove def remove

View File

@@ -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, :buildx_inspect, :validate_image, :first_mirror, to: :target delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
delegate :multiarch?, :local?, :remote?, to: "config.builder" :first_mirror, to: :target
include Clone include Clone
@@ -11,27 +11,43 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
end end
def target def target
if remote? if config.builder.multiarch?
if local? if config.builder.remote?
hybrid if config.builder.local?
multiarch_remote
else else
remote native_remote
end end
else else
local multiarch
end
else
if config.builder.cached?
native_cached
else
native
end
end end
end end
def remote def native
@remote ||= Kamal::Commands::Builder::Remote.new(config) @native ||= Kamal::Commands::Builder::Native.new(config)
end end
def local def native_cached
@local ||= Kamal::Commands::Builder::Local.new(config) @native ||= Kamal::Commands::Builder::Native::Cached.new(config)
end end
def hybrid def native_remote
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config) @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)
end end

View File

@@ -1,41 +1,20 @@
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 \ delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
: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
@@ -53,6 +32,14 @@ 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

View File

@@ -1,25 +0,0 @@
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

View File

@@ -1,24 +0,0 @@
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

View File

@@ -0,0 +1,41 @@
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

View File

@@ -0,0 +1,61 @@
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

View File

@@ -0,0 +1,20 @@
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

View File

@@ -0,0 +1,25 @@
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

View File

@@ -0,0 +1,67 @@
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

View File

@@ -1,57 +0,0 @@
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

View File

@@ -1,14 +1,5 @@
class Kamal::Commands::Hook < Kamal::Commands::Base class Kamal::Commands::Hook < Kamal::Commands::Base
def run(hook, **details) def run(hook, **details)
[ hook_file(hook), env: tags(**details).env ] [ Kamal::Hooks.file(hook), env: tags(**details).env ]
end
def hook_exists?(hook)
Pathname.new(hook_file(hook)).exist?
end
private
def hook_file(hook)
File.join(config.hooks_path, hook)
end end
end end

View File

@@ -7,7 +7,7 @@ require "erb"
require "net/ssh/proxy/jump" require "net/ssh/proxy/jump"
class Kamal::Configuration class Kamal::Configuration
delegate :service, :image, :labels, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true delegate :service, :image, :labels, :stop_wait_time, to: :raw_config, allow_nil: true
delegate :argumentize, :optionize, to: Kamal::Utils delegate :argumentize, :optionize, to: Kamal::Utils
attr_reader :destination, :raw_config attr_reader :destination, :raw_config
@@ -208,10 +208,6 @@ class Kamal::Configuration
end end
end end
def hooks_path
raw_config.hooks_path || ".kamal/hooks"
end
def asset_path def asset_path
raw_config.asset_path raw_config.asset_path
end end

View File

@@ -55,14 +55,14 @@ class Kamal::Configuration::Builder
builder_config["context"] || "." builder_config["context"] || "."
end end
def driver
builder_config.fetch("driver", "docker-container")
end
def local_arch def local_arch
builder_config["local"]["arch"] if local? builder_config["local"]["arch"] if local?
end end
def local_host
builder_config["local"]["host"] if local?
end
def remote_arch def remote_arch
builder_config["remote"]["arch"] if remote? builder_config["remote"]["arch"] if remote?
end end
@@ -114,36 +114,7 @@ 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

View File

@@ -1,10 +1,10 @@
# Builder # Builder
# #
# The builder configuration controls how the application is built with `docker build` # The builder configuration controls how the application is built with `docker build` or `docker buildx 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 build` to build a multiarch image for linux/amd64,linux/arm64 with that context # 2. Use `docker buildx 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,11 +18,6 @@ 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)

View File

@@ -74,10 +74,6 @@ env:
# To configure this, set the path to the assets: # To configure this, set the path to the assets:
asset_path: /path/to/assets asset_path: /path/to/assets
# Path to hooks, defaults to `.kamal/hooks`
# See https://kamal-deploy.org/docs/hooks for more information
hooks_path: /user_home/kamal/hooks
# Require destinations # Require destinations
# #
# Whether deployments require a destination to be specified, defaults to `false` # Whether deployments require a destination to be specified, defaults to `false`

View File

@@ -24,14 +24,14 @@ env:
# KAMAL_REGISTRY_PASSWORD=pw # KAMAL_REGISTRY_PASSWORD=pw
# DB_PASSWORD=secret123 # DB_PASSWORD=secret123
# ``` # ```
# See https://kamal-deploy.org/docs/commands/envify/ for how to use generated .env files. # See https://kamal-deploy.org/docs/commands/env/ for how to use generated .env files.
# #
# To pass the secrets you should list them under the `secret` key. When you do this the # 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. # other variables need to be moved under the `clear` key.
# #
# Unlike clear values, 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 # 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`. # The file is not updated when deploying, only when running `kamal env push`.
env: env:
clear: clear:
DB_USER: app DB_USER: app

View File

@@ -10,7 +10,7 @@ class CliBuildTest < CliTestCase
test "push" do test "push" do
with_build_directory do |build_directory| with_build_directory do |build_directory|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" } hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
@@ -21,12 +21,16 @@ 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 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 assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
end end
end end
end end
@@ -48,7 +52,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(:git, "-C", build_directory, :clean, "-fdx")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.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", ".") .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", ".")
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)
@@ -66,14 +70,14 @@ class CliBuildTest < CliTestCase
end end
test "push without clone" do test "push without clone" do
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" } hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
run_command("push", "--verbose", fixture: :without_clone).tap do |output| run_command("push", "--verbose", fixture: :without_clone).tap do |output|
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 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 assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
end end
end end
@@ -119,10 +123,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, "--name", "kamal-local", "--driver=docker-container") .with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :buildx, :inspect, "kamal-local") .with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
.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") }
@@ -136,7 +140,7 @@ class CliBuildTest < CliTestCase
.returns("") .returns("")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.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", ".") .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-app-multiarch", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
run_command("push").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
@@ -160,7 +164,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, :build ] } assert @executions.none? { |args| args[0..2] == [ :docker, :buildx, :build ] }
end end
test "pull" do test "pull" do
@@ -202,23 +206,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 --name kamal-local --driver=docker-container/, output assert_match /docker buildx create --use --name kamal-app-multiarch/, 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-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 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-remote-amd64-ssh---app-1-1-1-5 kamal-remote-amd64-ssh---app-1-1-1-5 --platform linux/amd64", output assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --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-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 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-remote-amd64-ssh---app-1-1-1-5-2122 kamal-remote-amd64-ssh---app-1-1-1-5-2122 --platform linux/amd64", output assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", output
end end
end end
@@ -235,7 +239,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-local/, output assert_match /docker buildx rm kamal-app-multiarch/, output
end end
end end
@@ -245,7 +249,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: local/, output assert_match /Builder: multiarch/, output
assert_match /docker builder info/, output assert_match /docker builder info/, output
end end
end end

View File

@@ -18,7 +18,7 @@ class CliTestCase < ActiveSupport::TestCase
private private
def fail_hook(hook) def fail_hook(hook)
@executions = [] @executions = []
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
SSHKit::Backend::Abstract.any_instance.stubs(:execute) SSHKit::Backend::Abstract.any_instance.stubs(:execute)
.with { |*args| @executions << args; args != [ ".kamal/hooks/#{hook}" ] } .with { |*args| @executions << args; args != [ ".kamal/hooks/#{hook}" ] }
@@ -36,8 +36,9 @@ 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(:execute) SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
.with(:docker, :buildx, :inspect, "kamal-local") .with { |*args| args[0..2] == [ :docker, :buildx, :inspect ] }
.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)

View File

@@ -8,7 +8,6 @@ class CliMainTest < CliTestCase
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false } invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:main:envify", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
Kamal::Cli::Main.any_instance.expects(:deploy) Kamal::Cli::Main.any_instance.expects(:deploy)
@@ -24,7 +23,6 @@ class CliMainTest < CliTestCase
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:main:envify", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
# deploy # deploy
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true))
@@ -58,10 +56,11 @@ class CliMainTest < CliTestCase
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" } hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" }
run_command("deploy", "--verbose").tap do |output| run_command("deploy", "--verbose").tap do |output|
assert_match "Running the pre-init hook...", output
assert_hook_ran "pre-connect", output, **hook_variables assert_hook_ran "pre-connect", output, **hook_variables
assert_match /Log into image registry/, output assert_match /Log into image registry/, output
assert_match /Build and push app image/, output assert_match /Build and push app image/, output
@@ -121,6 +120,10 @@ 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("")
@@ -155,6 +158,10 @@ 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("")
@@ -229,7 +236,7 @@ class CliMainTest < CliTestCase
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "redeploy" } hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "redeploy" }
@@ -290,7 +297,7 @@ class CliMainTest < CliTestCase
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-version-to-rollback$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'") .with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-version-to-rollback$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
.returns("unhealthy").at_least_once # health check .returns("unhealthy").at_least_once # health check
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
hook_variables = { version: 123, service_version: "app@123", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "rollback" } hook_variables = { version: 123, service_version: "app@123", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "rollback" }
run_command("rollback", "--verbose", "123", config_file: "deploy_with_accessories").tap do |output| run_command("rollback", "--verbose", "123", config_file: "deploy_with_accessories").tap do |output|
@@ -388,7 +395,7 @@ class CliMainTest < CliTestCase
end end
test "init" do test "init" do
Pathname.any_instance.expects(:exist?).returns(false).times(3) Pathname.any_instance.expects(:exist?).returns(false).times(4)
Pathname.any_instance.stubs(:mkpath) Pathname.any_instance.stubs(:mkpath)
FileUtils.stubs(:mkdir_p) FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r) FileUtils.stubs(:cp_r)
@@ -401,7 +408,7 @@ class CliMainTest < CliTestCase
end end
test "init with existing config" do test "init with existing config" do
Pathname.any_instance.expects(:exist?).returns(true).times(3) Pathname.any_instance.expects(:exist?).returns(true).times(4)
run_command("init").tap do |output| run_command("init").tap do |output|
assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output
@@ -409,7 +416,7 @@ class CliMainTest < CliTestCase
end end
test "init with bundle option" do test "init with bundle option" do
Pathname.any_instance.expects(:exist?).returns(false).times(4) Pathname.any_instance.expects(:exist?).returns(false).times(5)
Pathname.any_instance.stubs(:mkpath) Pathname.any_instance.stubs(:mkpath)
FileUtils.stubs(:mkdir_p) FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r) FileUtils.stubs(:cp_r)
@@ -426,7 +433,7 @@ class CliMainTest < CliTestCase
end end
test "init with bundle option and existing binstub" do test "init with bundle option and existing binstub" do
Pathname.any_instance.expects(:exist?).returns(true).times(4) Pathname.any_instance.expects(:exist?).returns(true).times(5)
Pathname.any_instance.stubs(:mkpath) Pathname.any_instance.stubs(:mkpath)
FileUtils.stubs(:mkdir_p) FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r) FileUtils.stubs(:cp_r)
@@ -438,50 +445,6 @@ class CliMainTest < CliTestCase
end end
end end
test "envify" do
with_test_dotenv(".env.erb": "HELLO=<%= 'world' %>") do
run_command("envify")
assert_equal("HELLO=world", File.read(".env"))
end
end
test "envify with blank line trimming" do
file = <<~EOF
HELLO=<%= 'world' %>
<% if true -%>
KEY=value
<% end -%>
EOF
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_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
end
test "envify with skip_push" do
Pathname.any_instance.expects(:exist?).returns(true).times(1)
File.expects(:read).with(".env.erb").returns("HELLO=<%= 'world' %>")
File.expects(:write).with(".env", "HELLO=world", perm: 0600)
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push").never
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 test "remove with confirmation" do
run_command("remove", "-y", config_file: "deploy_with_accessories").tap do |output| run_command("remove", "-y", config_file: "deploy_with_accessories").tap do |output|
assert_match /docker container stop traefik/, output assert_match /docker container stop traefik/, output
@@ -541,9 +504,12 @@ class CliMainTest < CliTestCase
FileUtils.cp_r("test/fixtures/", fixtures_dup) FileUtils.cp_r("test/fixtures/", fixtures_dup)
Dir.chdir(dir) do Dir.chdir(dir) do
FileUtils.mkdir_p(".kamal")
Dir.chdir(".kamal") do
files.each do |filename, contents| files.each do |filename, contents|
File.binwrite(filename.to_s, contents) File.binwrite(filename.to_s, contents)
end end
end
yield yield
end end
end end

View File

@@ -40,7 +40,8 @@ class CliServerTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null', raise_on_non_zero_exit: false).returns(true).at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null', raise_on_non_zero_exit: false).returns(true).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:sh, "-c", "'curl -fsSL https://get.docker.com || wget -O - https://get.docker.com || echo \"exit 1\"'", "|", :sh).at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:sh, "-c", "'curl -fsSL https://get.docker.com || wget -O - https://get.docker.com || echo \"exit 1\"'", "|", :sh).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) Kamal::Hooks.stubs(:exists?).returns(true)
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/pre-init", anything).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/pre-connect", anything).at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/pre-connect", anything).at_least_once
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/docker-setup", anything).at_least_once SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/docker-setup", anything).at_least_once

View File

@@ -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 "local", builder.name assert_equal "multiarch", builder.name
assert_equal \ assert_equal \
"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 .", "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 .",
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 "local", builder.name assert_equal "native", builder.name
assert_equal \ assert_equal \
"docker build --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .", "docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
builder.push.join(" ") 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 "local", builder.name assert_equal "native/cached", builder.name
assert_equal \ assert_equal \
"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 .", "docker buildx build --push -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
builder.push.join(" ") 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", "host" => "ssh://app@127.0.0.1" }, "cache" => { "type" => "gha" } }) builder = new_builder_command(builder: { "local" => {}, "remote" => {}, "cache" => { "type" => "gha" } })
assert_equal "hybrid", builder.name assert_equal "multiarch/remote", builder.name
assert_equal \ assert_equal \
"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 .", "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 .",
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 "local", builder.name assert_equal "multiarch", builder.name
assert_equal \ assert_equal \
"docker build --push --platform linux/amd64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .", "docker buildx build --push --platform linux/amd64 --builder kamal-app-multiarch -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", "host" => "ssh://app@host" }, "cache" => { "type" => "gha" } }) builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "cache" => { "type" => "gha" } })
assert_equal "remote", builder.name assert_equal "native/remote", builder.name
assert_equal \ assert_equal \
"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 .", "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 .",
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 build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..", "docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
builder.push.join(" ") 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 --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 .", "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",
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 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 .", "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 .",
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 --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .", "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",
builder.push.join(" ") builder.push.join(" ")
end end
@@ -133,31 +133,73 @@ 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 build --push --platform linux/amd64,linux/arm64 --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo", "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",
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 --push --builder kamal-local -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo", "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",
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 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", "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",
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", "host" => "ssh://app@host" }, "context" => "./foo" }) builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "context" => "./foo" })
assert_equal \ assert_equal \
"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", "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",
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(" ")

View File

@@ -28,6 +28,7 @@ class CommandsHookTest < ActiveSupport::TestCase
end end
test "run with custom hooks_path" do test "run with custom hooks_path" do
ENV["KAMAL_HOOKS_PATH"] = "custom/hooks/path"
assert_equal [ assert_equal [
"custom/hooks/path/foo", "custom/hooks/path/foo",
{ env: { { env: {
@@ -36,7 +37,9 @@ class CommandsHookTest < ActiveSupport::TestCase
"KAMAL_VERSION" => "123", "KAMAL_VERSION" => "123",
"KAMAL_SERVICE_VERSION" => "app@123", "KAMAL_SERVICE_VERSION" => "app@123",
"KAMAL_SERVICE" => "app" } } "KAMAL_SERVICE" => "app" } }
], new_command(hooks_path: "custom/hooks/path").run("foo") ], new_command.run("foo")
ensure
ENV.delete("KAMAL_HOOKS_PATH")
end end
private private

View File

@@ -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" }, "local" => { "arch" => "arm64", "host" => "unix:///Users/<%= `whoami`.strip %>/.docker/run/docker.sock" },
"remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" } "remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" }
} }
@@ -53,6 +53,7 @@ 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

View File

@@ -6,7 +6,7 @@ class ConfigurationValidationTest < ActiveSupport::TestCase
end end
test "wrong root types" do test "wrong root types" do
[ :service, :image, :asset_path, :hooks_path, :primary_role, :minimum_version, :run_directory ].each do |key| [ :service, :image, :asset_path, :primary_role, :minimum_version, :run_directory ].each do |key|
assert_error "#{key}: should be a string", **{ key => [] } assert_error "#{key}: should be a string", **{ key => [] }
end end

View File

@@ -2,8 +2,6 @@ require_relative "integration_test"
class AccessoryTest < IntegrationTest class AccessoryTest < IntegrationTest
test "boot, stop, start, restart, logs, remove" do test "boot, stop, start, restart, logs, remove" do
kamal :envify
kamal :accessory, :boot, :busybox kamal :accessory, :boot, :busybox
assert_accessory_running :busybox assert_accessory_running :busybox

View File

@@ -2,8 +2,6 @@ require_relative "integration_test"
class AppTest < IntegrationTest class AppTest < IntegrationTest
test "stop, start, boot, logs, images, containers, exec, remove" do test "stop, start, boot, logs, images, containers, exec, remove" do
kamal :envify
kamal :deploy kamal :deploy
assert_app_is_up assert_app_is_up

View File

@@ -4,8 +4,6 @@ class BrokenDeployTest < IntegrationTest
test "deploying a bad image" do test "deploying a bad image" do
@app = "app_with_roles" @app = "app_with_roles"
kamal :envify
first_version = latest_app_version first_version = latest_app_version
kamal :deploy kamal :deploy

View File

@@ -29,6 +29,8 @@ services:
context: docker/registry context: docker/registry
environment: environment:
- REGISTRY_HTTP_ADDR=0.0.0.0:4443 - REGISTRY_HTTP_ADDR=0.0.0.0:4443
- REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt
- REGISTRY_HTTP_TLS_KEY=/certs/domain.key
volumes: volumes:
- shared:/shared - shared:/shared
- registry:/var/lib/registry/ - registry:/var/lib/registry/

View File

@@ -22,6 +22,7 @@ COPY app_with_roles/ app_with_roles/
RUN rm -rf /root/.ssh RUN rm -rf /root/.ssh
RUN ln -s /shared/ssh /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.email "deployer@example.com"
RUN git config --global user.name "Deployer" RUN git config --global user.name "Deployer"

View File

@@ -1,2 +0,0 @@
SECRET_TOKEN='1234 with "中文"'
SECRET_TAG='TAGME'

View File

@@ -24,7 +24,6 @@ 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:

View File

@@ -1 +0,0 @@
SECRET_TOKEN='1234 with "中文"'

View File

@@ -18,7 +18,6 @@ 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:

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
dockerd --max-concurrent-downloads 1 --insecure-registry registry:4443 & dockerd --max-concurrent-downloads 1 &
exec sleep infinity exec sleep infinity

View File

@@ -1,3 +1,5 @@
#!/bin/sh #!/bin/sh
while [ ! -f /certs/domain.crt ]; do sleep 1; done
exec /entrypoint.sh /etc/docker/registry/config.yml exec /entrypoint.sh /etc/docker/registry/config.yml

View File

@@ -10,6 +10,8 @@ RUN mkdir ssh && \
COPY registry-dns.conf . COPY registry-dns.conf .
COPY boot.sh . 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 HEALTHCHECK --interval=1s CMD pgrep sleep
CMD ["./boot.sh"] CMD ["./boot.sh"]

View File

@@ -5,6 +5,7 @@ WORKDIR /work
RUN apt-get update --fix-missing && apt-get -y install openssh-client openssh-server docker.io 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 /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 RUN echo "HOST_TOKEN=abcd" >> /etc/environment

View File

@@ -4,6 +4,6 @@ while [ ! -f /root/.ssh/authorized_keys ]; do echo "Waiting for ssh keys"; sleep
service ssh restart service ssh restart
dockerd --max-concurrent-downloads 1 --insecure-registry registry:4443 & dockerd --max-concurrent-downloads 1 &
exec sleep infinity exec sleep infinity

View File

@@ -2,8 +2,6 @@ require_relative "integration_test"
class LockTest < IntegrationTest class LockTest < IntegrationTest
test "acquire, release, status" do test "acquire, release, status" do
kamal :envify
kamal :lock, :acquire, "-m 'Integration Tests'" kamal :lock, :acquire, "-m 'Integration Tests'"
status = kamal :lock, :status, capture: true status = kamal :lock, :status, capture: true

View File

@@ -1,8 +1,8 @@
require_relative "integration_test" require_relative "integration_test"
class MainTest < IntegrationTest class MainTest < IntegrationTest
test "envify, deploy, redeploy, rollback, details and audit" do test "env push, deploy, redeploy, rollback, details and audit" do
kamal :envify kamal :env, :push
assert_env_files assert_env_files
remove_local_env_file remove_local_env_file
@@ -12,19 +12,19 @@ class MainTest < IntegrationTest
kamal :deploy kamal :deploy
assert_app_is_up version: first_version assert_app_is_up version: first_version
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy" assert_hooks_ran "pre-init", "pre-connect", "pre-build", "pre-deploy", "post-deploy"
assert_envs version: first_version assert_envs version: first_version
second_version = update_app_rev second_version = update_app_rev
kamal :redeploy kamal :redeploy
assert_app_is_up version: second_version assert_app_is_up version: second_version
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy" assert_hooks_ran "pre-init", "pre-connect", "pre-build", "pre-deploy", "post-deploy"
assert_accumulated_assets first_version, second_version assert_accumulated_assets first_version, second_version
kamal :rollback, first_version kamal :rollback, first_version
assert_hooks_ran "pre-connect", "pre-deploy", "post-deploy" assert_hooks_ran "pre-init", "pre-connect", "pre-deploy", "post-deploy"
assert_app_is_up version: first_version assert_app_is_up version: first_version
details = kamal :details, capture: true details = kamal :details, capture: true
@@ -45,7 +45,7 @@ class MainTest < IntegrationTest
test "app with roles" do test "app with roles" do
@app = "app_with_roles" @app = "app_with_roles"
kamal :envify kamal :env, :push
version = latest_app_version version = latest_app_version
@@ -54,7 +54,7 @@ class MainTest < IntegrationTest
kamal :deploy kamal :deploy
assert_app_is_up version: version assert_app_is_up version: version
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy" assert_hooks_ran "pre-init", "pre-connect", "pre-build", "pre-deploy", "post-deploy"
assert_container_running host: :vm3, name: "app-workers-#{version}" assert_container_running host: :vm3, name: "app-workers-#{version}"
second_version = update_app_rev second_version = update_app_rev
@@ -87,7 +87,7 @@ class MainTest < IntegrationTest
kamal :remove, "-y" kamal :remove, "-y"
assert_no_images_or_containers assert_no_images_or_containers
kamal :envify kamal :env, :push
kamal :setup kamal :setup
assert_images_and_containers assert_images_and_containers
@@ -97,7 +97,7 @@ class MainTest < IntegrationTest
private private
def assert_local_env_file(contents) def assert_local_env_file(contents)
assert_equal contents, deployer_exec("cat .env", capture: true) assert_equal contents, deployer_exec("cat .kamal/.env", capture: true)
end end
def assert_envs(version:) def assert_envs(version:)
@@ -127,7 +127,7 @@ class MainTest < IntegrationTest
end end
def remove_local_env_file def remove_local_env_file
deployer_exec("rm .env") deployer_exec("rm .kamal/.env")
end end
def assert_remote_env_file(contents, vm:) def assert_remote_env_file(contents, vm:)

View File

@@ -2,8 +2,6 @@ require_relative "integration_test"
class TraefikTest < IntegrationTest class TraefikTest < IntegrationTest
test "boot, reboot, stop, start, restart, logs, remove" do test "boot, reboot, stop, start, restart, logs, remove" do
kamal :envify
kamal :traefik, :boot kamal :traefik, :boot
assert_traefik_running assert_traefik_running

View File

@@ -26,6 +26,10 @@ end
class ActiveSupport::TestCase class ActiveSupport::TestCase
include ActiveSupport::Testing::Stream include ActiveSupport::Testing::Stream
setup do
Kamal::Cli::Base.ran_pre_init_hook = false
end
private private
def stdouted def stdouted
capture(:stdout) { yield }.strip capture(:stdout) { yield }.strip