Compare commits

..

2 Commits

Author SHA1 Message Date
Donal McBreen
e7ebaa5abd Fix up tests 2024-08-26 11:06:41 +01:00
xiaohui
b12de87388 git clone with --recurse-submodules 2024-07-17 10:36:58 +08:00
28 changed files with 65 additions and 250 deletions

View File

@@ -24,12 +24,25 @@ jobs:
strategy: strategy:
matrix: matrix:
ruby-version: ruby-version:
- "2.7"
- "3.1" - "3.1"
- "3.2" - "3.2"
- "3.3" - "3.3"
gemfile: gemfile:
- Gemfile - Gemfile
- gemfiles/ruby_2.7.gemfile
- gemfiles/rails_edge.gemfile - gemfiles/rails_edge.gemfile
exclude:
- ruby-version: "2.7"
gemfile: Gemfile
- ruby-version: "2.7"
gemfile: gemfiles/rails_edge.gemfile
- ruby-version: "3.1"
gemfile: gemfiles/ruby_2.7.gemfile
- ruby-version: "3.2"
gemfile: gemfiles/ruby_2.7.gemfile
- ruby-version: "3.3"
gemfile: gemfiles/ruby_2.7.gemfile
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }} name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
continue-on-error: true continue-on-error: true

View File

@@ -1,7 +1,7 @@
# Use the official Ruby 3.2.0 Alpine image as the base image # Use the official Ruby 3.2.0 Alpine image as the base image
FROM ruby:3.2.0-alpine FROM ruby:3.2.0-alpine
# Install docker/buildx-bin # Install docker/buildx-bin
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
# Set the working directory to /kamal # Set the working directory to /kamal
@@ -14,7 +14,7 @@ COPY Gemfile Gemfile.lock kamal.gemspec ./
COPY lib/kamal/version.rb /kamal/lib/kamal/version.rb COPY lib/kamal/version.rb /kamal/lib/kamal/version.rb
# Install system dependencies # Install system dependencies
RUN apk add --no-cache build-base git docker openrc openssh-client-default \ RUN apk add --no-cache --update build-base git docker openrc openssh-client-default \
&& rc-update add docker boot \ && rc-update add docker boot \
&& gem install bundler --version=2.4.3 \ && gem install bundler --version=2.4.3 \
&& bundle install && bundle install

View File

@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
kamal (1.8.1) kamal (1.7.3)
activesupport (>= 7.0) activesupport (>= 7.0)
base64 (~> 0.2) base64 (~> 0.2)
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
@@ -9,8 +9,9 @@ PATH
dotenv (~> 2.8) dotenv (~> 2.8)
ed25519 (~> 1.2) ed25519 (~> 1.2)
net-ssh (~> 7.0) net-ssh (~> 7.0)
sshkit (>= 1.23.0, < 2.0) sshkit (>= 1.22.2, < 2.0)
thor (~> 1.2) thor (~> 1.2)
x25519 (~> 1.0, >= 1.0.10)
zeitwerk (~> 2.5) zeitwerk (~> 2.5)
GEM GEM
@@ -153,8 +154,9 @@ GEM
rubocop-rails rubocop-rails
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
sshkit (1.23.0) sshkit (1.22.2)
base64 base64
mutex_m
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-sftp (>= 2.1.2) net-sftp (>= 2.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
@@ -164,6 +166,7 @@ GEM
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0) unicode-display_width (2.5.0)
webrick (1.8.1) webrick (1.8.1)
x25519 (1.0.10)
zeitwerk (2.6.12) zeitwerk (2.6.12)
PLATFORMS PLATFORMS

View File

@@ -0,0 +1,6 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec path: "../"
gem "nokogiri", "~> 1.15.0"

View File

@@ -12,12 +12,13 @@ Gem::Specification.new do |spec|
spec.executables = %w[ kamal ] spec.executables = %w[ kamal ]
spec.add_dependency "activesupport", ">= 7.0" spec.add_dependency "activesupport", ">= 7.0"
spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0" spec.add_dependency "sshkit", ">= 1.22.2", "< 2.0"
spec.add_dependency "net-ssh", "~> 7.0" spec.add_dependency "net-ssh", "~> 7.0"
spec.add_dependency "thor", "~> 1.2" spec.add_dependency "thor", "~> 1.2"
spec.add_dependency "dotenv", "~> 2.8" spec.add_dependency "dotenv", "~> 2.8"
spec.add_dependency "zeitwerk", "~> 2.5" spec.add_dependency "zeitwerk", "~> 2.5"
spec.add_dependency "ed25519", "~> 1.2" spec.add_dependency "ed25519", "~> 1.2"
spec.add_dependency "x25519", "~> 1.0", ">= 1.0.10"
spec.add_dependency "bcrypt_pbkdf", "~> 1.0" spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
spec.add_dependency "concurrent-ruby", "~> 1.2" spec.add_dependency "concurrent-ruby", "~> 1.2"
spec.add_dependency "base64", "~> 0.2" spec.add_dependency "base64", "~> 0.2"

