Catch up with main
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
kamal (1.8.1)
|
kamal (2.0.0.alpha)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
@@ -10,7 +10,7 @@ PATH
|
|||||||
ed25519 (~> 1.2)
|
ed25519 (~> 1.2)
|
||||||
net-ssh (~> 7.0)
|
net-ssh (~> 7.0)
|
||||||
sshkit (>= 1.23.0, < 2.0)
|
sshkit (>= 1.23.0, < 2.0)
|
||||||
thor (~> 1.2)
|
thor (~> 1.3)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
|
|||||||
20
bin/docs
20
bin/docs
@@ -17,6 +17,7 @@ end
|
|||||||
|
|
||||||
DOCS = {
|
DOCS = {
|
||||||
"accessory" => "Accessories",
|
"accessory" => "Accessories",
|
||||||
|
"alias" => "Aliases",
|
||||||
"boot" => "Booting",
|
"boot" => "Booting",
|
||||||
"builder" => "Builders",
|
"builder" => "Builders",
|
||||||
"configuration" => "Configuration overview",
|
"configuration" => "Configuration overview",
|
||||||
@@ -67,26 +68,27 @@ class DocWriter
|
|||||||
output.puts
|
output.puts
|
||||||
place = :new_section
|
place = :new_section
|
||||||
elsif line =~ /^ *#/
|
elsif line =~ /^ *#/
|
||||||
generate_line(line, place: place)
|
generate_line(line, heading: place == :new_section)
|
||||||
place = :in_section
|
place = :in_section
|
||||||
else
|
else
|
||||||
output.puts "```yaml"
|
output.puts "```yaml"
|
||||||
output.print line
|
output.puts line
|
||||||
place = :in_yaml
|
place = :in_yaml
|
||||||
end
|
end
|
||||||
when :in_yaml
|
when :in_yaml, :in_empty_line_yaml
|
||||||
if line =~ /^ *#/
|
if line =~ /^ *#/
|
||||||
output.puts "```"
|
output.puts "```"
|
||||||
generate_line(line, place: :new_section)
|
generate_line(line, heading: place == :in_empty_line_yaml)
|
||||||
place = :in_section
|
place = :in_section
|
||||||
|
elsif line.empty?
|
||||||
|
place = :in_empty_line_yaml
|
||||||
else
|
else
|
||||||
output.puts
|
output.puts line
|
||||||
output.print line
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
output.puts "\n```" if place == :in_yaml
|
output.puts "```" if place == :in_yaml
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_header
|
def generate_header
|
||||||
@@ -98,7 +100,7 @@ class DocWriter
|
|||||||
output.puts
|
output.puts
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_line(line, place: :in_section)
|
def generate_line(line, heading: false)
|
||||||
line = line.gsub(/^ *#\s?/, "")
|
line = line.gsub(/^ *#\s?/, "")
|
||||||
|
|
||||||
if line =~ /(.*)kamal docs ([a-z]*)(.*)/
|
if line =~ /(.*)kamal docs ([a-z]*)(.*)/
|
||||||
@@ -109,7 +111,7 @@ class DocWriter
|
|||||||
line = "#{$1}[#{titlify($2.split("/").last)}](#{$2})#{$3}"
|
line = "#{$1}[#{titlify($2.split("/").last)}](#{$2})#{$3}"
|
||||||
end
|
end
|
||||||
|
|
||||||
if place == :new_section
|
if heading
|
||||||
output.puts "## [#{line}](##{linkify(line)})"
|
output.puts "## [#{line}](##{linkify(line)})"
|
||||||
else
|
else
|
||||||
output.puts line
|
output.puts line
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|||||||
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.23.0", "< 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.3"
|
||||||
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"
|
||||||
|
|||||||
9
lib/kamal/cli/alias/command.rb
Normal file
9
lib/kamal/cli/alias/command.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
|
||||||
|
def run(instance, args = [])
|
||||||
|
if (_alias = KAMAL.config.aliases[name])
|
||||||
|
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -71,11 +71,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "exec [CMD]", "Execute a custom command on servers within the app container (use --help to show options)"
|
desc "exec [CMD...]", "Execute a custom command on servers within the app container (use --help to show options)"
|
||||||
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
||||||
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
||||||
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
||||||
def exec(cmd)
|
def exec(*cmd)
|
||||||
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
env = options[:env]
|
env = options[:env]
|
||||||
case
|
case
|
||||||
when options[:interactive] && options[:reuse]
|
when options[:interactive] && options[:reuse]
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ module Kamal::Cli
|
|||||||
class Base < Thor
|
class Base < Thor
|
||||||
include SSHKit::DSL
|
include SSHKit::DSL
|
||||||
|
|
||||||
def self.exit_on_failure?() true end
|
def self.exit_on_failure?() false end
|
||||||
|
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
|
||||||
|
|
||||||
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
||||||
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
|
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
|
||||||
@@ -22,8 +23,14 @@ 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"
|
||||||
|
|
||||||
def initialize(*)
|
def initialize(args = [], local_options = {}, config = {})
|
||||||
super
|
if config[:current_command].is_a?(Kamal::Cli::Alias::Command)
|
||||||
|
# When Thor generates a dynamic command, it doesn't attempt to parse the arguments.
|
||||||
|
# For our purposes, it means the arguments are passed in args rather than local_options.
|
||||||
|
super([], args, config)
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
@original_env = ENV.to_h.dup
|
@original_env = ENV.to_h.dup
|
||||||
load_env
|
load_env
|
||||||
initialize_commander(options_with_subcommand_class_options)
|
initialize_commander(options_with_subcommand_class_options)
|
||||||
|
|||||||
@@ -30,27 +30,26 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
||||||
end
|
end
|
||||||
|
|
||||||
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
|
||||||
push = KAMAL.builder.push
|
|
||||||
|
|
||||||
run_locally do
|
run_locally do
|
||||||
begin
|
begin
|
||||||
context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
|
execute *KAMAL.builder.inspect_builder
|
||||||
|
|
||||||
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|no compatible builder|does not exist)/
|
||||||
warn "Missing compatible builder, so creating a new one first"
|
warn "Missing compatible builder, so creating a new one first"
|
||||||
|
begin
|
||||||
|
cli.remove
|
||||||
|
rescue SSHKit::Command::Failed
|
||||||
|
raise unless e.message =~ /(context not found|no builder|does not exist)/
|
||||||
|
end
|
||||||
cli.create
|
cli.create
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
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
|
||||||
@@ -72,7 +71,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
|
|
||||||
desc "create", "Create a build setup"
|
desc "create", "Create a build setup"
|
||||||
def create
|
def create
|
||||||
if (remote_host = KAMAL.config.builder.remote_host)
|
if (remote_host = KAMAL.config.builder.remote)
|
||||||
connect_to_remote_host(remote_host)
|
connect_to_remote_host(remote_host)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
class Kamal::Cli::Server < Kamal::Cli::Base
|
class Kamal::Cli::Server < Kamal::Cli::Base
|
||||||
desc "exec", "Run a custom command on the server (use --help to show options)"
|
desc "exec", "Run a custom command on the server (use --help to show options)"
|
||||||
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
||||||
def exec(cmd)
|
def exec(*cmd)
|
||||||
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
||||||
|
|
||||||
case
|
case
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ registry:
|
|||||||
password:
|
password:
|
||||||
- KAMAL_REGISTRY_PASSWORD
|
- KAMAL_REGISTRY_PASSWORD
|
||||||
|
|
||||||
|
# Configure builder setup.
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
# Inject ENV variables into containers (secrets come from .env).
|
# Inject ENV variables into containers (secrets come from .env).
|
||||||
# Remember to run `kamal env push` after making changes!
|
# Remember to run `kamal env push` after making changes!
|
||||||
# env:
|
# env:
|
||||||
@@ -30,16 +34,6 @@ registry:
|
|||||||
# ssh:
|
# ssh:
|
||||||
# user: app
|
# user: app
|
||||||
|
|
||||||
# Configure builder setup.
|
|
||||||
# builder:
|
|
||||||
# args:
|
|
||||||
# RUBY_VERSION: 3.2.0
|
|
||||||
# secrets:
|
|
||||||
# - GITHUB_TOKEN
|
|
||||||
# remote:
|
|
||||||
# arch: amd64
|
|
||||||
# host: ssh://app@192.168.0.1
|
|
||||||
|
|
||||||
# Use accessory services (secrets come from .env).
|
# Use accessory services (secrets come from .env).
|
||||||
# accessories:
|
# accessories:
|
||||||
# db:
|
# db:
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ class Kamal::Commander
|
|||||||
|
|
||||||
def specific_primary!
|
def specific_primary!
|
||||||
@specifics = nil
|
@specifics = nil
|
||||||
self.specific_hosts = [ config.primary_host ]
|
if specific_roles.present?
|
||||||
|
self.specific_hosts = [ specific_roles.first.primary_host ]
|
||||||
|
else
|
||||||
|
self.specific_hosts = [ config.primary_host ]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def specific_roles=(role_names)
|
def specific_roles=(role_names)
|
||||||
@@ -113,6 +117,10 @@ class Kamal::Commander
|
|||||||
@traefik ||= Kamal::Commands::Traefik.new(config)
|
@traefik ||= Kamal::Commands::Traefik.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def alias(name)
|
||||||
|
config.aliases[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def with_verbosity(level)
|
def with_verbosity(level)
|
||||||
old_level = self.verbosity
|
old_level = self.verbosity
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ module Kamal::Commands
|
|||||||
[ :git, *([ "-C", path ] if path), *args.compact ]
|
[ :git, *([ "-C", path ] if path), *args.compact ]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def grep(*args)
|
||||||
|
args.compact.unshift :grep
|
||||||
|
end
|
||||||
|
|
||||||
def tags(**details)
|
def tags(**details)
|
||||||
Kamal::Tags.from_config(config, **details)
|
Kamal::Tags.from_config(config, **details)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
require "active_support/core_ext/string/filters"
|
require "active_support/core_ext/string/filters"
|
||||||
|
|
||||||
class Kamal::Commands::Builder < Kamal::Commands::Base
|
class Kamal::Commands::Builder < Kamal::Commands::Base
|
||||||
delegate :create, :remove, :push, :clean, :pull, :info, :context_hosts, :config_context_hosts, :validate_image,
|
delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
|
||||||
:first_mirror, to: :target
|
delegate :local?, :remote?, :pack?, to: "config.builder"
|
||||||
|
|
||||||
include Clone
|
include Clone
|
||||||
|
|
||||||
@@ -11,49 +11,33 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def target
|
def target
|
||||||
if config.builder.multiarch?
|
if remote?
|
||||||
if config.builder.remote?
|
if local?
|
||||||
if config.builder.local?
|
hybrid
|
||||||
multiarch_remote
|
|
||||||
else
|
|
||||||
native_remote
|
|
||||||
end
|
|
||||||
elsif config.builder.pack?
|
|
||||||
pack
|
|
||||||
else
|
else
|
||||||
multiarch
|
remote
|
||||||
end
|
end
|
||||||
|
elsif pack?
|
||||||
|
pack
|
||||||
else
|
else
|
||||||
if config.builder.cached?
|
local
|
||||||
native_cached
|
|
||||||
else
|
|
||||||
native
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def native
|
def remote
|
||||||
@native ||= Kamal::Commands::Builder::Native.new(config)
|
@remote ||= Kamal::Commands::Builder::Remote.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_cached
|
def local
|
||||||
@native ||= Kamal::Commands::Builder::Native::Cached.new(config)
|
@local ||= Kamal::Commands::Builder::Local.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def native_remote
|
def hybrid
|
||||||
@native ||= Kamal::Commands::Builder::Native::Remote.new(config)
|
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
||||||
end
|
|
||||||
|
|
||||||
def multiarch
|
|
||||||
@multiarch ||= Kamal::Commands::Builder::Multiarch.new(config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def multiarch_remote
|
|
||||||
@multiarch_remote ||= Kamal::Commands::Builder::Multiarch::Remote.new(config)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def pack
|
def pack
|
||||||
@pack ||= Kamal::Commands::Builder::Native::Pack.new(config)
|
@pack ||= Kamal::Commands::Builder::Pack.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_local_dependencies_installed
|
def ensure_local_dependencies_installed
|
||||||
|
|||||||
@@ -1,20 +1,42 @@
|
|||||||
|
|
||||||
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||||
class BuilderError < StandardError; end
|
class BuilderError < StandardError; end
|
||||||
|
|
||||||
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
ENDPOINT_DOCKER_HOST_INSPECT = "'{{.Endpoints.docker.Host}}'"
|
||||||
|
|
||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
delegate :args, :secrets, :dockerfile, :target, :local_arch, :local_host, :pack_arch, :pack_builder, :pack_buildpacks, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
delegate \
|
||||||
|
:args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
|
||||||
|
:pack_builder, :pack_buildpacks,
|
||||||
|
:cache_from, :cache_to, :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 :buildx, :build,
|
||||||
|
"--push",
|
||||||
|
*platform_options(arches),
|
||||||
|
*([ "--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 inspect_builder
|
||||||
|
docker :buildx, :inspect, builder_name unless docker_driver?
|
||||||
|
end
|
||||||
|
|
||||||
def build_options
|
def build_options
|
||||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
||||||
end
|
end
|
||||||
@@ -32,14 +54,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
:true
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
def first_mirror
|
def first_mirror
|
||||||
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
end
|
end
|
||||||
@@ -88,7 +102,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
config.builder
|
config.builder
|
||||||
end
|
end
|
||||||
|
|
||||||
def context_host(builder_name)
|
def platform_options(arches)
|
||||||
docker :context, :inspect, builder_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT
|
argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
21
lib/kamal/commands/builder/hybrid.rb
Normal file
21
lib/kamal/commands/builder/hybrid.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_local_buildx
|
||||||
|
docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def append_remote_buildx
|
||||||
|
docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, remote_context_name
|
||||||
|
end
|
||||||
|
end
|
||||||
14
lib/kamal/commands/builder/local.rb
Normal file
14
lib/kamal/commands/builder/local.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
end
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
|
||||||
def create
|
|
||||||
docker :buildx, :create, "--use", "--name", builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
combine \
|
|
||||||
docker(:context, :ls),
|
|
||||||
docker(:buildx, :ls)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
"--platform", platform_names,
|
|
||||||
"--builder", builder_name,
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
docker :buildx, :inspect, builder_name, "> /dev/null"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-multiarch"
|
|
||||||
end
|
|
||||||
|
|
||||||
def platform_names
|
|
||||||
if local_arch
|
|
||||||
"linux/#{local_arch}"
|
|
||||||
else
|
|
||||||
"linux/amd64,linux/arm64"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Multiarch::Remote < Kamal::Commands::Builder::Multiarch
|
|
||||||
def create
|
|
||||||
combine \
|
|
||||||
create_contexts,
|
|
||||||
create_local_buildx,
|
|
||||||
append_remote_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
combine \
|
|
||||||
remove_contexts,
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
chain \
|
|
||||||
context_host(builder_name_with_arch(local_arch)),
|
|
||||||
context_host(builder_name_with_arch(remote_arch))
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[ local_host, remote_host ].compact
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
super + "-remote"
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_name_with_arch(arch)
|
|
||||||
"#{builder_name}-#{arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_local_buildx
|
|
||||||
docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local_arch), "--platform", "linux/#{local_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def append_remote_buildx
|
|
||||||
docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote_arch), "--platform", "linux/#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_contexts
|
|
||||||
combine \
|
|
||||||
create_context(local_arch, local_host),
|
|
||||||
create_context(remote_arch, remote_host)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_context(arch, host)
|
|
||||||
docker :context, :create, builder_name_with_arch(arch), "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_contexts
|
|
||||||
combine \
|
|
||||||
remove_context(local_arch),
|
|
||||||
remove_context(remote_arch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_context(arch)
|
|
||||||
docker :context, :rm, builder_name_with_arch(arch)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
|
|
||||||
def create
|
|
||||||
# No-op on native without cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
# No-op on native without cache
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
# No-op on native
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
combine \
|
|
||||||
docker(:build, *build_options, build_context),
|
|
||||||
docker(:push, config.absolute_image),
|
|
||||||
docker(:push, config.latest_image)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
|
|
||||||
def create
|
|
||||||
docker :buildx, :create, "--name", builder_name, "--use", "--driver=docker-container"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
docker :buildx, :inspect, builder_name, "> /dev/null"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-native-cached"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Native
|
|
||||||
def create
|
|
||||||
chain \
|
|
||||||
create_context,
|
|
||||||
create_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
chain \
|
|
||||||
remove_context,
|
|
||||||
remove_buildx
|
|
||||||
end
|
|
||||||
|
|
||||||
def info
|
|
||||||
chain \
|
|
||||||
docker(:context, :ls),
|
|
||||||
docker(:buildx, :ls)
|
|
||||||
end
|
|
||||||
|
|
||||||
def push
|
|
||||||
docker :buildx, :build,
|
|
||||||
"--push",
|
|
||||||
"--platform", platform,
|
|
||||||
"--builder", builder_name,
|
|
||||||
*build_options,
|
|
||||||
build_context
|
|
||||||
end
|
|
||||||
|
|
||||||
def context_hosts
|
|
||||||
context_host(builder_name_with_arch)
|
|
||||||
end
|
|
||||||
|
|
||||||
def config_context_hosts
|
|
||||||
[ remote_host ]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
"kamal-#{config.service}-native-remote"
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_name_with_arch
|
|
||||||
"#{builder_name}-#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def platform
|
|
||||||
"linux/#{remote_arch}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_context
|
|
||||||
docker :context, :create,
|
|
||||||
builder_name_with_arch, "--description", "'#{builder_name} #{remote_arch} native host'", "--docker", "'host=#{remote_host}'"
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_context
|
|
||||||
docker :context, :rm, builder_name_with_arch
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_buildx
|
|
||||||
docker :buildx, :create, "--name", builder_name, builder_name_with_arch, "--platform", platform
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_buildx
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class Kamal::Commands::Builder::Native::Pack < Kamal::Commands::Builder::Native
|
class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
|
||||||
def push
|
def push
|
||||||
combine \
|
combine \
|
||||||
pack(:build,
|
pack(:build,
|
||||||
@@ -17,7 +17,7 @@ class Kamal::Commands::Builder::Native::Pack < Kamal::Commands::Builder::Native
|
|||||||
|
|
||||||
private
|
private
|
||||||
def platform
|
def platform
|
||||||
"linux/#{pack_arch}"
|
"linux/#{local_arches.first}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def buildpacks
|
def buildpacks
|
||||||
63
lib/kamal/commands/builder/remote.rb
Normal file
63
lib/kamal/commands/builder/remote.rb
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
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 inspect_builder
|
||||||
|
combine \
|
||||||
|
combine inspect_buildx, inspect_remote_context,
|
||||||
|
[ "(echo no compatible builder && exit 1)" ],
|
||||||
|
by: "||"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def builder_name
|
||||||
|
"kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_context_name
|
||||||
|
"#{builder_name}-context"
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect_buildx
|
||||||
|
pipe \
|
||||||
|
docker(:buildx, :inspect, builder_name),
|
||||||
|
grep("-q", "Endpoint:.*#{remote_context_name}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect_remote_context
|
||||||
|
pipe \
|
||||||
|
docker(:context, :inspect, remote_context_name, "--format", ENDPOINT_DOCKER_HOST_INSPECT),
|
||||||
|
grep("-xq", remote)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_remote_context
|
||||||
|
docker :context, :create, remote_context_name, "--description", "'#{builder_name} host'", "--docker", "'host=#{remote}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_remote_context
|
||||||
|
docker :context, :rm, remote_context_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_buildx
|
||||||
|
docker :buildx, :create, "--name", builder_name, remote_context_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_buildx
|
||||||
|
docker :buildx, :rm, builder_name
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,7 +9,7 @@ class Kamal::Commands::Prune < Kamal::Commands::Base
|
|||||||
def tagged_images
|
def tagged_images
|
||||||
pipe \
|
pipe \
|
||||||
docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
|
docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
|
||||||
"grep -v -w \"#{active_image_list}\"",
|
grep("-v -w \"#{active_image_list}\""),
|
||||||
"while read image tag; do docker rmi $tag; done"
|
"while read image tag; do docker rmi $tag; done"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Kamal::Configuration
|
|||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_reader :destination, :raw_config
|
attr_reader :destination, :raw_config
|
||||||
attr_reader :accessories, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
|
attr_reader :accessories, :aliases, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
|
||||||
|
|
||||||
include Validation
|
include Validation
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ class Kamal::Configuration
|
|||||||
@registry = Registry.new(config: self)
|
@registry = Registry.new(config: self)
|
||||||
|
|
||||||
@accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
|
@accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
|
||||||
|
@aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
|
||||||
@boot = Boot.new(config: self)
|
@boot = Boot.new(config: self)
|
||||||
@builder = Builder.new(config: self)
|
@builder = Builder.new(config: self)
|
||||||
@env = Env.new(config: @raw_config.env || {})
|
@env = Env.new(config: @raw_config.env || {})
|
||||||
|
|||||||
15
lib/kamal/configuration/alias.rb
Normal file
15
lib/kamal/configuration/alias.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
class Kamal::Configuration::Alias
|
||||||
|
include Kamal::Configuration::Validation
|
||||||
|
|
||||||
|
attr_reader :name, :command
|
||||||
|
|
||||||
|
def initialize(name, config:)
|
||||||
|
@name, @command = name.inquiry, config.raw_config["aliases"][name]
|
||||||
|
|
||||||
|
validate! \
|
||||||
|
command,
|
||||||
|
example: validation_yml["aliases"]["uname"],
|
||||||
|
context: "aliases/#{name}",
|
||||||
|
with: Kamal::Configuration::Validator::Alias
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,16 +19,38 @@ class Kamal::Configuration::Builder
|
|||||||
builder_config
|
builder_config
|
||||||
end
|
end
|
||||||
|
|
||||||
def multiarch?
|
def remote
|
||||||
builder_config["multiarch"] != false
|
builder_config["remote"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def local?
|
def arches
|
||||||
!!builder_config["local"]
|
Array(builder_config.fetch("arch", default_arch))
|
||||||
|
end
|
||||||
|
|
||||||
|
def local_arches
|
||||||
|
@local_arches ||= if local_disabled?
|
||||||
|
[]
|
||||||
|
elsif remote
|
||||||
|
arches & [ Kamal::Utils.docker_arch ]
|
||||||
|
else
|
||||||
|
arches
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_arches
|
||||||
|
@remote_arches ||= if remote
|
||||||
|
arches - local_arches
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote?
|
def remote?
|
||||||
!!builder_config["remote"]
|
remote_arches.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def local?
|
||||||
|
!local_disabled? && (arches.empty? || local_arches.any?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached?
|
def cached?
|
||||||
@@ -59,12 +81,8 @@ class Kamal::Configuration::Builder
|
|||||||
builder_config["context"] || "."
|
builder_config["context"] || "."
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_arch
|
def driver
|
||||||
builder_config["local"]["arch"] if local?
|
builder_config.fetch("driver", "docker-container")
|
||||||
end
|
|
||||||
|
|
||||||
def local_host
|
|
||||||
builder_config["local"]["host"] if local?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def pack_arch
|
def pack_arch
|
||||||
@@ -79,12 +97,8 @@ class Kamal::Configuration::Builder
|
|||||||
builder_config["pack"]["buildpacks"] if pack?
|
builder_config["pack"]["buildpacks"] if pack?
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_arch
|
def local_disabled?
|
||||||
builder_config["remote"]["arch"] if remote?
|
builder_config["local"] == false
|
||||||
end
|
|
||||||
|
|
||||||
def remote_host
|
|
||||||
builder_config["remote"]["host"] if remote?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_from
|
def cache_from
|
||||||
@@ -130,7 +144,23 @@ class Kamal::Configuration::Builder
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def docker_driver?
|
||||||
|
driver == "docker"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def valid?
|
||||||
|
if docker_driver?
|
||||||
|
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support remote builders" if remote
|
||||||
|
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support caching" if cached?
|
||||||
|
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support multiple arches" if arches.many?
|
||||||
|
end
|
||||||
|
|
||||||
|
if @options["cache"] && @options["cache"]["type"]
|
||||||
|
raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
|
||||||
|
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
|
||||||
@@ -166,4 +196,8 @@ class Kamal::Configuration::Builder
|
|||||||
def pwd_sha
|
def pwd_sha
|
||||||
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def default_arch
|
||||||
|
docker_driver? ? [] : [ "amd64", "arm64" ]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
26
lib/kamal/configuration/docs/alias.yml
Normal file
26
lib/kamal/configuration/docs/alias.yml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Aliases
|
||||||
|
#
|
||||||
|
# Aliases are shortcuts for Kamal commands.
|
||||||
|
#
|
||||||
|
# For example, for a Rails app, you might open a console with:
|
||||||
|
#
|
||||||
|
# ```shell
|
||||||
|
# kamal app exec -i -r console "rails console"
|
||||||
|
# ```
|
||||||
|
#
|
||||||
|
# By defining an alias, like this:
|
||||||
|
aliases:
|
||||||
|
console: app exec -r console -i "rails console"
|
||||||
|
# You can now open the console with:
|
||||||
|
# ```shell
|
||||||
|
# kamal console
|
||||||
|
# ```
|
||||||
|
|
||||||
|
# Configuring aliases
|
||||||
|
#
|
||||||
|
# Aliases are defined in the root config under the alias key
|
||||||
|
#
|
||||||
|
# Each alias is named and can only contain lowercase letters, numbers, dashes and underscores.
|
||||||
|
|
||||||
|
aliases:
|
||||||
|
uname: app exec -p -q -r web "uname -a"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# Builder
|
# Builder
|
||||||
#
|
#
|
||||||
# The builder configuration controls how the application is built with `docker build` or `docker buildx build`
|
# The builder configuration controls how the application is built with `docker build`
|
||||||
#
|
#
|
||||||
# If no configuration is specified, Kamal will:
|
# If no configuration is specified, Kamal will:
|
||||||
# 1. Create a buildx context called `kamal-<service>-multiarch`
|
# 1. Create a buildx context called `kamal-local-docker-container`, using the docker-container driver
|
||||||
# 2. Use `docker buildx build` to build a multiarch image for linux/amd64,linux/arm64 with that context
|
# 2. Use `docker build` to build a multiarch image for linux/amd64,linux/arm64 with that context
|
||||||
#
|
#
|
||||||
# See https://kamal-deploy.org/docs/configuration/builder-examples/ for more information
|
# See https://kamal-deploy.org/docs/configuration/builder-examples/ for more information
|
||||||
|
|
||||||
@@ -12,37 +12,34 @@
|
|||||||
#
|
#
|
||||||
# Options go under the builder key in the root configuration.
|
# Options go under the builder key in the root configuration.
|
||||||
builder:
|
builder:
|
||||||
|
# Driver
|
||||||
|
#
|
||||||
|
# The build driver to use, defaults to `docker-container`
|
||||||
|
driver: docker
|
||||||
|
|
||||||
# Multiarch
|
# Arch
|
||||||
#
|
#
|
||||||
# Enables multiarch builds, defaults to `true`
|
# The architectures to build for, defaults to `[ amd64, arm64 ]`
|
||||||
multiarch: false
|
# Unless you are using the docker driver, when it defaults to the local architecture
|
||||||
|
# You can set an array or just a single value
|
||||||
# Local configuration
|
arch:
|
||||||
#
|
- amd64
|
||||||
# The build configuration for local builds, only used if multiarch is enabled (the default)
|
|
||||||
#
|
|
||||||
# If there is no remote configuration, by default we build for amd64 and arm64.
|
|
||||||
# If you only want to build for one architecture, you can specify it here.
|
|
||||||
# The docker socket is optional and uses the default docker host socket when not specified
|
|
||||||
local:
|
|
||||||
arch: amd64
|
|
||||||
host: /var/run/docker.sock
|
|
||||||
|
|
||||||
# Remote configuration
|
# Remote configuration
|
||||||
#
|
#
|
||||||
# The build configuration for remote builds, also only used if multiarch is enabled.
|
# If you have a remote builder, you can configure it here
|
||||||
# The arch is required and can be either amd64 or arm64.
|
remote: ssh://docker@docker-builder
|
||||||
remote:
|
|
||||||
arch: arm64
|
# Whether to allow local builds
|
||||||
host: ssh://docker@docker-builder
|
#
|
||||||
|
# Defaults to true
|
||||||
|
local: true
|
||||||
|
|
||||||
# Buildpack configuration
|
# Buildpack configuration
|
||||||
#
|
#
|
||||||
# The build configuration for using pack to build a Cloud Native Buildpack image.
|
# The build configuration for using pack to build a Cloud Native Buildpack image.
|
||||||
pack:
|
pack:
|
||||||
builder: heroku/builder:24
|
builder: heroku/builder:24
|
||||||
arch: amd64
|
|
||||||
buildpacks:
|
buildpacks:
|
||||||
- heroku/ruby
|
- heroku/ruby
|
||||||
- heroku/procfile
|
- heroku/procfile
|
||||||
@@ -51,7 +48,7 @@ builder:
|
|||||||
#
|
#
|
||||||
# The type must be either 'gha' or 'registry'
|
# The type must be either 'gha' or 'registry'
|
||||||
#
|
#
|
||||||
# The image is only used for registry cache
|
# The image is only used for registry cache. Not compatible with the docker driver
|
||||||
cache:
|
cache:
|
||||||
type: registry
|
type: registry
|
||||||
options: mode=max
|
options: mode=max
|
||||||
|
|||||||
@@ -166,3 +166,9 @@ healthcheck:
|
|||||||
# Docker logging configuration, see kamal docs logging
|
# Docker logging configuration, see kamal docs logging
|
||||||
logging:
|
logging:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
#
|
||||||
|
# Alias configuration, see kamal docs alias
|
||||||
|
aliases:
|
||||||
|
...
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Environment variables
|
# Environment variables
|
||||||
#
|
#
|
||||||
# Environment variables can be set directory in the Kamal configuration or
|
# Environment variables can be set directly in the Kamal configuration or
|
||||||
# for loaded from a .env file, for secrets that should not be checked into Git.
|
# loaded from a .env file, for secrets that should not be checked into Git.
|
||||||
|
|
||||||
# Reading environment variables from the configuration
|
# Reading environment variables from the configuration
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# On roles that are running Traefik, Kamal will supply a default healthcheck to `docker run`.
|
# On roles that are running Traefik, Kamal will supply a default healthcheck to `docker run`.
|
||||||
# For other roles, by default no healthcheck is supplied.
|
# For other roles, by default no healthcheck is supplied.
|
||||||
#
|
#
|
||||||
# If no healthcheck is supplied and the image does not define one, they we wait for the container
|
# If no healthcheck is supplied and the image does not define one, then we wait for the container
|
||||||
# to reach a running state and then pause for the readiness delay.
|
# to reach a running state and then pause for the readiness delay.
|
||||||
#
|
#
|
||||||
# The default healthcheck is `curl -f http://localhost:<port>/<path>`, so it assumes that `curl`
|
# The default healthcheck is `curl -f http://localhost:<port>/<path>`, so it assumes that `curl`
|
||||||
|
|||||||
@@ -13,32 +13,38 @@ class Kamal::Configuration::Validator
|
|||||||
|
|
||||||
private
|
private
|
||||||
def validate_against_example!(validation_config, example)
|
def validate_against_example!(validation_config, example)
|
||||||
validate_type! validation_config, Hash
|
validate_type! validation_config, example.class
|
||||||
|
|
||||||
check_unknown_keys! validation_config, example
|
if example.class == Hash
|
||||||
|
check_unknown_keys! validation_config, example
|
||||||
|
|
||||||
validation_config.each do |key, value|
|
validation_config.each do |key, value|
|
||||||
next if extension?(key)
|
next if extension?(key)
|
||||||
with_context(key) do
|
with_context(key) do
|
||||||
example_value = example[key]
|
example_value = example[key]
|
||||||
|
|
||||||
if example_value == "..."
|
if example_value == "..."
|
||||||
validate_type! value, *(Array if key == :servers), Hash
|
validate_type! value, *(Array if key == :servers), Hash
|
||||||
elsif key == "hosts"
|
elsif key == "hosts"
|
||||||
validate_servers! value
|
validate_servers! value
|
||||||
elsif example_value.is_a?(Array)
|
elsif example_value.is_a?(Array)
|
||||||
validate_array_of! value, example_value.first.class
|
if key == "arch"
|
||||||
elsif example_value.is_a?(Hash)
|
validate_array_of_or_type! value, example_value.first.class
|
||||||
case key.to_s
|
else
|
||||||
when "options", "args"
|
validate_array_of! value, example_value.first.class
|
||||||
validate_type! value, Hash
|
end
|
||||||
when "labels"
|
elsif example_value.is_a?(Hash)
|
||||||
validate_hash_of! value, example_value.first[1].class
|
case key.to_s
|
||||||
|
when "options", "args"
|
||||||
|
validate_type! value, Hash
|
||||||
|
when "labels"
|
||||||
|
validate_hash_of! value, example_value.first[1].class
|
||||||
|
else
|
||||||
|
validate_against_example! value, example_value
|
||||||
|
end
|
||||||
else
|
else
|
||||||
validate_against_example! value, example_value
|
validate_type! value, example_value.class
|
||||||
end
|
end
|
||||||
else
|
|
||||||
validate_type! value, example_value.class
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -69,6 +75,16 @@ class Kamal::Configuration::Validator
|
|||||||
value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_array_of_or_type!(value, type)
|
||||||
|
if value.is_a?(Array)
|
||||||
|
validate_array_of! value, type
|
||||||
|
else
|
||||||
|
validate_type! value, type
|
||||||
|
end
|
||||||
|
rescue Kamal::ConfigurationError
|
||||||
|
type_error(Array, type)
|
||||||
|
end
|
||||||
|
|
||||||
def validate_array_of!(array, type)
|
def validate_array_of!(array, type)
|
||||||
validate_type! array, Array
|
validate_type! array, Array
|
||||||
|
|
||||||
|
|||||||
15
lib/kamal/configuration/validator/alias.rb
Normal file
15
lib/kamal/configuration/validator/alias.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
class Kamal::Configuration::Validator::Alias < Kamal::Configuration::Validator
|
||||||
|
def validate!
|
||||||
|
super
|
||||||
|
|
||||||
|
name = context.delete_prefix("aliases/")
|
||||||
|
|
||||||
|
if name !~ /\A[a-z0-9_-]+\z/
|
||||||
|
error "Invalid alias name: '#{name}'. Must only contain lowercase letters, alphanumeric, hyphens and underscores."
|
||||||
|
end
|
||||||
|
|
||||||
|
if Kamal::Cli::Main.commands.include?(name)
|
||||||
|
error "Alias '#{name}' conflicts with a built-in command."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -5,5 +5,9 @@ class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
|
|||||||
if config["cache"] && config["cache"]["type"]
|
if config["cache"] && config["cache"]["type"]
|
||||||
error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
|
error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
error "Builder arch not set" unless config["arch"].present?
|
||||||
|
|
||||||
|
error "Cannot disable local builds, no remote is set" if config["local"] == false && config["remote"].blank?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -77,4 +77,20 @@ module Kamal::Utils
|
|||||||
def stable_sort!(elements, &block)
|
def stable_sort!(elements, &block)
|
||||||
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
|
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join_commands(commands)
|
||||||
|
commands.map(&:strip).join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def docker_arch
|
||||||
|
arch = `docker info --format '{{.Architecture}}'`.strip
|
||||||
|
case arch
|
||||||
|
when /aarch64/
|
||||||
|
"arm64"
|
||||||
|
when /x86_64/
|
||||||
|
"amd64"
|
||||||
|
else
|
||||||
|
arch
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Kamal
|
module Kamal
|
||||||
VERSION = "1.8.1"
|
VERSION = "2.0.0.alpha"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -247,6 +247,12 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "exec separate arguments" do
|
||||||
|
run_command("exec", "ruby", " -v").tap do |output|
|
||||||
|
assert_match "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:latest ruby -v", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "exec with reuse" do
|
test "exec with reuse" do
|
||||||
run_command("exec", "--reuse", "ruby -v").tap do |output|
|
run_command("exec", "--reuse", "ruby -v").tap do |output|
|
||||||
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version
|
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version
|
||||||
|
|||||||
@@ -21,16 +21,12 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
run_command("push", "--verbose").tap do |output|
|
run_command("push", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-build", output, **hook_variables
|
assert_hook_ran "pre-build", output, **hook_variables
|
||||||
assert_match /Cloning repo into build directory/, output
|
assert_match /Cloning repo into build directory/, output
|
||||||
assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output
|
assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output
|
||||||
assert_match /docker --version && docker buildx version/, output
|
assert_match /docker --version && docker buildx version/, output
|
||||||
assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output
|
assert_match /docker 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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -42,7 +38,7 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd)
|
.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,9 +46,10 @@ 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", "--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)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||||
@@ -77,7 +74,7 @@ class CliBuildTest < CliTestCase
|
|||||||
assert_no_match /Cloning repo into build directory/, output
|
assert_no_match /Cloning repo into build directory/, output
|
||||||
assert_hook_ran "pre-build", output, **hook_variables
|
assert_hook_ran "pre-build", output, **hook_variables
|
||||||
assert_match /docker --version && docker buildx version/, output
|
assert_match /docker --version && docker buildx version/, output
|
||||||
assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder kamal-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . as .*@localhost/, output
|
assert_match /docker 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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,7 +85,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)
|
||||||
@@ -123,10 +120,13 @@ class CliBuildTest < CliTestCase
|
|||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :create, "--use", "--name", "kamal-app-multiarch")
|
.with(:docker, :buildx, :rm, "kamal-local-docker-container")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
.with(:docker, :buildx, :create, "--name", "kamal-local-docker-container", "--driver=docker-container")
|
||||||
|
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
|
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
|
||||||
.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") }
|
||||||
@@ -140,7 +140,7 @@ class CliBuildTest < CliTestCase
|
|||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64,linux/arm64", "--builder", "kamal-app-multiarch", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".")
|
.with(:docker, :buildx, :build, "--push", "--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|
|
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
|
||||||
@@ -164,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, :buildx, :build ] }
|
assert @executions.none? { |args| args[0..2] == [ :docker, :build ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
test "pull" do
|
test "pull" do
|
||||||
@@ -206,23 +206,32 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "create" do
|
test "create" do
|
||||||
run_command("create").tap do |output|
|
run_command("create").tap do |output|
|
||||||
assert_match /docker buildx create --use --name kamal-app-multiarch/, output
|
assert_match /docker buildx create --name kamal-local-docker-container --driver=docker-container/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create remote" do
|
test "create remote" do
|
||||||
run_command("create", fixture: :with_remote_builder).tap do |output|
|
run_command("create", fixture: :with_remote_builder).tap do |output|
|
||||||
assert_match "Running /usr/bin/env true on 1.1.1.5", output
|
assert_match "Running /usr/bin/env true on 1.1.1.5", output
|
||||||
assert_match "docker context create kamal-app-native-remote-amd64 --description 'kamal-app-native-remote amd64 native host' --docker 'host=ssh://app@1.1.1.5'", output
|
assert_match "docker context create kamal-remote-ssh---app-1-1-1-5-context --description 'kamal-remote-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output
|
||||||
assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", output
|
assert_match "docker buildx create --name kamal-remote-ssh---app-1-1-1-5 kamal-remote-ssh---app-1-1-1-5-context", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create remote with custom ports" do
|
test "create remote with custom ports" do
|
||||||
run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output|
|
run_command("create", fixture: :with_remote_builder_and_custom_ports).tap do |output|
|
||||||
assert_match "Running /usr/bin/env true on 1.1.1.5", output
|
assert_match "Running /usr/bin/env true on 1.1.1.5", output
|
||||||
assert_match "docker context create kamal-app-native-remote-amd64 --description 'kamal-app-native-remote amd64 native host' --docker 'host=ssh://app@1.1.1.5:2122'", output
|
assert_match "docker context create kamal-remote-ssh---app-1-1-1-5-2122-context --description 'kamal-remote-ssh---app-1-1-1-5-2122 host' --docker 'host=ssh://app@1.1.1.5:2122'", output
|
||||||
assert_match "docker buildx create --name kamal-app-native-remote kamal-app-native-remote-amd64 --platform linux/amd64", output
|
assert_match "docker buildx create --name kamal-remote-ssh---app-1-1-1-5-2122 kamal-remote-ssh---app-1-1-1-5-2122-context", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create hybrid" do
|
||||||
|
run_command("create", fixture: :with_hybrid_builder).tap do |output|
|
||||||
|
assert_match "Running /usr/bin/env true on 1.1.1.5", output
|
||||||
|
assert_match "docker buildx create --platform linux/#{Kamal::Utils.docker_arch} --name kamal-hybrid-docker-container-ssh---app-1-1-1-5 --driver=docker-container", output
|
||||||
|
assert_match "docker context create kamal-hybrid-docker-container-ssh---app-1-1-1-5-context --description 'kamal-hybrid-docker-container-ssh---app-1-1-1-5 host' --docker 'host=ssh://app@1.1.1.5'", output
|
||||||
|
assert_match "docker buildx create --platform linux/#{Kamal::Utils.docker_arch == "amd64" ? "arm64" : "amd64"} --append --name kamal-hybrid-docker-container-ssh---app-1-1-1-5 kamal-hybrid-docker-container-ssh---app-1-1-1-5-context", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -239,7 +248,7 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "remove" do
|
test "remove" do
|
||||||
run_command("remove").tap do |output|
|
run_command("remove").tap do |output|
|
||||||
assert_match /docker buildx rm kamal-app-multiarch/, output
|
assert_match /docker buildx rm kamal-local/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -249,7 +258,7 @@ class CliBuildTest < CliTestCase
|
|||||||
.returns("docker builder info")
|
.returns("docker builder info")
|
||||||
|
|
||||||
run_command("details").tap do |output|
|
run_command("details").tap do |output|
|
||||||
assert_match /Builder: multiarch/, output
|
assert_match /Builder: local/, output
|
||||||
assert_match /docker builder info/, output
|
assert_match /docker builder info/, output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
.with { |arg1, arg2| arg1 == :mkdir && arg2 == ".kamal/locks/app" }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
.with { |arg1, arg2| arg1 == :rm && arg2 == ".kamal/locks/app/details" }
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |*args| args[0..2] == [ :docker, :buildx, :inspect ] }
|
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
|
||||||
.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)
|
||||||
@@ -63,4 +62,12 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_match expected, output
|
assert_match expected, output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_argv(*argv)
|
||||||
|
old_argv = ARGV
|
||||||
|
ARGV.replace(*argv)
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
ARGV.replace(old_argv)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -121,10 +121,6 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
.returns("")
|
.returns("")
|
||||||
@@ -159,10 +155,6 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
.with(:git, "-C", anything, :status, "--porcelain")
|
||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :buildx, :inspect, "kamal-app-multiarch", "> /dev/null")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
.with(:docker, :info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
|
||||||
.returns("")
|
.returns("")
|
||||||
@@ -537,9 +529,47 @@ class CliMainTest < CliTestCase
|
|||||||
assert_equal Kamal::VERSION, version
|
assert_equal Kamal::VERSION, version
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "run an alias for details" do
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:details")
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:details")
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:details", [ "all" ])
|
||||||
|
|
||||||
|
run_command("info", config_file: "deploy_with_aliases")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run an alias for a console" do
|
||||||
|
run_command("console", config_file: "deploy_with_aliases").tap do |output|
|
||||||
|
assert_match "docker exec app-console-999 bin/console on 1.1.1.5", output
|
||||||
|
assert_match "App Host: 1.1.1.5", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run an alias for a console overriding role" do
|
||||||
|
run_command("console", "-r", "workers", config_file: "deploy_with_aliases").tap do |output|
|
||||||
|
assert_match "docker exec app-workers-999 bin/console on 1.1.1.3", output
|
||||||
|
assert_match "App Host: 1.1.1.3", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run an alias for a console passing command" do
|
||||||
|
run_command("exec", "bin/job", config_file: "deploy_with_aliases").tap do |output|
|
||||||
|
assert_match "docker exec app-console-999 bin/job on 1.1.1.5", output
|
||||||
|
assert_match "App Host: 1.1.1.5", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "append to command with an alias" do
|
||||||
|
run_command("rails", "db:migrate:status", config_file: "deploy_with_aliases").tap do |output|
|
||||||
|
assert_match "docker exec app-console-999 rails db:migrate:status on 1.1.1.5", output
|
||||||
|
assert_match "App Host: 1.1.1.5", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command, config_file: "deploy_simple")
|
def run_command(*command, config_file: "deploy_simple")
|
||||||
stdouted { Kamal::Cli::Main.start([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) }
|
with_argv([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) do
|
||||||
|
stdouted { Kamal::Cli::Main.start }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_test_dotenv(**files)
|
def with_test_dotenv(**files)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ require_relative "cli_test_case"
|
|||||||
class CliServerTest < CliTestCase
|
class CliServerTest < CliTestCase
|
||||||
test "running a command with exec" do
|
test "running a command with exec" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||||
.with("date", verbosity: 1)
|
.with("date", verbosity: 1)
|
||||||
.returns("Today")
|
.returns("Today")
|
||||||
|
|
||||||
hosts = "1.1.1.1".."1.1.1.4"
|
hosts = "1.1.1.1".."1.1.1.4"
|
||||||
run_command("exec", "date").tap do |output|
|
run_command("exec", "date").tap do |output|
|
||||||
@@ -15,6 +15,20 @@ class CliServerTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "running a command with exec multiple arguments" do
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||||
|
.with("date -j", verbosity: 1)
|
||||||
|
.returns("Today")
|
||||||
|
|
||||||
|
hosts = "1.1.1.1".."1.1.1.4"
|
||||||
|
run_command("exec", "date", "-j").tap do |output|
|
||||||
|
hosts.map do |host|
|
||||||
|
assert_match "Running 'date -j' on #{hosts.to_a.join(', ')}...", output
|
||||||
|
assert_match "App Host: #{host}\nToday", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "bootstrap already installed" do
|
test "bootstrap already installed" do
|
||||||
stub_setup
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ "1.1.1.1" ],
|
servers: [ "1.1.1.1" ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
accessories: {
|
accessories: {
|
||||||
"mysql" => {
|
"mysql" => {
|
||||||
"image" => "private.registry/mysql:8.0",
|
"image" => "private.registry/mysql:8.0",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
ENV["RAILS_MASTER_KEY"] = "456"
|
ENV["RAILS_MASTER_KEY"] = "456"
|
||||||
Kamal::Configuration.any_instance.stubs(:run_id).returns("12345678901234567890123456789012")
|
Kamal::Configuration.any_instance.stubs(:run_id).returns("12345678901234567890123456789012")
|
||||||
|
|
||||||
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } }
|
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] }, builder: { "arch" => "amd64" } }
|
||||||
end
|
end
|
||||||
|
|
||||||
teardown do
|
teardown do
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
freeze_time
|
freeze_time
|
||||||
|
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ]
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, builder: { "arch" => "amd64" }, servers: [ "1.1.1.1" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@auditor = new_command
|
@auditor = new_command
|
||||||
|
|||||||
@@ -2,54 +2,62 @@ require "test_helper"
|
|||||||
|
|
||||||
class CommandsBuilderTest < ActiveSupport::TestCase
|
class CommandsBuilderTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ] }
|
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], builder: { "arch" => "amd64" } }
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target multiarch by default" do
|
test "target linux/amd64 locally by default" do
|
||||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||||
assert_equal "multiarch", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native when multiarch is off" do
|
test "target specified arch locally by default" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false })
|
builder = new_builder_command(builder: { "arch" => [ "amd64" ] })
|
||||||
assert_equal "native", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
|
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native cached when multiarch is off and cache is set" do
|
test "build with caching" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||||
assert_equal "native/cached", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target multiarch remote when local and remote is set" do
|
test "hybrid build if remote is set and building multiarch" do
|
||||||
builder = new_builder_command(builder: { "local" => {}, "remote" => {}, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" } })
|
||||||
assert_equal "multiarch/remote", builder.name
|
assert_equal "hybrid", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch-remote -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target multiarch local when arch is set" do
|
test "remote build if remote is set and local disabled" do
|
||||||
builder = new_builder_command(builder: { "local" => { "arch" => "amd64" } })
|
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" }, "local" => false })
|
||||||
assert_equal "multiarch", builder.name
|
assert_equal "remote", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "target native remote when only remote is set" do
|
test "target remote when remote set and arch is non local" do
|
||||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
||||||
assert_equal "native/remote", builder.name
|
assert_equal "remote", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64 --builder kamal-app-native-remote -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
|
builder.push.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "target local when remote set and arch is local" do
|
||||||
|
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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -101,28 +109,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
test "build context" do
|
test "build context" do
|
||||||
builder = new_builder_command(builder: { "context" => ".." })
|
builder = new_builder_command(builder: { "context" => ".." })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ..",
|
"docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -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 "push with build args" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "args" => { "a" => 1, "b" => 2 } })
|
|
||||||
assert_equal \
|
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "multiarch push with build args" do
|
|
||||||
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
builder = new_builder_command(builder: { "args" => { "a" => 1, "b" => 2 } })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile .",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "native push with build secrets" do
|
test "push with build secrets" do
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "secrets" => [ "a", "b" ] })
|
builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile . && docker push dhh/app:123 && docker push dhh/app:latest",
|
"docker 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 .",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -138,76 +139,13 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the 'service' label\" && exit 1)", new_builder_command.validate_image.join(" ")
|
assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the 'service' label\" && exit 1)", new_builder_command.validate_image.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiarch context build" do
|
test "context build" do
|
||||||
builder = new_builder_command(builder: { "context" => "./foo" })
|
builder = new_builder_command(builder: { "context" => "./foo" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --push --platform linux/amd64,linux/arm64 --builder kamal-app-multiarch -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
"docker 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",
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "native context build" do
|
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo" })
|
|
||||||
assert_equal \
|
|
||||||
"docker build -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo && docker push dhh/app:123 && docker push dhh/app:latest",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "cached context build" do
|
|
||||||
builder = new_builder_command(builder: { "multiarch" => false, "context" => "./foo", "cache" => { "type" => "gha" } })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --push -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile ./foo",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "remote context build" do
|
|
||||||
builder = new_builder_command(builder: { "remote" => { "arch" => "amd64" }, "context" => "./foo" })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --push --platform linux/amd64 --builder kamal-app-native-remote -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo",
|
|
||||||
builder.push.join(" ")
|
|
||||||
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(" ")
|
||||||
@@ -215,10 +153,18 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
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.deep_merge(additional_config), version: "123"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_directory
|
def build_directory
|
||||||
"#{Dir.tmpdir}/kamal-clones/app/kamal/"
|
"#{Dir.tmpdir}/kamal-clones/app/kamal/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def local_arch
|
||||||
|
Kamal::Utils.docker_arch
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_arch
|
||||||
|
Kamal::Utils.docker_arch == "arm64" ? "amd64" : "arm64"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ require "test_helper"
|
|||||||
class CommandsDockerTest < ActiveSupport::TestCase
|
class CommandsDockerTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ]
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], builder: { "arch" => "amd64" }
|
||||||
}
|
}
|
||||||
@docker = Kamal::Commands::Docker.new(Kamal::Configuration.new(@config))
|
@docker = Kamal::Commands::Docker.new(Kamal::Configuration.new(@config))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class CommandsHookTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
builder: { "arch" => "amd64" }, traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@performer = Kamal::Git.email.presence || `whoami`.chomp
|
@performer = Kamal::Git.email.presence || `whoami`.chomp
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class CommandsLockTest < ActiveSupport::TestCase
|
|||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
builder: { "arch" => "amd64" }, traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class CommandsPruneTest < ActiveSupport::TestCase
|
|||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
builder: { "arch" => "amd64" }, traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
|||||||
"password" => "secret",
|
"password" => "secret",
|
||||||
"server" => "hub.docker.com"
|
"server" => "hub.docker.com"
|
||||||
},
|
},
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
servers: [ "1.1.1.1" ]
|
servers: [ "1.1.1.1" ]
|
||||||
}
|
}
|
||||||
@registry = Kamal::Commands::Registry.new Kamal::Configuration.new(@config)
|
@registry = Kamal::Commands::Registry.new Kamal::Configuration.new(@config)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class CommandsServerTest < ActiveSupport::TestCase
|
|||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
builder: { "arch" => "amd64" }, traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
@image = "traefik:test"
|
@image = "traefik:test"
|
||||||
|
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], builder: { "arch" => "amd64" },
|
||||||
traefik: { "image" => @image, "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
traefik: { "image" => @image, "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
|||||||
"web" => [ "1.1.1.1", "1.1.1.2" ],
|
"web" => [ "1.1.1.1", "1.1.1.2" ],
|
||||||
"workers" => [ "1.1.1.3", "1.1.1.4" ]
|
"workers" => [ "1.1.1.3", "1.1.1.4" ]
|
||||||
},
|
},
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: { "REDIS_URL" => "redis://x/y" },
|
env: { "REDIS_URL" => "redis://x/y" },
|
||||||
accessories: {
|
accessories: {
|
||||||
"mysql" => {
|
"mysql" => {
|
||||||
|
|||||||
@@ -4,28 +4,12 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
setup do
|
setup do
|
||||||
@deploy = {
|
@deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ "1.1.1.1" ]
|
builder: { "arch" => "amd64" }, servers: [ "1.1.1.1" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
@deploy_with_builder_option = {
|
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
|
||||||
servers: [ "1.1.1.1" ],
|
|
||||||
builder: {}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "multiarch?" do
|
|
||||||
assert_equal true, config.builder.multiarch?
|
|
||||||
end
|
|
||||||
|
|
||||||
test "setting multiarch to false" do
|
|
||||||
@deploy_with_builder_option[:builder] = { "multiarch" => false }
|
|
||||||
|
|
||||||
assert_equal false, config_with_builder_option.builder.multiarch?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "local?" do
|
test "local?" do
|
||||||
assert_equal false, config.builder.local?
|
assert_equal true, config.builder.local?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remote?" do
|
test "remote?" do
|
||||||
@@ -37,41 +21,33 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "pack? with pack builder" do
|
test "pack? with pack builder" do
|
||||||
@deploy[:builder] = { "pack" => { "builder" => "heroku/builder:24" } }
|
@deploy[:builder] = { "arch" => "arm64", "pack" => { "builder" => "heroku/builder:24" } }
|
||||||
|
|
||||||
assert config.builder.pack?
|
assert config.builder.pack?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "pack details" do
|
test "pack details" do
|
||||||
@deploy[:builder] = { "pack" => { "arch" => "amd64", "builder" => "heroku/builder:24", "buildpacks" => [ "heroku/ruby", "heroku/procfile" ] } }
|
@deploy[:builder] = { "arch" => "amd64", "pack" => { "builder" => "heroku/builder:24", "buildpacks" => [ "heroku/ruby", "heroku/procfile" ] } }
|
||||||
|
|
||||||
assert_equal "amd64", config.builder.pack_arch
|
|
||||||
assert_equal "heroku/builder:24", config.builder.pack_builder
|
assert_equal "heroku/builder:24", config.builder.pack_builder
|
||||||
assert_equal [ "heroku/ruby", "heroku/procfile" ], config.builder.pack_buildpacks
|
assert_equal [ "heroku/ruby", "heroku/procfile" ], config.builder.pack_buildpacks
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remote_arch" do
|
test "remote" do
|
||||||
assert_nil config.builder.remote_arch
|
assert_nil config.builder.remote
|
||||||
end
|
|
||||||
|
|
||||||
test "remote_host" do
|
|
||||||
assert_nil config.builder.remote_host
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "setting both local and remote configs" do
|
test "setting both local and remote configs" do
|
||||||
@deploy_with_builder_option[:builder] = {
|
@deploy[:builder] = {
|
||||||
"local" => { "arch" => "arm64", "host" => "unix:///Users/<%= `whoami`.strip %>/.docker/run/docker.sock" },
|
"arch" => [ "amd64", "arm64" ],
|
||||||
"remote" => { "arch" => "amd64", "host" => "ssh://root@192.168.0.1" }
|
"remote" => "ssh://root@192.168.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_equal true, config_with_builder_option.builder.local?
|
assert_equal true, config.builder.local?
|
||||||
assert_equal true, config_with_builder_option.builder.remote?
|
assert_equal true, config.builder.remote?
|
||||||
|
|
||||||
assert_equal "amd64", config_with_builder_option.builder.remote_arch
|
assert_equal [ "amd64", "arm64" ], config.builder.arches
|
||||||
assert_equal "ssh://root@192.168.0.1", config_with_builder_option.builder.remote_host
|
assert_equal "ssh://root@192.168.0.1", config.builder.remote
|
||||||
|
|
||||||
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
|
||||||
@@ -79,10 +55,10 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "invalid cache type specified" do
|
test "invalid cache type specified" do
|
||||||
@deploy_with_builder_option[:builder] = { "cache" => { "type" => "invalid" } }
|
@deploy[:builder]["cache"] = { "type" => "invalid" }
|
||||||
|
|
||||||
assert_raises(Kamal::ConfigurationError) do
|
assert_raises(Kamal::ConfigurationError) do
|
||||||
config_with_builder_option.builder
|
config.builder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -95,32 +71,32 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting gha cache" do
|
test "setting gha cache" do
|
||||||
@deploy_with_builder_option[:builder] = { "cache" => { "type" => "gha", "options" => "mode=max" } }
|
@deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "gha", "options" => "mode=max" } }
|
||||||
|
|
||||||
assert_equal "type=gha", config_with_builder_option.builder.cache_from
|
assert_equal "type=gha", config.builder.cache_from
|
||||||
assert_equal "type=gha,mode=max", config_with_builder_option.builder.cache_to
|
assert_equal "type=gha,mode=max", config.builder.cache_to
|
||||||
end
|
end
|
||||||
|
|
||||||
test "setting registry cache" do
|
test "setting registry cache" do
|
||||||
@deploy_with_builder_option[:builder] = { "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } }
|
@deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } }
|
||||||
|
|
||||||
assert_equal "type=registry,ref=dhh/app-build-cache", config_with_builder_option.builder.cache_from
|
assert_equal "type=registry,ref=dhh/app-build-cache", config.builder.cache_from
|
||||||
assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=dhh/app-build-cache", config_with_builder_option.builder.cache_to
|
assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=dhh/app-build-cache", config.builder.cache_to
|
||||||
end
|
end
|
||||||
|
|
||||||
test "setting registry cache when using a custom registry" do
|
test "setting registry cache when using a custom registry" do
|
||||||
@deploy_with_builder_option[:registry]["server"] = "registry.example.com"
|
@deploy[:registry]["server"] = "registry.example.com"
|
||||||
@deploy_with_builder_option[:builder] = { "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } }
|
@deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } }
|
||||||
|
|
||||||
assert_equal "type=registry,ref=registry.example.com/dhh/app-build-cache", config_with_builder_option.builder.cache_from
|
assert_equal "type=registry,ref=registry.example.com/dhh/app-build-cache", config.builder.cache_from
|
||||||
assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=registry.example.com/dhh/app-build-cache", config_with_builder_option.builder.cache_to
|
assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=registry.example.com/dhh/app-build-cache", config.builder.cache_to
|
||||||
end
|
end
|
||||||
|
|
||||||
test "setting registry cache with image" do
|
test "setting registry cache with image" do
|
||||||
@deploy_with_builder_option[:builder] = { "cache" => { "type" => "registry", "image" => "kamal", "options" => "mode=max" } }
|
@deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "image" => "kamal", "options" => "mode=max" } }
|
||||||
|
|
||||||
assert_equal "type=registry,ref=kamal", config_with_builder_option.builder.cache_from
|
assert_equal "type=registry,ref=kamal", config.builder.cache_from
|
||||||
assert_equal "type=registry,mode=max,ref=kamal", config_with_builder_option.builder.cache_to
|
assert_equal "type=registry,mode=max,ref=kamal", config.builder.cache_to
|
||||||
end
|
end
|
||||||
|
|
||||||
test "args" do
|
test "args" do
|
||||||
@@ -128,9 +104,9 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting args" do
|
test "setting args" do
|
||||||
@deploy_with_builder_option[:builder] = { "args" => { "key" => "value" } }
|
@deploy[:builder]["args"] = { "key" => "value" }
|
||||||
|
|
||||||
assert_equal({ "key" => "value" }, config_with_builder_option.builder.args)
|
assert_equal({ "key" => "value" }, config.builder.args)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "secrets" do
|
test "secrets" do
|
||||||
@@ -138,9 +114,9 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting secrets" do
|
test "setting secrets" do
|
||||||
@deploy_with_builder_option[:builder] = { "secrets" => [ "GITHUB_TOKEN" ] }
|
@deploy[:builder]["secrets"] = [ "GITHUB_TOKEN" ]
|
||||||
|
|
||||||
assert_equal [ "GITHUB_TOKEN" ], config_with_builder_option.builder.secrets
|
assert_equal [ "GITHUB_TOKEN" ], config.builder.secrets
|
||||||
end
|
end
|
||||||
|
|
||||||
test "dockerfile" do
|
test "dockerfile" do
|
||||||
@@ -148,9 +124,9 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting dockerfile" do
|
test "setting dockerfile" do
|
||||||
@deploy_with_builder_option[:builder] = { "dockerfile" => "Dockerfile.dev" }
|
@deploy[:builder]["dockerfile"] = "Dockerfile.dev"
|
||||||
|
|
||||||
assert_equal "Dockerfile.dev", config_with_builder_option.builder.dockerfile
|
assert_equal "Dockerfile.dev", config.builder.dockerfile
|
||||||
end
|
end
|
||||||
|
|
||||||
test "context" do
|
test "context" do
|
||||||
@@ -158,9 +134,9 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting context" do
|
test "setting context" do
|
||||||
@deploy_with_builder_option[:builder] = { "context" => ".." }
|
@deploy[:builder]["context"] = ".."
|
||||||
|
|
||||||
assert_equal "..", config_with_builder_option.builder.context
|
assert_equal "..", config.builder.context
|
||||||
end
|
end
|
||||||
|
|
||||||
test "ssh" do
|
test "ssh" do
|
||||||
@@ -168,17 +144,30 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "setting ssh params" do
|
test "setting ssh params" do
|
||||||
@deploy_with_builder_option[:builder] = { "ssh" => "default=$SSH_AUTH_SOCK" }
|
@deploy[:builder]["ssh"] = "default=$SSH_AUTH_SOCK"
|
||||||
|
|
||||||
assert_equal "default=$SSH_AUTH_SOCK", config_with_builder_option.builder.ssh
|
assert_equal "default=$SSH_AUTH_SOCK", config.builder.ssh
|
||||||
|
end
|
||||||
|
|
||||||
|
test "local disabled but no remote set" do
|
||||||
|
@deploy[:builder]["local"] = false
|
||||||
|
|
||||||
|
assert_raises(Kamal::ConfigurationError) do
|
||||||
|
config.builder
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "local disabled all arches are remote" do
|
||||||
|
@deploy[:builder]["local"] = false
|
||||||
|
@deploy[:builder]["remote"] = "ssh://root@192.168.0.1"
|
||||||
|
@deploy[:builder]["arch"] = [ "amd64", "arm64" ]
|
||||||
|
|
||||||
|
assert_equal [], config.builder.local_arches
|
||||||
|
assert_equal [ "amd64", "arm64" ], config.builder.remote_arches
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def config
|
def config
|
||||||
Kamal::Configuration.new(@deploy)
|
Kamal::Configuration.new(@deploy)
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_with_builder_option
|
|
||||||
Kamal::Configuration.new(@deploy_with_builder_option)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
4
test/configuration/env/tags_test.rb
vendored
4
test/configuration/env/tags_test.rb
vendored
@@ -5,6 +5,7 @@ class ConfigurationEnvTagsTest < ActiveSupport::TestCase
|
|||||||
@deploy = {
|
@deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ { "1.1.1.1" => "odd" }, { "1.1.1.2" => "even" }, { "1.1.1.3" => [ "odd", "three" ] } ],
|
servers: [ { "1.1.1.1" => "odd" }, { "1.1.1.2" => "even" }, { "1.1.1.3" => [ "odd", "three" ] } ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: {
|
env: {
|
||||||
"clear" => { "REDIS_URL" => "redis://x/y", "THREE" => "false" },
|
"clear" => { "REDIS_URL" => "redis://x/y", "THREE" => "false" },
|
||||||
"tags" => {
|
"tags" => {
|
||||||
@@ -64,6 +65,7 @@ class ConfigurationEnvTagsTest < ActiveSupport::TestCase
|
|||||||
deploy = {
|
deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ { "1.1.1.1" => [ "first", "second" ] } ],
|
servers: [ { "1.1.1.1" => [ "first", "second" ] } ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: {
|
env: {
|
||||||
"tags" => {
|
"tags" => {
|
||||||
"first" => { "TYPE" => "first" },
|
"first" => { "TYPE" => "first" },
|
||||||
@@ -82,6 +84,7 @@ class ConfigurationEnvTagsTest < ActiveSupport::TestCase
|
|||||||
deploy = {
|
deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ { "1.1.1.1" => "secrets" } ],
|
servers: [ { "1.1.1.1" => "secrets" } ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: {
|
env: {
|
||||||
"tags" => {
|
"tags" => {
|
||||||
"secrets" => { "secret" => [ "PASSWORD" ] }
|
"secrets" => { "secret" => [ "PASSWORD" ] }
|
||||||
@@ -99,6 +102,7 @@ class ConfigurationEnvTagsTest < ActiveSupport::TestCase
|
|||||||
deploy = {
|
deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ { "1.1.1.1" => "clearly" } ],
|
servers: [ { "1.1.1.1" => "clearly" } ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: {
|
env: {
|
||||||
"tags" => {
|
"tags" => {
|
||||||
"clearly" => { "clear" => { "FOO" => "bar" } }
|
"clearly" => { "clear" => { "FOO" => "bar" } }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
@deploy = {
|
@deploy = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ "1.1.1.1", "1.1.1.2" ],
|
servers: [ "1.1.1.1", "1.1.1.2" ],
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: { "REDIS_URL" => "redis://x/y" }
|
env: { "REDIS_URL" => "redis://x/y" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class ConfigurationSshTest < ActiveSupport::TestCase
|
|||||||
@deploy = {
|
@deploy = {
|
||||||
service: "app", image: "dhh/app",
|
service: "app", image: "dhh/app",
|
||||||
registry: { "username" => "dhh", "password" => "secret" },
|
registry: { "username" => "dhh", "password" => "secret" },
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: { "REDIS_URL" => "redis://x/y" },
|
env: { "REDIS_URL" => "redis://x/y" },
|
||||||
servers: [ "1.1.1.1", "1.1.1.2" ],
|
servers: [ "1.1.1.1", "1.1.1.2" ],
|
||||||
volumes: [ "/local/path:/container/path" ]
|
volumes: [ "/local/path:/container/path" ]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class ConfigurationSshkitTest < ActiveSupport::TestCase
|
|||||||
service: "app", image: "dhh/app",
|
service: "app", image: "dhh/app",
|
||||||
registry: { "username" => "dhh", "password" => "secret" },
|
registry: { "username" => "dhh", "password" => "secret" },
|
||||||
env: { "REDIS_URL" => "redis://x/y" },
|
env: { "REDIS_URL" => "redis://x/y" },
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
servers: [ "1.1.1.1", "1.1.1.2" ],
|
servers: [ "1.1.1.1", "1.1.1.2" ],
|
||||||
volumes: [ "/local/path:/container/path" ]
|
volumes: [ "/local/path:/container/path" ]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,10 +90,8 @@ class ConfigurationValidationTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "builder" do
|
test "builder" do
|
||||||
assert_error "builder: unknown key: foo", builder: { "foo" => "bar" }
|
assert_error "builder: unknown key: foo", builder: { "foo" => "bar" }
|
||||||
assert_error "builder/remote: should be a hash", builder: { "remote" => true }
|
assert_error "builder/remote: should be a string", builder: { "remote" => { "foo" => "bar" } }
|
||||||
assert_error "builder/remote: unknown key: foo", builder: { "remote" => { "foo" => "bar" } }
|
assert_error "builder/arch: should be an array or a string", builder: { "arch" => {} }
|
||||||
assert_error "builder/local: unknown key: foo", builder: { "local" => { "foo" => "bar" } }
|
|
||||||
assert_error "builder/remote/arch: should be a string", builder: { "remote" => { "arch" => [] } }
|
|
||||||
assert_error "builder/args: should be a hash", builder: { "args" => [ "foo" ] }
|
assert_error "builder/args: should be a hash", builder: { "args" => [ "foo" ] }
|
||||||
assert_error "builder/cache/options: should be a string", builder: { "cache" => { "options" => [] } }
|
assert_error "builder/cache/options: should be a string", builder: { "cache" => { "options" => [] } }
|
||||||
end
|
end
|
||||||
@@ -103,6 +101,7 @@ class ConfigurationValidationTest < ActiveSupport::TestCase
|
|||||||
valid_config = {
|
valid_config = {
|
||||||
service: "app",
|
service: "app",
|
||||||
image: "app",
|
image: "app",
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
registry: { "username" => "user", "password" => "secret" },
|
registry: { "username" => "user", "password" => "secret" },
|
||||||
servers: [ "1.1.1.1" ]
|
servers: [ "1.1.1.1" ]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
@deploy = {
|
@deploy = {
|
||||||
service: "app", image: "dhh/app",
|
service: "app", image: "dhh/app",
|
||||||
registry: { "username" => "dhh", "password" => "secret" },
|
registry: { "username" => "dhh", "password" => "secret" },
|
||||||
|
builder: { "arch" => "amd64" },
|
||||||
env: { "REDIS_URL" => "redis://x/y" },
|
env: { "REDIS_URL" => "redis://x/y" },
|
||||||
servers: [ "1.1.1.1", "1.1.1.2" ],
|
servers: [ "1.1.1.1", "1.1.1.2" ],
|
||||||
volumes: [ "/local/path:/container/path" ]
|
volumes: [ "/local/path:/container/path" ]
|
||||||
@@ -121,7 +122,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
test "version from uncommitted context" do
|
test "version from uncommitted context" do
|
||||||
ENV.delete("VERSION")
|
ENV.delete("VERSION")
|
||||||
|
|
||||||
config = Kamal::Configuration.new(@deploy.tap { |c| c[:builder] = { "context" => "." } })
|
config = Kamal::Configuration.new(@deploy.tap { |c| c[:builder]["context"] = "." })
|
||||||
|
|
||||||
Kamal::Git.expects(:revision).returns("git-version")
|
Kamal::Git.expects(:revision).returns("git-version")
|
||||||
Kamal::Git.expects(:uncommitted_changes).returns("M file\n")
|
Kamal::Git.expects(:uncommitted_changes).returns("M file\n")
|
||||||
@@ -267,7 +268,7 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
ssh_options: { user: "root", port: 22, log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
ssh_options: { user: "root", port: 22, log_level: :fatal, keepalive: true, keepalive_interval: 30 },
|
||||||
sshkit: {},
|
sshkit: {},
|
||||||
volume_args: [ "--volume", "/local/path:/container/path" ],
|
volume_args: [ "--volume", "/local/path:/container/path" ],
|
||||||
builder: {},
|
builder: { "arch" => "amd64" },
|
||||||
logging: [ "--log-opt", "max-size=\"10m\"" ],
|
logging: [ "--log-opt", "max-size=\"10m\"" ],
|
||||||
healthcheck: { "cmd"=>"curl -f http://localhost:3000/up || exit 1", "interval" => "1s", "path"=>"/up", "port"=>3000, "max_attempts" => 7, "cord" => "/tmp/kamal-cord", "log_lines" => 50 } }
|
healthcheck: { "cmd"=>"curl -f http://localhost:3000/up || exit 1", "interval" => "1s", "path"=>"/up", "port"=>3000, "max_attempts" => 7, "cord" => "/tmp/kamal-cord", "log_lines" => 50 } }
|
||||||
|
|
||||||
|
|||||||
2
test/fixtures/deploy.erb.yml
vendored
2
test/fixtures/deploy.erb.yml
vendored
@@ -9,3 +9,5 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: <%= "my-user" %>
|
username: <%= "my-user" %>
|
||||||
password: <%= "my-password" %>
|
password: <%= "my-password" %>
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
2
test/fixtures/deploy_for_dest.yml
vendored
2
test/fixtures/deploy_for_dest.yml
vendored
@@ -4,3 +4,5 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: <%= "my-user" %>
|
username: <%= "my-user" %>
|
||||||
password: <%= "my-password" %>
|
password: <%= "my-password" %>
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
2
test/fixtures/deploy_for_required_dest.yml
vendored
2
test/fixtures/deploy_for_required_dest.yml
vendored
@@ -4,4 +4,6 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: <%= "my-user" %>
|
username: <%= "my-user" %>
|
||||||
password: <%= "my-password" %>
|
password: <%= "my-password" %>
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
require_destination: true
|
require_destination: true
|
||||||
|
|||||||
@@ -17,4 +17,6 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
primary_role: web_tokyo
|
primary_role: web_tokyo
|
||||||
|
|||||||
2
test/fixtures/deploy_simple.yml
vendored
2
test/fixtures/deploy_simple.yml
vendored
@@ -6,3 +6,5 @@ servers:
|
|||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
2
test/fixtures/deploy_with_accessories.yml
vendored
2
test/fixtures/deploy_with_accessories.yml
vendored
@@ -10,6 +10,8 @@ servers:
|
|||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
accessories:
|
accessories:
|
||||||
mysql:
|
mysql:
|
||||||
|
|||||||
23
test/fixtures/deploy_with_aliases.yml
vendored
Normal file
23
test/fixtures/deploy_with_aliases.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
service: app
|
||||||
|
image: dhh/app
|
||||||
|
servers:
|
||||||
|
web:
|
||||||
|
- 1.1.1.1
|
||||||
|
- 1.1.1.2
|
||||||
|
workers:
|
||||||
|
hosts:
|
||||||
|
- 1.1.1.3
|
||||||
|
- 1.1.1.4
|
||||||
|
console:
|
||||||
|
hosts:
|
||||||
|
- 1.1.1.5
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
registry:
|
||||||
|
username: user
|
||||||
|
password: pw
|
||||||
|
aliases:
|
||||||
|
info: details
|
||||||
|
console: app exec --reuse -p -r console "bin/console"
|
||||||
|
exec: app exec --reuse -p -r console
|
||||||
|
rails: app exec --reuse -p -r console rails
|
||||||
2
test/fixtures/deploy_with_assets.yml
vendored
2
test/fixtures/deploy_with_assets.yml
vendored
@@ -6,4 +6,6 @@ servers:
|
|||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
asset_path: /public/assets
|
asset_path: /public/assets
|
||||||
|
|||||||
2
test/fixtures/deploy_with_boot_strategy.yml
vendored
2
test/fixtures/deploy_with_boot_strategy.yml
vendored
@@ -7,6 +7,8 @@ servers:
|
|||||||
workers:
|
workers:
|
||||||
- "1.1.1.3"
|
- "1.1.1.3"
|
||||||
- "1.1.1.4"
|
- "1.1.1.4"
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
|
|||||||
2
test/fixtures/deploy_with_env_tags.yml
vendored
2
test/fixtures/deploy_with_env_tags.yml
vendored
@@ -11,6 +11,8 @@ servers:
|
|||||||
- 1.1.1.4: site1
|
- 1.1.1.4: site1
|
||||||
- 1.2.1.3: site2
|
- 1.2.1.3: site2
|
||||||
- 1.2.1.4: [ site2 experimental ]
|
- 1.2.1.4: [ site2 experimental ]
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
env:
|
env:
|
||||||
clear:
|
clear:
|
||||||
TEST: "root"
|
TEST: "root"
|
||||||
|
|||||||
2
test/fixtures/deploy_with_extensions.yml
vendored
2
test/fixtures/deploy_with_extensions.yml
vendored
@@ -21,4 +21,6 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
primary_role: web_tokyo
|
primary_role: web_tokyo
|
||||||
|
|||||||
42
test/fixtures/deploy_with_hybrid_builder.yml
vendored
Normal file
42
test/fixtures/deploy_with_hybrid_builder.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
service: app
|
||||||
|
image: dhh/app
|
||||||
|
servers:
|
||||||
|
web:
|
||||||
|
- "1.1.1.1"
|
||||||
|
- "1.1.1.2"
|
||||||
|
workers:
|
||||||
|
- "1.1.1.3"
|
||||||
|
- "1.1.1.4"
|
||||||
|
registry:
|
||||||
|
username: user
|
||||||
|
password: pw
|
||||||
|
|
||||||
|
accessories:
|
||||||
|
mysql:
|
||||||
|
image: mysql:5.7
|
||||||
|
host: 1.1.1.3
|
||||||
|
port: 3306
|
||||||
|
env:
|
||||||
|
clear:
|
||||||
|
MYSQL_ROOT_HOST: '%'
|
||||||
|
secret:
|
||||||
|
- MYSQL_ROOT_PASSWORD
|
||||||
|
files:
|
||||||
|
- test/fixtures/files/my.cnf:/etc/mysql/my.cnf
|
||||||
|
directories:
|
||||||
|
- data:/var/lib/mysql
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
roles:
|
||||||
|
- web
|
||||||
|
port: 6379
|
||||||
|
directories:
|
||||||
|
- data:/data
|
||||||
|
|
||||||
|
readiness_delay: 0
|
||||||
|
|
||||||
|
builder:
|
||||||
|
arch:
|
||||||
|
- arm64
|
||||||
|
- amd64
|
||||||
|
remote: ssh://app@1.1.1.5
|
||||||
@@ -7,6 +7,8 @@ servers:
|
|||||||
workers:
|
workers:
|
||||||
- "1.1.1.3"
|
- "1.1.1.3"
|
||||||
- "1.1.1.4"
|
- "1.1.1.4"
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ servers:
|
|||||||
hosts:
|
hosts:
|
||||||
- 1.1.1.3
|
- 1.1.1.3
|
||||||
- 1.1.1.4
|
- 1.1.1.4
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
env:
|
env:
|
||||||
REDIS_URL: redis://x/y
|
REDIS_URL: redis://x/y
|
||||||
registry:
|
registry:
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ servers:
|
|||||||
workers:
|
workers:
|
||||||
- "1.1.1.3"
|
- "1.1.1.3"
|
||||||
- "1.1.1.4"
|
- "1.1.1.4"
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
|
|||||||
5
test/fixtures/deploy_with_remote_builder.yml
vendored
5
test/fixtures/deploy_with_remote_builder.yml
vendored
@@ -36,6 +36,5 @@ accessories:
|
|||||||
readiness_delay: 0
|
readiness_delay: 0
|
||||||
|
|
||||||
builder:
|
builder:
|
||||||
remote:
|
arch: <%= Kamal::Utils.docker_arch == "arm64" ? "amd64" : "arm64" %>
|
||||||
arch: amd64
|
remote: ssh://app@1.1.1.5
|
||||||
host: ssh://app@1.1.1.5
|
|
||||||
|
|||||||
@@ -40,6 +40,5 @@ ssh:
|
|||||||
port: 22
|
port: 22
|
||||||
|
|
||||||
builder:
|
builder:
|
||||||
remote:
|
arch: <%= Kamal::Utils.docker_arch == "arm64" ? "amd64" : "arm64" %>
|
||||||
arch: amd64
|
remote: ssh://app@1.1.1.5:2122
|
||||||
host: ssh://app@1.1.1.5:2122
|
|
||||||
|
|||||||
2
test/fixtures/deploy_with_roles.yml
vendored
2
test/fixtures/deploy_with_roles.yml
vendored
@@ -14,3 +14,5 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
2
test/fixtures/deploy_with_secrets.yml
vendored
2
test/fixtures/deploy_with_secrets.yml
vendored
@@ -9,3 +9,5 @@ registry:
|
|||||||
env:
|
env:
|
||||||
secret:
|
secret:
|
||||||
- PASSWORD
|
- PASSWORD
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
@@ -13,3 +13,5 @@ registry:
|
|||||||
server: registry.digitalocean.com
|
server: registry.digitalocean.com
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ servers:
|
|||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
1
test/fixtures/deploy_without_clone.yml
vendored
1
test/fixtures/deploy_without_clone.yml
vendored
@@ -36,4 +36,5 @@ accessories:
|
|||||||
readiness_delay: 0
|
readiness_delay: 0
|
||||||
|
|
||||||
builder:
|
builder:
|
||||||
|
arch: amd64
|
||||||
context: "."
|
context: "."
|
||||||
|
|||||||
2
test/fixtures/deploy_workers_only.yml
vendored
2
test/fixtures/deploy_workers_only.yml
vendored
@@ -10,3 +10,5 @@ primary_role: workers
|
|||||||
registry:
|
registry:
|
||||||
username: user
|
username: user
|
||||||
password: pw
|
password: pw
|
||||||
|
builder:
|
||||||
|
arch: amd64
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
echo "About to lock..."
|
echo "About to lock..."
|
||||||
if [ "$KAMAL_HOSTS" != "vm1,vm2" ]; then
|
|
||||||
echo "Expected hosts to be 'vm1,vm2', got $KAMAL_HOSTS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect
|
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ registry:
|
|||||||
username: root
|
username: root
|
||||||
password: root
|
password: root
|
||||||
builder:
|
builder:
|
||||||
multiarch: false
|
driver: docker
|
||||||
|
arch: <%= Kamal::Utils.docker_arch %>
|
||||||
args:
|
args:
|
||||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
echo "About to lock..."
|
echo "About to lock..."
|
||||||
if [ "$KAMAL_HOSTS" != "vm1,vm2,vm3" ]; then
|
|
||||||
echo "Expected hosts to be 'vm1,vm2,vm3', got $KAMAL_HOSTS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect
|
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ registry:
|
|||||||
username: root
|
username: root
|
||||||
password: root
|
password: root
|
||||||
builder:
|
builder:
|
||||||
multiarch: false
|
driver: docker
|
||||||
|
arch: <%= Kamal::Utils.docker_arch %>
|
||||||
args:
|
args:
|
||||||
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
COMMIT_SHA: <%= `git rev-parse HEAD` %>
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -37,3 +38,7 @@ accessories:
|
|||||||
- web
|
- web
|
||||||
stop_wait_time: 1
|
stop_wait_time: 1
|
||||||
readiness_delay: 0
|
readiness_delay: 0
|
||||||
|
aliases:
|
||||||
|
whome: version
|
||||||
|
worker_hostname: app exec -r workers -q --reuse hostname
|
||||||
|
uname: server exec -q -p uname
|
||||||
|
|||||||
@@ -77,11 +77,27 @@ class MainTest < IntegrationTest
|
|||||||
assert_equal "app-#{version}", config[:service_with_version]
|
assert_equal "app-#{version}", config[:service_with_version]
|
||||||
assert_equal [], config[:volume_args]
|
assert_equal [], config[:volume_args]
|
||||||
assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
||||||
assert_equal({ "multiarch" => false, "args" => { "COMMIT_SHA" => version } }, config[:builder])
|
assert_equal({ "driver" => "docker", "arch" => "amd64", "args" => { "COMMIT_SHA" => version } }, config[:builder])
|
||||||
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
|
assert_equal [ "--log-opt", "max-size=\"10m\"" ], config[:logging]
|
||||||
assert_equal({ "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1", "interval"=>"1s", "max_attempts"=>3, "port"=>3000, "path"=>"/up", "cord"=>"/tmp/kamal-cord", "log_lines"=>50 }, config[:healthcheck])
|
assert_equal({ "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1", "interval"=>"1s", "max_attempts"=>3, "port"=>3000, "path"=>"/up", "cord"=>"/tmp/kamal-cord", "log_lines"=>50 }, config[:healthcheck])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "aliases" do
|
||||||
|
@app = "app_with_roles"
|
||||||
|
|
||||||
|
kamal :envify
|
||||||
|
kamal :deploy
|
||||||
|
|
||||||
|
output = kamal :whome, capture: true
|
||||||
|
assert_equal Kamal::VERSION, output
|
||||||
|
|
||||||
|
output = kamal :worker_hostname, capture: true
|
||||||
|
assert_match /App Host: vm3\nvm3-[0-9a-f]{12}$/, output
|
||||||
|
|
||||||
|
output = kamal :uname, "-o", capture: true
|
||||||
|
assert_match "App Host: vm1\nGNU/Linux", output
|
||||||
|
end
|
||||||
|
|
||||||
test "setup and remove" do
|
test "setup and remove" do
|
||||||
# Check remove completes when nothing has been setup yet
|
# Check remove completes when nothing has been setup yet
|
||||||
kamal :remove, "-y"
|
kamal :remove, "-y"
|
||||||
|
|||||||
Reference in New Issue
Block a user