Catch up with 2.5.1
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
run: bundle exec rubocop --parallel
|
||||
tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby-version:
|
||||
- "3.1"
|
||||
@@ -37,7 +38,6 @@ jobs:
|
||||
gemfile: gemfiles/rails_edge.gemfile
|
||||
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
env:
|
||||
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
||||
steps:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
kamal (2.4.0)
|
||||
kamal (2.5.1)
|
||||
activesupport (>= 7.0)
|
||||
base64 (~> 0.2)
|
||||
bcrypt_pbkdf (~> 1.0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
|
||||
def run(instance, args = [])
|
||||
if (_alias = KAMAL.config.aliases[name])
|
||||
KAMAL.reset
|
||||
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
|
||||
else
|
||||
super
|
||||
|
||||
@@ -16,12 +16,20 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
# Primary hosts and roles are returned first, so they can open the barrier
|
||||
barrier = Kamal::Cli::Healthcheck::Barrier.new
|
||||
|
||||
on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
|
||||
host_boot_groups.each do |hosts|
|
||||
host_list = Array(hosts).join(",")
|
||||
run_hook "pre-app-boot", hosts: host_list
|
||||
|
||||
on(hosts) do |host|
|
||||
KAMAL.roles_on(host).each do |role|
|
||||
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
|
||||
end
|
||||
end
|
||||
|
||||
run_hook "post-app-boot", hosts: host_list
|
||||
sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
|
||||
end
|
||||
|
||||
# Tag once the app booted on all hosts
|
||||
on(KAMAL.hosts) do |host|
|
||||
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
||||
@@ -340,4 +348,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def host_boot_groups
|
||||
KAMAL.config.boot.limit ? KAMAL.hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.hosts ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@ module Kamal::Cli
|
||||
super
|
||||
end
|
||||
|
||||
initialize_commander unless config[:invoked_via_subcommand]
|
||||
initialize_commander unless KAMAL.configured?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -5,11 +5,12 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
|
||||
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
||||
def deliver
|
||||
push
|
||||
pull
|
||||
invoke :push
|
||||
invoke :pull
|
||||
end
|
||||
|
||||
desc "push", "Build and push app image to registry"
|
||||
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
||||
def push
|
||||
cli = self
|
||||
|
||||
@@ -49,7 +50,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
end
|
||||
|
||||
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
||||
push = KAMAL.builder.push
|
||||
push = KAMAL.builder.push(cli.options[:output])
|
||||
|
||||
KAMAL.with_verbosity(:debug) do
|
||||
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
||||
@@ -108,6 +109,41 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
end
|
||||
end
|
||||
|
||||
desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
|
||||
option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
||||
def dev
|
||||
cli = self
|
||||
|
||||
ensure_docker_installed
|
||||
|
||||
docker_included_files = Set.new(Kamal::Docker.included_files)
|
||||
git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
|
||||
git_untracked_files = Set.new(Kamal::Git.untracked_files)
|
||||
|
||||
docker_uncommitted_files = docker_included_files & git_uncommitted_files
|
||||
if docker_uncommitted_files.any?
|
||||
say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
|
||||
docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
|
||||
say
|
||||
end
|
||||
|
||||
docker_untracked_files = docker_included_files & git_untracked_files
|
||||
if docker_untracked_files.any?
|
||||
say "WARNING: Untracked files will be present in the dev container:", :yellow
|
||||
docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
|
||||
say
|
||||
end
|
||||
|
||||
with_env(KAMAL.config.builder.secrets) do
|
||||
run_locally do
|
||||
build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
|
||||
KAMAL.with_verbosity(:debug) do
|
||||
execute(*build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def connect_to_remote_host(remote_host)
|
||||
remote_uri = URI.parse(remote_host)
|
||||
|
||||
@@ -3,7 +3,7 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
|
||||
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
||||
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
||||
def login
|
||||
ensure_docker_installed
|
||||
ensure_docker_installed unless options[:skip_local]
|
||||
|
||||
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
||||
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
||||
|
||||
3
lib/kamal/cli/templates/sample_hooks/post-app-boot.sample
Executable file
3
lib/kamal/cli/templates/sample_hooks/post-app-boot.sample
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
||||
3
lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample
Executable file
3
lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
||||
@@ -4,13 +4,20 @@ require "active_support/core_ext/object/blank"
|
||||
|
||||
class Kamal::Commander
|
||||
attr_accessor :verbosity, :holding_lock, :connected
|
||||
attr_reader :specific_roles, :specific_hosts
|
||||
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
|
||||
|
||||
def initialize
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
self.verbosity = :info
|
||||
self.holding_lock = false
|
||||
self.connected = false
|
||||
@specifics = nil
|
||||
@specifics = @specific_roles = @specific_hosts = nil
|
||||
@config = @config_kwargs = nil
|
||||
@commands = {}
|
||||
end
|
||||
|
||||
def config
|
||||
@@ -28,8 +35,6 @@ class Kamal::Commander
|
||||
@config || @config_kwargs
|
||||
end
|
||||
|
||||
attr_reader :specific_roles, :specific_hosts
|
||||
|
||||
def specific_primary!
|
||||
@specifics = nil
|
||||
if specific_roles.present?
|
||||
@@ -89,35 +94,35 @@ class Kamal::Commander
|
||||
end
|
||||
|
||||
def builder
|
||||
@builder ||= Kamal::Commands::Builder.new(config)
|
||||
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
|
||||
end
|
||||
|
||||
def docker
|
||||
@docker ||= Kamal::Commands::Docker.new(config)
|
||||
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
|
||||
end
|
||||
|
||||
def hook
|
||||
@hook ||= Kamal::Commands::Hook.new(config)
|
||||
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
|
||||
end
|
||||
|
||||
def lock
|
||||
@lock ||= Kamal::Commands::Lock.new(config)
|
||||
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
||||
end
|
||||
|
||||
def proxy
|
||||
@proxy ||= Kamal::Commands::Proxy.new(config)
|
||||
@commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
|
||||
end
|
||||
|
||||
def prune
|
||||
@prune ||= Kamal::Commands::Prune.new(config)
|
||||
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
|
||||
end
|
||||
|
||||
def registry
|
||||
@registry ||= Kamal::Commands::Registry.new(config)
|
||||
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
|
||||
end
|
||||
|
||||
def server
|
||||
@server ||= Kamal::Commands::Server.new(config)
|
||||
@commands[:server] ||= Kamal::Commands::Server.new(config)
|
||||
end
|
||||
|
||||
def alias(name)
|
||||
@@ -136,14 +141,6 @@ class Kamal::Commander
|
||||
SSHKit.config.output_verbosity = old_level
|
||||
end
|
||||
|
||||
def boot_strategy
|
||||
if config.boot.limit.present?
|
||||
{ in: :groups, limit: config.boot.limit, wait: config.boot.wait }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def holding_lock?
|
||||
self.holding_lock
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require "active_support/core_ext/string/filters"
|
||||
|
||||
class Kamal::Commands::Builder < Kamal::Commands::Base
|
||||
delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
|
||||
delegate :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
|
||||
delegate :local?, :remote?, :pack?, :cloud?, to: "config.builder"
|
||||
|
||||
include Clone
|
||||
|
||||
@@ -14,11 +14,12 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
docker :image, :rm, "--force", config.absolute_image
|
||||
end
|
||||
|
||||
def push
|
||||
def push(export_action = "registry", tag_as_dirty: false)
|
||||
docker :buildx, :build,
|
||||
"--push",
|
||||
"--output=type=#{export_action}",
|
||||
*platform_options(arches),
|
||||
*([ "--builder", builder_name ] unless docker_driver?),
|
||||
*build_tag_options(tag_as_dirty: tag_as_dirty),
|
||||
*build_options,
|
||||
build_context
|
||||
end
|
||||
@@ -38,7 +39,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def build_options
|
||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
|
||||
[ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
|
||||
end
|
||||
|
||||
def build_context
|
||||
@@ -59,8 +60,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
private
|
||||
def build_tags
|
||||
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
||||
def build_tag_names(tag_as_dirty: false)
|
||||
tag_names = [ config.absolute_image, config.latest_image ]
|
||||
tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
|
||||
tag_names
|
||||
end
|
||||
|
||||
def build_tag_options(tag_as_dirty: false)
|
||||
build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
|
||||
end
|
||||
|
||||
def build_cache
|
||||
|
||||
@@ -16,6 +16,8 @@ class Kamal::Configuration::Accessory
|
||||
context: "accessories/#{name}",
|
||||
with: Kamal::Configuration::Validator::Accessory
|
||||
|
||||
ensure_valid_roles
|
||||
|
||||
@env = initialize_env
|
||||
@proxy = initialize_proxy if running_proxy?
|
||||
@registry = initialize_registry if accessory_config["registry"].present?
|
||||
@@ -200,13 +202,17 @@ class Kamal::Configuration::Accessory
|
||||
|
||||
def hosts_from_roles
|
||||
if accessory_config.key?("roles")
|
||||
accessory_config["roles"].flat_map do |role|
|
||||
config.role(role)&.hosts || raise(Kamal::ConfigurationError, "Unknown role in accessories config: '#{role}'")
|
||||
end
|
||||
accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
|
||||
end
|
||||
end
|
||||
|
||||
def network
|
||||
accessory_config["network"] || DEFAULT_NETWORK
|
||||
end
|
||||
|
||||
def ensure_valid_roles
|
||||
if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
|
||||
raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,5 +118,6 @@ accessories:
|
||||
|
||||
# Proxy
|
||||
#
|
||||
# You can run your accessory behind the Kamal proxy. See kamal docs proxy for more information
|
||||
proxy:
|
||||
...
|
||||
|
||||
30
lib/kamal/docker.rb
Normal file
30
lib/kamal/docker.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require "tempfile"
|
||||
require "open3"
|
||||
|
||||
module Kamal::Docker
|
||||
extend self
|
||||
BUILD_CHECK_TAG = "kamal-local-build-check"
|
||||
|
||||
def included_files
|
||||
Tempfile.create do |dockerfile|
|
||||
dockerfile.write(<<~DOCKERFILE)
|
||||
FROM busybox
|
||||
COPY . app
|
||||
WORKDIR app
|
||||
CMD find . -type f | sed "s|^\./||"
|
||||
DOCKERFILE
|
||||
dockerfile.close
|
||||
|
||||
cmd = "docker buildx build -t=#{BUILD_CHECK_TAG} -f=#{dockerfile.path} ."
|
||||
system(cmd) || raise("failed to build check image")
|
||||
end
|
||||
|
||||
cmd = "docker run --rm #{BUILD_CHECK_TAG}"
|
||||
out, err, status = Open3.capture3(cmd)
|
||||
unless status
|
||||
raise "failed to run check image:\n#{err}"
|
||||
end
|
||||
|
||||
out.lines.map(&:strip)
|
||||
end
|
||||
end
|
||||
@@ -24,4 +24,14 @@ module Kamal::Git
|
||||
def root
|
||||
`git rev-parse --show-toplevel`.strip
|
||||
end
|
||||
|
||||
# returns an array of relative path names of files with uncommitted changes
|
||||
def uncommitted_files
|
||||
`git ls-files --modified`.lines.map(&:strip)
|
||||
end
|
||||
|
||||
# returns an array of relative path names of untracked files, including gitignored files
|
||||
def untracked_files
|
||||
`git ls-files --others`.lines.map(&:strip)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Base
|
||||
def requires_account?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
def login(_account)
|
||||
nil
|
||||
end
|
||||
|
||||
def fetch_secrets(secrets, from:, account:, session:)
|
||||
def fetch_secrets(secrets, from:, account: nil, session:)
|
||||
{}.tap do |results|
|
||||
get_from_secrets_manager(prefixed_secrets(secrets, from: from), account: account).each do |secret|
|
||||
secret_name = secret["Name"]
|
||||
@@ -19,8 +23,12 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba
|
||||
end
|
||||
end
|
||||
|
||||
def get_from_secrets_manager(secrets, account:)
|
||||
`aws secretsmanager batch-get-secret-value --secret-id-list #{secrets.map(&:shellescape).join(" ")} --profile #{account.shellescape}`.tap do |secrets|
|
||||
def get_from_secrets_manager(secrets, account: nil)
|
||||
args = [ "aws", "secretsmanager", "batch-get-secret-value", "--secret-id-list" ] + secrets.map(&:shellescape)
|
||||
args += [ "--profile", account.shellescape ] if account
|
||||
cmd = args.join(" ")
|
||||
|
||||
`#{cmd}`.tap do |secrets|
|
||||
raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success?
|
||||
|
||||
secrets = JSON.parse(secrets)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Kamal
|
||||
VERSION = "2.4.0"
|
||||
VERSION = "2.5.1"
|
||||
end
|
||||
|
||||
@@ -37,13 +37,20 @@ class CliAppTest < CliTestCase
|
||||
end
|
||||
|
||||
test "boot uses group strategy when specified" do
|
||||
Kamal::Cli::App.any_instance.stubs(:on).with("1.1.1.1").times(2) # ensure locks dir, acquire & release lock
|
||||
Kamal::Cli::App.any_instance.stubs(:on).with([ "1.1.1.1" ]) # tag container
|
||||
Kamal::Cli::App.any_instance.stubs(:on).with("1.1.1.1").twice
|
||||
Kamal::Cli::App.any_instance.stubs(:on).with([ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ]).times(3)
|
||||
|
||||
# Strategy is used when booting the containers
|
||||
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.1" ], in: :groups, limit: 3, wait: 2).with_block_given
|
||||
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.1", "1.1.1.2", "1.1.1.3" ]).with_block_given
|
||||
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.4" ]).with_block_given
|
||||
Object.any_instance.expects(:sleep).with(2).twice
|
||||
|
||||
run_command("boot", config: :with_boot_strategy)
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
|
||||
run_command("boot", config: :with_boot_strategy, host: nil).tap do |output|
|
||||
assert_hook_ran "pre-app-boot", output, count: 2
|
||||
assert_hook_ran "post-app-boot", output, count: 2
|
||||
end
|
||||
end
|
||||
|
||||
test "boot errors don't leave lock in place" do
|
||||
|
||||
@@ -11,7 +11,6 @@ class CliBuildTest < CliTestCase
|
||||
test "push" do
|
||||
with_build_directory do |build_directory|
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||
@@ -22,11 +21,33 @@ class CliBuildTest < CliTestCase
|
||||
.returns("")
|
||||
|
||||
run_command("push", "--verbose").tap do |output|
|
||||
assert_hook_ran "pre-build", output, **hook_variables
|
||||
assert_hook_ran "pre-build", 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 /docker --version && docker buildx version/, output
|
||||
assert_match /docker buildx build --push --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
||||
assert_match /docker buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "push --output=docker" do
|
||||
with_build_directory do |build_directory|
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||
.returns(Kamal::Git.revision)
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||
.with(:git, "-C", anything, :status, "--porcelain")
|
||||
.returns("")
|
||||
|
||||
run_command("push", "--output=docker", "--verbose").tap do |output|
|
||||
assert_hook_ran "pre-build", 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 /docker --version && docker buildx version/, output
|
||||
assert_match /docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -49,7 +70,7 @@ class CliBuildTest < CliTestCase
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init")
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
||||
.with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-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)
|
||||
@@ -68,13 +89,12 @@ class CliBuildTest < CliTestCase
|
||||
|
||||
test "push without clone" do
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
|
||||
|
||||
run_command("push", "--verbose", fixture: :without_clone).tap do |output|
|
||||
assert_no_match /Cloning repo into build directory/, output
|
||||
assert_hook_ran "pre-build", output, **hook_variables
|
||||
assert_hook_ran "pre-build", output
|
||||
assert_match /docker --version && docker buildx version/, output
|
||||
assert_match /docker buildx build --push --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
|
||||
assert_match /docker buildx build --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -140,7 +160,7 @@ class CliBuildTest < CliTestCase
|
||||
.returns("")
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
||||
.with(:docker, :buildx, :build, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-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
|
||||
@@ -275,6 +295,30 @@ class CliBuildTest < CliTestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "dev" do
|
||||
with_build_directory do |build_directory|
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
|
||||
run_command("dev", "--verbose").tap do |output|
|
||||
assert_no_match(/Cloning repo into build directory/, output)
|
||||
assert_match(/docker --version && docker buildx version/, output)
|
||||
assert_match(/docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. as .*@localhost/, output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "dev --output=local" do
|
||||
with_build_directory do |build_directory|
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
|
||||
run_command("dev", "--output=local", "--verbose").tap do |output|
|
||||
assert_no_match(/Cloning repo into build directory/, output)
|
||||
assert_match(/docker --version && docker buildx version/, output)
|
||||
assert_match(/docker buildx build --output=type=local --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. as .*@localhost/, output)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command, fixture: :with_accessories)
|
||||
stdouted { Kamal::Cli::Build.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) }
|
||||
|
||||
@@ -40,8 +40,9 @@ class CliTestCase < ActiveSupport::TestCase
|
||||
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
|
||||
end
|
||||
|
||||
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false, secrets: false)
|
||||
assert_match %r{usr/bin/env\s\.kamal/hooks/#{hook}}, output
|
||||
def assert_hook_ran(hook, output, count: 1)
|
||||
regexp = ([ "/usr/bin/env .kamal/hooks/#{hook}" ] * count).join(".*")
|
||||
assert_match /#{regexp}/m, output
|
||||
end
|
||||
|
||||
def with_argv(*argv)
|
||||
|
||||
@@ -53,17 +53,16 @@ class CliMainTest < CliTestCase
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
|
||||
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" }
|
||||
|
||||
run_command("deploy", "--verbose").tap do |output|
|
||||
assert_hook_ran "pre-connect", output, **hook_variables
|
||||
assert_hook_ran "pre-connect", output
|
||||
assert_match /Log into image registry/, output
|
||||
assert_match /Build and push app image/, output
|
||||
assert_hook_ran "pre-deploy", output, **hook_variables, secrets: true
|
||||
assert_hook_ran "pre-deploy", output
|
||||
assert_match /Ensure kamal-proxy is running/, output
|
||||
assert_match /Detect stale containers/, output
|
||||
assert_match /Prune old containers and images/, output
|
||||
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true, secrets: true
|
||||
assert_hook_ran "post-deploy", output
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -205,14 +204,12 @@ class CliMainTest < CliTestCase
|
||||
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||
|
||||
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "redeploy" }
|
||||
|
||||
run_command("redeploy", "--verbose").tap do |output|
|
||||
assert_hook_ran "pre-connect", output, **hook_variables
|
||||
assert_hook_ran "pre-connect", output
|
||||
assert_match /Build and push app image/, output
|
||||
assert_hook_ran "pre-deploy", output, **hook_variables
|
||||
assert_hook_ran "pre-deploy", output
|
||||
assert_match /Running the pre-deploy hook.../, output
|
||||
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true
|
||||
assert_hook_ran "post-deploy", output
|
||||
end
|
||||
end
|
||||
|
||||
@@ -258,14 +255,13 @@ class CliMainTest < CliTestCase
|
||||
.returns("running").at_least_once # health check
|
||||
|
||||
Kamal::Commands::Hook.any_instance.stubs(:hook_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" }
|
||||
|
||||
run_command("rollback", "--verbose", "123", config_file: "deploy_with_accessories").tap do |output|
|
||||
assert_hook_ran "pre-deploy", output, **hook_variables
|
||||
assert_hook_ran "pre-deploy", output
|
||||
assert_match "docker tag dhh/app:123 dhh/app:latest", output
|
||||
assert_match "docker run --detach --restart unless-stopped --name app-web-123", output
|
||||
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
||||
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true
|
||||
assert_hook_ran "post-deploy", output
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -52,6 +52,18 @@ class CliRegistryTest < CliTestCase
|
||||
assert_raises(Kamal::Cli::DependencyError) { run_command("login") }
|
||||
end
|
||||
|
||||
test "allow remote login with no docker" do
|
||||
stub_setup
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||
.raises(SSHKit::Command::Failed.new("command not found"))
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with { |*args| args[0..1] == [ :docker, :login ] }
|
||||
|
||||
assert_nothing_raised { run_command("login", "--skip-local") }
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
|
||||
@@ -104,28 +104,6 @@ class CommanderTest < ActiveSupport::TestCase
|
||||
assert_equal [ "web", "workers" ], @kamal.roles_on("1.1.1.1").map(&:name)
|
||||
end
|
||||
|
||||
test "default group strategy" do
|
||||
assert_empty @kamal.boot_strategy
|
||||
end
|
||||
|
||||
test "specific limit group strategy" do
|
||||
configure_with(:deploy_with_boot_strategy)
|
||||
|
||||
assert_equal({ in: :groups, limit: 3, wait: 2 }, @kamal.boot_strategy)
|
||||
end
|
||||
|
||||
test "percentage-based group strategy" do
|
||||
configure_with(:deploy_with_percentage_boot_strategy)
|
||||
|
||||
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||
end
|
||||
|
||||
test "percentage-based group strategy limit is at least 1" do
|
||||
configure_with(:deploy_with_low_percentage_boot_strategy)
|
||||
|
||||
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||
end
|
||||
|
||||
test "try to match the primary role from a list of specific roles" do
|
||||
configure_with(:deploy_primary_web_role_override)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||
assert_equal "local", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -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
|
||||
|
||||
@@ -17,7 +17,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "amd64" ] })
|
||||
assert_equal "local", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||
assert_equal "local", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -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
|
||||
|
||||
@@ -33,7 +33,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "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-hybrid-docker-container-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 --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-hybrid-docker-container-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
|
||||
|
||||
@@ -41,7 +41,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" }, "local" => false })
|
||||
assert_equal "remote", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-remote-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 --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-remote-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
|
||||
|
||||
@@ -49,7 +49,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
||||
assert_equal "remote", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/#{remote_arch} --builder kamal-remote-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 --output=type=registry --platform linux/#{remote_arch} --builder kamal-remote-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
|
||||
|
||||
@@ -57,7 +57,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
||||
assert_equal "local", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/#{local_arch} --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/#{local_arch} --builder kamal-local-docker-container -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
|
||||
|
||||
@@ -91,14 +91,14 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "driver" => "cloud docker-org-name/builder-name" })
|
||||
assert_equal "cloud", builder.name
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/#{local_arch} --builder cloud-docker-org-name-builder-name -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/#{local_arch} --builder cloud-docker-org-name-builder-name -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "build args" do
|
||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile",
|
||||
"--label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
@@ -107,7 +107,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
FileUtils.touch("Dockerfile")
|
||||
builder = new_builder_command(builder: { "secrets" => [ "token_a", "token_b" ] })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile",
|
||||
"--label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
end
|
||||
@@ -116,7 +116,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
Pathname.any_instance.expects(:exist?).returns(true).once
|
||||
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz",
|
||||
"--label service=\"app\" --file Dockerfile.xyz",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
@@ -131,21 +131,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
test "build target" do
|
||||
builder = new_builder_command(builder: { "target" => "prod" })
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --target prod",
|
||||
"--label service=\"app\" --file Dockerfile --target prod",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
test "build context" do
|
||||
builder = new_builder_command(builder: { "context" => ".." })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "push with build args" do
|
||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -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
|
||||
|
||||
@@ -154,7 +154,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
FileUtils.touch("Dockerfile")
|
||||
builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
end
|
||||
@@ -163,7 +163,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
builder = new_builder_command(builder: { "ssh" => "default=$SSH_AUTH_SOCK" })
|
||||
|
||||
assert_equal \
|
||||
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --ssh default=$SSH_AUTH_SOCK",
|
||||
"--label service=\"app\" --file Dockerfile --ssh default=$SSH_AUTH_SOCK",
|
||||
builder.target.build_options.join(" ")
|
||||
end
|
||||
|
||||
@@ -174,35 +174,35 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
test "context build" do
|
||||
builder = new_builder_command(builder: { "context" => "./foo" })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "push with provenance" do
|
||||
builder = new_builder_command(builder: { "provenance" => "mode=max" })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "push with provenance false" do
|
||||
builder = new_builder_command(builder: { "provenance" => false })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "push with sbom" do
|
||||
builder = new_builder_command(builder: { "sbom" => true })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom true .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom true .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
test "push with sbom false" do
|
||||
builder = new_builder_command(builder: { "sbom" => false })
|
||||
assert_equal \
|
||||
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom false .",
|
||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom false .",
|
||||
builder.push.join(" ")
|
||||
end
|
||||
|
||||
|
||||
54
test/configuration/boot_test.rb
Normal file
54
test/configuration/boot_test.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
require "test_helper"
|
||||
|
||||
class ConfigurationBootTest < ActiveSupport::TestCase
|
||||
test "no group strategy" do
|
||||
deploy = {
|
||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, builder: { "arch" => "amd64" },
|
||||
servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => [ "1.1.1.3", "1.1.1.4" ] }
|
||||
}
|
||||
|
||||
config = Kamal::Configuration.new(deploy)
|
||||
|
||||
assert_nil config.boot.limit
|
||||
assert_nil config.boot.wait
|
||||
end
|
||||
|
||||
test "specific limit group strategy" do
|
||||
deploy = {
|
||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, builder: { "arch" => "amd64" },
|
||||
servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => [ "1.1.1.3", "1.1.1.4" ] },
|
||||
boot: { "limit" => 3, "wait" => 2 }
|
||||
}
|
||||
|
||||
config = Kamal::Configuration.new(deploy)
|
||||
|
||||
assert_equal 3, config.boot.limit
|
||||
assert_equal 2, config.boot.wait
|
||||
end
|
||||
|
||||
test "percentage-based group strategy" do
|
||||
deploy = {
|
||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, builder: { "arch" => "amd64" },
|
||||
servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => [ "1.1.1.3", "1.1.1.4" ] },
|
||||
boot: { "limit" => "50%", "wait" => 2 }
|
||||
}
|
||||
|
||||
config = Kamal::Configuration.new(deploy)
|
||||
|
||||
assert_equal 2, config.boot.limit
|
||||
assert_equal 2, config.boot.wait
|
||||
end
|
||||
|
||||
test "percentage-based group strategy limit is at least 1" do
|
||||
deploy = {
|
||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, builder: { "arch" => "amd64" },
|
||||
servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => [ "1.1.1.3", "1.1.1.4" ] },
|
||||
boot: { "limit" => "1%", "wait" => 2 }
|
||||
}
|
||||
|
||||
config = Kamal::Configuration.new(deploy)
|
||||
|
||||
assert_equal 1, config.boot.limit
|
||||
assert_equal 2, config.boot.wait
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
service: app
|
||||
image: dhh/app
|
||||
servers:
|
||||
web:
|
||||
- "1.1.1.1"
|
||||
- "1.1.1.2"
|
||||
workers:
|
||||
- "1.1.1.3"
|
||||
- "1.1.1.4"
|
||||
builder:
|
||||
arch: amd64
|
||||
|
||||
registry:
|
||||
username: user
|
||||
password: pw
|
||||
|
||||
boot:
|
||||
limit: 1%
|
||||
wait: 2
|
||||
@@ -1,19 +0,0 @@
|
||||
service: app
|
||||
image: dhh/app
|
||||
servers:
|
||||
web:
|
||||
- "1.1.1.1"
|
||||
- "1.1.1.2"
|
||||
workers:
|
||||
- "1.1.1.3"
|
||||
- "1.1.1.4"
|
||||
builder:
|
||||
arch: amd64
|
||||
|
||||
registry:
|
||||
username: user
|
||||
password: pw
|
||||
|
||||
boot:
|
||||
limit: 1%
|
||||
wait: 2
|
||||
@@ -15,7 +15,9 @@ class AppTest < IntegrationTest
|
||||
# kamal app start does not wait
|
||||
wait_for_app_to_be_up
|
||||
|
||||
kamal :app, :boot
|
||||
output = kamal :app, :boot, "--verbose", capture: true
|
||||
assert_match "Booting app on vm1,vm2...", output
|
||||
assert_match "Booted app on vm1,vm2...", output
|
||||
|
||||
wait_for_app_to_be_up
|
||||
|
||||
|
||||
3
test/integration/docker/deployer/app/.kamal/hooks/post-app-boot
Executable file
3
test/integration/docker/deployer/app/.kamal/hooks/post-app-boot
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "Booted app on ${KAMAL_HOSTS}..."
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/post-app-boot
|
||||
3
test/integration/docker/deployer/app/.kamal/hooks/pre-app-boot
Executable file
3
test/integration/docker/deployer/app/.kamal/hooks/pre-app-boot
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
echo "Booting app on ${KAMAL_HOSTS}..."
|
||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-app-boot
|
||||
@@ -8,19 +8,19 @@ class MainTest < IntegrationTest
|
||||
|
||||
kamal :deploy
|
||||
assert_app_is_up version: first_version
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy"
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "pre-app-boot", "post-app-boot", "post-deploy"
|
||||
assert_envs version: first_version
|
||||
|
||||
second_version = update_app_rev
|
||||
|
||||
kamal :redeploy
|
||||
assert_app_is_up version: second_version
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy"
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "pre-app-boot", "post-app-boot", "post-deploy"
|
||||
|
||||
assert_accumulated_assets first_version, second_version
|
||||
|
||||
kamal :rollback, first_version
|
||||
assert_hooks_ran "pre-connect", "pre-deploy", "post-deploy"
|
||||
assert_hooks_ran "pre-connect", "pre-deploy", "pre-app-boot", "post-app-boot", "post-deploy"
|
||||
assert_app_is_up version: first_version
|
||||
|
||||
details = kamal :details, capture: true
|
||||
|
||||
@@ -156,14 +156,45 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase
|
||||
assert_equal "AWS CLI is not installed", error.message
|
||||
end
|
||||
|
||||
test "fetch without account option omits --profile" do
|
||||
stub_ticks.with("aws --version 2> /dev/null")
|
||||
stub_ticks
|
||||
.with("aws secretsmanager batch-get-secret-value --secret-id-list secret/KEY1 secret/KEY2")
|
||||
.returns(<<~JSON)
|
||||
{
|
||||
"SecretValues": [
|
||||
{
|
||||
"ARN": "arn:aws:secretsmanager:us-east-1:aaaaaaaaaaaa:secret:secret",
|
||||
"Name": "secret",
|
||||
"VersionId": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv",
|
||||
"SecretString": "{\\"KEY1\\":\\"VALUE1\\", \\"KEY2\\":\\"VALUE2\\"}",
|
||||
"VersionStages": [
|
||||
"AWSCURRENT"
|
||||
],
|
||||
"CreatedDate": "2024-01-01T00:00:00.000000"
|
||||
}
|
||||
],
|
||||
"Errors": []
|
||||
}
|
||||
JSON
|
||||
|
||||
json = JSON.parse(shellunescape(run_command("fetch", "--from", "secret", "KEY1", "KEY2", account: nil)))
|
||||
|
||||
expected_json = {
|
||||
"secret/KEY1"=>"VALUE1",
|
||||
"secret/KEY2"=>"VALUE2"
|
||||
}
|
||||
assert_equal expected_json, json
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
def run_command(*command, account: "default")
|
||||
stdouted do
|
||||
Kamal::Cli::Secrets.start \
|
||||
[ *command,
|
||||
args = [ *command,
|
||||
"-c", "test/fixtures/deploy_with_accessories.yml",
|
||||
"--adapter", "aws_secrets_manager",
|
||||
"--account", "default" ]
|
||||
"--adapter", "aws_secrets_manager" ]
|
||||
args += [ "--account", account ] if account
|
||||
Kamal::Cli::Secrets.start(args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,6 +36,10 @@ class ActiveSupport::TestCase
|
||||
extend Rails::LineFiltering
|
||||
|
||||
private
|
||||
setup do
|
||||
SSHKit::Backend::Netssh.pool.close_connections
|
||||
end
|
||||
|
||||
def stdouted
|
||||
capture(:stdout) { yield }.strip
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user