View File

@@ -25,17 +25,12 @@ module Kamal::Cli
def initialize(*) def initialize(*)
super super
@original_env = ENV.to_h.dup @original_env = ENV.to_h.dup
load_env load_envs
initialize_commander(options_with_subcommand_class_options) initialize_commander(options_with_subcommand_class_options)
end end
private private
def reload_env def load_envs
reset_env
load_env
end
def load_env
if destination = options[:destination] if destination = options[:destination]
Dotenv.load(".env.#{destination}", ".env") Dotenv.load(".env.#{destination}", ".env")
else else
@@ -43,27 +38,10 @@ module Kamal::Cli
end end
end end
def reset_env def reload_envs
replace_env @original_env
end
def replace_env(env)
ENV.clear ENV.clear
ENV.update(env) ENV.update(@original_env)
end load_envs
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

View File

@@ -59,14 +59,11 @@ class Kamal::Cli::Build < Kamal::Cli::Base
desc "pull", "Pull app image from registry onto servers" desc "pull", "Pull app image from registry onto servers"
def pull def pull
if (first_hosts = mirror_hosts).any? on(KAMAL.hosts) do
#  Pull on a single host per mirror first to seed them execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
pull_on_hosts(first_hosts) execute *KAMAL.builder.pull
say "Pulling image on remaining hosts...", :magenta execute *KAMAL.builder.validate_image
pull_on_hosts(KAMAL.hosts - first_hosts)
else
pull_on_hosts(KAMAL.hosts)
end end
end end
@@ -134,28 +131,4 @@ class Kamal::Cli::Build < Kamal::Cli::Base
end end
end end
end end
def mirror_hosts
if KAMAL.hosts.many?
mirror_hosts = Concurrent::Hash.new
on(KAMAL.hosts) do |host|
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
rescue SSHKit::Command::Failed => e
raise unless e.message =~ /error calling index: reflect: slice index out of range/
end
mirror_hosts.values
else
[]
end
end
def pull_on_hosts(hosts)
on(hosts) do
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
execute *KAMAL.builder.pull
execute *KAMAL.builder.validate_image
end
end
end end

View File

@@ -191,12 +191,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
end end
if Pathname.new(File.expand_path(env_template_path)).exist? if Pathname.new(File.expand_path(env_template_path)).exist?
# Ensure existing env doesn't pollute template evaluation File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
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] unless options[:skip_push]
reload_env reload_envs
invoke "kamal:cli:env:push", options invoke "kamal:cli:env:push", options
end end
else else

View File

@@ -2,7 +2,7 @@ require "active_support/core_ext/string/filters"
class Kamal::Commands::Builder < Kamal::Commands::Base class Kamal::Commands::Builder < Kamal::Commands::Base
delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image, delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
:first_mirror, to: :target to: :target
include Clone include Clone

View File

@@ -40,10 +40,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
[] []
end end
def first_mirror
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
end
private private
def build_tags def build_tags
[ "-t", config.absolute_image, "-t", config.latest_image ] [ "-t", config.absolute_image, "-t", config.latest_image ]

View File

@@ -6,7 +6,7 @@ module Kamal::Commands::Builder::Clone
end end
def clone def clone
git :clone, Kamal::Git.root, path: clone_directory git :clone, Kamal::Git.root, "--recurse-submodules", path: clone_directory
end end
def clone_reset_steps def clone_reset_steps
@@ -14,7 +14,8 @@ module Kamal::Commands::Builder::Clone
git(:remote, "set-url", :origin, Kamal::Git.root, path: build_directory), git(:remote, "set-url", :origin, Kamal::Git.root, path: build_directory),
git(:fetch, :origin, path: build_directory), git(:fetch, :origin, path: build_directory),
git(:reset, "--hard", Kamal::Git.revision, path: build_directory), git(:reset, "--hard", Kamal::Git.revision, path: build_directory),
git(:clean, "-fdx", path: build_directory) git(:clean, "-fdx", path: build_directory),
git(:submodule, :update, "--init", path: build_directory)
] ]
end end

