Compare commits
19 Commits
v2.0.0.bet
...
local-dock
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f3d33cb5 | ||
|
|
9fd4001a88 | ||
|
|
6aa707e233 | ||
|
|
e71bfcbadd | ||
|
|
567309596a | ||
|
|
b89ec2bf63 | ||
|
|
3172adca30 | ||
|
|
04d21f45bb | ||
|
|
eabd57350c | ||
|
|
487f6f5f53 | ||
|
|
d98500982d | ||
|
|
8693e968c1 | ||
|
|
6ab5fc9459 | ||
|
|
6fc2915884 | ||
|
|
afa6898a82 | ||
|
|
384b36d158 | ||
|
|
6df169a4fb | ||
|
|
ab109afc52 | ||
|
|
a6a48c456c |
@@ -1,7 +1,7 @@
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
kamal (2.0.0.beta2)
|
||||
kamal (2.0.0.rc2)
|
||||
activesupport (>= 7.0)
|
||||
base64 (~> 0.2)
|
||||
bcrypt_pbkdf (~> 1.0)
|
||||
@@ -9,6 +9,7 @@ PATH
|
||||
dotenv (~> 3.1)
|
||||
ed25519 (~> 1.2)
|
||||
net-ssh (~> 7.0)
|
||||
net-ssh-gateway
|
||||
sshkit (>= 1.23.0, < 2.0)
|
||||
thor (~> 1.3)
|
||||
zeitwerk (~> 2.5)
|
||||
@@ -79,6 +80,8 @@ GEM
|
||||
net-sftp (4.0.0)
|
||||
net-ssh (>= 5.0.0, < 8.0.0)
|
||||
net-ssh (7.2.3)
|
||||
net-ssh-gateway (2.0.0)
|
||||
net-ssh (>= 4.0.0)
|
||||
nokogiri (1.16.7-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.7-x86_64-darwin)
|
||||
|
||||
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
|
||||
spec.add_dependency "activesupport", ">= 7.0"
|
||||
spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0"
|
||||
spec.add_dependency "net-ssh", "~> 7.0"
|
||||
spec.add_dependency "net-ssh-gateway"
|
||||
spec.add_dependency "thor", "~> 1.3"
|
||||
spec.add_dependency "dotenv", "~> 3.1"
|
||||
spec.add_dependency "zeitwerk", "~> 2.5"
|
||||
|
||||
@@ -147,23 +147,25 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||
option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
|
||||
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
||||
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
||||
def logs(name)
|
||||
with_accessory(name) do |accessory, hosts|
|
||||
grep = options[:grep]
|
||||
grep_options = options[:grep_options]
|
||||
timestamps = !options[:skip_timestamps]
|
||||
|
||||
if options[:follow]
|
||||
run_locally do
|
||||
info "Following logs on #{hosts}..."
|
||||
info accessory.follow_logs(grep: grep, grep_options: grep_options)
|
||||
exec accessory.follow_logs(grep: grep, grep_options: grep_options)
|
||||
info accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
|
||||
exec accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
|
||||
end
|
||||
else
|
||||
since = options[:since]
|
||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||
|
||||
on(hosts) do
|
||||
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep, grep_options: grep_options))
|
||||
puts capture_with_info(*accessory.logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -273,7 +275,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
||||
def prepare(name)
|
||||
with_accessory(name) do |accessory, hosts|
|
||||
on(hosts) do
|
||||
execute *KAMAL.registry.login
|
||||
execute *KAMAL.registry.login unless KAMAL.config.registry.local?
|
||||
execute *KAMAL.docker.create_network
|
||||
rescue SSHKit::Command::Failed => e
|
||||
raise unless e.message.include?("already exists")
|
||||
|
||||
@@ -188,12 +188,14 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||
option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
|
||||
option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
|
||||
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
||||
def logs
|
||||
# FIXME: Catch when app containers aren't running
|
||||
|
||||
grep = options[:grep]
|
||||
grep_options = options[:grep_options]
|
||||
since = options[:since]
|
||||
timestamps = !options[:skip_timestamps]
|
||||
|
||||
if options[:follow]
|
||||
lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
|
||||
@@ -205,8 +207,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
role = KAMAL.roles_on(KAMAL.primary_host).first
|
||||
|
||||
app = KAMAL.app(role: role, host: host)
|
||||
info app.follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep, grep_options: grep_options)
|
||||
exec app.follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep, grep_options: grep_options)
|
||||
info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
||||
exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
||||
end
|
||||
else
|
||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||
@@ -216,7 +218,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
|
||||
roles.each do |role|
|
||||
begin
|
||||
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(since: since, lines: lines, grep: grep, grep_options: grep_options))
|
||||
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
||||
rescue SSHKit::Command::Failed
|
||||
puts_by_host host, "Nothing found"
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
require "uri"
|
||||
require "net/ssh"
|
||||
|
||||
class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
class BuildError < StandardError; end
|
||||
@@ -60,6 +61,8 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
|
||||
desc "pull", "Pull app image from registry onto servers"
|
||||
def pull
|
||||
tunnels = Kamal::Cli::Tunnel::RemotePorts.new(KAMAL.hosts, KAMAL.config.registry.local_port).tap(&:open) if KAMAL.config.registry.local?
|
||||
|
||||
if (first_hosts = mirror_hosts).any?
|
||||
# Pull on a single host per mirror first to seed them
|
||||
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
|
||||
@@ -69,6 +72,8 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
else
|
||||
pull_on_hosts(KAMAL.hosts)
|
||||
end
|
||||
ensure
|
||||
tunnels&.close
|
||||
end
|
||||
|
||||
desc "create", "Create a build setup"
|
||||
@@ -152,7 +157,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
end
|
||||
|
||||
def pull_on_hosts(hosts)
|
||||
on(hosts) do
|
||||
on(hosts) do |host|
|
||||
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
||||
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
||||
execute *KAMAL.builder.pull
|
||||
|
||||
@@ -22,7 +22,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
||||
invoke_options = deploy_options
|
||||
|
||||
say "Log into image registry...", :magenta
|
||||
invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
|
||||
invoke "kamal:cli:registry:setup", [], invoke_options.merge(skip_local: options[:skip_push])
|
||||
|
||||
if options[:skip_push]
|
||||
say "Pull app image...", :magenta
|
||||
@@ -184,7 +184,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
||||
invoke "kamal:cli:app:remove", [], options.without(:confirmed)
|
||||
invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
|
||||
invoke "kamal:cli:accessory:remove", [ "all" ], options
|
||||
invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
|
||||
invoke "kamal:cli:registry:remove", [], options.without(:confirmed).merge(skip_local: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
||||
end
|
||||
|
||||
on(KAMAL.proxy_hosts) do |host|
|
||||
execute *KAMAL.registry.login
|
||||
execute *KAMAL.registry.login unless KAMAL.config.registry.local?
|
||||
|
||||
version = capture_with_info(*KAMAL.proxy.version).strip.presence
|
||||
|
||||
@@ -33,7 +33,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
||||
run_hook "pre-proxy-reboot", hosts: host_list
|
||||
on(hosts) do |host|
|
||||
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
||||
execute *KAMAL.registry.login
|
||||
execute *KAMAL.registry.login unless KAMAL.config.registry.local?
|
||||
|
||||
"Stopping and removing Traefik on #{host}, if running..."
|
||||
execute *KAMAL.proxy.cleanup_traefik
|
||||
@@ -76,7 +76,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
||||
run_hook "pre-proxy-reboot", hosts: host_list
|
||||
on(hosts) do |host|
|
||||
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
||||
execute *KAMAL.registry.login
|
||||
execute *KAMAL.registry.login unless KAMAL.config.registry.local?
|
||||
|
||||
"Stopping and removing Traefik on #{host}, if running..."
|
||||
execute *KAMAL.proxy.cleanup_traefik
|
||||
@@ -140,21 +140,23 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
||||
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
||||
def logs
|
||||
grep = options[:grep]
|
||||
timestamps = !options[:skip_timestamps]
|
||||
|
||||
if options[:follow]
|
||||
run_locally do
|
||||
info "Following logs on #{KAMAL.primary_host}..."
|
||||
info KAMAL.proxy.follow_logs(host: KAMAL.primary_host, grep: grep)
|
||||
exec KAMAL.proxy.follow_logs(host: KAMAL.primary_host, grep: grep)
|
||||
info KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
||||
exec KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
||||
end
|
||||
else
|
||||
since = options[:since]
|
||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||
|
||||
on(KAMAL.proxy_hosts) do |host|
|
||||
puts_by_host host, capture(*KAMAL.proxy.logs(since: since, lines: lines, grep: grep)), type: "Proxy"
|
||||
puts_by_host host, capture(*KAMAL.proxy.logs(timestamps: timestamps, since: since, lines: lines, grep: grep)), type: "Proxy"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
class Kamal::Cli::Registry < Kamal::Cli::Base
|
||||
desc "login", "Log in to registry locally and remotely"
|
||||
desc "login", "Setup local registry or log in to remote registry locally and remotely"
|
||||
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
||||
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
||||
def login
|
||||
def setup
|
||||
if KAMAL.registry.local?
|
||||
run_locally { execute *KAMAL.registry.setup } unless options[:skip_local]
|
||||
else
|
||||
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
||||
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
||||
end
|
||||
end
|
||||
|
||||
desc "logout", "Log out of registry locally and remotely"
|
||||
desc "remove", "Remove local registry or log out of remote registry locally and remotely"
|
||||
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
||||
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
||||
def logout
|
||||
def remove
|
||||
if KAMAL.registry.local?
|
||||
run_locally { execute *KAMAL.registry.remove, raise_on_non_zero_exit: false } unless options[:skip_local]
|
||||
else
|
||||
run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
|
||||
on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
66
lib/kamal/cli/tunnel/remote_ports.rb
Normal file
66
lib/kamal/cli/tunnel/remote_ports.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
Signal.trap "SIGPROF" do
|
||||
Thread.list.each do |thread|
|
||||
puts thread.name
|
||||
puts thread.backtrace.map { |bt| " #{bt}" }
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
require "concurrent/map"
|
||||
|
||||
class Kamal::Cli::Tunnel::RemotePorts
|
||||
attr_reader :hosts, :port
|
||||
|
||||
def initialize(hosts, port)
|
||||
@hosts = hosts
|
||||
@port = port
|
||||
@open = false
|
||||
end
|
||||
|
||||
def open
|
||||
@open = true
|
||||
@opened = Concurrent::Map.new
|
||||
|
||||
@threads = hosts.map do |host|
|
||||
Thread.new do
|
||||
Net::SSH.start(host, KAMAL.config.ssh.user) do |ssh|
|
||||
forwarding = nil
|
||||
ssh.forward.remote(port, "localhost", port, "localhost") do |actual_remote_port|
|
||||
forwarding = !!actual_remote_port
|
||||
:no_exception # will yield the exception on my own thread
|
||||
end
|
||||
ssh.loop { forwarding.nil? }
|
||||
if forwarding
|
||||
@opened[host] = true
|
||||
ssh.loop(0.1) { @open }
|
||||
|
||||
ssh.forward.cancel_remote(port, "localhost")
|
||||
ssh.loop(0.1) { ssh.forward.active_remotes.include?([ port, "localhost" ]) }
|
||||
else
|
||||
@opened[host] = false
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
@opened[host] = false
|
||||
|
||||
puts e.message
|
||||
puts e.backtrace
|
||||
end
|
||||
end
|
||||
|
||||
loop do
|
||||
break if @opened.size == hosts.size
|
||||
sleep 0.1
|
||||
end
|
||||
|
||||
raise "Could not open tunnels on #{opened.reject { |k, v| v }.join(", ")}" unless @opened.values.all?
|
||||
end
|
||||
|
||||
def close
|
||||
p "Closing"
|
||||
@open = false
|
||||
p "Joining"
|
||||
@threads.each(&:join)
|
||||
p "Joined"
|
||||
end
|
||||
end
|
||||
@@ -39,16 +39,16 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
|
||||
def logs(since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
pipe \
|
||||
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
|
||||
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
||||
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
||||
end
|
||||
|
||||
def follow_logs(grep: nil, grep_options: nil)
|
||||
def follow_logs(timestamps: true, grep: nil, grep_options: nil)
|
||||
run_over_ssh \
|
||||
pipe \
|
||||
docker(:logs, service_name, "--timestamps", "--tail", "10", "--follow", "2>&1"),
|
||||
docker(:logs, service_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
||||
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
module Kamal::Commands::App::Logging
|
||||
def logs(version: nil, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
def logs(version: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
pipe \
|
||||
version ? container_id_for_version(version) : current_running_container_id,
|
||||
"xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
||||
"xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
||||
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
||||
end
|
||||
|
||||
def follow_logs(host:, lines: nil, grep: nil, grep_options: nil)
|
||||
def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil)
|
||||
run_over_ssh \
|
||||
pipe(
|
||||
current_running_container_id,
|
||||
"xargs docker logs --timestamps#{" --tail #{lines}" if lines} --follow 2>&1",
|
||||
"xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1",
|
||||
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
||||
),
|
||||
host: host
|
||||
|
||||
@@ -35,15 +35,15 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
|
||||
[ :cut, "-d:", "-f2" ]
|
||||
end
|
||||
|
||||
def logs(since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||
pipe \
|
||||
docker(:logs, container_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
|
||||
docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
||||
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
||||
end
|
||||
|
||||
def follow_logs(host:, grep: nil, grep_options: nil)
|
||||
def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
|
||||
run_over_ssh pipe(
|
||||
docker(:logs, container_name, "--timestamps", "--tail", "10", "--follow", "2>&1"),
|
||||
docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
||||
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
||||
).join(" "), host: host
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Kamal::Commands::Registry < Kamal::Commands::Base
|
||||
delegate :registry, to: :config
|
||||
delegate :local?, :local_port, to: :registry
|
||||
|
||||
def login
|
||||
docker :login,
|
||||
@@ -11,4 +12,26 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
|
||||
def logout
|
||||
docker :logout, registry.server
|
||||
end
|
||||
|
||||
def setup
|
||||
combine \
|
||||
docker(:start, "kamal-docker-registry"),
|
||||
docker(:run, "--detach", "-p", "127.0.0.1:#{local_port}:5000", "--name", "kamal-docker-registry", "registry:2"),
|
||||
by: "||"
|
||||
end
|
||||
|
||||
def remove
|
||||
combine \
|
||||
docker(:stop, "kamal-docker-registry"),
|
||||
docker(:rm, "kamal-docker-registry"),
|
||||
by: "&&"
|
||||
end
|
||||
|
||||
def logout
|
||||
docker :logout, registry.server
|
||||
end
|
||||
|
||||
def tunnel(host)
|
||||
run_over_ssh "-R", "#{local_port}:localhost:#{local_port}", host: host
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
#
|
||||
# The builder configuration controls how the application is built with `docker build`
|
||||
#
|
||||
# If no configuration is specified, Kamal will:
|
||||
# 1. Create a buildx context called `kamal-local-docker-container`, using the docker-container driver
|
||||
# 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
|
||||
|
||||
# Builder options
|
||||
@@ -78,7 +74,7 @@ builder:
|
||||
|
||||
# Build secrets
|
||||
#
|
||||
# Values are read from the .kamal/secrets.
|
||||
# Values are read from .kamal/secrets.
|
||||
#
|
||||
secrets:
|
||||
- SECRET1
|
||||
|
||||
@@ -36,6 +36,8 @@ image: my-image
|
||||
labels:
|
||||
my-label: my-value
|
||||
|
||||
# Volumes
|
||||
#
|
||||
# Additional volumes to mount into the container
|
||||
volumes:
|
||||
- /path/on/host:/path/in/container:ro
|
||||
@@ -58,7 +60,7 @@ servers:
|
||||
env:
|
||||
...
|
||||
|
||||
# Asset Bridging
|
||||
# Asset Path
|
||||
#
|
||||
# Used for asset bridging across deployments, default to `nil`
|
||||
#
|
||||
@@ -74,6 +76,8 @@ env:
|
||||
# To configure this, set the path to the assets:
|
||||
asset_path: /path/to/assets
|
||||
|
||||
# Hooks path
|
||||
#
|
||||
# Path to hooks, defaults to `.kamal/hooks`
|
||||
# See https://kamal-deploy.org/docs/hooks for more information
|
||||
hooks_path: /user_home/kamal/hooks
|
||||
@@ -83,7 +87,7 @@ hooks_path: /user_home/kamal/hooks
|
||||
# Whether deployments require a destination to be specified, defaults to `false`
|
||||
require_destination: true
|
||||
|
||||
# The primary role
|
||||
# Primary role
|
||||
#
|
||||
# This defaults to `web`, but if you have no web role, you can change this
|
||||
primary_role: workers
|
||||
|
||||
@@ -12,11 +12,16 @@ env:
|
||||
DATABASE_HOST: mysql-db1
|
||||
DATABASE_PORT: 3306
|
||||
|
||||
# Using .kamal/secrets file to load required environment variables
|
||||
# Secrets
|
||||
#
|
||||
# Kamal uses dotenv to automatically load environment variables set in the .kamal/secrets file.
|
||||
# Kamal uses dotenv to automatically load environment variables set in the `.kamal/secrets` file.
|
||||
#
|
||||
# This file can be used to set variables like KAMAL_REGISTRY_PASSWORD or database passwords.
|
||||
# If you are using destinations, secrets will instead be read from `.kamal/secrets-<DESTINATION>` if
|
||||
# it exists.
|
||||
#
|
||||
# Common secrets across all destinations can be set in `.kamal/secrets-common`.
|
||||
#
|
||||
# This file can be used to set variables like `KAMAL_REGISTRY_PASSWORD` or database passwords.
|
||||
# You can use variable or command substitution in the secrets file.
|
||||
#
|
||||
# ```
|
||||
@@ -24,6 +29,14 @@ env:
|
||||
# RAILS_MASTER_KEY=$(cat config/master.key)
|
||||
# ```
|
||||
#
|
||||
# You can also use [secret helpers](../commands/secrets) for some common password managers.
|
||||
# ```
|
||||
# SECRETS=$(kamal secrets fetch ...)
|
||||
#
|
||||
# REGISTRY_PASSWORD=$(kamal secrets extract REGISTRY_PASSWORD $SECRETS)
|
||||
# DB_PASSWORD=$(kamal secrets extract DB_PASSWORD $SECRETS)
|
||||
# ```
|
||||
#
|
||||
# If you store secrets directly in .kamal/secrets, ensure that it is not checked into version control.
|
||||
#
|
||||
# To pass the secrets you should list them under the `secret` key. When you do this the
|
||||
|
||||
@@ -47,7 +47,7 @@ proxy:
|
||||
# Response timeout
|
||||
#
|
||||
# How long to wait for requests to complete before timing out, defaults to 30 seconds
|
||||
response_timeout: 10s
|
||||
response_timeout: 10
|
||||
|
||||
# Healthcheck
|
||||
#
|
||||
@@ -91,7 +91,7 @@ proxy:
|
||||
|
||||
# Forward headers
|
||||
#
|
||||
# Whether to forward the X-Forwarded-For and X-Forwarded-Proto headers (defaults to false)
|
||||
# Whether to forward the X-Forwarded-For and X-Forwarded-Proto headers.
|
||||
#
|
||||
# If you are behind a trusted proxy, you can set this to true to forward the headers.
|
||||
#
|
||||
|
||||
@@ -26,8 +26,12 @@ servers:
|
||||
#
|
||||
# When there are other options to set, the list of hosts goes under the `hosts` key
|
||||
#
|
||||
# By default only the primary role uses a proxy, but you can set `proxy` to change
|
||||
# it.
|
||||
# By default only the primary role uses a proxy.
|
||||
#
|
||||
# For other roles, you can set it to `proxy: true` enable it and inherit the root proxy
|
||||
# configuration or provide a map of options to override the root configuration.
|
||||
#
|
||||
# For the primary role, you can set `proxy: false` to disable the proxy.
|
||||
#
|
||||
# You can also set a custom cmd to run in the container, and overwrite other settings
|
||||
# from the root configuration.
|
||||
|
||||
@@ -21,6 +21,14 @@ class Kamal::Configuration::Registry
|
||||
lookup("password")
|
||||
end
|
||||
|
||||
def local?
|
||||
server&.match?("^localhost[:$]")
|
||||
end
|
||||
|
||||
def local_port
|
||||
local? ? (server.split(":").last.to_i || 80) : nil
|
||||
end
|
||||
|
||||
private
|
||||
def lookup(key)
|
||||
if registry_config[key].is_a?(Array)
|
||||
|
||||
@@ -15,6 +15,7 @@ class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validato
|
||||
with_context(key) do
|
||||
value = config[key]
|
||||
|
||||
unless config["server"]&.match?("^localhost[:$]")
|
||||
error "is required" unless value.present?
|
||||
|
||||
unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
|
||||
@@ -23,3 +24,4 @@ class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validato
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Kamal
|
||||
VERSION = "2.0.0.beta2"
|
||||
VERSION = "2.0.0.rc2"
|
||||
end
|
||||
|
||||
@@ -315,11 +315,11 @@ class CliAppTest < CliTestCase
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
||||
.with("ssh -t root@1.1.1.1 'sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1| xargs docker logs --timestamps --tail 10 2>&1'")
|
||||
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --tail 100 2>&1", run_command("logs")
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --tail 100 2>&1", run_command("logs")
|
||||
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1 | grep 'hey'", run_command("logs", "--grep", "hey")
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 2>&1 | grep 'hey'", run_command("logs", "--grep", "hey")
|
||||
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1 | grep 'hey' -C 2", run_command("logs", "--grep", "hey", "--grep-options", "-C 2")
|
||||
assert_match "sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 2>&1 | grep 'hey' -C 2", run_command("logs", "--grep", "hey", "--grep-options", "-C 2")
|
||||
end
|
||||
|
||||
test "logs with follow" do
|
||||
|
||||
@@ -22,7 +22,7 @@ class CliMainTest < CliTestCase
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
||||
# deploy
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: true))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||
@@ -46,7 +46,7 @@ class CliMainTest < CliTestCase
|
||||
with_test_secrets("secrets" => "DB_PASSWORD=secret") do
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true }
|
||||
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||
@@ -72,7 +72,7 @@ class CliMainTest < CliTestCase
|
||||
test "deploy with skip_push" do
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: true))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||
@@ -159,7 +159,7 @@ class CliMainTest < CliTestCase
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, :skip_local => false }
|
||||
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke)
|
||||
.with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
|
||||
.with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false))
|
||||
.raises(RuntimeError)
|
||||
|
||||
assert_not KAMAL.holding_lock?
|
||||
@@ -172,7 +172,7 @@ class CliMainTest < CliTestCase
|
||||
test "deploy with skipped hooks" do
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => true }
|
||||
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||
@@ -187,7 +187,7 @@ class CliMainTest < CliTestCase
|
||||
test "deploy with missing secrets" do
|
||||
invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false }
|
||||
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false))
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||
@@ -289,6 +289,16 @@ class CliMainTest < CliTestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "remove" do
|
||||
options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_hooks" => false, "confirmed" => true }
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:remove", [], options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:remove", [], options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:remove", [ "all" ], options)
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:remove", [], options.merge(skip_local: true))
|
||||
|
||||
run_command("remove", "-y")
|
||||
end
|
||||
|
||||
test "details" do
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:details")
|
||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:details")
|
||||
|
||||
@@ -1,50 +1,62 @@
|
||||
require_relative "cli_test_case"
|
||||
|
||||
class CliRegistryTest < CliTestCase
|
||||
test "login" do
|
||||
run_command("login").tap do |output|
|
||||
test "setup" do
|
||||
run_command("setup").tap do |output|
|
||||
assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output
|
||||
assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "login skip local" do
|
||||
run_command("login", "-L").tap do |output|
|
||||
test "setup skip local" do
|
||||
run_command("setup", "-L").tap do |output|
|
||||
assert_no_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output
|
||||
assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "login skip remote" do
|
||||
run_command("login", "-R").tap do |output|
|
||||
test "setup skip remote" do
|
||||
run_command("setup", "-R").tap do |output|
|
||||
assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output
|
||||
assert_no_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "logout" do
|
||||
run_command("logout").tap do |output|
|
||||
test "remove" do
|
||||
run_command("remove").tap do |output|
|
||||
assert_match /docker logout as .*@localhost/, output
|
||||
assert_match /docker logout on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "logout skip local" do
|
||||
run_command("logout", "-L").tap do |output|
|
||||
test "remove skip local" do
|
||||
run_command("remove", "-L").tap do |output|
|
||||
assert_no_match /docker logout as .*@localhost/, output
|
||||
assert_match /docker logout on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "logout skip remote" do
|
||||
run_command("logout", "-R").tap do |output|
|
||||
test "remove skip remote" do
|
||||
run_command("remove", "-R").tap do |output|
|
||||
assert_match /docker logout as .*@localhost/, output
|
||||
assert_no_match /docker logout on 1.1.1.\d/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "setup local registry" do
|
||||
run_command("setup", fixture: :with_local_registry).tap do |output|
|
||||
assert_match /docker start kamal-docker-registry || docker run --detach -p 127.0.0.1:5000:5000 --name kamal-docker-registry registry:2 as .*@localhost/, output
|
||||
end
|
||||
end
|
||||
|
||||
test "remove local registry" do
|
||||
run_command("remove", fixture: :with_local_registry).tap do |output|
|
||||
assert_match /docker stop kamal-docker-registry && docker rm kamal-docker-registry as .*@localhost/, output
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def run_command(*command)
|
||||
stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
||||
def run_command(*command, fixture: :with_accessories)
|
||||
stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -130,12 +130,20 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||
assert_equal \
|
||||
"docker logs app-mysql --since 5m --tail 100 --timestamps 2>&1 | grep 'thing' -C 2",
|
||||
new_command(:mysql).logs(since: "5m", lines: 100, grep: "thing", grep_options: "-C 2").join(" ")
|
||||
|
||||
assert_equal \
|
||||
"docker logs app-mysql --since 5m --tail 100 2>&1 | grep 'thing' -C 2",
|
||||
new_command(:mysql).logs(timestamps: false, since: "5m", lines: 100, grep: "thing", grep_options: "-C 2").join(" ")
|
||||
end
|
||||
|
||||
test "follow logs" do
|
||||
assert_equal \
|
||||
"ssh -t root@1.1.1.5 -p 22 'docker logs app-mysql --timestamps --tail 10 --follow 2>&1'",
|
||||
new_command(:mysql).follow_logs
|
||||
|
||||
assert_equal \
|
||||
"ssh -t root@1.1.1.5 -p 22 'docker logs app-mysql --tail 10 --follow 2>&1'",
|
||||
new_command(:mysql).follow_logs(timestamps: false)
|
||||
end
|
||||
|
||||
test "remove container" do
|
||||
|
||||
@@ -129,49 +129,49 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
|
||||
test "logs" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 2>&1",
|
||||
new_command.logs.join(" ")
|
||||
end
|
||||
|
||||
test "logs with since" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m 2>&1",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --since 5m 2>&1",
|
||||
new_command.logs(since: "5m").join(" ")
|
||||
end
|
||||
|
||||
test "logs with lines" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --tail 100 2>&1",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --tail 100 2>&1",
|
||||
new_command.logs(lines: "100").join(" ")
|
||||
end
|
||||
|
||||
test "logs with since and lines" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m --tail 100 2>&1",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --since 5m --tail 100 2>&1",
|
||||
new_command.logs(since: "5m", lines: "100").join(" ")
|
||||
end
|
||||
|
||||
test "logs with grep" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1 | grep 'my-id'",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 2>&1 | grep 'my-id'",
|
||||
new_command.logs(grep: "my-id").join(" ")
|
||||
end
|
||||
|
||||
test "logs with grep and grep options" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1 | grep 'my-id' -C 2",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 2>&1 | grep 'my-id' -C 2",
|
||||
new_command.logs(grep: "my-id", grep_options: "-C 2").join(" ")
|
||||
end
|
||||
|
||||
test "logs with since, grep and grep options" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m 2>&1 | grep 'my-id' -C 2",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --since 5m 2>&1 | grep 'my-id' -C 2",
|
||||
new_command.logs(since: "5m", grep: "my-id", grep_options: "-C 2").join(" ")
|
||||
end
|
||||
|
||||
test "logs with since and grep" do
|
||||
assert_equal \
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
|
||||
"sh -c 'docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --since 5m 2>&1 | grep 'my-id'",
|
||||
new_command.logs(since: "5m", grep: "my-id").join(" ")
|
||||
end
|
||||
|
||||
@@ -191,6 +191,10 @@ class CommandsAppTest < ActiveSupport::TestCase
|
||||
assert_equal \
|
||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 123 --follow 2>&1 | grep \"Completed\"'",
|
||||
new_command.follow_logs(host: "app-1", lines: 123, grep: "Completed")
|
||||
|
||||
assert_equal \
|
||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --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 --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --tail 123 --follow 2>&1 | grep \"Completed\"'",
|
||||
new_command.follow_logs(host: "app-1", timestamps: false, lines: 123, grep: "Completed")
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -77,6 +77,12 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
||||
new_command.logs(lines: 10).join(" ")
|
||||
end
|
||||
|
||||
test "proxy logs without timestamps" do
|
||||
assert_equal \
|
||||
"docker logs kamal-proxy 2>&1",
|
||||
new_command.logs(timestamps: false).join(" ")
|
||||
end
|
||||
|
||||
test "proxy logs with grep hello!" do
|
||||
assert_equal \
|
||||
"docker logs kamal-proxy --timestamps 2>&1 | grep 'hello!'",
|
||||
|
||||
@@ -55,6 +55,15 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
||||
registry.logout.join(" ")
|
||||
end
|
||||
|
||||
test "registry setup" do
|
||||
@config[:registry] = { "server" => "localhost:5000" }
|
||||
assert_equal "docker start kamal-docker-registry || docker run --detach -p 5000:5000 --name kamal-docker-registry registry:2", registry.setup.join(" ")
|
||||
end
|
||||
|
||||
test "registry remove" do
|
||||
assert_equal "docker stop kamal-docker-registry && docker rm kamal-docker-registry", registry.remove.join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
def registry
|
||||
Kamal::Commands::Registry.new Kamal::Configuration.new(@config)
|
||||
|
||||
37
test/fixtures/deploy_with_local_registry.yml
vendored
Normal file
37
test/fixtures/deploy_with_local_registry.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
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:
|
||||
server: localhost:5000
|
||||
builder:
|
||||
arch: amd64
|
||||
|
||||
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
|
||||
@@ -27,14 +27,14 @@ class AppTest < IntegrationTest
|
||||
images = kamal :app, :images, capture: true
|
||||
assert_match "App Host: vm1", images
|
||||
assert_match "App Host: vm2", images
|
||||
assert_match /registry:4443\/app\s+#{latest_app_version}/, images
|
||||
assert_match /registry:4443\/app\s+latest/, images
|
||||
assert_match /localhost:5000\/app\s+#{latest_app_version}/, images
|
||||
assert_match /localhost:5000\/app\s+latest/, images
|
||||
|
||||
containers = kamal :app, :containers, capture: true
|
||||
assert_match "App Host: vm1", containers
|
||||
assert_match "App Host: vm2", containers
|
||||
assert_match "registry:4443/app:#{latest_app_version}", containers
|
||||
assert_match "registry:4443/app:latest", containers
|
||||
assert_match "localhost:5000/app:#{latest_app_version}", containers
|
||||
assert_match "localhost:5000/app:latest", containers
|
||||
|
||||
exec_output = kamal :app, :exec, :ps, capture: true
|
||||
assert_match "App Host: vm1", exec_output
|
||||
|
||||
@@ -26,9 +26,7 @@ readiness_delay: 0
|
||||
proxy:
|
||||
host: 127.0.0.1
|
||||
registry:
|
||||
server: registry:4443
|
||||
username: root
|
||||
password: root
|
||||
server: localhost:5000
|
||||
builder:
|
||||
driver: docker
|
||||
arch: <%= Kamal::Utils.docker_arch %>
|
||||
|
||||
@@ -29,7 +29,7 @@ class MainTest < IntegrationTest
|
||||
assert_match /App Host: vm1/, details
|
||||
assert_match /App Host: vm2/, details
|
||||
assert_match /basecamp\/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}/, details
|
||||
assert_match /registry:4443\/app:#{first_version}/, details
|
||||
assert_match /localhost:5000\/app:#{first_version}/, details
|
||||
|
||||
audit = kamal :audit, capture: true
|
||||
assert_match /Booted app version #{first_version}.*Booted app version #{second_version}.*Booted app version #{first_version}.*/m, audit
|
||||
@@ -63,8 +63,8 @@ class MainTest < IntegrationTest
|
||||
assert_equal [ "vm1", "vm2" ], config[:hosts]
|
||||
assert_equal "vm1", config[:primary_host]
|
||||
assert_equal version, config[:version]
|
||||
assert_equal "registry:4443/app", config[:repository]
|
||||
assert_equal "registry:4443/app:#{version}", config[:absolute_image]
|
||||
assert_equal "localhost:5000/app", config[:repository]
|
||||
assert_equal "localhost:5000/app:#{version}", config[:absolute_image]
|
||||
assert_equal "app-#{version}", config[:service_with_version]
|
||||
assert_equal [], config[:volume_args]
|
||||
assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])
|
||||
|
||||
Reference in New Issue
Block a user