View File

@@ -47,7 +47,7 @@ class Kamal::Configuration
@destination = destination @destination = destination
@declared_version = version @declared_version = version
validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration validate! raw_config, example: validation_yml.symbolize_keys, context: ""
# Eager load config to validate it, these are first as they have dependencies later on # Eager load config to validate it, these are first as they have dependencies later on
@servers = Servers.new(config: self) @servers = Servers.new(config: self)

View File

@@ -2,24 +2,13 @@
# #
# Configuration is read from the `config/deploy.yml` # Configuration is read from the `config/deploy.yml`
# #
# Destinations
#
# When running commands, you can specify a destination with the `-d` flag, # When running commands, you can specify a destination with the `-d` flag,
# e.g. `kamal deploy -d staging` # e.g. `kamal deploy -d staging`
# #
# In this case the configuration will also be read from `config/deploy.staging.yml` # In this case the configuration will also be read from `config/deploy.staging.yml`
# and merged with the base configuration. # and merged with the base configuration.
# Extensions
# #
# Kamal will not accept unrecognized keys in the configuration file. # The available configuration options are explained below.
#
# However, you might want to declare a configuration block using YAML anchors
# and aliases to avoid repetition.
#
# You can use prefix a configuration section with `x-` to indicate that it is an
# extension. Kamal will ignore the extension and not raise an error.
# The service name # The service name
# This is a required value. It is used as the container name prefix. # This is a required value. It is used as the container name prefix.

View File

@@ -44,23 +44,3 @@ ssh:
# Defaults to `fatal`. Set this to debug if you are having # Defaults to `fatal`. Set this to debug if you are having
# SSH connection issues. # SSH connection issues.
log_level: debug log_level: debug
# Keys Only
#
# Set to true to use only private keys from keys and key_data parameters,
# even if ssh-agent offers more identities. This option is intended for
# situations where ssh-agent offers many different identites or you have
# a need to overwrite all identites and force a single one.
keys_only: false
# Keys
#
# An array of file names of private keys to use for publickey
# and hostbased authentication
keys: [ "~/.ssh/id.pem" ]
# Key Data
#
# An array of strings, with each element of the array being
# a raw private key in PEM format.
key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]

View File

@@ -26,20 +26,8 @@ class Kamal::Configuration::Ssh
end end
end end
def keys_only
ssh_config["keys_only"]
end
def keys
ssh_config["keys"]
end
def key_data
ssh_config["key_data"]
end
def options def options
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
end end
def to_h def to_h

View File

@@ -15,10 +15,11 @@ class Kamal::Configuration::Validator
def validate_against_example!(validation_config, example) def validate_against_example!(validation_config, example)
validate_type! validation_config, Hash validate_type! validation_config, Hash
check_unknown_keys! validation_config, example if (unknown_keys = validation_config.keys - example.keys).any?
unknown_keys_error unknown_keys
end
validation_config.each do |key, value| validation_config.each do |key, value|
next if extension?(key)
with_context(key) do with_context(key) do
example_value = example[key] example_value = example[key]
@@ -136,18 +137,4 @@ class Kamal::Configuration::Validator
ensure ensure
@context = old_context @context = old_context
end end
def allow_extensions?
false
end
def extension?(key)
key.to_s.start_with?("x-")
end
def check_unknown_keys!(config, example)
unknown_keys = config.keys - example.keys
unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
unknown_keys_error unknown_keys if unknown_keys.present?
end
end end

View File

@@ -1,6 +0,0 @@
class Kamal::Configuration::Validator::Configuration < Kamal::Configuration::Validator
private
def allow_extensions?
true
end
end

View File

@@ -9,10 +9,6 @@ module Kamal::Git
`git config user.name`.strip `git config user.name`.strip
end end
def email
`git config user.email`.strip
end
def revision def revision
`git rev-parse HEAD`.strip `git rev-parse HEAD`.strip
end end

View File

@@ -10,7 +10,7 @@ class Kamal::Tags
def default_tags(config) def default_tags(config)
{ recorded_at: Time.now.utc.iso8601, { recorded_at: Time.now.utc.iso8601,
performer: Kamal::Git.email.presence || `whoami`.chomp, performer: `whoami`.chomp,
destination: config.destination, destination: config.destination,
version: config.version, version: config.version,
service_version: service_version(config), service_version: service_version(config),

View File

@@ -1,3 +1,3 @@
module Kamal module Kamal
VERSION = "1.8.1" VERSION = "1.7.3"
end end

View File

@@ -42,7 +42,7 @@ class CliBuildTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version") SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd) .with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
.raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory")) .raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory"))
.then .then
.returns(true) .returns(true)
@@ -50,6 +50,7 @@ class CliBuildTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :fetch, :origin) SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :fetch, :origin)
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :reset, "--hard", Kamal::Git.revision) SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :reset, "--hard", Kamal::Git.revision)
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx") SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :clean, "-fdx")
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-app-multiarch", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") .with(:docker, :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", ".")
@@ -88,7 +89,7 @@ class CliBuildTest < CliTestCase
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version") SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
SSHKit::Backend::Abstract.any_instance.expects(:execute) SSHKit::Backend::Abstract.any_instance.expects(:execute)
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd) .with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
.raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory")) .raises(SSHKit::Command::Failed.new("fatal: destination path 'kamal' already exists and is not an empty directory"))
.then .then
.returns(true) .returns(true)
@@ -169,41 +170,12 @@ class CliBuildTest < CliTestCase
test "pull" do test "pull" do
run_command("pull").tap do |output| run_command("pull").tap do |output|
assert_match /docker info --format '{{index .RegistryConfig.Mirrors 0}}'/, output
assert_match /docker image rm --force dhh\/app:999/, output assert_match /docker image rm --force dhh\/app:999/, output
assert_match /docker pull dhh\/app:999/, output assert_match /docker pull dhh\/app:999/, output
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output
end end
end end
test "pull with mirror" do
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
.returns("registry-mirror.example.com")
.at_least_once
run_command("pull").tap do |output|
assert_match /Pulling image on 1\.1\.1\.\d to seed the mirror\.\.\./, output
assert_match "Pulling image on remaining hosts...", output
assert_equal 4, output.scan(/docker pull dhh\/app:999/).size, output
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output
end
end
test "pull with mirrors" do
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
.returns("registry-mirror.example.com", "registry-mirror2.example.com")
.at_least_once
run_command("pull").tap do |output|
assert_match /Pulling image on 1\.1\.1\.\d, 1\.1\.1\.\d to seed the mirrors\.\.\./, output
assert_match "Pulling image on remaining hosts...", output
assert_equal 4, output.scan(/docker pull dhh\/app:999/).size, output
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output
end
end
test "create" do test "create" do
run_command("create").tap do |output| run_command("create").tap do |output|
assert_match /docker buildx create --use --name kamal-app-multiarch/, output assert_match /docker buildx create --use --name kamal-app-multiarch/, output

View File

@@ -42,13 +42,12 @@ class CliTestCase < ActiveSupport::TestCase
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)
whoami = `whoami`.chomp performer = `whoami`.strip
performer = Kamal::Git.email.presence || whoami
service = service_version.split("@").first service = service_version.split("@").first
assert_match "Running the #{hook} hook...\n", output assert_match "Running the #{hook} hook...\n", output
expected = %r{Running\s/usr/bin/env\s\.kamal/hooks/#{hook}\sas\s#{whoami}@localhost\n\s expected = %r{Running\s/usr/bin/env\s\.kamal/hooks/#{hook}\sas\s#{performer}@localhost\n\s
DEBUG\s\[[0-9a-f]*\]\sCommand:\s\(\sexport\s DEBUG\s\[[0-9a-f]*\]\sCommand:\s\(\sexport\s
KAMAL_RECORDED_AT=\"\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\"\s KAMAL_RECORDED_AT=\"\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ\"\s
KAMAL_PERFORMER=\"#{performer}\"\s KAMAL_PERFORMER=\"#{performer}\"\s

View File

@@ -1,9 +1,6 @@
require_relative "cli_test_case" require_relative "cli_test_case"
class CliMainTest < CliTestCase class CliMainTest < CliTestCase
setup { @original_env = ENV.to_h.dup }
teardown { ENV.clear; ENV.update @original_env }
test "setup" do test "setup" do
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 }
@@ -125,11 +122,6 @@ class CliMainTest < CliTestCase
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null") .with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
.returns("") .returns("")
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
.returns("")
.at_least_once
assert_raises(Kamal::Cli::LockError) do assert_raises(Kamal::Cli::LockError) do
run_command("deploy") run_command("deploy")
end end
@@ -163,11 +155,6 @@ class CliMainTest < CliTestCase
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null") .with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
.returns("") .returns("")
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
.returns("")
.at_least_once
assert_raises(SSHKit::Runner::ExecuteError) do assert_raises(SSHKit::Runner::ExecuteError) do
run_command("deploy") run_command("deploy")
end end
@@ -447,7 +434,7 @@ class CliMainTest < CliTestCase
end end
test "envify" do test "envify" do
with_test_dotenv(".env.erb": "HELLO=<%= 'world' %>") do with_test_dot_env_erb(contents: "HELLO=<%= 'world' %>") do
run_command("envify") run_command("envify")
assert_equal("HELLO=world", File.read(".env")) assert_equal("HELLO=world", File.read(".env"))
end end
@@ -461,14 +448,14 @@ class CliMainTest < CliTestCase
<% end -%> <% end -%>
EOF EOF
with_test_dotenv(".env.erb": file) do with_test_dot_env_erb(contents: file) do
run_command("envify") run_command("envify")
assert_equal("HELLO=world\nKEY=value\n", File.read(".env")) assert_equal("HELLO=world\nKEY=value\n", File.read(".env"))
end end
end end
test "envify with destination" do test "envify with destination" do
with_test_dotenv(".env.world.erb": "HELLO=<%= 'world' %>") do with_test_dot_env_erb(contents: "HELLO=<%= 'world' %>", file: ".env.world.erb") do
run_command("envify", "-d", "world", config_file: "deploy_for_dest") run_command("envify", "-d", "world", config_file: "deploy_for_dest")
assert_equal "HELLO=world", File.read(".env.world") assert_equal "HELLO=world", File.read(".env.world")
end end
@@ -483,13 +470,6 @@ class CliMainTest < CliTestCase
run_command("envify", "--skip-push") run_command("envify", "--skip-push")
end 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
@@ -542,16 +522,14 @@ class CliMainTest < CliTestCase
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) } stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) }
end end
def with_test_dotenv(**files) def with_test_dot_env_erb(contents:, file: ".env.erb")
Dir.mktmpdir do |dir| Dir.mktmpdir do |dir|
fixtures_dup = File.join(dir, "test") fixtures_dup = File.join(dir, "test")
FileUtils.mkdir_p(fixtures_dup) FileUtils.mkdir_p(fixtures_dup)
FileUtils.cp_r("test/fixtures/", fixtures_dup) FileUtils.cp_r("test/fixtures/", fixtures_dup)
Dir.chdir(dir) do Dir.chdir(dir) do
files.each do |filename, contents| File.write(file, contents)
File.binwrite(filename.to_s, contents)
end
yield yield
end end
end end

View File

@@ -12,7 +12,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
} }
@auditor = new_command @auditor = new_command
@performer = Kamal::Git.email.presence || `whoami`.chomp @performer = `whoami`.strip
@recorded_at = Time.now.utc.iso8601 @recorded_at = Time.now.utc.iso8601
end end

View File

@@ -200,11 +200,6 @@ class CommandsBuilderTest < ActiveSupport::TestCase
assert_equal [ "unix:///var/run/docker.sock", "ssh://host" ], command.config_context_hosts assert_equal [ "unix:///var/run/docker.sock", "ssh://host" ], command.config_context_hosts
end end
test "mirror count" do
command = new_builder_command
assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ")
end
private private
def new_builder_command(additional_config = {}) def new_builder_command(additional_config = {})
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.merge(additional_config), version: "123")) Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.merge(additional_config), version: "123"))

View File

@@ -11,7 +11,7 @@ class CommandsHookTest < ActiveSupport::TestCase
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } } traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
} }
@performer = Kamal::Git.email.presence || `whoami`.chomp @performer = `whoami`.strip
@recorded_at = Time.now.utc.iso8601 @recorded_at = Time.now.utc.iso8601
end end

View File

@@ -344,12 +344,4 @@ class ConfigurationTest < ActiveSupport::TestCase
assert_raises(Kamal::ConfigurationError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) } assert_raises(Kamal::ConfigurationError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) }
end end
test "extensions" do
dest_config_file = Pathname.new(File.expand_path("fixtures/deploy_with_extensions.yml", __dir__))
config = Kamal::Configuration.create_from config_file: dest_config_file
assert_equal config.role(:web_tokyo).running_traefik?, true
assert_equal config.role(:web_chicago).running_traefik?, true
end
end end

View File

@@ -1,24 +0,0 @@
x-web: &web
traefik: true
service: app
image: dhh/app
servers:
web_chicago:
<<: *web
hosts:
- 1.1.1.1
- 1.1.1.2
web_tokyo:
<<: *web
hosts:
- 1.1.1.3
- 1.1.1.4
env:
REDIS_URL: redis://x/y
registry:
server: registry.digitalocean.com
username: user
password: pw
primary_role: web_tokyo