Compare commits
1 Commits
v2.6.0
...
proxy-with
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46e3085052 |
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -4,7 +4,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
|
||||||
jobs:
|
jobs:
|
||||||
rubocop:
|
rubocop:
|
||||||
name: RuboCop
|
name: RuboCop
|
||||||
@@ -23,13 +22,11 @@ jobs:
|
|||||||
run: bundle exec rubocop --parallel
|
run: bundle exec rubocop --parallel
|
||||||
tests:
|
tests:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
ruby-version:
|
ruby-version:
|
||||||
- "3.1"
|
- "3.1"
|
||||||
- "3.2"
|
- "3.2"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
- "3.4"
|
|
||||||
gemfile:
|
gemfile:
|
||||||
- Gemfile
|
- Gemfile
|
||||||
- gemfiles/rails_edge.gemfile
|
- gemfiles/rails_edge.gemfile
|
||||||
@@ -38,14 +35,12 @@ jobs:
|
|||||||
gemfile: gemfiles/rails_edge.gemfile
|
gemfile: gemfiles/rails_edge.gemfile
|
||||||
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
env:
|
env:
|
||||||
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Remove gemfile.lock
|
|
||||||
run: rm Gemfile.lock
|
|
||||||
|
|
||||||
- name: Install Ruby
|
- name: Install Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
@@ -54,5 +49,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: bin/test
|
run: bin/test
|
||||||
env:
|
|
||||||
RUBYOPT: ${{ startsWith(matrix.ruby-version, '3.4.') && '--enable=frozen-string-literal' || '' }}
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
FROM ruby:3.4-alpine
|
# Use the official Ruby 3.2.0 Alpine image as the base image
|
||||||
|
FROM ruby:3.2.0-alpine
|
||||||
|
|
||||||
# Install docker/buildx-bin
|
# Install docker/buildx-bin
|
||||||
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||||
@@ -13,9 +14,9 @@ COPY Gemfile Gemfile.lock kamal.gemspec ./
|
|||||||
COPY lib/kamal/version.rb /kamal/lib/kamal/version.rb
|
COPY lib/kamal/version.rb /kamal/lib/kamal/version.rb
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apk add --no-cache build-base git docker openrc openssh-client-default yaml-dev \
|
RUN apk add --no-cache build-base git docker openrc openssh-client-default \
|
||||||
&& rc-update add docker boot \
|
&& rc-update add docker boot \
|
||||||
&& gem install bundler --version=2.6.5 \
|
&& gem install bundler --version=2.4.3 \
|
||||||
&& bundle install
|
&& bundle install
|
||||||
|
|
||||||
# Copy the rest of our application code into the container.
|
# Copy the rest of our application code into the container.
|
||||||
|
|||||||
143
Gemfile.lock
143
Gemfile.lock
@@ -1,157 +1,152 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
kamal (2.6.0)
|
kamal (2.1.1)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
concurrent-ruby (~> 1.2)
|
concurrent-ruby (~> 1.2)
|
||||||
dotenv (~> 3.1)
|
dotenv (~> 3.1)
|
||||||
ed25519 (~> 1.4)
|
ed25519 (~> 1.2)
|
||||||
net-ssh (~> 7.3)
|
net-ssh (~> 7.0)
|
||||||
sshkit (>= 1.23.0, < 2.0)
|
sshkit (>= 1.23.0, < 2.0)
|
||||||
thor (~> 1.3)
|
thor (~> 1.3)
|
||||||
zeitwerk (>= 2.6.18, < 3.0)
|
zeitwerk (~> 2.5)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionpack (8.0.0.1)
|
actionpack (7.1.3.4)
|
||||||
actionview (= 8.0.0.1)
|
actionview (= 7.1.3.4)
|
||||||
activesupport (= 8.0.0.1)
|
activesupport (= 7.1.3.4)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
actionview (7.1.3.4)
|
||||||
actionview (8.0.0.1)
|
activesupport (= 7.1.3.4)
|
||||||
activesupport (= 8.0.0.1)
|
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activesupport (8.0.0.1)
|
activesupport (7.1.3.4)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
drb
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
logger (>= 1.4.2)
|
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
securerandom (>= 0.3)
|
mutex_m
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0)
|
||||||
uri (>= 0.13.1)
|
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcrypt_pbkdf (1.1.1)
|
bcrypt_pbkdf (1.1.1)
|
||||||
benchmark (0.4.0)
|
bcrypt_pbkdf (1.1.1-arm64-darwin)
|
||||||
|
bcrypt_pbkdf (1.1.1-x86_64-darwin)
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.1.8)
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
concurrent-ruby (1.3.4)
|
concurrent-ruby (1.3.3)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
date (3.4.1)
|
|
||||||
debug (1.9.2)
|
debug (1.9.2)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
dotenv (3.1.5)
|
dotenv (3.1.2)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
ed25519 (1.4.0)
|
ed25519 (1.3.0)
|
||||||
erubi (1.13.0)
|
erubi (1.13.0)
|
||||||
i18n (1.14.6)
|
i18n (1.14.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
io-console (0.8.0)
|
io-console (0.7.2)
|
||||||
irb (1.14.2)
|
irb (1.14.0)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
json (2.9.0)
|
json (2.7.2)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
logger (1.6.3)
|
loofah (2.22.0)
|
||||||
loofah (2.23.1)
|
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
minitest (5.25.4)
|
minitest (5.24.1)
|
||||||
mocha (2.7.1)
|
mocha (2.4.5)
|
||||||
ruby2_keywords (>= 0.0.5)
|
ruby2_keywords (>= 0.0.5)
|
||||||
|
mutex_m (0.2.0)
|
||||||
net-scp (4.0.0)
|
net-scp (4.0.0)
|
||||||
net-ssh (>= 2.6.5, < 8.0.0)
|
net-ssh (>= 2.6.5, < 8.0.0)
|
||||||
net-sftp (4.0.0)
|
net-sftp (4.0.0)
|
||||||
net-ssh (>= 5.0.0, < 8.0.0)
|
net-ssh (>= 5.0.0, < 8.0.0)
|
||||||
net-ssh (7.3.0)
|
net-ssh (7.2.3)
|
||||||
nokogiri (1.18.8-aarch64-linux-musl)
|
nokogiri (1.16.7-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-arm64-darwin)
|
nokogiri (1.16.7-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-x86_64-darwin)
|
nokogiri (1.16.7-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
parallel (1.25.1)
|
||||||
racc (~> 1.4)
|
parser (3.3.4.0)
|
||||||
nokogiri (1.18.8-x86_64-linux-musl)
|
|
||||||
racc (~> 1.4)
|
|
||||||
ostruct (0.6.1)
|
|
||||||
parallel (1.26.3)
|
|
||||||
parser (3.3.6.0)
|
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
psych (5.2.1)
|
psych (5.1.2)
|
||||||
date
|
|
||||||
stringio
|
stringio
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.12)
|
rack (3.1.7)
|
||||||
rack-session (2.0.0)
|
rack-session (2.0.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.2.1)
|
rackup (2.1.0)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
|
webrick (~> 1.8)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.2)
|
rails-html-sanitizer (1.6.0)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
nokogiri (~> 1.14)
|
||||||
railties (8.0.0.1)
|
railties (7.1.3.4)
|
||||||
actionpack (= 8.0.0.1)
|
actionpack (= 7.1.3.4)
|
||||||
activesupport (= 8.0.0.1)
|
activesupport (= 7.1.3.4)
|
||||||
irb (~> 1.13)
|
irb
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0, >= 1.2.2)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.2.1)
|
rake (13.2.1)
|
||||||
rdoc (6.8.1)
|
rdoc (6.7.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
regexp_parser (2.9.3)
|
regexp_parser (2.9.2)
|
||||||
reline (0.5.12)
|
reline (0.5.9)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rubocop (1.69.2)
|
rexml (3.3.4)
|
||||||
|
strscan
|
||||||
|
rubocop (1.65.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.0)
|
regexp_parser (>= 2.4, < 3.0)
|
||||||
rubocop-ast (>= 1.36.2, < 2.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.36.2)
|
rubocop-ast (1.32.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-minitest (0.36.0)
|
rubocop-minitest (0.35.1)
|
||||||
rubocop (>= 1.61, < 2.0)
|
rubocop (>= 1.61, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-performance (1.23.0)
|
rubocop-performance (1.21.1)
|
||||||
rubocop (>= 1.48.1, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails (2.27.0)
|
rubocop-rails (2.25.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.52.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails-omakase (1.0.0)
|
rubocop-rails-omakase (1.0.0)
|
||||||
rubocop
|
rubocop
|
||||||
@@ -160,30 +155,24 @@ GEM
|
|||||||
rubocop-rails
|
rubocop-rails
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
securerandom (0.4.0)
|
sshkit (1.23.0)
|
||||||
sshkit (1.23.2)
|
|
||||||
base64
|
base64
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-sftp (>= 2.1.2)
|
net-sftp (>= 2.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
ostruct
|
stringio (3.1.1)
|
||||||
stringio (3.1.2)
|
strscan (3.1.0)
|
||||||
thor (1.3.2)
|
thor (1.3.1)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (3.1.2)
|
unicode-display_width (2.5.0)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
webrick (1.8.1)
|
||||||
unicode-emoji (4.0.4)
|
zeitwerk (2.6.17)
|
||||||
uri (1.0.3)
|
|
||||||
useragent (0.16.11)
|
|
||||||
zeitwerk (2.7.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux-musl
|
|
||||||
arm64-darwin
|
arm64-darwin
|
||||||
x86_64-darwin
|
x86_64-darwin
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
x86_64-linux-musl
|
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
debug
|
debug
|
||||||
@@ -193,4 +182,4 @@ DEPENDENCIES
|
|||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.6.5
|
2.4.3
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ 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.3"
|
spec.add_dependency "net-ssh", "~> 7.0"
|
||||||
spec.add_dependency "thor", "~> 1.3"
|
spec.add_dependency "thor", "~> 1.3"
|
||||||
spec.add_dependency "dotenv", "~> 3.1"
|
spec.add_dependency "dotenv", "~> 3.1"
|
||||||
spec.add_dependency "zeitwerk", ">= 2.6.18", "< 3.0"
|
spec.add_dependency "zeitwerk", "~> 2.5"
|
||||||
spec.add_dependency "ed25519", "~> 1.4"
|
spec.add_dependency "ed25519", "~> 1.2"
|
||||||
spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
|
spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
|
||||||
spec.add_dependency "concurrent-ruby", "~> 1.2"
|
spec.add_dependency "concurrent-ruby", "~> 1.2"
|
||||||
spec.add_dependency "base64", "~> 0.2"
|
spec.add_dependency "base64", "~> 0.2"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ module Kamal::Cli
|
|||||||
class BootError < StandardError; end
|
class BootError < StandardError; end
|
||||||
class HookError < StandardError; end
|
class HookError < StandardError; end
|
||||||
class LockError < StandardError; end
|
class LockError < StandardError; end
|
||||||
class DependencyError < StandardError; end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# SSHKit uses instance eval, so we need a global const for ergonomics
|
# SSHKit uses instance eval, so we need a global const for ergonomics
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
require "active_support/core_ext/array/conversions"
|
|
||||||
require "concurrent/array"
|
|
||||||
|
|
||||||
class Kamal::Cli::Accessory < Kamal::Cli::Base
|
class Kamal::Cli::Accessory < Kamal::Cli::Base
|
||||||
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
||||||
def boot(name, prepare: true)
|
def boot(name, prepare: true)
|
||||||
@@ -11,16 +8,6 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
prepare(name) if prepare
|
prepare(name) if prepare
|
||||||
|
|
||||||
with_accessory(name) do |accessory, hosts|
|
with_accessory(name) do |accessory, hosts|
|
||||||
booted_hosts = Concurrent::Array.new
|
|
||||||
on(hosts) do |host|
|
|
||||||
booted_hosts << host.to_s if capture_with_info(*accessory.info(all: true, quiet: true)).strip.presence
|
|
||||||
end
|
|
||||||
|
|
||||||
if booted_hosts.any?
|
|
||||||
say "Skipping booting `#{name}` on #{booted_hosts.sort.join(", ")}, a container already exists", :yellow
|
|
||||||
hosts -= booted_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
directories(name)
|
directories(name)
|
||||||
upload(name)
|
upload(name)
|
||||||
|
|
||||||
@@ -29,11 +16,6 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
execute *accessory.ensure_env_directory
|
execute *accessory.ensure_env_directory
|
||||||
upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
|
upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
|
||||||
execute *accessory.run
|
execute *accessory.run
|
||||||
|
|
||||||
if accessory.running_proxy?
|
|
||||||
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
|
||||||
execute *accessory.deploy(target: target)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -91,10 +73,6 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
on(hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.start
|
execute *accessory.start
|
||||||
if accessory.running_proxy?
|
|
||||||
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
|
||||||
execute *accessory.deploy(target: target)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -107,11 +85,6 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
on(hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.stop, raise_on_non_zero_exit: false
|
execute *accessory.stop, raise_on_non_zero_exit: false
|
||||||
|
|
||||||
if accessory.running_proxy?
|
|
||||||
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
|
||||||
execute *accessory.remove if target
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -137,37 +110,32 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
|
desc "exec [NAME] [CMD]", "Execute a custom command on servers (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"
|
||||||
def exec(name, *cmd)
|
def exec(name, cmd)
|
||||||
pre_connect_if_required
|
|
||||||
|
|
||||||
cmd = Kamal::Utils.join_commands(cmd)
|
|
||||||
with_accessory(name) do |accessory, hosts|
|
with_accessory(name) do |accessory, hosts|
|
||||||
case
|
case
|
||||||
when options[:interactive] && options[:reuse]
|
when options[:interactive] && options[:reuse]
|
||||||
say "Launching interactive command via SSH from existing container...", :magenta
|
say "Launching interactive command with via SSH from existing container...", :magenta
|
||||||
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
||||||
|
|
||||||
when options[:interactive]
|
when options[:interactive]
|
||||||
say "Launching interactive command via SSH from new container...", :magenta
|
say "Launching interactive command via SSH from new container...", :magenta
|
||||||
on(accessory.hosts.first) { execute *KAMAL.registry.login }
|
|
||||||
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
||||||
|
|
||||||
when options[:reuse]
|
when options[:reuse]
|
||||||
say "Launching command from existing container...", :magenta
|
say "Launching command from existing container...", :magenta
|
||||||
on(hosts) do |host|
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
|
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
say "Launching command from new container...", :magenta
|
say "Launching command from new container...", :magenta
|
||||||
on(hosts) do |host|
|
on(hosts) do
|
||||||
execute *KAMAL.registry.login
|
|
||||||
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
|
capture_with_info(*accessory.execute_in_new_container(cmd))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -177,7 +145,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
||||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
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 :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||||
option :grep_options, desc: "Additional options supplied to grep"
|
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 :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"
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
||||||
def logs(name)
|
def logs(name)
|
||||||
@@ -290,7 +258,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def accessory_hosts(accessory)
|
def accessory_hosts(accessory)
|
||||||
KAMAL.accessory_hosts & accessory.hosts
|
if KAMAL.specific_hosts&.any?
|
||||||
|
KAMAL.specific_hosts & accessory.hosts
|
||||||
|
else
|
||||||
|
accessory.hosts
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_accessory(name)
|
def remove_accessory(name)
|
||||||
@@ -303,7 +275,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
def prepare(name)
|
def prepare(name)
|
||||||
with_accessory(name) do |accessory, hosts|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.registry.login(registry_config: accessory.registry)
|
execute *KAMAL.registry.login
|
||||||
execute *KAMAL.docker.create_network
|
execute *KAMAL.docker.create_network
|
||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
raise unless e.message.include?("already exists")
|
raise unless e.message.include?("already exists")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
|
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
|
||||||
def run(instance, args = [])
|
def run(instance, args = [])
|
||||||
if (_alias = KAMAL.config.aliases[name])
|
if (_alias = KAMAL.config.aliases[name])
|
||||||
KAMAL.reset
|
|
||||||
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
|
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
|
|||||||
@@ -7,33 +7,23 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
||||||
|
|
||||||
# Assets are prepared in a separate step to ensure they are on all hosts before booting
|
# Assets are prepared in a separate step to ensure they are on all hosts before booting
|
||||||
on(KAMAL.app_hosts) do
|
on(KAMAL.hosts) do
|
||||||
Kamal::Cli::App::ErrorPages.new(host, self).run
|
|
||||||
|
|
||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
Kamal::Cli::App::Assets.new(host, role, self).run
|
Kamal::Cli::App::PrepareAssets.new(host, role, self).run
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Primary hosts and roles are returned first, so they can open the barrier
|
# Primary hosts and roles are returned first, so they can open the barrier
|
||||||
barrier = Kamal::Cli::Healthcheck::Barrier.new
|
barrier = Kamal::Cli::Healthcheck::Barrier.new
|
||||||
|
|
||||||
host_boot_groups.each do |hosts|
|
on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
|
||||||
host_list = Array(hosts).join(",")
|
KAMAL.roles_on(host).each do |role|
|
||||||
run_hook "pre-app-boot", hosts: host_list
|
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
|
||||||
|
|
||||||
on(hosts) do |host|
|
|
||||||
KAMAL.roles_on(host).each do |role|
|
|
||||||
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
run_hook "post-app-boot", hosts: host_list
|
|
||||||
sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tag once the app booted on all hosts
|
# Tag once the app booted on all hosts
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
||||||
execute *KAMAL.app.tag_latest_image
|
execute *KAMAL.app.tag_latest_image
|
||||||
end
|
end
|
||||||
@@ -44,7 +34,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
desc "start", "Start existing app container on servers"
|
desc "start", "Start existing app container on servers"
|
||||||
def start
|
def start
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -67,7 +57,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
desc "stop", "Stop app container on servers"
|
desc "stop", "Stop app container on servers"
|
||||||
def stop
|
def stop
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -91,7 +81,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
# FIXME: Drop in favor of just containers?
|
# FIXME: Drop in favor of just containers?
|
||||||
desc "details", "Show details about app containers"
|
desc "details", "Show details about app containers"
|
||||||
def details
|
def details
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -104,21 +94,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
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"
|
||||||
option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
|
|
||||||
def exec(*cmd)
|
def exec(*cmd)
|
||||||
pre_connect_if_required
|
|
||||||
|
|
||||||
if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
|
|
||||||
raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
|
|
||||||
end
|
|
||||||
|
|
||||||
if cmd.empty?
|
|
||||||
raise ArgumentError, "No command provided. You must specify a command to execute."
|
|
||||||
end
|
|
||||||
|
|
||||||
cmd = Kamal::Utils.join_commands(cmd)
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
env = options[:env]
|
env = options[:env]
|
||||||
detach = options[:detach]
|
|
||||||
case
|
case
|
||||||
when options[:interactive] && options[:reuse]
|
when options[:interactive] && options[:reuse]
|
||||||
say "Get current version of running container...", :magenta unless options[:version]
|
say "Get current version of running container...", :magenta unless options[:version]
|
||||||
@@ -131,7 +109,6 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
say "Get most recent version available as an image...", :magenta unless options[:version]
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
||||||
using_version(version_or_latest) do |version|
|
using_version(version_or_latest) do |version|
|
||||||
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
||||||
on(KAMAL.primary_host) { execute *KAMAL.registry.login }
|
|
||||||
run_locally do
|
run_locally do
|
||||||
exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_new_container_over_ssh(cmd, env: env)
|
exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_new_container_over_ssh(cmd, env: env)
|
||||||
end
|
end
|
||||||
@@ -142,7 +119,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
using_version(options[:version] || current_running_version) do |version|
|
using_version(options[:version] || current_running_version) do |version|
|
||||||
say "Launching command with version #{version} from existing container...", :magenta
|
say "Launching command with version #{version} from existing container...", :magenta
|
||||||
|
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -156,14 +133,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
say "Get most recent version available as an image...", :magenta unless options[:version]
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
||||||
using_version(version_or_latest) do |version|
|
using_version(version_or_latest) do |version|
|
||||||
say "Launching command with version #{version} from new container...", :magenta
|
say "Launching command with version #{version} from new container...", :magenta
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
execute *KAMAL.registry.login
|
|
||||||
|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
||||||
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
|
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -172,7 +147,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
|
|
||||||
desc "containers", "Show app containers on servers"
|
desc "containers", "Show app containers on servers"
|
||||||
def containers
|
def containers
|
||||||
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
|
on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "stale_containers", "Detect app stale containers"
|
desc "stale_containers", "Detect app stale containers"
|
||||||
@@ -181,7 +156,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
stop = options[:stop]
|
stop = options[:stop]
|
||||||
|
|
||||||
with_lock_if_stopping do
|
with_lock_if_stopping do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -204,24 +179,22 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
|
|
||||||
desc "images", "Show app images on servers"
|
desc "images", "Show app images on servers"
|
||||||
def images
|
def images
|
||||||
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
|
on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
||||||
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
||||||
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
||||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||||
option :grep_options, desc: "Additional options supplied to grep"
|
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 :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"
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
||||||
option :container_id, desc: "Docker container ID to fetch logs"
|
|
||||||
def logs
|
def logs
|
||||||
# FIXME: Catch when app containers aren't running
|
# FIXME: Catch when app containers aren't running
|
||||||
|
|
||||||
grep = options[:grep]
|
grep = options[:grep]
|
||||||
grep_options = options[:grep_options]
|
grep_options = options[:grep_options]
|
||||||
since = options[:since]
|
since = options[:since]
|
||||||
container_id = options[:container_id]
|
|
||||||
timestamps = !options[:skip_timestamps]
|
timestamps = !options[:skip_timestamps]
|
||||||
|
|
||||||
if options[:follow]
|
if options[:follow]
|
||||||
@@ -234,18 +207,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
role = KAMAL.roles_on(KAMAL.primary_host).first
|
role = KAMAL.roles_on(KAMAL.primary_host).first
|
||||||
|
|
||||||
app = KAMAL.app(role: role, host: host)
|
app = KAMAL.app(role: role, host: host)
|
||||||
info app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, 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, container_id: container_id, 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
|
end
|
||||||
else
|
else
|
||||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||||
|
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
begin
|
begin
|
||||||
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, 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
|
rescue SSHKit::Command::Failed
|
||||||
puts_by_host host, "Nothing found"
|
puts_by_host host, "Nothing found"
|
||||||
end
|
end
|
||||||
@@ -260,44 +233,14 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
stop
|
stop
|
||||||
remove_containers
|
remove_containers
|
||||||
remove_images
|
remove_images
|
||||||
remove_app_directories
|
remove_app_directory
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "live", "Set the app to live mode"
|
|
||||||
def live
|
|
||||||
with_lock do
|
|
||||||
on(KAMAL.proxy_hosts) do |host|
|
|
||||||
roles = KAMAL.roles_on(host)
|
|
||||||
|
|
||||||
roles.each do |role|
|
|
||||||
execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "maintenance", "Set the app to maintenance mode"
|
|
||||||
option :drain_timeout, type: :numeric, desc: "How long to allow in-flight requests to complete (defaults to drain_timeout from config)"
|
|
||||||
option :message, type: :string, desc: "Message to display to clients while stopped"
|
|
||||||
def maintenance
|
|
||||||
maintenance_options = { drain_timeout: options[:drain_timeout] || KAMAL.config.drain_timeout, message: options[:message] }
|
|
||||||
|
|
||||||
with_lock do
|
|
||||||
on(KAMAL.proxy_hosts) do |host|
|
|
||||||
roles = KAMAL.roles_on(host)
|
|
||||||
|
|
||||||
roles.each do |role|
|
|
||||||
execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
||||||
def remove_container(version)
|
def remove_container(version)
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -311,7 +254,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
desc "remove_containers", "Remove all app containers from servers", hide: true
|
desc "remove_containers", "Remove all app containers from servers", hide: true
|
||||||
def remove_containers
|
def remove_containers
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
@@ -325,33 +268,30 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
desc "remove_images", "Remove all app images from servers", hide: true
|
desc "remove_images", "Remove all app images from servers", hide: true
|
||||||
def remove_images
|
def remove_images
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do
|
on(KAMAL.hosts) do
|
||||||
execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
|
execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
|
||||||
execute *KAMAL.app.remove_images
|
execute *KAMAL.app.remove_images
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "remove_app_directories", "Remove the app directories from servers", hide: true
|
desc "remove_app_directory", "Remove the service directory from servers", hide: true
|
||||||
def remove_app_directories
|
def remove_app_directory
|
||||||
with_lock do
|
with_lock do
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
roles = KAMAL.roles_on(host)
|
roles = KAMAL.roles_on(host)
|
||||||
|
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}", role: role), verbosity: :debug
|
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory} on all servers", role: role), verbosity: :debug
|
||||||
execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
|
execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
|
|
||||||
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}"), verbosity: :debug
|
|
||||||
execute *KAMAL.app.remove_proxy_app_directory, raise_on_non_zero_exit: false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "version", "Show app version currently running on servers"
|
desc "version", "Show app version currently running on servers"
|
||||||
def version
|
def version
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
role = KAMAL.roles_on(host).first
|
role = KAMAL.roles_on(host).first
|
||||||
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
|
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
|
||||||
end
|
end
|
||||||
@@ -392,8 +332,4 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
yield
|
yield
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def host_boot_groups
|
|
||||||
KAMAL.config.boot.limit ? KAMAL.app_hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.app_hosts ]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class Kamal::Cli::App::Boot
|
|||||||
|
|
||||||
def start_new_version
|
def start_new_version
|
||||||
audit "Booted app version #{version}"
|
audit "Booted app version #{version}"
|
||||||
hostname = "#{host.to_s[0...51].chomp(".")}-#{SecureRandom.hex(6)}"
|
hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}"
|
||||||
|
|
||||||
execute *app.ensure_env_directory
|
execute *app.ensure_env_directory
|
||||||
upload! role.secrets_io(host), role.secrets_path, mode: "0600"
|
upload! role.secrets_io(host), role.secrets_path, mode: "0600"
|
||||||
@@ -70,7 +70,6 @@ class Kamal::Cli::App::Boot
|
|||||||
def stop_old_version(version)
|
def stop_old_version(version)
|
||||||
execute *app.stop(version: version), raise_on_non_zero_exit: false
|
execute *app.stop(version: version), raise_on_non_zero_exit: false
|
||||||
execute *app.clean_up_assets if assets?
|
execute *app.clean_up_assets if assets?
|
||||||
execute *app.clean_up_error_pages if KAMAL.config.error_pages_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def release_barrier
|
def release_barrier
|
||||||
@@ -92,7 +91,7 @@ class Kamal::Cli::App::Boot
|
|||||||
if barrier.close
|
if barrier.close
|
||||||
info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
|
info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
|
||||||
begin
|
begin
|
||||||
error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
|
error capture_with_info(*app.logs(version: version))
|
||||||
error capture_with_info(*app.container_health_log(version: version))
|
error capture_with_info(*app.container_health_log(version: version))
|
||||||
rescue SSHKit::Command::Failed
|
rescue SSHKit::Command::Failed
|
||||||
error "Could not fetch logs for #{version}"
|
error "Could not fetch logs for #{version}"
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
class Kamal::Cli::App::ErrorPages
|
|
||||||
ERROR_PAGES_GLOB = "{4??.html,5??.html}"
|
|
||||||
|
|
||||||
attr_reader :host, :sshkit
|
|
||||||
delegate :upload!, :execute, to: :sshkit
|
|
||||||
|
|
||||||
def initialize(host, sshkit)
|
|
||||||
@host = host
|
|
||||||
@sshkit = sshkit
|
|
||||||
end
|
|
||||||
|
|
||||||
def run
|
|
||||||
if KAMAL.config.error_pages_path
|
|
||||||
with_error_pages_tmpdir do |local_error_pages_dir|
|
|
||||||
execute *KAMAL.app.create_error_pages_directory
|
|
||||||
upload! local_error_pages_dir, KAMAL.config.proxy_boot.error_pages_directory, mode: "0700", recursive: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def with_error_pages_tmpdir
|
|
||||||
Dir.mktmpdir("kamal-error-pages") do |tmpdir|
|
|
||||||
error_pages_dir = File.join(tmpdir, KAMAL.config.version)
|
|
||||||
FileUtils.mkdir(error_pages_dir)
|
|
||||||
|
|
||||||
if (files = Dir[File.join(KAMAL.config.error_pages_path, ERROR_PAGES_GLOB)]).any?
|
|
||||||
FileUtils.cp(files, error_pages_dir)
|
|
||||||
yield error_pages_dir
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
class Kamal::Cli::App::Assets
|
class Kamal::Cli::App::PrepareAssets
|
||||||
attr_reader :host, :role, :sshkit
|
attr_reader :host, :role, :sshkit
|
||||||
delegate :execute, :capture_with_info, :info, to: :sshkit
|
delegate :execute, :capture_with_info, :info, to: :sshkit
|
||||||
delegate :assets?, to: :role
|
delegate :assets?, to: :role
|
||||||
@@ -5,7 +5,7 @@ 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
|
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"
|
||||||
@@ -30,7 +30,6 @@ module Kamal::Cli
|
|||||||
else
|
else
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
initialize_commander unless KAMAL.configured?
|
initialize_commander unless KAMAL.configured?
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -133,13 +132,7 @@ module Kamal::Cli
|
|||||||
|
|
||||||
def run_hook(hook, **extra_details)
|
def run_hook(hook, **extra_details)
|
||||||
if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook)
|
if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook)
|
||||||
details = {
|
details = { hosts: KAMAL.hosts.join(","), command: command, subcommand: subcommand }
|
||||||
hosts: KAMAL.hosts.join(","),
|
|
||||||
roles: KAMAL.specific_roles&.join(","),
|
|
||||||
lock: KAMAL.holding_lock?.to_s,
|
|
||||||
command: command,
|
|
||||||
subcommand: subcommand
|
|
||||||
}.compact
|
|
||||||
|
|
||||||
say "Running the #{hook} hook...", :magenta
|
say "Running the #{hook} hook...", :magenta
|
||||||
with_env KAMAL.hook.env(**details, **extra_details) do
|
with_env KAMAL.hook.env(**details, **extra_details) do
|
||||||
@@ -153,16 +146,12 @@ module Kamal::Cli
|
|||||||
end
|
end
|
||||||
|
|
||||||
def on(*args, &block)
|
def on(*args, &block)
|
||||||
pre_connect_if_required
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def pre_connect_if_required
|
|
||||||
if !KAMAL.connected?
|
if !KAMAL.connected?
|
||||||
run_hook "pre-connect"
|
run_hook "pre-connect"
|
||||||
KAMAL.connected = true
|
KAMAL.connected = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
def command
|
def command
|
||||||
@@ -205,19 +194,5 @@ module Kamal::Cli
|
|||||||
ENV.clear
|
ENV.clear
|
||||||
ENV.update(current_env)
|
ENV.update(current_env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_docker_installed
|
|
||||||
run_locally do
|
|
||||||
begin
|
|
||||||
execute *KAMAL.builder.ensure_docker_installed
|
|
||||||
rescue SSHKit::Command::Failed => e
|
|
||||||
error = e.message =~ /command not found/ ?
|
|
||||||
"Docker is not installed locally" :
|
|
||||||
"Docker buildx plugin is not installed locally"
|
|
||||||
|
|
||||||
raise DependencyError, error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,18 +5,15 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
|
|
||||||
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
||||||
def deliver
|
def deliver
|
||||||
invoke :push
|
push
|
||||||
invoke :pull
|
pull
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "push", "Build and push app image to registry"
|
desc "push", "Build and push app image to registry"
|
||||||
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
|
||||||
def push
|
def push
|
||||||
cli = self
|
cli = self
|
||||||
|
|
||||||
ensure_docker_installed
|
verify_local_dependencies
|
||||||
login_to_registry_locally
|
|
||||||
|
|
||||||
run_hook "pre-build"
|
run_hook "pre-build"
|
||||||
|
|
||||||
uncommitted_changes = Kamal::Git.uncommitted_changes
|
uncommitted_changes = Kamal::Git.uncommitted_changes
|
||||||
@@ -52,7 +49,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
||||||
push = KAMAL.builder.push(cli.options[:output])
|
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 }
|
||||||
@@ -63,16 +60,14 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
|
|
||||||
desc "pull", "Pull app image from registry onto servers"
|
desc "pull", "Pull app image from registry onto servers"
|
||||||
def pull
|
def pull
|
||||||
login_to_registry_remotely
|
|
||||||
|
|
||||||
if (first_hosts = mirror_hosts).any?
|
if (first_hosts = mirror_hosts).any?
|
||||||
# Pull on a single host per mirror first to seed them
|
# 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
|
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
|
||||||
pull_on_hosts(first_hosts)
|
pull_on_hosts(first_hosts)
|
||||||
say "Pulling image on remaining hosts...", :magenta
|
say "Pulling image on remaining hosts...", :magenta
|
||||||
pull_on_hosts(KAMAL.app_hosts - first_hosts)
|
pull_on_hosts(KAMAL.hosts - first_hosts)
|
||||||
else
|
else
|
||||||
pull_on_hosts(KAMAL.app_hosts)
|
pull_on_hosts(KAMAL.hosts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -113,42 +108,21 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
|
private
|
||||||
option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
def verify_local_dependencies
|
||||||
def dev
|
|
||||||
cli = self
|
|
||||||
|
|
||||||
ensure_docker_installed
|
|
||||||
|
|
||||||
docker_included_files = Set.new(Kamal::Docker.included_files)
|
|
||||||
git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
|
|
||||||
git_untracked_files = Set.new(Kamal::Git.untracked_files)
|
|
||||||
|
|
||||||
docker_uncommitted_files = docker_included_files & git_uncommitted_files
|
|
||||||
if docker_uncommitted_files.any?
|
|
||||||
say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
|
|
||||||
docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
|
|
||||||
say
|
|
||||||
end
|
|
||||||
|
|
||||||
docker_untracked_files = docker_included_files & git_untracked_files
|
|
||||||
if docker_untracked_files.any?
|
|
||||||
say "WARNING: Untracked files will be present in the dev container:", :yellow
|
|
||||||
docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
|
|
||||||
say
|
|
||||||
end
|
|
||||||
|
|
||||||
with_env(KAMAL.config.builder.secrets) do
|
|
||||||
run_locally do
|
run_locally do
|
||||||
build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
|
begin
|
||||||
KAMAL.with_verbosity(:debug) do
|
execute *KAMAL.builder.ensure_local_dependencies_installed
|
||||||
execute(*build)
|
rescue SSHKit::Command::Failed => e
|
||||||
|
build_error = e.message =~ /command not found/ ?
|
||||||
|
"Docker is not installed locally" :
|
||||||
|
"Docker buildx plugin is not installed locally"
|
||||||
|
|
||||||
|
raise BuildError, build_error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def connect_to_remote_host(remote_host)
|
def connect_to_remote_host(remote_host)
|
||||||
remote_uri = URI.parse(remote_host)
|
remote_uri = URI.parse(remote_host)
|
||||||
if remote_uri.scheme == "ssh"
|
if remote_uri.scheme == "ssh"
|
||||||
@@ -163,9 +137,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def mirror_hosts
|
def mirror_hosts
|
||||||
if KAMAL.app_hosts.many?
|
if KAMAL.hosts.many?
|
||||||
mirror_hosts = Concurrent::Hash.new
|
mirror_hosts = Concurrent::Hash.new
|
||||||
on(KAMAL.app_hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
|
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
|
||||||
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
|
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
|
||||||
rescue SSHKit::Command::Failed => e
|
rescue SSHKit::Command::Failed => e
|
||||||
@@ -185,16 +159,4 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|||||||
execute *KAMAL.builder.validate_image
|
execute *KAMAL.builder.validate_image
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def login_to_registry_locally
|
|
||||||
run_locally do
|
|
||||||
execute *KAMAL.registry.login
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def login_to_registry_remotely
|
|
||||||
on(KAMAL.app_hosts) do
|
|
||||||
execute *KAMAL.registry.login
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,17 +9,21 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
say "Ensure Docker is installed...", :magenta
|
say "Ensure Docker is installed...", :magenta
|
||||||
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
||||||
|
|
||||||
deploy(boot_accessories: true)
|
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
|
||||||
|
deploy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "deploy", "Deploy app to servers"
|
desc "deploy", "Deploy app to servers"
|
||||||
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
||||||
def deploy(boot_accessories: false)
|
def deploy
|
||||||
runtime = print_runtime do
|
runtime = print_runtime do
|
||||||
invoke_options = deploy_options
|
invoke_options = deploy_options
|
||||||
|
|
||||||
|
say "Log into image registry...", :magenta
|
||||||
|
invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
|
||||||
|
|
||||||
if options[:skip_push]
|
if options[:skip_push]
|
||||||
say "Pull app image...", :magenta
|
say "Pull app image...", :magenta
|
||||||
invoke "kamal:cli:build:pull", [], invoke_options
|
invoke "kamal:cli:build:pull", [], invoke_options
|
||||||
@@ -34,8 +38,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
say "Ensure kamal-proxy is running...", :magenta
|
say "Ensure kamal-proxy is running...", :magenta
|
||||||
invoke "kamal:cli:proxy:boot", [], invoke_options
|
invoke "kamal:cli:proxy:boot", [], invoke_options
|
||||||
|
|
||||||
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
|
|
||||||
|
|
||||||
say "Detect stale containers...", :magenta
|
say "Detect stale containers...", :magenta
|
||||||
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
|
run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
|
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
|
||||||
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
||||||
def redeploy
|
def redeploy
|
||||||
runtime = print_runtime do
|
runtime = print_runtime do
|
||||||
@@ -133,7 +135,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
puts "No documentation found for #{section}"
|
puts "No documentation found for #{section}"
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "init", "Create config stub in config/deploy.yml and secrets stub in .kamal"
|
desc "init", "Create config stub in config/deploy.yml and env stub in .env"
|
||||||
option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
|
option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
|
||||||
def init
|
def init
|
||||||
require "fileutils"
|
require "fileutils"
|
||||||
@@ -194,10 +196,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
|
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
|
||||||
with_lock do
|
with_lock do
|
||||||
if options[:rolling]
|
if options[:rolling]
|
||||||
KAMAL.hosts.each do |host|
|
(KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
|
||||||
KAMAL.with_specific_hosts(host) do
|
KAMAL.with_specific_hosts(host) do
|
||||||
say "Upgrading #{host}...", :magenta
|
say "Upgrading #{host}...", :magenta
|
||||||
if KAMAL.app_hosts.include?(host)
|
if KAMAL.hosts.include?(host)
|
||||||
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
|
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
|
||||||
reset_invocation(Kamal::Cli::Proxy)
|
reset_invocation(Kamal::Cli::Proxy)
|
||||||
end
|
end
|
||||||
@@ -253,7 +255,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|||||||
private
|
private
|
||||||
def container_available?(version)
|
def container_available?(version)
|
||||||
begin
|
begin
|
||||||
on(KAMAL.app_hosts) do
|
on(KAMAL.hosts) do
|
||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
|
container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
|
||||||
raise "Container not found" unless container_id.present?
|
raise "Container not found" unless container_id.present?
|
||||||
|
|||||||
@@ -13,87 +13,38 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|||||||
|
|
||||||
version = capture_with_info(*KAMAL.proxy.version).strip.presence
|
version = capture_with_info(*KAMAL.proxy.version).strip.presence
|
||||||
|
|
||||||
if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
|
||||||
raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}"
|
raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
|
||||||
end
|
end
|
||||||
execute *KAMAL.proxy.ensure_apps_config_directory
|
|
||||||
execute *KAMAL.proxy.start_or_run
|
execute *KAMAL.proxy.start_or_run
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
|
desc "boot_config <set|get|clear>", "Mange kamal-proxy boot configuration"
|
||||||
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
||||||
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
|
option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host"
|
||||||
option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
|
option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host"
|
||||||
option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host"
|
|
||||||
option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs"
|
|
||||||
option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image"
|
|
||||||
option :repository, type: :string, default: nil, desc: "Repository for the proxy image"
|
|
||||||
option :image_version, type: :string, default: nil, desc: "Version of the proxy to run"
|
|
||||||
option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on"
|
|
||||||
option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode"
|
|
||||||
option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
|
option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
|
||||||
def boot_config(subcommand)
|
def boot_config(subcommand)
|
||||||
proxy_boot_config = KAMAL.config.proxy_boot
|
|
||||||
|
|
||||||
case subcommand
|
case subcommand
|
||||||
when "set"
|
when "set"
|
||||||
boot_options = [
|
boot_options = [
|
||||||
*(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
|
*(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port]) if options[:publish]),
|
||||||
*(proxy_boot_config.logging_args(options[:log_max_size])),
|
|
||||||
*("--expose=#{options[:metrics_port]}" if options[:metrics_port]),
|
|
||||||
*options[:docker_options].map { |option| "--#{option}" }
|
*options[:docker_options].map { |option| "--#{option}" }
|
||||||
]
|
]
|
||||||
|
|
||||||
image = [
|
|
||||||
options[:registry].presence,
|
|
||||||
options[:repository].presence || proxy_boot_config.repository_name,
|
|
||||||
proxy_boot_config.image_name
|
|
||||||
].compact.join("/")
|
|
||||||
|
|
||||||
image_version = options[:image_version]
|
|
||||||
|
|
||||||
run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
|
|
||||||
run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
|
|
||||||
|
|
||||||
on(KAMAL.proxy_hosts) do |host|
|
on(KAMAL.proxy_hosts) do |host|
|
||||||
execute(*KAMAL.proxy.ensure_proxy_directory)
|
execute(*KAMAL.proxy.ensure_proxy_directory)
|
||||||
if boot_options != proxy_boot_config.default_boot_options
|
upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file
|
||||||
upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file
|
|
||||||
else
|
|
||||||
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
|
||||||
end
|
|
||||||
|
|
||||||
if image != proxy_boot_config.image_default
|
|
||||||
upload! StringIO.new(image), proxy_boot_config.image_file
|
|
||||||
else
|
|
||||||
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
|
||||||
end
|
|
||||||
|
|
||||||
if image_version
|
|
||||||
upload! StringIO.new(image_version), proxy_boot_config.image_version_file
|
|
||||||
else
|
|
||||||
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
|
||||||
end
|
|
||||||
|
|
||||||
if run_command
|
|
||||||
upload! StringIO.new(run_command), proxy_boot_config.run_command_file
|
|
||||||
else
|
|
||||||
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
when "get"
|
when "get"
|
||||||
|
|
||||||
on(KAMAL.proxy_hosts) do |host|
|
on(KAMAL.proxy_hosts) do |host|
|
||||||
puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}"
|
puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.get_boot_options)}"
|
||||||
end
|
end
|
||||||
when "reset"
|
when "reset"
|
||||||
on(KAMAL.proxy_hosts) do |host|
|
on(KAMAL.proxy_hosts) do |host|
|
||||||
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
execute *KAMAL.proxy.reset_boot_options
|
||||||
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
|
||||||
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
|
||||||
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
|
raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
|
||||||
@@ -114,10 +65,12 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|||||||
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
||||||
execute *KAMAL.registry.login
|
execute *KAMAL.registry.login
|
||||||
|
|
||||||
|
"Stopping and removing Traefik on #{host}, if running..."
|
||||||
|
execute *KAMAL.proxy.cleanup_traefik
|
||||||
|
|
||||||
"Stopping and removing kamal-proxy on #{host}, if running..."
|
"Stopping and removing kamal-proxy on #{host}, if running..."
|
||||||
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
||||||
execute *KAMAL.proxy.remove_container
|
execute *KAMAL.proxy.remove_container
|
||||||
execute *KAMAL.proxy.ensure_apps_config_directory
|
|
||||||
|
|
||||||
execute *KAMAL.proxy.run
|
execute *KAMAL.proxy.run
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
|
|||||||
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
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"
|
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
||||||
def login
|
def login
|
||||||
ensure_docker_installed unless options[:skip_local]
|
|
||||||
|
|
||||||
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
||||||
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
class Kamal::Cli::Secrets < Kamal::Cli::Base
|
class Kamal::Cli::Secrets < Kamal::Cli::Base
|
||||||
desc "fetch [SECRETS...]", "Fetch secrets from a vault"
|
desc "fetch [SECRETS...]", "Fetch secrets from a vault"
|
||||||
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
|
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
|
||||||
option :account, type: :string, required: false, desc: "The account identifier or username"
|
option :account, type: :string, required: true, desc: "The account identifier or username"
|
||||||
option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
|
option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
|
||||||
option :inline, type: :boolean, required: false, hidden: true
|
option :inline, type: :boolean, required: false, hidden: true
|
||||||
def fetch(*secrets)
|
def fetch(*secrets)
|
||||||
adapter = initialize_adapter(options[:adapter])
|
results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
|
||||||
|
|
||||||
if adapter.requires_account? && options[:account].blank?
|
|
||||||
return puts "No value provided for required options '--account'"
|
|
||||||
end
|
|
||||||
|
|
||||||
results = adapter.fetch(secrets, **options.slice(:account, :from).symbolize_keys)
|
|
||||||
|
|
||||||
return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
|
return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
|
||||||
end
|
end
|
||||||
@@ -35,7 +29,7 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def initialize_adapter(adapter)
|
def adapter(adapter)
|
||||||
Kamal::Secrets::Adapters.lookup(adapter)
|
Kamal::Secrets::Adapters.lookup(adapter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ 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)
|
||||||
pre_connect_if_required
|
|
||||||
|
|
||||||
cmd = Kamal::Utils.join_commands(cmd)
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
hosts = KAMAL.hosts
|
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
||||||
|
|
||||||
case
|
case
|
||||||
when options[:interactive]
|
when options[:interactive]
|
||||||
@@ -29,7 +27,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|||||||
with_lock do
|
with_lock do
|
||||||
missing = []
|
missing = []
|
||||||
|
|
||||||
on(KAMAL.hosts) do |host|
|
on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
|
||||||
unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
|
unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
|
||||||
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
||||||
info "Missing Docker on #{host}. Installing…"
|
info "Missing Docker on #{host}. Installing…"
|
||||||
|
|||||||
@@ -13,14 +13,12 @@ servers:
|
|||||||
# - 192.168.0.1
|
# - 192.168.0.1
|
||||||
# cmd: bin/jobs
|
# cmd: bin/jobs
|
||||||
|
|
||||||
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
|
# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
|
||||||
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
|
# Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!).
|
||||||
#
|
|
||||||
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
|
|
||||||
proxy:
|
proxy:
|
||||||
ssl: true
|
ssl: true
|
||||||
host: app.example.com
|
host: app.example.com
|
||||||
# Proxy connects to your container on port 80 by default.
|
# kamal-proxy connects to your container over port 80, use `app_port` to specify a different port.
|
||||||
# app_port: 3000
|
# app_port: 3000
|
||||||
|
|
||||||
# Credentials for your image host.
|
# Credentials for your image host.
|
||||||
@@ -36,9 +34,6 @@ registry:
|
|||||||
# Configure builder setup.
|
# Configure builder setup.
|
||||||
builder:
|
builder:
|
||||||
arch: amd64
|
arch: amd64
|
||||||
# Pass in additional build args needed for your Dockerfile.
|
|
||||||
# args:
|
|
||||||
# RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
|
|
||||||
|
|
||||||
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
||||||
#
|
#
|
||||||
@@ -49,7 +44,7 @@ builder:
|
|||||||
# - RAILS_MASTER_KEY
|
# - RAILS_MASTER_KEY
|
||||||
|
|
||||||
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
||||||
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
|
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
|
||||||
#
|
#
|
||||||
# aliases:
|
# aliases:
|
||||||
# shell: app exec --interactive --reuse "bash"
|
# shell: app exec --interactive --reuse "bash"
|
||||||
@@ -94,7 +89,7 @@ builder:
|
|||||||
# directories:
|
# directories:
|
||||||
# - data:/var/lib/mysql
|
# - data:/var/lib/mysql
|
||||||
# redis:
|
# redis:
|
||||||
# image: valkey/valkey:8
|
# image: redis:7.0
|
||||||
# host: 192.168.0.2
|
# host: 192.168.0.2
|
||||||
# port: 6379
|
# port: 6379
|
||||||
# directories:
|
# directories:
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
echo "Docker set up on $KAMAL_HOSTS..."
|
# A sample docker-setup hook
|
||||||
|
#
|
||||||
|
# Sets up a Docker network on defined hosts which can then be used by the application’s containers
|
||||||
|
|
||||||
|
hosts = ENV["KAMAL_HOSTS"].split(",")
|
||||||
|
|
||||||
|
hosts.each do |ip|
|
||||||
|
destination = "root@#{ip}"
|
||||||
|
puts "Creating a Docker network \"kamal\" on #{destination}"
|
||||||
|
`ssh #{destination} docker network create kamal`
|
||||||
|
end
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
# KAMAL_PERFORMER
|
# KAMAL_PERFORMER
|
||||||
# KAMAL_VERSION
|
# KAMAL_VERSION
|
||||||
# KAMAL_HOSTS
|
# KAMAL_HOSTS
|
||||||
# KAMAL_ROLES (if set)
|
# KAMAL_ROLE (if set)
|
||||||
# KAMAL_DESTINATION (if set)
|
# KAMAL_DESTINATION (if set)
|
||||||
# KAMAL_RUNTIME
|
# KAMAL_RUNTIME
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
# KAMAL_PERFORMER
|
# KAMAL_PERFORMER
|
||||||
# KAMAL_VERSION
|
# KAMAL_VERSION
|
||||||
# KAMAL_HOSTS
|
# KAMAL_HOSTS
|
||||||
# KAMAL_ROLES (if set)
|
# KAMAL_ROLE (if set)
|
||||||
# KAMAL_DESTINATION (if set)
|
# KAMAL_DESTINATION (if set)
|
||||||
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# KAMAL_PERFORMER
|
# KAMAL_PERFORMER
|
||||||
# KAMAL_VERSION
|
# KAMAL_VERSION
|
||||||
# KAMAL_HOSTS
|
# KAMAL_HOSTS
|
||||||
# KAMAL_ROLES (if set)
|
# KAMAL_ROLE (if set)
|
||||||
# KAMAL_DESTINATION (if set)
|
# KAMAL_DESTINATION (if set)
|
||||||
# KAMAL_RUNTIME
|
# KAMAL_RUNTIME
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
# KAMAL_HOSTS
|
# KAMAL_HOSTS
|
||||||
# KAMAL_COMMAND
|
# KAMAL_COMMAND
|
||||||
# KAMAL_SUBCOMMAND
|
# KAMAL_SUBCOMMAND
|
||||||
# KAMAL_ROLES (if set)
|
# KAMAL_ROLE (if set)
|
||||||
# KAMAL_DESTINATION (if set)
|
# KAMAL_DESTINATION (if set)
|
||||||
|
|
||||||
# Only check the build status for production deployments
|
# Only check the build status for production deployments
|
||||||
@@ -82,12 +82,11 @@ end
|
|||||||
|
|
||||||
$stdout.sync = true
|
$stdout.sync = true
|
||||||
|
|
||||||
|
puts "Checking build status..."
|
||||||
|
attempts = 0
|
||||||
|
checks = GithubStatusChecks.new
|
||||||
|
|
||||||
begin
|
begin
|
||||||
puts "Checking build status..."
|
|
||||||
|
|
||||||
attempts = 0
|
|
||||||
checks = GithubStatusChecks.new
|
|
||||||
|
|
||||||
loop do
|
loop do
|
||||||
case checks.state
|
case checks.state
|
||||||
when "success"
|
when "success"
|
||||||
|
|||||||
@@ -4,20 +4,13 @@ require "active_support/core_ext/object/blank"
|
|||||||
|
|
||||||
class Kamal::Commander
|
class Kamal::Commander
|
||||||
attr_accessor :verbosity, :holding_lock, :connected
|
attr_accessor :verbosity, :holding_lock, :connected
|
||||||
attr_reader :specific_roles, :specific_hosts
|
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
|
||||||
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
|
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
reset
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset
|
|
||||||
self.verbosity = :info
|
self.verbosity = :info
|
||||||
self.holding_lock = ENV["KAMAL_LOCK"] == "true"
|
self.holding_lock = false
|
||||||
self.connected = false
|
self.connected = false
|
||||||
@specifics = @specific_roles = @specific_hosts = nil
|
@specifics = nil
|
||||||
@config = @config_kwargs = nil
|
|
||||||
@commands = {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def config
|
def config
|
||||||
@@ -35,6 +28,8 @@ class Kamal::Commander
|
|||||||
@config || @config_kwargs
|
@config || @config_kwargs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
attr_reader :specific_roles, :specific_hosts
|
||||||
|
|
||||||
def specific_primary!
|
def specific_primary!
|
||||||
@specifics = nil
|
@specifics = nil
|
||||||
if specific_roles.present?
|
if specific_roles.present?
|
||||||
@@ -81,6 +76,11 @@ class Kamal::Commander
|
|||||||
config.accessories&.collect(&:name) || []
|
config.accessories&.collect(&:name) || []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessories_on(host)
|
||||||
|
config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def app(role: nil, host: nil)
|
def app(role: nil, host: nil)
|
||||||
Kamal::Commands::App.new(config, role: role, host: host)
|
Kamal::Commands::App.new(config, role: role, host: host)
|
||||||
end
|
end
|
||||||
@@ -94,41 +94,42 @@ class Kamal::Commander
|
|||||||
end
|
end
|
||||||
|
|
||||||
def builder
|
def builder
|
||||||
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
|
@builder ||= Kamal::Commands::Builder.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def docker
|
def docker
|
||||||
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
|
@docker ||= Kamal::Commands::Docker.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def hook
|
def hook
|
||||||
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
|
@hook ||= Kamal::Commands::Hook.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def lock
|
def lock
|
||||||
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
@lock ||= Kamal::Commands::Lock.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def proxy
|
def proxy
|
||||||
@commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
|
@proxy ||= Kamal::Commands::Proxy.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def prune
|
def prune
|
||||||
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
|
@prune ||= Kamal::Commands::Prune.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def registry
|
def registry
|
||||||
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
|
@registry ||= Kamal::Commands::Registry.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def server
|
def server
|
||||||
@commands[:server] ||= Kamal::Commands::Server.new(config)
|
@server ||= Kamal::Commands::Server.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def alias(name)
|
def alias(name)
|
||||||
config.aliases[name]
|
config.aliases[name]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def with_verbosity(level)
|
def with_verbosity(level)
|
||||||
old_level = self.verbosity
|
old_level = self.verbosity
|
||||||
|
|
||||||
@@ -141,6 +142,14 @@ class Kamal::Commander
|
|||||||
SSHKit.config.output_verbosity = old_level
|
SSHKit.config.output_verbosity = old_level
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def boot_strategy
|
||||||
|
if config.boot.limit.present?
|
||||||
|
{ in: :groups, limit: config.boot.limit, wait: config.boot.wait }
|
||||||
|
else
|
||||||
|
{}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def holding_lock?
|
def holding_lock?
|
||||||
self.holding_lock
|
self.holding_lock
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ class Kamal::Commander::Specifics
|
|||||||
roles.select { |role| role.hosts.include?(host.to_s) }
|
roles.select { |role| role.hosts.include?(host.to_s) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def app_hosts
|
|
||||||
config.app_hosts & specified_hosts
|
|
||||||
end
|
|
||||||
|
|
||||||
def proxy_hosts
|
def proxy_hosts
|
||||||
config.proxy_hosts & specified_hosts
|
config.proxy_hosts & specified_hosts
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||||
include Proxy
|
|
||||||
|
|
||||||
attr_reader :accessory_config
|
attr_reader :accessory_config
|
||||||
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
||||||
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
|
:publish_args, :env_args, :volume_args, :label_args, :option_args,
|
||||||
:secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
|
:secrets_io, :secrets_path, :env_directory,
|
||||||
to: :accessory_config
|
to: :accessory_config
|
||||||
|
|
||||||
def initialize(config, name:)
|
def initialize(config, name:)
|
||||||
@@ -17,7 +15,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
"--name", service_name,
|
"--name", service_name,
|
||||||
"--detach",
|
"--detach",
|
||||||
"--restart", "unless-stopped",
|
"--restart", "unless-stopped",
|
||||||
*network_args,
|
"--network", "kamal",
|
||||||
*config.logging_args,
|
*config.logging_args,
|
||||||
*publish_args,
|
*publish_args,
|
||||||
*env_args,
|
*env_args,
|
||||||
@@ -36,10 +34,11 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
docker :container, :stop, service_name
|
docker :container, :stop, service_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def info(all: false, quiet: false)
|
def info
|
||||||
docker :ps, *("-a" if all), *("-q" if quiet), *service_filter
|
docker :ps, *service_filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
||||||
@@ -53,6 +52,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def execute_in_existing_container(*command, interactive: false)
|
def execute_in_existing_container(*command, interactive: false)
|
||||||
docker :exec,
|
docker :exec,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
@@ -64,7 +64,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
docker :run,
|
docker :run,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
"--rm",
|
"--rm",
|
||||||
*network_args,
|
"--network", "kamal",
|
||||||
*env_args,
|
*env_args,
|
||||||
*volume_args,
|
*volume_args,
|
||||||
image,
|
image,
|
||||||
@@ -83,6 +83,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|||||||
super command, host: hosts.first
|
super command, host: hosts.first
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def ensure_local_file_present(local_file)
|
def ensure_local_file_present(local_file)
|
||||||
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
||||||
raise "Missing file: #{local_file}"
|
raise "Missing file: #{local_file}"
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
module Kamal::Commands::Accessory::Proxy
|
|
||||||
delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
|
|
||||||
|
|
||||||
def deploy(target:)
|
|
||||||
proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
proxy_exec :remove, service_name
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def proxy_exec(*command)
|
|
||||||
docker :exec, proxy_container_name, "kamal-proxy", *command
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Commands::App < Kamal::Commands::Base
|
class Kamal::Commands::App < Kamal::Commands::Base
|
||||||
include Assets, Containers, ErrorPages, Execution, Images, Logging, Proxy
|
include Assets, Containers, Execution, Images, Logging, Proxy
|
||||||
|
|
||||||
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def info
|
def info
|
||||||
docker :ps, *container_filter_args
|
docker :ps, *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
|
|
||||||
def list_versions(*docker_args, statuses: nil)
|
def list_versions(*docker_args, statuses: nil)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||||
extract_version_from_name
|
extract_version_from_name
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -91,15 +91,11 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def latest_container(format:, filters: nil)
|
def latest_container(format:, filters: nil)
|
||||||
docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
||||||
end
|
end
|
||||||
|
|
||||||
def container_filter_args(statuses: nil)
|
def filter_args(statuses: nil)
|
||||||
argumentize "--filter", container_filters(statuses: statuses)
|
argumentize "--filter", filters(statuses: statuses)
|
||||||
end
|
|
||||||
|
|
||||||
def image_filter_args
|
|
||||||
argumentize "--filter", image_filters
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_version_from_name
|
def extract_version_from_name
|
||||||
@@ -107,17 +103,13 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
%(while read line; do echo ${line##{role.container_prefix}-}; done)
|
%(while read line; do echo ${line##{role.container_prefix}-}; done)
|
||||||
end
|
end
|
||||||
|
|
||||||
def container_filters(statuses: nil)
|
def filters(statuses: nil)
|
||||||
[ "label=service=#{config.service}" ].tap do |filters|
|
[ "label=service=#{config.service}" ].tap do |filters|
|
||||||
filters << "label=destination=#{config.destination}"
|
filters << "label=destination=#{config.destination}" if config.destination
|
||||||
filters << "label=role=#{role}" if role
|
filters << "label=role=#{role}" if role
|
||||||
statuses&.each do |status|
|
statuses&.each do |status|
|
||||||
filters << "status=#{status}"
|
filters << "status=#{status}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_filters
|
|
||||||
[ "label=service=#{config.service}" ]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ module Kamal::Commands::App::Assets
|
|||||||
|
|
||||||
combine \
|
combine \
|
||||||
make_directory(role.asset_extracted_directory),
|
make_directory(role.asset_extracted_directory),
|
||||||
[ *docker(:container, :rm, asset_container, "2> /dev/null"), "|| true" ],
|
[ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ],
|
||||||
docker(:container, :create, "--name", asset_container, config.absolute_image),
|
docker(:run, "--name", asset_container, "--detach", "--rm", "--entrypoint", "sleep", config.absolute_image, "1000000"),
|
||||||
docker(:container, :cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
|
docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
|
||||||
docker(:container, :rm, asset_container),
|
docker(:stop, "-t 1", asset_container),
|
||||||
by: "&&"
|
by: "&&"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Containers
|
|||||||
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
||||||
|
|
||||||
def list_containers
|
def list_containers
|
||||||
docker :container, :ls, "--all", *container_filter_args
|
docker :container, :ls, "--all", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_container_names
|
def list_container_names
|
||||||
@@ -20,7 +20,7 @@ module Kamal::Commands::App::Containers
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_containers
|
def remove_containers
|
||||||
docker :container, :prune, "--force", *container_filter_args
|
docker :container, :prune, "--force", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def container_health_log(version:)
|
def container_health_log(version:)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
module Kamal::Commands::App::ErrorPages
|
|
||||||
def create_error_pages_directory
|
|
||||||
make_directory(config.proxy_boot.error_pages_directory)
|
|
||||||
end
|
|
||||||
|
|
||||||
def clean_up_error_pages
|
|
||||||
[ :find, config.proxy_boot.error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -7,15 +7,13 @@ module Kamal::Commands::App::Execution
|
|||||||
*command
|
*command
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_in_new_container(*command, interactive: false, detach: false, env:)
|
def execute_in_new_container(*command, interactive: false, env:)
|
||||||
docker :run,
|
docker :run,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
("--detach" if detach),
|
"--rm",
|
||||||
("--rm" unless detach),
|
|
||||||
"--network", "kamal",
|
"--network", "kamal",
|
||||||
*role&.env_args(host),
|
*role&.env_args(host),
|
||||||
*argumentize("--env", env),
|
*argumentize("--env", env),
|
||||||
*role.logging_args,
|
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role&.option_args,
|
*role&.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module Kamal::Commands::App::Images
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_images
|
def remove_images
|
||||||
docker :image, :prune, "--all", "--force", *image_filter_args
|
docker :image, :prune, "--all", "--force", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_latest_image
|
def tag_latest_image
|
||||||
|
|||||||
@@ -1,28 +1,18 @@
|
|||||||
module Kamal::Commands::App::Logging
|
module Kamal::Commands::App::Logging
|
||||||
def logs(container_id: nil, timestamps: true, 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 \
|
pipe \
|
||||||
container_id_command(container_id),
|
version ? container_id_for_version(version) : current_running_container_id,
|
||||||
"xargs docker logs#{" --timestamps" if timestamps}#{" --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)
|
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_logs(host:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil)
|
def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil)
|
||||||
run_over_ssh \
|
run_over_ssh \
|
||||||
pipe(
|
pipe(
|
||||||
container_id_command(container_id),
|
current_running_container_id,
|
||||||
"xargs docker logs#{" --timestamps" if 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)
|
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
||||||
),
|
),
|
||||||
host: host
|
host: host
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def container_id_command(container_id)
|
|
||||||
case container_id
|
|
||||||
when Array then container_id
|
|
||||||
when String, Symbol then "echo #{container_id}"
|
|
||||||
else current_running_container_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
module Kamal::Commands::App::Proxy
|
module Kamal::Commands::App::Proxy
|
||||||
delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
|
delegate :proxy_container_name, to: :config
|
||||||
|
|
||||||
def deploy(target:)
|
def deploy(target:)
|
||||||
proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
|
proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
|
||||||
@@ -9,18 +9,6 @@ module Kamal::Commands::App::Proxy
|
|||||||
proxy_exec :remove, role.container_prefix
|
proxy_exec :remove, role.container_prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
def live
|
|
||||||
proxy_exec :resume, role.container_prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
def maintenance(**options)
|
|
||||||
proxy_exec :stop, role.container_prefix, *role.proxy.stop_command_args(**options)
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_proxy_app_directory
|
|
||||||
remove_directory config.proxy_boot.app_directory
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def proxy_exec(*command)
|
def proxy_exec(*command)
|
||||||
docker :exec, proxy_container_name, "kamal-proxy", *command
|
docker :exec, proxy_container_name, "kamal-proxy", *command
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
class Kamal::Commands::Auditor < Kamal::Commands::Base
|
class Kamal::Commands::Auditor < Kamal::Commands::Base
|
||||||
attr_reader :details
|
attr_reader :details
|
||||||
delegate :escape_shell_value, to: Kamal::Utils
|
|
||||||
|
|
||||||
def initialize(config, **details)
|
def initialize(config, **details)
|
||||||
super(config)
|
super(config)
|
||||||
@@ -10,8 +9,11 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
|
|||||||
# Runs remotely
|
# Runs remotely
|
||||||
def record(line, **details)
|
def record(line, **details)
|
||||||
combine \
|
combine \
|
||||||
make_run_directory,
|
[ :mkdir, "-p", config.run_directory ],
|
||||||
append([ :echo, escape_shell_value(audit_line(line, **details)) ], audit_log_file)
|
append(
|
||||||
|
[ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ],
|
||||||
|
audit_log_file
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reveal
|
def reveal
|
||||||
@@ -28,12 +30,4 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
|
|||||||
def audit_tags(**details)
|
def audit_tags(**details)
|
||||||
tags(**self.details, **details)
|
tags(**self.details, **details)
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_run_directory
|
|
||||||
[ :mkdir, "-p", config.run_directory ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def audit_line(line, **details)
|
|
||||||
"#{audit_tags(**details).except(:version, :service_version, :service)} #{line}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ module Kamal::Commands
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run_over_ssh(*command, host:)
|
def run_over_ssh(*command, host:)
|
||||||
"ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
|
"ssh".tap do |cmd|
|
||||||
|
if config.ssh.proxy && config.ssh.proxy.is_a?(Net::SSH::Proxy::Jump)
|
||||||
|
cmd << " -J #{config.ssh.proxy.jump_proxies}"
|
||||||
|
elsif config.ssh.proxy && config.ssh.proxy.is_a?(Net::SSH::Proxy::Command)
|
||||||
|
cmd << " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
|
||||||
|
end
|
||||||
|
cmd << " -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def container_id_for(container_name:, only_running: false)
|
def container_id_for(container_name:, only_running: false)
|
||||||
@@ -34,12 +41,6 @@ module Kamal::Commands
|
|||||||
[ :rm, path ]
|
[ :rm, path ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_docker_installed
|
|
||||||
combine \
|
|
||||||
ensure_local_docker_installed,
|
|
||||||
ensure_local_buildx_installed
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def combine(*commands, by: "&&")
|
def combine(*commands, by: "&&")
|
||||||
commands
|
commands
|
||||||
@@ -68,10 +69,6 @@ module Kamal::Commands
|
|||||||
combine *commands, by: "||"
|
combine *commands, by: "||"
|
||||||
end
|
end
|
||||||
|
|
||||||
def substitute(*commands)
|
|
||||||
"\$\(#{commands.join(" ")}\)"
|
|
||||||
end
|
|
||||||
|
|
||||||
def xargs(command)
|
def xargs(command)
|
||||||
[ :xargs, command ].flatten
|
[ :xargs, command ].flatten
|
||||||
end
|
end
|
||||||
@@ -95,32 +92,5 @@ module Kamal::Commands
|
|||||||
def tags(**details)
|
def tags(**details)
|
||||||
Kamal::Tags.from_config(config, **details)
|
Kamal::Tags.from_config(config, **details)
|
||||||
end
|
end
|
||||||
|
|
||||||
def ssh_proxy_args
|
|
||||||
case config.ssh.proxy
|
|
||||||
when Net::SSH::Proxy::Jump
|
|
||||||
" -J #{config.ssh.proxy.jump_proxies}"
|
|
||||||
when Net::SSH::Proxy::Command
|
|
||||||
" -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ssh_keys_args
|
|
||||||
"#{ ssh_keys.join("") if ssh_keys}" + "#{" -o IdentitiesOnly=yes" if config.ssh&.keys_only}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def ssh_keys
|
|
||||||
config.ssh.keys&.map do |key|
|
|
||||||
" -i #{key}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_local_docker_installed
|
|
||||||
docker "--version"
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_local_buildx_installed
|
|
||||||
docker :buildx, "version"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
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, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
|
delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
|
||||||
delegate :local?, :remote?, :cloud?, to: "config.builder"
|
delegate :local?, :remote?, to: "config.builder"
|
||||||
|
|
||||||
include Clone
|
include Clone
|
||||||
|
|
||||||
@@ -17,8 +17,6 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
|
|||||||
else
|
else
|
||||||
remote
|
remote
|
||||||
end
|
end
|
||||||
elsif cloud?
|
|
||||||
cloud
|
|
||||||
else
|
else
|
||||||
local
|
local
|
||||||
end
|
end
|
||||||
@@ -36,7 +34,23 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
|
|||||||
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
@hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cloud
|
|
||||||
@cloud ||= Kamal::Commands::Builder::Cloud.new(config)
|
def ensure_local_dependencies_installed
|
||||||
|
if name.native?
|
||||||
|
ensure_local_docker_installed
|
||||||
|
else
|
||||||
|
combine \
|
||||||
|
ensure_local_docker_installed,
|
||||||
|
ensure_local_buildx_installed
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def ensure_local_docker_installed
|
||||||
|
docker "--version"
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_local_buildx_installed
|
||||||
|
docker :buildx, "version"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,22 +6,20 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
delegate \
|
delegate \
|
||||||
:args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
|
:args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
|
||||||
:cache_from, :cache_to, :ssh, :provenance, :sbom, :driver, :docker_driver?,
|
:cache_from, :cache_to, :ssh, :driver, :docker_driver?,
|
||||||
to: :builder_config
|
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(export_action = "registry", tag_as_dirty: false)
|
def push
|
||||||
docker :buildx, :build,
|
docker :buildx, :build,
|
||||||
"--output=type=#{export_action}",
|
"--push",
|
||||||
*platform_options(arches),
|
*platform_options(arches),
|
||||||
*([ "--builder", builder_name ] unless docker_driver?),
|
*([ "--builder", builder_name ] unless docker_driver?),
|
||||||
*build_tag_options(tag_as_dirty: tag_as_dirty),
|
|
||||||
*build_options,
|
*build_options,
|
||||||
build_context,
|
build_context
|
||||||
"2>&1"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def pull
|
def pull
|
||||||
@@ -39,7 +37,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_options
|
def build_options
|
||||||
[ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
|
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_context
|
def build_context
|
||||||
@@ -60,14 +58,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def build_tag_names(tag_as_dirty: false)
|
def build_tags
|
||||||
tag_names = [ config.absolute_image, config.latest_image ]
|
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
||||||
tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
|
|
||||||
tag_names
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_tag_options(tag_as_dirty: false)
|
|
||||||
build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_cache
|
def build_cache
|
||||||
@@ -105,14 +97,6 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
argumentize "--ssh", ssh if ssh.present?
|
argumentize "--ssh", ssh if ssh.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def builder_provenance
|
|
||||||
argumentize "--provenance", provenance unless provenance.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_sbom
|
|
||||||
argumentize "--sbom", sbom unless sbom.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def builder_config
|
def builder_config
|
||||||
config.builder
|
config.builder
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
module Kamal::Commands::Builder::Clone
|
module Kamal::Commands::Builder::Clone
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
delegate :clone_directory, :build_directory, to: :"config.builder"
|
||||||
|
end
|
||||||
|
|
||||||
def clone
|
def clone
|
||||||
git :clone, escaped_root, "--recurse-submodules", path: config.builder.clone_directory.shellescape
|
git :clone, Kamal::Git.root, "--recurse-submodules", path: clone_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone_reset_steps
|
def clone_reset_steps
|
||||||
[
|
[
|
||||||
git(:remote, "set-url", :origin, escaped_root, path: escaped_build_directory),
|
git(:remote, "set-url", :origin, Kamal::Git.root, path: build_directory),
|
||||||
git(:fetch, :origin, path: escaped_build_directory),
|
git(:fetch, :origin, path: build_directory),
|
||||||
git(:reset, "--hard", Kamal::Git.revision, path: escaped_build_directory),
|
git(:reset, "--hard", Kamal::Git.revision, path: build_directory),
|
||||||
git(:clean, "-fdx", path: escaped_build_directory),
|
git(:clean, "-fdx", path: build_directory),
|
||||||
git(:submodule, :update, "--init", path: escaped_build_directory)
|
git(:submodule, :update, "--init", path: build_directory)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone_status
|
def clone_status
|
||||||
git :status, "--porcelain", path: escaped_build_directory
|
git :status, "--porcelain", path: build_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def clone_revision
|
def clone_revision
|
||||||
git :"rev-parse", :HEAD, path: escaped_build_directory
|
git :"rev-parse", :HEAD, path: build_directory
|
||||||
end
|
|
||||||
|
|
||||||
def escaped_root
|
|
||||||
Kamal::Git.root.shellescape
|
|
||||||
end
|
|
||||||
|
|
||||||
def escaped_build_directory
|
|
||||||
config.builder.build_directory.shellescape
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
class Kamal::Commands::Builder::Cloud < Kamal::Commands::Builder::Base
|
|
||||||
# Expects `driver` to be of format "cloud docker-org-name/builder-name"
|
|
||||||
|
|
||||||
def create
|
|
||||||
docker :buildx, :create, "--driver", driver
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove
|
|
||||||
docker :buildx, :rm, builder_name
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def builder_name
|
|
||||||
driver.gsub(/[ \/]/, "-")
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect_buildx
|
|
||||||
pipe \
|
|
||||||
docker(:buildx, :inspect, builder_name),
|
|
||||||
grep("-q", "Endpoint:.*cloud://.*")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -2,7 +2,14 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
|
|||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
def run
|
def run
|
||||||
pipe boot_config, xargs(docker_run)
|
docker :run,
|
||||||
|
"--name", container_name,
|
||||||
|
"--network", "kamal",
|
||||||
|
"--detach",
|
||||||
|
"--restart", "unless-stopped",
|
||||||
|
"--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
|
||||||
|
"\$\(#{get_boot_options.join(" ")}\)",
|
||||||
|
config.proxy_image
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
@@ -24,7 +31,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
|
|||||||
def version
|
def version
|
||||||
pipe \
|
pipe \
|
||||||
docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
|
docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
|
||||||
[ :awk, "-F:", "'{print \$NF}'" ]
|
[ :cut, "-d:", "-f2" ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
||||||
@@ -58,70 +65,23 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def ensure_proxy_directory
|
def ensure_proxy_directory
|
||||||
make_directory config.proxy_boot.host_directory
|
make_directory config.proxy_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_proxy_directory
|
def remove_proxy_directory
|
||||||
remove_directory config.proxy_boot.host_directory
|
remove_directory config.proxy_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_apps_config_directory
|
def get_boot_options
|
||||||
make_directory config.proxy_boot.apps_directory
|
combine [ :cat, config.proxy_options_file ], [ :echo, "\"#{config.proxy_options_default.join(" ")}\"" ], by: "||"
|
||||||
end
|
|
||||||
|
|
||||||
def boot_config
|
|
||||||
[ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_boot_options
|
|
||||||
read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" "))
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_image
|
|
||||||
read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default)
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_image_version
|
|
||||||
read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_run_command
|
|
||||||
read_file(config.proxy_boot.run_command_file)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_boot_options
|
def reset_boot_options
|
||||||
remove_file config.proxy_boot.options_file
|
remove_file config.proxy_options_file
|
||||||
end
|
|
||||||
|
|
||||||
def reset_image
|
|
||||||
remove_file config.proxy_boot.image_file
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_image_version
|
|
||||||
remove_file config.proxy_boot.image_version_file
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_run_command
|
|
||||||
remove_file config.proxy_boot.run_command_file
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def container_name
|
def container_name
|
||||||
config.proxy_boot.container_name
|
config.proxy_container_name
|
||||||
end
|
|
||||||
|
|
||||||
def read_file(file, default: nil)
|
|
||||||
combine [ :cat, file, "2>", "/dev/null" ], [ :echo, "\"#{default}\"" ], by: "||"
|
|
||||||
end
|
|
||||||
|
|
||||||
def docker_run
|
|
||||||
docker \
|
|
||||||
:run,
|
|
||||||
"--name", container_name,
|
|
||||||
"--network", "kamal",
|
|
||||||
"--detach",
|
|
||||||
"--restart", "unless-stopped",
|
|
||||||
"--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
|
|
||||||
*config.proxy_boot.apps_volume.docker_args
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
class Kamal::Commands::Registry < Kamal::Commands::Base
|
class Kamal::Commands::Registry < Kamal::Commands::Base
|
||||||
def login(registry_config: nil)
|
delegate :registry, to: :config
|
||||||
registry_config ||= config.registry
|
|
||||||
|
|
||||||
|
def login
|
||||||
docker :login,
|
docker :login,
|
||||||
registry_config.server,
|
registry.server,
|
||||||
"-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
|
"-u", sensitive(Kamal::Utils.escape_shell_value(registry.username)),
|
||||||
"-p", sensitive(Kamal::Utils.escape_shell_value(registry_config.password))
|
"-p", sensitive(Kamal::Utils.escape_shell_value(registry.password))
|
||||||
end
|
end
|
||||||
|
|
||||||
def logout(registry_config: nil)
|
def logout
|
||||||
registry_config ||= config.registry
|
docker :logout, registry.server
|
||||||
|
|
||||||
docker :logout, registry_config.server
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ class Kamal::Configuration
|
|||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_reader :destination, :raw_config, :secrets
|
attr_reader :destination, :raw_config, :secrets
|
||||||
attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :proxy_boot, :servers, :ssh, :sshkit, :registry
|
attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :servers, :ssh, :sshkit, :registry
|
||||||
|
|
||||||
include Validation
|
include Validation
|
||||||
|
|
||||||
|
PROXY_MINIMUM_VERSION = "v0.7.0"
|
||||||
|
PROXY_HTTP_PORT = 80
|
||||||
|
PROXY_HTTPS_PORT = 443
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def create_from(config_file:, destination: nil, version: nil)
|
def create_from(config_file:, destination: nil, version: nil)
|
||||||
ENV["KAMAL_DESTINATION"] = destination
|
ENV["KAMAL_DESTINATION"] = destination
|
||||||
@@ -32,7 +36,7 @@ class Kamal::Configuration
|
|||||||
if file.exist?
|
if file.exist?
|
||||||
# Newer Psych doesn't load aliases by default
|
# Newer Psych doesn't load aliases by default
|
||||||
load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
||||||
YAML.send(load_method, ERB.new(File.read(file)).result).symbolize_keys
|
YAML.send(load_method, ERB.new(IO.read(file)).result).symbolize_keys
|
||||||
else
|
else
|
||||||
raise "Configuration file not found in #{file}"
|
raise "Configuration file not found in #{file}"
|
||||||
end
|
end
|
||||||
@@ -54,7 +58,7 @@ class Kamal::Configuration
|
|||||||
|
|
||||||
# Eager load config to validate it, these are first as they have dependencies later on
|
# Eager load config to validate it, these are first as they have dependencies later on
|
||||||
@servers = Servers.new(config: self)
|
@servers = Servers.new(config: self)
|
||||||
@registry = Registry.new(config: @raw_config, secrets: secrets)
|
@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) ] } || {}
|
@aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
|
||||||
@@ -63,8 +67,7 @@ class Kamal::Configuration
|
|||||||
@env = Env.new(config: @raw_config.env || {}, secrets: secrets)
|
@env = Env.new(config: @raw_config.env || {}, secrets: secrets)
|
||||||
|
|
||||||
@logging = Logging.new(logging_config: @raw_config.logging)
|
@logging = Logging.new(logging_config: @raw_config.logging)
|
||||||
@proxy = Proxy.new(config: self, proxy_config: @raw_config.key?(:proxy) ? @raw_config.proxy : {})
|
@proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy || {})
|
||||||
@proxy_boot = Proxy::Boot.new(config: self)
|
|
||||||
@ssh = Ssh.new(config: self)
|
@ssh = Ssh.new(config: self)
|
||||||
@sshkit = Sshkit.new(config: self)
|
@sshkit = Sshkit.new(config: self)
|
||||||
|
|
||||||
@@ -78,6 +81,7 @@ class Kamal::Configuration
|
|||||||
ensure_unique_hosts_for_ssl_roles
|
ensure_unique_hosts_for_ssl_roles
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def version=(version)
|
def version=(version)
|
||||||
@declared_version = version
|
@declared_version = version
|
||||||
end
|
end
|
||||||
@@ -101,9 +105,6 @@ class Kamal::Configuration
|
|||||||
raw_config.minimum_version
|
raw_config.minimum_version
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_and_destination
|
|
||||||
[ service, destination ].compact.join("-")
|
|
||||||
end
|
|
||||||
|
|
||||||
def roles
|
def roles
|
||||||
servers.roles
|
servers.roles
|
||||||
@@ -117,14 +118,11 @@ class Kamal::Configuration
|
|||||||
accessories.detect { |a| a.name == name.to_s }
|
accessories.detect { |a| a.name == name.to_s }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def all_hosts
|
def all_hosts
|
||||||
(roles + accessories).flat_map(&:hosts).uniq
|
(roles + accessories).flat_map(&:hosts).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def app_hosts
|
|
||||||
roles.flat_map(&:hosts).uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def primary_host
|
def primary_host
|
||||||
primary_role&.primary_host
|
primary_role&.primary_host
|
||||||
end
|
end
|
||||||
@@ -149,12 +147,8 @@ class Kamal::Configuration
|
|||||||
proxy_roles.flat_map(&:name)
|
proxy_roles.flat_map(&:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def proxy_accessories
|
|
||||||
accessories.select(&:running_proxy?)
|
|
||||||
end
|
|
||||||
|
|
||||||
def proxy_hosts
|
def proxy_hosts
|
||||||
(proxy_roles.flat_map(&:hosts) + proxy_accessories.flat_map(&:hosts)).uniq
|
proxy_roles.flat_map(&:hosts).uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def repository
|
def repository
|
||||||
@@ -185,6 +179,7 @@ class Kamal::Configuration
|
|||||||
raw_config.retain_containers || 5
|
raw_config.retain_containers || 5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def volume_args
|
def volume_args
|
||||||
if raw_config.volumes.present?
|
if raw_config.volumes.present?
|
||||||
argumentize "--volume", raw_config.volumes
|
argumentize "--volume", raw_config.volumes
|
||||||
@@ -197,6 +192,7 @@ class Kamal::Configuration
|
|||||||
logging.args
|
logging.args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def readiness_delay
|
def readiness_delay
|
||||||
raw_config.readiness_delay || 7
|
raw_config.readiness_delay || 7
|
||||||
end
|
end
|
||||||
@@ -209,6 +205,7 @@ class Kamal::Configuration
|
|||||||
raw_config.drain_timeout || 30
|
raw_config.drain_timeout || 30
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def run_directory
|
def run_directory
|
||||||
".kamal"
|
".kamal"
|
||||||
end
|
end
|
||||||
@@ -218,7 +215,7 @@ class Kamal::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def app_directory
|
def app_directory
|
||||||
File.join apps_directory, service_and_destination
|
File.join apps_directory, [ service, destination ].compact.join("-")
|
||||||
end
|
end
|
||||||
|
|
||||||
def env_directory
|
def env_directory
|
||||||
@@ -229,6 +226,7 @@ class Kamal::Configuration
|
|||||||
File.join app_directory, "assets"
|
File.join app_directory, "assets"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def hooks_path
|
def hooks_path
|
||||||
raw_config.hooks_path || ".kamal/hooks"
|
raw_config.hooks_path || ".kamal/hooks"
|
||||||
end
|
end
|
||||||
@@ -237,9 +235,6 @@ class Kamal::Configuration
|
|||||||
raw_config.asset_path
|
raw_config.asset_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_pages_path
|
|
||||||
raw_config.error_pages_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def env_tags
|
def env_tags
|
||||||
@env_tags ||= if (tags = raw_config.env["tags"])
|
@env_tags ||= if (tags = raw_config.env["tags"])
|
||||||
@@ -253,6 +248,31 @@ class Kamal::Configuration
|
|||||||
env_tags.detect { |t| t.name == name.to_s }
|
env_tags.detect { |t| t.name == name.to_s }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def proxy_publish_args(http_port, https_port)
|
||||||
|
argumentize "--publish", [ "#{http_port}:#{PROXY_HTTP_PORT}", "#{https_port}:#{PROXY_HTTPS_PORT}" ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_options_default
|
||||||
|
proxy_publish_args PROXY_HTTP_PORT, PROXY_HTTPS_PORT
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_image
|
||||||
|
"basecamp/kamal-proxy:#{PROXY_MINIMUM_VERSION}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_container_name
|
||||||
|
"kamal-proxy"
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_directory
|
||||||
|
File.join run_directory, "proxy"
|
||||||
|
end
|
||||||
|
|
||||||
|
def proxy_options_file
|
||||||
|
File.join proxy_directory, "options"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def to_h
|
def to_h
|
||||||
{
|
{
|
||||||
roles: role_names,
|
roles: role_names,
|
||||||
@@ -282,26 +302,22 @@ class Kamal::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def ensure_required_keys_present
|
def ensure_required_keys_present
|
||||||
%i[ service image registry ].each do |key|
|
%i[ service image registry servers ].each do |key|
|
||||||
raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
if raw_config.servers.nil?
|
unless role(primary_role_name).present?
|
||||||
raise Kamal::ConfigurationError, "No servers or accessories specified" unless raw_config.accessories.present?
|
raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
|
||||||
else
|
end
|
||||||
unless role(primary_role_name).present?
|
|
||||||
raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
|
|
||||||
end
|
|
||||||
|
|
||||||
if primary_role.hosts.empty?
|
if primary_role.hosts.empty?
|
||||||
raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
|
raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
|
||||||
end
|
end
|
||||||
|
|
||||||
unless allow_empty_roles?
|
unless allow_empty_roles?
|
||||||
roles.each do |role|
|
roles.each do |role|
|
||||||
if role.hosts.empty?
|
if role.hosts.empty?
|
||||||
raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
class Kamal::Configuration::Accessory
|
class Kamal::Configuration::Accessory
|
||||||
include Kamal::Configuration::Validation
|
include Kamal::Configuration::Validation
|
||||||
|
|
||||||
DEFAULT_NETWORK = "kamal"
|
|
||||||
|
|
||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_reader :name, :env, :proxy, :registry
|
attr_reader :name, :accessory_config, :env
|
||||||
|
|
||||||
def initialize(name, config:)
|
def initialize(name, config:)
|
||||||
@name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
|
@name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
|
||||||
@@ -16,11 +14,10 @@ class Kamal::Configuration::Accessory
|
|||||||
context: "accessories/#{name}",
|
context: "accessories/#{name}",
|
||||||
with: Kamal::Configuration::Validator::Accessory
|
with: Kamal::Configuration::Validator::Accessory
|
||||||
|
|
||||||
ensure_valid_roles
|
@env = Kamal::Configuration::Env.new \
|
||||||
|
config: accessory_config.fetch("env", {}),
|
||||||
@env = initialize_env
|
secrets: config.secrets,
|
||||||
@proxy = initialize_proxy if running_proxy?
|
context: "accessories/#{name}/env"
|
||||||
@registry = initialize_registry if accessory_config["registry"].present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_name
|
def service_name
|
||||||
@@ -28,11 +25,11 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def image
|
def image
|
||||||
[ registry&.server, accessory_config["image"] ].compact.join("/")
|
accessory_config["image"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def hosts
|
def hosts
|
||||||
hosts_from_host || hosts_from_hosts || hosts_from_roles || hosts_from_tags
|
hosts_from_host || hosts_from_hosts || hosts_from_roles
|
||||||
end
|
end
|
||||||
|
|
||||||
def port
|
def port
|
||||||
@@ -41,10 +38,6 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def network_args
|
|
||||||
argumentize "--network", network
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish_args
|
def publish_args
|
||||||
argumentize "--publish", port if port
|
argumentize "--publish", port if port
|
||||||
end
|
end
|
||||||
@@ -107,33 +100,8 @@ class Kamal::Configuration::Accessory
|
|||||||
accessory_config["cmd"]
|
accessory_config["cmd"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def running_proxy?
|
|
||||||
accessory_config["proxy"].present?
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_reader :config, :accessory_config
|
attr_accessor :config
|
||||||
|
|
||||||
def initialize_env
|
|
||||||
Kamal::Configuration::Env.new \
|
|
||||||
config: accessory_config.fetch("env", {}),
|
|
||||||
secrets: config.secrets,
|
|
||||||
context: "accessories/#{name}/env"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_proxy
|
|
||||||
Kamal::Configuration::Proxy.new \
|
|
||||||
config: config,
|
|
||||||
proxy_config: accessory_config["proxy"],
|
|
||||||
context: "accessories/#{name}/proxy"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_registry
|
|
||||||
Kamal::Configuration::Registry.new \
|
|
||||||
config: accessory_config,
|
|
||||||
secrets: config.secrets,
|
|
||||||
context: "accessories/#{name}/registry"
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_labels
|
def default_labels
|
||||||
{ "service" => service_name }
|
{ "service" => service_name }
|
||||||
@@ -155,7 +123,7 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def read_dynamic_file(local_file)
|
def read_dynamic_file(local_file)
|
||||||
StringIO.new(ERB.new(File.read(local_file)).result)
|
StringIO.new(ERB.new(IO.read(local_file)).result)
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_remote_file(remote_file)
|
def expand_remote_file(remote_file)
|
||||||
@@ -201,40 +169,8 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def hosts_from_roles
|
def hosts_from_roles
|
||||||
if accessory_config.key?("role")
|
if accessory_config.key?("roles")
|
||||||
config.role(accessory_config["role"])&.hosts
|
accessory_config["roles"].flat_map { |role| config.role(role).hosts }
|
||||||
elsif accessory_config.key?("roles")
|
|
||||||
accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def hosts_from_tags
|
|
||||||
if accessory_config.key?("tag")
|
|
||||||
extract_hosts_from_config_with_tag(accessory_config["tag"])
|
|
||||||
elsif accessory_config.key?("tags")
|
|
||||||
accessory_config["tags"].flat_map { |tag| extract_hosts_from_config_with_tag(tag) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_hosts_from_config_with_tag(tag)
|
|
||||||
if (servers_with_roles = config.raw_config.servers).is_a?(Hash)
|
|
||||||
servers_with_roles.flat_map do |role, servers_in_role|
|
|
||||||
servers_in_role.filter_map do |host|
|
|
||||||
host.keys.first if host.is_a?(Hash) && host.values.first.include?(tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def network
|
|
||||||
accessory_config["network"] || DEFAULT_NETWORK
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_valid_roles
|
|
||||||
if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
|
|
||||||
raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
|
|
||||||
elsif accessory_config["role"] && !config.role(accessory_config["role"])
|
|
||||||
raise Kamal::ConfigurationError, "accessories/#{name}: unknown role #{accessory_config["role"]}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,10 +53,6 @@ class Kamal::Configuration::Builder
|
|||||||
!local_disabled? && (arches.empty? || local_arches.any?)
|
!local_disabled? && (arches.empty? || local_arches.any?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cloud?
|
|
||||||
driver.start_with? "cloud"
|
|
||||||
end
|
|
||||||
|
|
||||||
def cached?
|
def cached?
|
||||||
!!builder_config["cache"]
|
!!builder_config["cache"]
|
||||||
end
|
end
|
||||||
@@ -115,14 +111,6 @@ class Kamal::Configuration::Builder
|
|||||||
builder_config["ssh"]
|
builder_config["ssh"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def provenance
|
|
||||||
builder_config["provenance"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def sbom
|
|
||||||
builder_config["sbom"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def git_clone?
|
def git_clone?
|
||||||
Kamal::Git.used? && builder_config["context"].nil?
|
Kamal::Git.used? && builder_config["context"].nil?
|
||||||
end
|
end
|
||||||
@@ -178,7 +166,7 @@ class Kamal::Configuration::Builder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def cache_to_config_for_registry
|
def cache_to_config_for_registry
|
||||||
[ "type=registry", "ref=#{cache_image_ref}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
|
[ "type=registry", builder_config["cache"]&.fetch("options", nil), "ref=#{cache_image_ref}" ].compact.join(",")
|
||||||
end
|
end
|
||||||
|
|
||||||
def repo_basename
|
def repo_basename
|
||||||
|
|||||||
@@ -23,41 +23,18 @@ accessories:
|
|||||||
|
|
||||||
# Image
|
# Image
|
||||||
#
|
#
|
||||||
# The Docker image to use.
|
# The Docker image to use, prefix it with a registry if not using Docker Hub:
|
||||||
# Prefix it with its server when using root level registry different from Docker Hub.
|
|
||||||
# Define registry directly or via anchors when it differs from root level registry.
|
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
|
|
||||||
# Registry
|
|
||||||
#
|
|
||||||
# By default accessories use Docker Hub registry.
|
|
||||||
# You can specify different registry per accessory with this option.
|
|
||||||
# Don't prefix image with this registry server.
|
|
||||||
# Use anchors if you need to set the same specific registry for several accessories.
|
|
||||||
#
|
|
||||||
# ```yml
|
|
||||||
# registry:
|
|
||||||
# <<: *specific-registry
|
|
||||||
# ```
|
|
||||||
#
|
|
||||||
# See kamal docs registry for more information:
|
|
||||||
registry:
|
|
||||||
...
|
|
||||||
|
|
||||||
# Accessory hosts
|
# Accessory hosts
|
||||||
#
|
#
|
||||||
# Specify one of `host`, `hosts`, `role`, `roles`, `tag` or `tags`:
|
# Specify one of `host`, `hosts`, or `roles`:
|
||||||
host: mysql-db1
|
host: mysql-db1
|
||||||
hosts:
|
hosts:
|
||||||
- mysql-db1
|
- mysql-db1
|
||||||
- mysql-db2
|
- mysql-db2
|
||||||
role: mysql
|
|
||||||
roles:
|
roles:
|
||||||
- mysql
|
- mysql
|
||||||
tag: writer
|
|
||||||
tags:
|
|
||||||
- writer
|
|
||||||
- reader
|
|
||||||
|
|
||||||
# Custom command
|
# Custom command
|
||||||
#
|
#
|
||||||
@@ -66,8 +43,8 @@ accessories:
|
|||||||
|
|
||||||
# Port mappings
|
# Port mappings
|
||||||
#
|
#
|
||||||
# See [https://docs.docker.com/network/](https://docs.docker.com/network/), and
|
# See https://docs.docker.com/network/, and especially note the warning about the security
|
||||||
# especially note the warning about the security implications of exposing ports publicly.
|
# implications of exposing ports publicly.
|
||||||
port: "127.0.0.1:3306:3306"
|
port: "127.0.0.1:3306:3306"
|
||||||
|
|
||||||
# Labels
|
# Labels
|
||||||
@@ -113,16 +90,3 @@ accessories:
|
|||||||
# They are not created or copied before mounting:
|
# They are not created or copied before mounting:
|
||||||
volumes:
|
volumes:
|
||||||
- /path/to/mysql-logs:/var/log/mysql
|
- /path/to/mysql-logs:/var/log/mysql
|
||||||
|
|
||||||
# Network
|
|
||||||
#
|
|
||||||
# The network the accessory will be attached to.
|
|
||||||
#
|
|
||||||
# Defaults to kamal:
|
|
||||||
network: custom
|
|
||||||
|
|
||||||
# Proxy
|
|
||||||
#
|
|
||||||
# You can run your accessory behind the Kamal proxy. See kamal docs proxy for more information
|
|
||||||
proxy:
|
|
||||||
...
|
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
# For example, for a Rails app, you might open a console with:
|
# For example, for a Rails app, you might open a console with:
|
||||||
#
|
#
|
||||||
# ```shell
|
# ```shell
|
||||||
# kamal app exec -i --reuse "bin/rails console"
|
# kamal app exec -i -r console "rails console"
|
||||||
# ```
|
# ```
|
||||||
#
|
#
|
||||||
# By defining an alias, like this:
|
# By defining an alias, like this:
|
||||||
aliases:
|
aliases:
|
||||||
console: app exec -i --reuse "bin/rails console"
|
console: app exec -r console -i "rails console"
|
||||||
# You can now open the console with:
|
# You can now open the console with:
|
||||||
#
|
#
|
||||||
# ```shell
|
# ```shell
|
||||||
|
|||||||
@@ -102,18 +102,3 @@ builder:
|
|||||||
#
|
#
|
||||||
# The build driver to use, defaults to `docker-container`:
|
# The build driver to use, defaults to `docker-container`:
|
||||||
driver: docker
|
driver: docker
|
||||||
#
|
|
||||||
# If you want to use Docker Build Cloud (https://www.docker.com/products/build-cloud/), you can set the driver to:
|
|
||||||
driver: cloud org-name/builder-name
|
|
||||||
|
|
||||||
# Provenance
|
|
||||||
#
|
|
||||||
# It is used to configure provenance attestations for the build result.
|
|
||||||
# The value can also be a boolean to enable or disable provenance attestations.
|
|
||||||
provenance: mode=max
|
|
||||||
|
|
||||||
# SBOM (Software Bill of Materials)
|
|
||||||
#
|
|
||||||
# It is used to configure SBOM generation for the build result.
|
|
||||||
# The value can also be a boolean to enable or disable SBOM generation.
|
|
||||||
sbom: true
|
|
||||||
|
|||||||
@@ -82,12 +82,6 @@ asset_path: /path/to/assets
|
|||||||
# See https://kamal-deploy.org/docs/hooks for more information:
|
# See https://kamal-deploy.org/docs/hooks for more information:
|
||||||
hooks_path: /user_home/kamal/hooks
|
hooks_path: /user_home/kamal/hooks
|
||||||
|
|
||||||
# Error pages
|
|
||||||
#
|
|
||||||
# A directory relative to the app root to find error pages for the proxy to serve.
|
|
||||||
# Any files in the format 4xx.html or 5xx.html will be copied to the hosts.
|
|
||||||
error_pages_path: public
|
|
||||||
|
|
||||||
# Require destinations
|
# Require destinations
|
||||||
#
|
#
|
||||||
# Whether deployments require a destination to be specified, defaults to `false`:
|
# Whether deployments require a destination to be specified, defaults to `false`:
|
||||||
|
|||||||
@@ -51,37 +51,6 @@ env:
|
|||||||
secret:
|
secret:
|
||||||
- DB_PASSWORD
|
- DB_PASSWORD
|
||||||
|
|
||||||
# Aliased secrets
|
|
||||||
#
|
|
||||||
# You can also alias secrets to other secrets using a `:` separator.
|
|
||||||
#
|
|
||||||
# This is useful when the ENV name is different from the secret name. For example, if you have two
|
|
||||||
# places where you need to define the ENV variable `DB_PASSWORD`, but the value is different depending
|
|
||||||
# on the context.
|
|
||||||
#
|
|
||||||
# ```shell
|
|
||||||
# SECRETS=$(kamal secrets fetch ...)
|
|
||||||
#
|
|
||||||
# MAIN_DB_PASSWORD=$(kamal secrets extract MAIN_DB_PASSWORD $SECRETS)
|
|
||||||
# SECONDARY_DB_PASSWORD=$(kamal secrets extract SECONDARY_DB_PASSWORD $SECRETS)
|
|
||||||
# ```
|
|
||||||
env:
|
|
||||||
secret:
|
|
||||||
- DB_PASSWORD:MAIN_DB_PASSWORD
|
|
||||||
tags:
|
|
||||||
secondary_db:
|
|
||||||
secret:
|
|
||||||
- DB_PASSWORD:SECONDARY_DB_PASSWORD
|
|
||||||
accessories:
|
|
||||||
main_db_accessory:
|
|
||||||
env:
|
|
||||||
secret:
|
|
||||||
- DB_PASSWORD:MAIN_DB_PASSWORD
|
|
||||||
secondary_db_accessory:
|
|
||||||
env:
|
|
||||||
secret:
|
|
||||||
- DB_PASSWORD:SECONDARY_DB_PASSWORD
|
|
||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
#
|
#
|
||||||
# Tags are used to add extra env variables to specific hosts.
|
# Tags are used to add extra env variables to specific hosts.
|
||||||
|
|||||||
@@ -46,29 +46,9 @@ proxy:
|
|||||||
# The host value must point to the server we are deploying to, and port 443 must be
|
# The host value must point to the server we are deploying to, and port 443 must be
|
||||||
# open for the Let's Encrypt challenge to succeed.
|
# open for the Let's Encrypt challenge to succeed.
|
||||||
#
|
#
|
||||||
# If you set `ssl` to `true`, `kamal-proxy` will stop forwarding headers to your app,
|
|
||||||
# unless you explicitly set `forward_headers: true`
|
|
||||||
#
|
|
||||||
# Defaults to `false`:
|
# Defaults to `false`:
|
||||||
ssl: true
|
ssl: true
|
||||||
|
|
||||||
# SSL redirect
|
|
||||||
#
|
|
||||||
# By default, kamal-proxy will redirect all HTTP requests to HTTPS when SSL is enabled.
|
|
||||||
# If you prefer that HTTP traffic is passed through to your application (along with
|
|
||||||
# HTTPS traffic), you can disable this redirect by setting `ssl_redirect: false`:
|
|
||||||
ssl_redirect: false
|
|
||||||
|
|
||||||
# Forward headers
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and
|
|
||||||
# will forward them if it is set to `false`.
|
|
||||||
forward_headers: true
|
|
||||||
|
|
||||||
# Response timeout
|
# Response timeout
|
||||||
#
|
#
|
||||||
# How long to wait for requests to complete before timing out, defaults to 30 seconds:
|
# How long to wait for requests to complete before timing out, defaults to 30 seconds:
|
||||||
@@ -113,3 +93,13 @@ proxy:
|
|||||||
response_headers:
|
response_headers:
|
||||||
- X-Request-ID
|
- X-Request-ID
|
||||||
- X-Request-Start
|
- X-Request-Start
|
||||||
|
|
||||||
|
# Forward headers
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and
|
||||||
|
# will forward them if it is set to `false`.
|
||||||
|
forward_headers: true
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
#
|
#
|
||||||
# The default registry is Docker Hub, but you can change it using `registry/server`.
|
# The default registry is Docker Hub, but you can change it using `registry/server`.
|
||||||
#
|
#
|
||||||
# By default, Docker Hub creates public repositories. To avoid making your images public,
|
|
||||||
# set up a private repository before deploying, or change the default repository privacy
|
|
||||||
# settings to private in your [Docker Hub settings](https://hub.docker.com/repository-settings/default-privacy).
|
|
||||||
#
|
|
||||||
# A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret
|
# A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret
|
||||||
# in the local environment:
|
# in the local environment:
|
||||||
registry:
|
registry:
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ ssh:
|
|||||||
|
|
||||||
# Proxy host
|
# Proxy host
|
||||||
#
|
#
|
||||||
# Specified in the form <host> or <user>@<host>:
|
# Specified in the form <host> or <user>@<host>
|
||||||
proxy: root@proxy-host
|
proxy: proxy-host
|
||||||
|
|
||||||
# Proxy command
|
# Proxy command
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
class Kamal::Configuration::Env
|
class Kamal::Configuration::Env
|
||||||
include Kamal::Configuration::Validation
|
include Kamal::Configuration::Validation
|
||||||
|
|
||||||
attr_reader :context, :clear, :secret_keys
|
attr_reader :context, :secrets
|
||||||
|
attr_reader :clear, :secret_keys
|
||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
|
|
||||||
def initialize(config:, secrets:, context: "env")
|
def initialize(config:, secrets:, context: "env")
|
||||||
@@ -17,22 +18,12 @@ class Kamal::Configuration::Env
|
|||||||
end
|
end
|
||||||
|
|
||||||
def secrets_io
|
def secrets_io
|
||||||
Kamal::EnvFile.new(aliased_secrets).to_io
|
Kamal::EnvFile.new(secret_keys.to_h { |key| [ key, secrets[key] ] }).to_io
|
||||||
end
|
end
|
||||||
|
|
||||||
def merge(other)
|
def merge(other)
|
||||||
self.class.new \
|
self.class.new \
|
||||||
config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
|
config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
|
||||||
secrets: @secrets
|
secrets: secrets
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
def aliased_secrets
|
|
||||||
secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key| @secrets[secret_key] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_alias(key)
|
|
||||||
key_name, key_aliased_to = key.split(":", 2)
|
|
||||||
[ key_name, key_aliased_to || key_name ]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Kamal::Configuration::Proxy
|
|||||||
def deploy_options
|
def deploy_options
|
||||||
{
|
{
|
||||||
host: hosts,
|
host: hosts,
|
||||||
tls: proxy_config["ssl"].presence,
|
tls: proxy_config["ssl"],
|
||||||
"deploy-timeout": seconds_duration(config.deploy_timeout),
|
"deploy-timeout": seconds_duration(config.deploy_timeout),
|
||||||
"drain-timeout": seconds_duration(config.drain_timeout),
|
"drain-timeout": seconds_duration(config.drain_timeout),
|
||||||
"health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
|
"health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
|
||||||
@@ -42,10 +42,8 @@ class Kamal::Configuration::Proxy
|
|||||||
"max-request-body": proxy_config.dig("buffering", "max_request_body"),
|
"max-request-body": proxy_config.dig("buffering", "max_request_body"),
|
||||||
"max-response-body": proxy_config.dig("buffering", "max_response_body"),
|
"max-response-body": proxy_config.dig("buffering", "max_response_body"),
|
||||||
"forward-headers": proxy_config.dig("forward_headers"),
|
"forward-headers": proxy_config.dig("forward_headers"),
|
||||||
"tls-redirect": proxy_config.dig("ssl_redirect"),
|
|
||||||
"log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
|
"log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
|
||||||
"log-response-header": proxy_config.dig("logging", "response_headers"),
|
"log-response-header": proxy_config.dig("logging", "response_headers")
|
||||||
"error-pages": error_pages
|
|
||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -53,17 +51,6 @@ class Kamal::Configuration::Proxy
|
|||||||
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
|
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
|
||||||
end
|
end
|
||||||
|
|
||||||
def stop_options(drain_timeout: nil, message: nil)
|
|
||||||
{
|
|
||||||
"drain-timeout": seconds_duration(drain_timeout),
|
|
||||||
message: message
|
|
||||||
}.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
def stop_command_args(**options)
|
|
||||||
optionize stop_options(**options), with: "="
|
|
||||||
end
|
|
||||||
|
|
||||||
def merge(other)
|
def merge(other)
|
||||||
self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
|
self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
|
||||||
end
|
end
|
||||||
@@ -72,8 +59,4 @@ class Kamal::Configuration::Proxy
|
|||||||
def seconds_duration(value)
|
def seconds_duration(value)
|
||||||
value ? "#{value}s" : nil
|
value ? "#{value}s" : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def error_pages
|
|
||||||
File.join config.proxy_boot.error_pages_container_directory, config.version if config.error_pages_path
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
class Kamal::Configuration::Proxy::Boot
|
|
||||||
MINIMUM_VERSION = "v0.9.0"
|
|
||||||
DEFAULT_HTTP_PORT = 80
|
|
||||||
DEFAULT_HTTPS_PORT = 443
|
|
||||||
DEFAULT_LOG_MAX_SIZE = "10m"
|
|
||||||
|
|
||||||
attr_reader :config
|
|
||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
||||||
|
|
||||||
def initialize(config:)
|
|
||||||
@config = config
|
|
||||||
end
|
|
||||||
|
|
||||||
def publish_args(http_port, https_port, bind_ips = nil)
|
|
||||||
ensure_valid_bind_ips(bind_ips)
|
|
||||||
|
|
||||||
(bind_ips || [ nil ]).map do |bind_ip|
|
|
||||||
bind_ip = format_bind_ip(bind_ip)
|
|
||||||
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
|
||||||
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
|
||||||
|
|
||||||
argumentize "--publish", [ publish_http, publish_https ]
|
|
||||||
end.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
def logging_args(max_size)
|
|
||||||
argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_boot_options
|
|
||||||
[
|
|
||||||
*(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)),
|
|
||||||
*(logging_args(DEFAULT_LOG_MAX_SIZE))
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def repository_name
|
|
||||||
"basecamp"
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_name
|
|
||||||
"kamal-proxy"
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_default
|
|
||||||
"#{repository_name}/#{image_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def container_name
|
|
||||||
"kamal-proxy"
|
|
||||||
end
|
|
||||||
|
|
||||||
def host_directory
|
|
||||||
File.join config.run_directory, "proxy"
|
|
||||||
end
|
|
||||||
|
|
||||||
def options_file
|
|
||||||
File.join host_directory, "options"
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_file
|
|
||||||
File.join host_directory, "image"
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_version_file
|
|
||||||
File.join host_directory, "image_version"
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_command_file
|
|
||||||
File.join host_directory, "run_command"
|
|
||||||
end
|
|
||||||
|
|
||||||
def apps_directory
|
|
||||||
File.join host_directory, "apps-config"
|
|
||||||
end
|
|
||||||
|
|
||||||
def apps_container_directory
|
|
||||||
"/home/kamal-proxy/.apps-config"
|
|
||||||
end
|
|
||||||
|
|
||||||
def apps_volume
|
|
||||||
Kamal::Configuration::Volume.new \
|
|
||||||
host_path: apps_directory,
|
|
||||||
container_path: apps_container_directory
|
|
||||||
end
|
|
||||||
|
|
||||||
def app_directory
|
|
||||||
File.join apps_directory, config.service_and_destination
|
|
||||||
end
|
|
||||||
|
|
||||||
def app_container_directory
|
|
||||||
File.join apps_container_directory, config.service_and_destination
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_pages_directory
|
|
||||||
File.join app_directory, "error_pages"
|
|
||||||
end
|
|
||||||
|
|
||||||
def error_pages_container_directory
|
|
||||||
File.join app_container_directory, "error_pages"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def ensure_valid_bind_ips(bind_ips)
|
|
||||||
bind_ips.present? && bind_ips.each do |ip|
|
|
||||||
next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
|
|
||||||
raise ArgumentError, "Invalid publish IP address: #{ip}"
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_bind_ip(ip)
|
|
||||||
# Ensure IPv6 address inside square brackets - e.g. [::1]
|
|
||||||
if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/
|
|
||||||
"[#{ip}]"
|
|
||||||
else
|
|
||||||
ip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
class Kamal::Configuration::Registry
|
class Kamal::Configuration::Registry
|
||||||
include Kamal::Configuration::Validation
|
include Kamal::Configuration::Validation
|
||||||
|
|
||||||
def initialize(config:, secrets:, context: "registry")
|
attr_reader :registry_config, :secrets
|
||||||
@registry_config = config["registry"] || {}
|
|
||||||
@secrets = secrets
|
def initialize(config:)
|
||||||
validate! registry_config, context: context, with: Kamal::Configuration::Validator::Registry
|
@registry_config = config.raw_config.registry || {}
|
||||||
|
@secrets = config.secrets
|
||||||
|
validate! registry_config, with: Kamal::Configuration::Validator::Registry
|
||||||
end
|
end
|
||||||
|
|
||||||
def server
|
def server
|
||||||
@@ -20,8 +22,6 @@ class Kamal::Configuration::Registry
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
attr_reader :registry_config, :secrets
|
|
||||||
|
|
||||||
def lookup(key)
|
def lookup(key)
|
||||||
if registry_config[key].is_a?(Array)
|
if registry_config[key].is_a?(Array)
|
||||||
secrets[registry_config[key].first]
|
secrets[registry_config[key].first]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Kamal::Configuration::Role
|
|||||||
def initialize(name, config:)
|
def initialize(name, config:)
|
||||||
@name, @config = name.inquiry, config
|
@name, @config = name.inquiry, config
|
||||||
validate! \
|
validate! \
|
||||||
role_config,
|
specializations,
|
||||||
example: validation_yml["servers"]["workers"],
|
example: validation_yml["servers"]["workers"],
|
||||||
context: "servers/#{name}",
|
context: "servers/#{name}",
|
||||||
with: Kamal::Configuration::Validator::Role
|
with: Kamal::Configuration::Validator::Role
|
||||||
@@ -204,11 +204,11 @@ class Kamal::Configuration::Role
|
|||||||
end
|
end
|
||||||
|
|
||||||
def specializations
|
def specializations
|
||||||
@specializations ||= role_config.is_a?(Array) ? {} : role_config
|
if config.raw_config.servers.is_a?(Array) || config.raw_config.servers[name].is_a?(Array)
|
||||||
end
|
{}
|
||||||
|
else
|
||||||
def role_config
|
config.raw_config.servers[name]
|
||||||
@role_config ||= config.raw_config.servers.is_a?(Array) ? {} : config.raw_config.servers[name]
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def custom_labels
|
def custom_labels
|
||||||
|
|||||||
@@ -13,13 +13,6 @@ class Kamal::Configuration::Servers
|
|||||||
|
|
||||||
private
|
private
|
||||||
def role_names
|
def role_names
|
||||||
case servers_config
|
servers_config.is_a?(Array) ? [ "web" ] : servers_config.keys.sort
|
||||||
when Array
|
|
||||||
[ "web" ]
|
|
||||||
when NilClass
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
servers_config.keys.sort
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ class Kamal::Configuration::Ssh
|
|||||||
end
|
end
|
||||||
|
|
||||||
def proxy
|
def proxy
|
||||||
if (proxy = ssh_config["proxy"])
|
if proxy = ssh_config["proxy"]
|
||||||
Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
|
Net::SSH::Proxy::Jump.new(proxy)
|
||||||
elsif (proxy_command = ssh_config["proxy_command"])
|
elsif proxy_command = ssh_config["proxy_command"]
|
||||||
Net::SSH::Proxy::Command.new(proxy_command)
|
Net::SSH::Proxy::Command.new(proxy_command)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -168,10 +168,4 @@ class Kamal::Configuration::Validator
|
|||||||
unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
|
unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
|
||||||
unknown_keys_error unknown_keys if unknown_keys.present?
|
unknown_keys_error unknown_keys if unknown_keys.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_docker_options!(options)
|
|
||||||
if options
|
|
||||||
error "Cannot set restart policy in docker options, unless-stopped is required" if options["restart"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ class Kamal::Configuration::Validator::Accessory < Kamal::Configuration::Validat
|
|||||||
def validate!
|
def validate!
|
||||||
super
|
super
|
||||||
|
|
||||||
if (config.keys & [ "host", "hosts", "role", "roles", "tag", "tags" ]).size != 1
|
if (config.keys & [ "host", "hosts", "roles" ]).size != 1
|
||||||
error "specify one of `host`, `hosts`, `role`, `roles`, `tag` or `tags`"
|
error "specify one of `host`, `hosts` or `roles`"
|
||||||
end
|
end
|
||||||
|
|
||||||
validate_docker_options!(config["options"])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ class Kamal::Configuration::Validator::Role < Kamal::Configuration::Validator
|
|||||||
validate_type! config, Array, Hash
|
validate_type! config, Array, Hash
|
||||||
|
|
||||||
if config.is_a?(Array)
|
if config.is_a?(Array)
|
||||||
validate_servers!(config)
|
validate_servers! "servers", config
|
||||||
else
|
else
|
||||||
super
|
super
|
||||||
validate_docker_options!(config["options"])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class Kamal::Configuration::Validator::Servers < Kamal::Configuration::Validator
|
class Kamal::Configuration::Validator::Servers < Kamal::Configuration::Validator
|
||||||
def validate!
|
def validate!
|
||||||
validate_type! config, Array, Hash, NilClass
|
validate_type! config, Array, Hash
|
||||||
|
|
||||||
validate_servers! config if config.is_a?(Array)
|
validate_servers! config if config.is_a?(Array)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
require "tempfile"
|
|
||||||
require "open3"
|
|
||||||
|
|
||||||
module Kamal::Docker
|
|
||||||
extend self
|
|
||||||
BUILD_CHECK_TAG = "kamal-local-build-check"
|
|
||||||
|
|
||||||
def included_files
|
|
||||||
Tempfile.create do |dockerfile|
|
|
||||||
dockerfile.write(<<~DOCKERFILE)
|
|
||||||
FROM busybox
|
|
||||||
COPY . app
|
|
||||||
WORKDIR app
|
|
||||||
CMD find . -type f | sed "s|^\./||"
|
|
||||||
DOCKERFILE
|
|
||||||
dockerfile.close
|
|
||||||
|
|
||||||
cmd = "docker buildx build -t=#{BUILD_CHECK_TAG} -f=#{dockerfile.path} ."
|
|
||||||
system(cmd) || raise("failed to build check image")
|
|
||||||
end
|
|
||||||
|
|
||||||
cmd = "docker run --rm #{BUILD_CHECK_TAG}"
|
|
||||||
out, err, status = Open3.capture3(cmd)
|
|
||||||
unless status
|
|
||||||
raise "failed to run check image:\n#{err}"
|
|
||||||
end
|
|
||||||
|
|
||||||
out.lines.map(&:strip)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -37,8 +37,6 @@ class Kamal::EnvFile
|
|||||||
def escape_docker_env_file_ascii_value(value)
|
def escape_docker_env_file_ascii_value(value)
|
||||||
# Doublequotes are treated literally in docker env files
|
# Doublequotes are treated literally in docker env files
|
||||||
# so remove leading and trailing ones and unescape any others
|
# so remove leading and trailing ones and unescape any others
|
||||||
value.to_s.dump[1..-2]
|
value.to_s.dump[1..-2].gsub(/\\"/, "\"")
|
||||||
.gsub(/\\"/, "\"")
|
|
||||||
.gsub(/\\#/, "#")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,14 +24,4 @@ module Kamal::Git
|
|||||||
def root
|
def root
|
||||||
`git rev-parse --show-toplevel`.strip
|
`git rev-parse --show-toplevel`.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns an array of relative path names of files with uncommitted changes
|
|
||||||
def uncommitted_files
|
|
||||||
`git ls-files --modified`.lines.map(&:strip)
|
|
||||||
end
|
|
||||||
|
|
||||||
# returns an array of relative path names of untracked files, including gitignored files
|
|
||||||
def untracked_files
|
|
||||||
`git ls-files --others`.lines.map(&:strip)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
require "dotenv"
|
require "dotenv"
|
||||||
|
|
||||||
class Kamal::Secrets
|
class Kamal::Secrets
|
||||||
|
attr_reader :secrets_files
|
||||||
|
|
||||||
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
||||||
|
|
||||||
def initialize(destination: nil)
|
def initialize(destination: nil)
|
||||||
@destination = destination
|
@secrets_files = \
|
||||||
|
[ ".kamal/secrets-common", ".kamal/secrets#{(".#{destination}" if destination)}" ].select { |f| File.exist?(f) }
|
||||||
@mutex = Mutex.new
|
@mutex = Mutex.new
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -14,10 +17,10 @@ class Kamal::Secrets
|
|||||||
secrets.fetch(key)
|
secrets.fetch(key)
|
||||||
end
|
end
|
||||||
rescue KeyError
|
rescue KeyError
|
||||||
if secrets_files.present?
|
if secrets_files
|
||||||
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
|
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
|
||||||
else
|
else
|
||||||
raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files (#{secrets_filenames.join(", ")}) provided"
|
raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files provided"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -25,18 +28,10 @@ class Kamal::Secrets
|
|||||||
secrets
|
secrets
|
||||||
end
|
end
|
||||||
|
|
||||||
def secrets_files
|
|
||||||
@secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def secrets
|
def secrets
|
||||||
@secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
|
@secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
|
||||||
secrets.merge!(::Dotenv.parse(secrets_file, overwrite: true))
|
secrets.merge!(::Dotenv.parse(secrets_file))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def secrets_filenames
|
|
||||||
[ ".kamal/secrets-common", ".kamal/secrets#{(".#{@destination}" if @destination)}" ]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ module Kamal::Secrets::Adapters
|
|||||||
def self.lookup(name)
|
def self.lookup(name)
|
||||||
name = "one_password" if name.downcase == "1password"
|
name = "one_password" if name.downcase == "1password"
|
||||||
name = "last_pass" if name.downcase == "lastpass"
|
name = "last_pass" if name.downcase == "lastpass"
|
||||||
name = "gcp_secret_manager" if name.downcase == "gcp"
|
|
||||||
name = "bitwarden_secrets_manager" if name.downcase == "bitwarden-sm"
|
|
||||||
adapter_class(name)
|
adapter_class(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Base
|
|
||||||
def requires_account?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def login(_account)
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account: nil, session:)
|
|
||||||
{}.tap do |results|
|
|
||||||
get_from_secrets_manager(prefixed_secrets(secrets, from: from), account: account).each do |secret|
|
|
||||||
secret_name = secret["Name"]
|
|
||||||
secret_string = JSON.parse(secret["SecretString"])
|
|
||||||
|
|
||||||
secret_string.each do |key, value|
|
|
||||||
results["#{secret_name}/#{key}"] = value
|
|
||||||
end
|
|
||||||
rescue JSON::ParserError
|
|
||||||
results["#{secret_name}"] = secret["SecretString"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_from_secrets_manager(secrets, account: nil)
|
|
||||||
args = [ "aws", "secretsmanager", "batch-get-secret-value", "--secret-id-list" ] + secrets.map(&:shellescape)
|
|
||||||
args += [ "--profile", account.shellescape ] if account
|
|
||||||
args += [ "--output", "json" ]
|
|
||||||
cmd = args.join(" ")
|
|
||||||
|
|
||||||
`#{cmd}`.tap do |secrets|
|
|
||||||
raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success?
|
|
||||||
|
|
||||||
secrets = JSON.parse(secrets)
|
|
||||||
|
|
||||||
return secrets["SecretValues"] unless secrets["Errors"].present?
|
|
||||||
|
|
||||||
raise RuntimeError, secrets["Errors"].map { |error| "#{error['SecretId']}: #{error['Message']}" }.join(" ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "AWS CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`aws --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
class Kamal::Secrets::Adapters::Base
|
class Kamal::Secrets::Adapters::Base
|
||||||
delegate :optionize, to: Kamal::Utils
|
delegate :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
def fetch(secrets, account: nil, from: nil)
|
def fetch(secrets, account:, from: nil)
|
||||||
raise RuntimeError, "Missing required option '--account'" if requires_account? && account.blank?
|
|
||||||
|
|
||||||
check_dependencies!
|
|
||||||
|
|
||||||
session = login(account)
|
session = login(account)
|
||||||
fetch_secrets(secrets, from: from, account: account, session: session)
|
full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
|
||||||
end
|
fetch_secrets(full_secrets, account: account, session: session)
|
||||||
|
|
||||||
def requires_account?
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -22,12 +15,4 @@ class Kamal::Secrets::Adapters::Base
|
|||||||
def fetch_secrets(...)
|
def fetch_secrets(...)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
def prefixed_secrets(secrets, from:)
|
|
||||||
secrets.map { |secret| [ from, secret ].compact.join("/") }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -21,19 +21,22 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
|||||||
session
|
session
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
def fetch_secrets(secrets, account:, session:)
|
||||||
{}.tap do |results|
|
{}.tap do |results|
|
||||||
items_fields(prefixed_secrets(secrets, from: from)).each do |item, fields|
|
items_fields(secrets).each do |item, fields|
|
||||||
item_json = run_command("get item #{item.shellescape}", session: session, raw: true)
|
item_json = run_command("get item #{item.shellescape}", session: session, raw: true)
|
||||||
raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success?
|
raise RuntimeError, "Could not read #{secret} from Bitwarden" unless $?.success?
|
||||||
item_json = JSON.parse(item_json)
|
item_json = JSON.parse(item_json)
|
||||||
|
|
||||||
if fields.any?
|
if fields.any?
|
||||||
results.merge! fetch_secrets_from_fields(fields, item, item_json)
|
fields.each do |field|
|
||||||
|
item_field = item_json["fields"].find { |f| f["name"] == field }
|
||||||
|
raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
|
||||||
|
value = item_field["value"]
|
||||||
|
results["#{item}/#{field}"] = value
|
||||||
|
end
|
||||||
elsif item_json.dig("login", "password")
|
elsif item_json.dig("login", "password")
|
||||||
results[item] = item_json.dig("login", "password")
|
results[item] = item_json.dig("login", "password")
|
||||||
elsif item_json["fields"]&.any?
|
|
||||||
fields = item_json["fields"].pluck("name")
|
|
||||||
results.merge! fetch_secrets_from_fields(fields, item, item_json)
|
|
||||||
else
|
else
|
||||||
raise RuntimeError, "Item #{item} is not a login type item and no fields were specified"
|
raise RuntimeError, "Item #{item} is not a login type item and no fields were specified"
|
||||||
end
|
end
|
||||||
@@ -41,15 +44,6 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_secrets_from_fields(fields, item, item_json)
|
|
||||||
fields.to_h do |field|
|
|
||||||
item_field = item_json["fields"].find { |f| f["name"] == field }
|
|
||||||
raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
|
|
||||||
value = item_field["value"]
|
|
||||||
[ "#{item}/#{field}", value ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def items_fields(secrets)
|
def items_fields(secrets)
|
||||||
{}.tap do |items|
|
{}.tap do |items|
|
||||||
secrets.each do |secret|
|
secrets.each do |secret|
|
||||||
@@ -69,13 +63,4 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
|||||||
result = `#{full_command}`.strip
|
result = `#{full_command}`.strip
|
||||||
raw ? result : JSON.parse(result)
|
raw ? result : JSON.parse(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "Bitwarden CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`bw --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
class Kamal::Secrets::Adapters::BitwardenSecretsManager < Kamal::Secrets::Adapters::Base
|
|
||||||
def requires_account?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
LIST_ALL_SELECTOR = "all"
|
|
||||||
LIST_ALL_FROM_PROJECT_SUFFIX = "/all"
|
|
||||||
LIST_COMMAND = "secret list -o env"
|
|
||||||
GET_COMMAND = "secret get -o env"
|
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
|
||||||
raise RuntimeError, "You must specify what to retrieve from Bitwarden Secrets Manager" if secrets.length == 0
|
|
||||||
|
|
||||||
secrets = prefixed_secrets(secrets, from: from)
|
|
||||||
command, project = extract_command_and_project(secrets)
|
|
||||||
|
|
||||||
{}.tap do |results|
|
|
||||||
if command.nil?
|
|
||||||
secrets.each do |secret_uuid|
|
|
||||||
secret = run_command("#{GET_COMMAND} #{secret_uuid.shellescape}")
|
|
||||||
raise RuntimeError, "Could not read #{secret_uuid} from Bitwarden Secrets Manager" unless $?.success?
|
|
||||||
key, value = parse_secret(secret)
|
|
||||||
results[key] = value
|
|
||||||
end
|
|
||||||
else
|
|
||||||
secrets = run_command(command)
|
|
||||||
raise RuntimeError, "Could not read secrets from Bitwarden Secrets Manager" unless $?.success?
|
|
||||||
secrets.split("\n").each do |secret|
|
|
||||||
key, value = parse_secret(secret)
|
|
||||||
results[key] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_command_and_project(secrets)
|
|
||||||
if secrets.length == 1
|
|
||||||
if secrets[0] == LIST_ALL_SELECTOR
|
|
||||||
[ LIST_COMMAND, nil ]
|
|
||||||
elsif secrets[0].end_with?(LIST_ALL_FROM_PROJECT_SUFFIX)
|
|
||||||
project = secrets[0].split(LIST_ALL_FROM_PROJECT_SUFFIX).first
|
|
||||||
[ "#{LIST_COMMAND} #{project.shellescape}", project ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_secret(secret)
|
|
||||||
key, value = secret.split("=", 2)
|
|
||||||
value = value.gsub(/^"|"$/, "")
|
|
||||||
[ key, value ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_command(command, session: nil)
|
|
||||||
full_command = [ "bws", command ].join(" ")
|
|
||||||
`#{full_command}`
|
|
||||||
end
|
|
||||||
|
|
||||||
def login(account)
|
|
||||||
run_command("run 'echo OK'")
|
|
||||||
raise RuntimeError, "Could not authenticate to Bitwarden Secrets Manager. Did you set a valid access token?" unless $?.success?
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "Bitwarden Secrets Manager CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`bws --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
class Kamal::Secrets::Adapters::Doppler < Kamal::Secrets::Adapters::Base
|
|
||||||
def requires_account?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def login(*)
|
|
||||||
unless loggedin?
|
|
||||||
`doppler login -y`
|
|
||||||
raise RuntimeError, "Failed to login to Doppler" unless $?.success?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def loggedin?
|
|
||||||
`doppler me --json 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, **)
|
|
||||||
secrets = prefixed_secrets(secrets, from: from)
|
|
||||||
flags = secrets_get_flags(secrets)
|
|
||||||
|
|
||||||
secret_names = secrets.collect { |s| s.split("/").last }
|
|
||||||
|
|
||||||
items = `doppler secrets get #{secret_names.map(&:shellescape).join(" ")} --json #{flags}`
|
|
||||||
raise RuntimeError, "Could not read #{secrets} from Doppler" unless $?.success?
|
|
||||||
|
|
||||||
items = JSON.parse(items)
|
|
||||||
|
|
||||||
items.transform_values { |value| value["computed"] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def secrets_get_flags(secrets)
|
|
||||||
unless service_token_set?
|
|
||||||
project, config, _ = secrets.first.split("/")
|
|
||||||
|
|
||||||
unless project && config
|
|
||||||
raise RuntimeError, "Missing project or config from '--from=project/config' option"
|
|
||||||
end
|
|
||||||
|
|
||||||
project_and_config_flags = "-p #{project.shellescape} -c #{config.shellescape}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def service_token_set?
|
|
||||||
ENV["DOPPLER_TOKEN"] && ENV["DOPPLER_TOKEN"][0, 5] == "dp.st"
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "Doppler CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`doppler --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
##
|
|
||||||
# Enpass is different from most password managers, in a way that it's offline and doesn't need an account.
|
|
||||||
#
|
|
||||||
# Usage
|
|
||||||
#
|
|
||||||
# Fetch all password from FooBar item
|
|
||||||
# `kamal secrets fetch --adapter enpass --from /Users/YOUR_USERNAME/Library/Containers/in.sinew.Enpass-Desktop/Data/Documents/Vaults/primary FooBar`
|
|
||||||
#
|
|
||||||
# Fetch only DB_PASSWORD from FooBar item
|
|
||||||
# `kamal secrets fetch --adapter enpass --from /Users/YOUR_USERNAME/Library/Containers/in.sinew.Enpass-Desktop/Data/Documents/Vaults/primary FooBar/DB_PASSWORD`
|
|
||||||
class Kamal::Secrets::Adapters::Enpass < Kamal::Secrets::Adapters::Base
|
|
||||||
def requires_account?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
|
||||||
secrets_titles = fetch_secret_titles(secrets)
|
|
||||||
|
|
||||||
result = `enpass-cli -json -vault #{from.shellescape} show #{secrets_titles.map(&:shellescape).join(" ")}`.strip
|
|
||||||
|
|
||||||
parse_result_and_take_secrets(result, secrets)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "Enpass CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`enpass-cli version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
|
|
||||||
def login(account)
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_secret_titles(secrets)
|
|
||||||
secrets.reduce(Set.new) do |secret_titles, secret|
|
|
||||||
# Sometimes secrets contain a '/', when the intent is to fetch a single password for an item. Example: FooBar/DB_PASSWORD
|
|
||||||
# Another case is, when the intent is to fetch all passwords for an item. Example: FooBar (and FooBar may have multiple different passwords)
|
|
||||||
key, separator, value = secret.rpartition("/")
|
|
||||||
if key.empty?
|
|
||||||
secret_titles << value
|
|
||||||
else
|
|
||||||
secret_titles << key
|
|
||||||
end
|
|
||||||
end.to_a
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_result_and_take_secrets(unparsed_result, secrets)
|
|
||||||
result = JSON.parse(unparsed_result)
|
|
||||||
|
|
||||||
result.reduce({}) do |secrets_with_passwords, item|
|
|
||||||
title = item["title"]
|
|
||||||
label = item["label"]
|
|
||||||
password = item["password"]
|
|
||||||
|
|
||||||
if title && password.present?
|
|
||||||
key = [ title, label ].compact.reject(&:empty?).join("/")
|
|
||||||
|
|
||||||
if secrets.include?(title) || secrets.include?(key)
|
|
||||||
raise RuntimeError, "#{key} is present more than once" if secrets_with_passwords[key]
|
|
||||||
secrets_with_passwords[key] = password
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
secrets_with_passwords
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
class Kamal::Secrets::Adapters::GcpSecretManager < Kamal::Secrets::Adapters::Base
|
|
||||||
private
|
|
||||||
def login(account)
|
|
||||||
# Since only the account option is passed from the cli, we'll use it for both account and service account
|
|
||||||
# impersonation.
|
|
||||||
#
|
|
||||||
# Syntax:
|
|
||||||
# ACCOUNT: USER | USER "|" DELEGATION_CHAIN
|
|
||||||
# USER: DEFAULT_USER | EMAIL
|
|
||||||
# DELEGATION_CHAIN: EMAIL | EMAIL "," DELEGATION_CHAIN
|
|
||||||
# EMAIL: <The email address of the user or service account, like "my-user@example.com" >
|
|
||||||
# DEFAULT_USER: "default"
|
|
||||||
#
|
|
||||||
# Some valid examples:
|
|
||||||
# - "my-user@example.com" sets the user
|
|
||||||
# - "my-user@example.com|my-service-user@example.com" will use my-user and enable service account impersonation as my-service-user
|
|
||||||
# - "default" will use the default user and no impersonation
|
|
||||||
# - "default|my-service-user@example.com" will use the default user, and enable service account impersonation as my-service-user
|
|
||||||
# - "default|my-service-user@example.com,another-service-user@example.com" same as above, but with an impersonation delegation chain
|
|
||||||
|
|
||||||
unless logged_in?
|
|
||||||
`gcloud auth login`
|
|
||||||
raise RuntimeError, "could not login to gcloud" unless logged_in?
|
|
||||||
end
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
|
||||||
user, service_account = parse_account(account)
|
|
||||||
|
|
||||||
{}.tap do |results|
|
|
||||||
secrets_with_metadata(prefixed_secrets(secrets, from: from)).each do |secret, (project, secret_name, secret_version)|
|
|
||||||
item_name = "#{project}/#{secret_name}"
|
|
||||||
results[item_name] = fetch_secret(project, secret_name, secret_version, user, service_account)
|
|
||||||
raise RuntimeError, "Could not read #{item_name} from Google Secret Manager" unless $?.success?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_secret(project, secret_name, secret_version, user, service_account)
|
|
||||||
secret = run_command(
|
|
||||||
"secrets versions access #{secret_version.shellescape} --secret=#{secret_name.shellescape}",
|
|
||||||
project: project,
|
|
||||||
user: user,
|
|
||||||
service_account: service_account
|
|
||||||
)
|
|
||||||
Base64.decode64(secret.dig("payload", "data"))
|
|
||||||
end
|
|
||||||
|
|
||||||
# The secret needs to at least contain a secret name, but project name, and secret version can also be specified.
|
|
||||||
#
|
|
||||||
# The string "default" can be used to refer to the default project configured for gcloud.
|
|
||||||
#
|
|
||||||
# The version can be either the string "latest", or a version number.
|
|
||||||
#
|
|
||||||
# The following formats are valid:
|
|
||||||
#
|
|
||||||
# - The following are all equivalent, and sets project: default, secret name: my-secret, version: latest
|
|
||||||
# - "my-secret"
|
|
||||||
# - "default/my-secret"
|
|
||||||
# - "default/my-secret/latest"
|
|
||||||
# - "my-secret/latest" in combination with --from=default
|
|
||||||
# - "my-secret/123" (only in combination with --from=some-project) -> project: some-project, secret name: my-secret, version: 123
|
|
||||||
# - "some-project/my-secret/123" -> project: some-project, secret name: my-secret, version: 123
|
|
||||||
def secrets_with_metadata(secrets)
|
|
||||||
{}.tap do |items|
|
|
||||||
secrets.each do |secret|
|
|
||||||
parts = secret.split("/")
|
|
||||||
parts.unshift("default") if parts.length == 1
|
|
||||||
project = parts.shift
|
|
||||||
secret_name = parts.shift
|
|
||||||
secret_version = parts.shift || "latest"
|
|
||||||
|
|
||||||
items[secret] = [ project, secret_name, secret_version ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_command(command, project: "default", user: "default", service_account: nil)
|
|
||||||
full_command = [ "gcloud", command ]
|
|
||||||
full_command << "--project=#{project.shellescape}" unless project == "default"
|
|
||||||
full_command << "--account=#{user.shellescape}" unless user == "default"
|
|
||||||
full_command << "--impersonate-service-account=#{service_account.shellescape}" if service_account
|
|
||||||
full_command << "--format=json"
|
|
||||||
full_command = full_command.join(" ")
|
|
||||||
|
|
||||||
result = `#{full_command}`.strip
|
|
||||||
JSON.parse(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "gcloud CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`gcloud --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
|
|
||||||
def logged_in?
|
|
||||||
JSON.parse(`gcloud auth list --format=json`).any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_account(account)
|
|
||||||
account.split("|", 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_user?(candidate)
|
|
||||||
candidate.include?("@")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -11,8 +11,7 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
|
|||||||
`lpass status --color never`.strip == "Logged in as #{account}."
|
`lpass status --color never`.strip == "Logged in as #{account}."
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
def fetch_secrets(secrets, account:, session:)
|
||||||
secrets = prefixed_secrets(secrets, from: from)
|
|
||||||
items = `lpass show #{secrets.map(&:shellescape).join(" ")} --json`
|
items = `lpass show #{secrets.map(&:shellescape).join(" ")} --json`
|
||||||
raise RuntimeError, "Could not read #{secrets} from LastPass" unless $?.success?
|
raise RuntimeError, "Could not read #{secrets} from LastPass" unless $?.success?
|
||||||
|
|
||||||
@@ -24,17 +23,8 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
if (missing_items = secrets - results.keys).any?
|
if (missing_items = secrets - results.keys).any?
|
||||||
raise RuntimeError, "Could not find #{missing_items.join(", ")} in LastPass"
|
raise RuntimeError, "Could not find #{missing_items.join(", ")} in LassPass"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "LastPass CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`lpass --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base
|
|||||||
$?.success?
|
$?.success?
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
def fetch_secrets(secrets, account:, session:)
|
||||||
{}.tap do |results|
|
{}.tap do |results|
|
||||||
vaults_items_fields(prefixed_secrets(secrets, from: from)).map do |vault, items|
|
vaults_items_fields(secrets).map do |vault, items|
|
||||||
items.each do |item, fields|
|
items.each do |item, fields|
|
||||||
fields_json = JSON.parse(op_item_get(vault, item, fields, account: account, session: session))
|
fields_json = JSON.parse(op_item_get(vault, item, fields, account: account, session: session))
|
||||||
fields_json = [ fields_json ] if fields.one?
|
fields_json = [ fields_json ] if fields.one?
|
||||||
@@ -58,13 +58,4 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base
|
|||||||
raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
|
raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
raise RuntimeError, "1Password CLI is not installed" unless cli_installed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def cli_installed?
|
|
||||||
`op --version 2> /dev/null`
|
|
||||||
$?.success?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_secrets(secrets, from:, account:, session:)
|
def fetch_secrets(secrets, account:, session:)
|
||||||
prefixed_secrets(secrets, from: from).to_h { |secret| [ secret, secret.reverse ] }
|
secrets.to_h { |secret| [ secret, secret.reverse ] }
|
||||||
end
|
|
||||||
|
|
||||||
def check_dependencies!
|
|
||||||
# no op
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
|||||||
::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub }
|
::Dotenv::Parser.substitutions.map! { |sub| sub == ::Dotenv::Substitutions::Command ? self : sub }
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(value, env, overwrite: false)
|
def call(value, _env, overwrite: false)
|
||||||
# Process interpolated shell commands
|
# Process interpolated shell commands
|
||||||
value.gsub(Dotenv::Substitutions::Command.singleton_class::INTERPOLATED_SHELL_COMMAND) do |*|
|
value.gsub(Dotenv::Substitutions::Command.singleton_class::INTERPOLATED_SHELL_COMMAND) do |*|
|
||||||
# Eliminate opening and closing parentheses
|
# Eliminate opening and closing parentheses
|
||||||
@@ -14,7 +14,6 @@ class Kamal::Secrets::Dotenv::InlineCommandSubstitution
|
|||||||
# Command is escaped, don't replace it.
|
# Command is escaped, don't replace it.
|
||||||
$LAST_MATCH_INFO[0][1..]
|
$LAST_MATCH_INFO[0][1..]
|
||||||
else
|
else
|
||||||
command = ::Dotenv::Substitutions::Variable.call(command, env)
|
|
||||||
if command =~ /\A\s*kamal\s*secrets\s+/
|
if command =~ /\A\s*kamal\s*secrets\s+/
|
||||||
# Inline the command
|
# Inline the command
|
||||||
inline_secrets_command(command)
|
inline_secrets_command(command)
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ module Kamal::Utils
|
|||||||
attr = "#{key}=#{escape_shell_value(value)}"
|
attr = "#{key}=#{escape_shell_value(value)}"
|
||||||
attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
|
attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
|
||||||
[ argument, attr ]
|
[ argument, attr ]
|
||||||
elsif value == false
|
|
||||||
[ argument, "#{key}=false" ]
|
|
||||||
else
|
else
|
||||||
[ argument, key ]
|
[ argument, key ]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Kamal
|
module Kamal
|
||||||
VERSION = "2.6.0"
|
VERSION = "2.1.1"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
||||||
|
|
||||||
run_command("boot", "mysql").tap do |output|
|
run_command("boot", "mysql").tap do |output|
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.3", output
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" private.registry/mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -24,21 +24,17 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:directories).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:directories).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:directories).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("busybox")
|
|
||||||
|
|
||||||
run_command("boot", "all").tap do |output|
|
run_command("boot", "all").tap do |output|
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.3", output
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.1", output
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.2", output
|
assert_match /docker login.*on 1.1.1.2/, output
|
||||||
assert_match "docker login other.registry -u [REDACTED] -p [REDACTED] on 1.1.1.3", output
|
|
||||||
assert_match /docker network create kamal.*on 1.1.1.1/, output
|
assert_match /docker network create kamal.*on 1.1.1.1/, output
|
||||||
assert_match /docker network create kamal.*on 1.1.1.2/, output
|
assert_match /docker network create kamal.*on 1.1.1.2/, output
|
||||||
assert_match /docker network create kamal.*on 1.1.1.3/, output
|
assert_match /docker network create kamal.*on 1.1.1.3/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" private.registry/mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
||||||
assert_match "docker run --name custom-box --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --env-file .kamal/apps/app/env/accessories/busybox.env --label service=\"custom-box\" other.registry/busybox:latest on 1.1.1.3", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,16 +60,13 @@ class CliAccessoryTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "reboot all" do
|
test "reboot all" do
|
||||||
Kamal::Commands::Registry.any_instance.expects(:login).times(4)
|
Kamal::Commands::Registry.any_instance.expects(:login).times(3)
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:stop).with("mysql")
|
Kamal::Cli::Accessory.any_instance.expects(:stop).with("mysql")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("mysql")
|
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("mysql")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:boot).with("mysql", prepare: false)
|
Kamal::Cli::Accessory.any_instance.expects(:boot).with("mysql", prepare: false)
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:stop).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:stop).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:boot).with("redis", prepare: false)
|
Kamal::Cli::Accessory.any_instance.expects(:boot).with("redis", prepare: false)
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:stop).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:boot).with("busybox", prepare: false)
|
|
||||||
|
|
||||||
run_command("reboot", "all")
|
run_command("reboot", "all")
|
||||||
end
|
end
|
||||||
@@ -101,7 +94,7 @@ class CliAccessoryTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "details with non-existent accessory" do
|
test "details with non-existent accessory" do
|
||||||
assert_equal "No accessory by the name of 'hello' (options: mysql, redis, and busybox)", stderred { run_command("details", "hello") }
|
assert_equal "No accessory by the name of 'hello' (options: mysql and redis)", stderred { run_command("details", "hello") }
|
||||||
end
|
end
|
||||||
|
|
||||||
test "details with all" do
|
test "details with all" do
|
||||||
@@ -115,7 +108,6 @@ class CliAccessoryTest < CliTestCase
|
|||||||
|
|
||||||
test "exec" do
|
test "exec" do
|
||||||
run_command("exec", "mysql", "mysql -v").tap do |output|
|
run_command("exec", "mysql", "mysql -v").tap do |output|
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED]", output
|
|
||||||
assert_match "Launching command from new container", output
|
assert_match "Launching command from new container", output
|
||||||
assert_match "mysql -v", output
|
assert_match "mysql -v", output
|
||||||
end
|
end
|
||||||
@@ -188,10 +180,6 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_image).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:remove_image).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_service_directory).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:remove_service_directory).with("redis")
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:stop).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_image).with("busybox")
|
|
||||||
Kamal::Cli::Accessory.any_instance.expects(:remove_service_directory).with("busybox")
|
|
||||||
|
|
||||||
run_command("remove", "all", "-y")
|
run_command("remove", "all", "-y")
|
||||||
end
|
end
|
||||||
@@ -201,7 +189,7 @@ class CliAccessoryTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "remove_image" do
|
test "remove_image" do
|
||||||
assert_match "docker image rm --force private.registry/mysql:5.7", run_command("remove_image", "mysql")
|
assert_match "docker image rm --force mysql", run_command("remove_image", "mysql")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove_service_directory" do
|
test "remove_service_directory" do
|
||||||
@@ -213,8 +201,8 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
|
|
||||||
run_command("boot", "redis", "--hosts", "1.1.1.1").tap do |output|
|
run_command("boot", "redis", "--hosts", "1.1.1.1").tap do |output|
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.1", output
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
assert_no_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.2", output
|
assert_no_match /docker login.*on 1.1.1.2/, output
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||||
assert_no_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
assert_no_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
||||||
end
|
end
|
||||||
@@ -225,8 +213,8 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
|
|
||||||
run_command("boot", "redis", "--hosts", "1.1.1.1,1.1.1.3").tap do |output|
|
run_command("boot", "redis", "--hosts", "1.1.1.1,1.1.1.3").tap do |output|
|
||||||
assert_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.1", output
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
assert_no_match "docker login private.registry -u [REDACTED] -p [REDACTED] on 1.1.1.3", output
|
assert_no_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||||
assert_no_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.3", output
|
assert_no_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.3", output
|
||||||
end
|
end
|
||||||
@@ -237,7 +225,7 @@ class CliAccessoryTest < CliTestCase
|
|||||||
assert_match "Upgrading all accessories on 1.1.1.3,1.1.1.1,1.1.1.2...", output
|
assert_match "Upgrading all accessories on 1.1.1.3,1.1.1.1,1.1.1.2...", output
|
||||||
assert_match "docker network create kamal on 1.1.1.3", output
|
assert_match "docker network create kamal on 1.1.1.3", output
|
||||||
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST="%" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" private.registry/mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST="%" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
assert_match "Upgraded all accessories on 1.1.1.3,1.1.1.1,1.1.1.2...", output
|
assert_match "Upgraded all accessories on 1.1.1.3,1.1.1.1,1.1.1.2...", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -247,26 +235,14 @@ class CliAccessoryTest < CliTestCase
|
|||||||
assert_match "Upgrading all accessories on 1.1.1.3...", output
|
assert_match "Upgrading all accessories on 1.1.1.3...", output
|
||||||
assert_match "docker network create kamal on 1.1.1.3", output
|
assert_match "docker network create kamal on 1.1.1.3", output
|
||||||
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
assert_match "docker container stop app-mysql on 1.1.1.3", output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST="%" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" private.registry/mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST="%" --env-file .kamal/apps/app/env/accessories/mysql.env --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
assert_match "Upgraded all accessories on 1.1.1.3", output
|
assert_match "Upgraded all accessories on 1.1.1.3", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot with web role filter" do
|
|
||||||
run_command("boot", "redis", "-r", "web").tap do |output|
|
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/apps/app/env/accessories/redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot with workers role filter" do
|
|
||||||
run_command("boot", "redis", "-r", "workers").tap do |output|
|
|
||||||
assert_no_match "docker run", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Accessory.start([ *command, "-c", "test/fixtures/deploy_with_accessories_with_different_registries.yml" ]) }
|
stdouted { Kamal::Cli::Accessory.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class CliAppTest < CliTestCase
|
|||||||
.returns("12345678") # running version
|
.returns("12345678") # running version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("123") # old version
|
.returns("123") # old version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
@@ -37,20 +37,13 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "boot uses group strategy when specified" do
|
test "boot uses group strategy when specified" do
|
||||||
Kamal::Cli::App.any_instance.stubs(:on).with("1.1.1.1").twice
|
Kamal::Cli::App.any_instance.stubs(:on).with("1.1.1.1").times(2) # ensure locks dir, acquire & release lock
|
||||||
Kamal::Cli::App.any_instance.stubs(:on).with([ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ]).times(3)
|
Kamal::Cli::App.any_instance.stubs(:on).with([ "1.1.1.1" ]) # tag container
|
||||||
|
|
||||||
# Strategy is used when booting the containers
|
# Strategy is used when booting the containers
|
||||||
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.1", "1.1.1.2", "1.1.1.3" ]).with_block_given
|
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.1" ], in: :groups, limit: 3, wait: 2).with_block_given
|
||||||
Kamal::Cli::App.any_instance.expects(:on).with([ "1.1.1.4" ]).with_block_given
|
|
||||||
Object.any_instance.expects(:sleep).with(2).twice
|
|
||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
run_command("boot", config: :with_boot_strategy)
|
||||||
|
|
||||||
run_command("boot", config: :with_boot_strategy, host: nil).tap do |output|
|
|
||||||
assert_hook_ran "pre-app-boot", output, count: 2
|
|
||||||
assert_hook_ran "post-app-boot", output, count: 2
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot errors don't leave lock in place" do
|
test "boot errors don't leave lock in place" do
|
||||||
@@ -70,7 +63,7 @@ class CliAppTest < CliTestCase
|
|||||||
.returns("12345678") # running version
|
.returns("12345678") # running version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("123").twice # old version
|
.returns("123").twice # old version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
@@ -80,7 +73,7 @@ class CliAppTest < CliTestCase
|
|||||||
run_command("boot", config: :with_assets).tap do |output|
|
run_command("boot", config: :with_assets).tap do |output|
|
||||||
assert_match "docker tag dhh/app:latest dhh/app:latest", output
|
assert_match "docker tag dhh/app:latest dhh/app:latest", output
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/assets/volumes/web-latest ; cp -rnT .kamal/apps/app/assets/extracted/web-latest .kamal/apps/app/assets/volumes/web-latest ; cp -rnT .kamal/apps/app/assets/extracted/web-latest .kamal/apps/app/assets/volumes/web-123 || true ; cp -rnT .kamal/apps/app/assets/extracted/web-123 .kamal/apps/app/assets/volumes/web-latest || true", output
|
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/assets/volumes/web-latest ; cp -rnT .kamal/apps/app/assets/extracted/web-latest .kamal/apps/app/assets/volumes/web-latest ; cp -rnT .kamal/apps/app/assets/extracted/web-latest .kamal/apps/app/assets/volumes/web-123 || true ; cp -rnT .kamal/apps/app/assets/extracted/web-123 .kamal/apps/app/assets/volumes/web-latest || true", output
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/assets/extracted/web-latest && docker container rm app-web-assets 2> /dev/null || true && docker container create --name app-web-assets dhh/app:latest && docker container cp -L app-web-assets:/public/assets/. .kamal/apps/app/assets/extracted/web-latest && docker container rm app-web-assets", output
|
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/assets/extracted/web-latest && docker stop -t 1 app-web-assets 2> /dev/null || true && docker run --name app-web-assets --detach --rm --entrypoint sleep dhh/app:latest 1000000 && docker cp -L app-web-assets:/public/assets/. .kamal/apps/app/assets/extracted/web-latest && docker stop -t 1 app-web-assets", output
|
||||||
assert_match /docker run --detach --restart unless-stopped --name app-web-latest --network kamal --hostname 1.1.1.1-[0-9a-f]{12} /, output
|
assert_match /docker run --detach --restart unless-stopped --name app-web-latest --network kamal --hostname 1.1.1.1-[0-9a-f]{12} /, output
|
||||||
assert_match "docker container ls --all --filter name=^app-web-123$ --quiet | xargs docker stop", output
|
assert_match "docker container ls --all --filter name=^app-web-123$ --quiet | xargs docker stop", output
|
||||||
assert_match "/usr/bin/env find .kamal/apps/app/assets/extracted -maxdepth 1 -name 'web-*' ! -name web-latest -exec rm -rf \"{}\" + ; find .kamal/apps/app/assets/volumes -maxdepth 1 -name 'web-*' ! -name web-latest -exec rm -rf \"{}\" +", output
|
assert_match "/usr/bin/env find .kamal/apps/app/assets/extracted -maxdepth 1 -name 'web-*' ! -name web-latest -exec rm -rf \"{}\" + ; find .kamal/apps/app/assets/volumes -maxdepth 1 -name 'web-*' ! -name web-latest -exec rm -rf \"{}\" +", output
|
||||||
@@ -99,7 +92,7 @@ class CliAppTest < CliTestCase
|
|||||||
.returns("12345678") # running version
|
.returns("12345678") # running version
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("123") # old version
|
.returns("123") # old version
|
||||||
|
|
||||||
run_command("boot", config: :with_env_tags).tap do |output|
|
run_command("boot", config: :with_env_tags).tap do |output|
|
||||||
@@ -192,34 +185,6 @@ class CliAppTest < CliTestCase
|
|||||||
Thread.report_on_exception = true
|
Thread.report_on_exception = true
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot with only workers" do
|
|
||||||
Object.any_instance.stubs(:sleep)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("123") # old version
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
|
||||||
.returns("running").at_least_once # workers health check
|
|
||||||
|
|
||||||
run_command("boot", config: :with_only_workers, host: nil).tap do |output|
|
|
||||||
assert_match /First workers container is healthy on 1.1.1.\d, booting any other roles/, output
|
|
||||||
assert_no_match "kamal-proxy", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot with error pages" do
|
|
||||||
with_error_pages(directory: "public") do
|
|
||||||
stub_running
|
|
||||||
run_command("boot", config: :with_error_pages).tap do |output|
|
|
||||||
assert_match /Uploading .*kamal-error-pages.*\/latest to \.kamal\/proxy\/apps-config\/app\/error_pages/, output
|
|
||||||
assert_match "docker tag dhh/app:latest dhh/app:latest", output
|
|
||||||
assert_match /docker run --detach --restart unless-stopped --name app-web-latest --network kamal --hostname 1.1.1.1-[0-9a-f]{12} /, output
|
|
||||||
assert_match "docker container ls --all --filter name=^app-web-123$ --quiet | xargs docker stop", output
|
|
||||||
assert_match "Running /usr/bin/env find .kamal/proxy/apps-config/app/error_pages -mindepth 1 -maxdepth 1 ! -name latest -exec rm -rf {} + on 1.1.1.1", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("999") # old version
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("999") # old version
|
||||||
|
|
||||||
@@ -231,17 +196,17 @@ class CliAppTest < CliTestCase
|
|||||||
|
|
||||||
test "stop" do
|
test "stop" do
|
||||||
run_command("stop").tap do |output|
|
run_command("stop").tap do |output|
|
||||||
assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop", output
|
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 stop", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "stale_containers" do
|
test "stale_containers" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=destination=", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
||||||
.returns("12345678\n87654321\n")
|
.returns("12345678\n87654321\n")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("12345678\n")
|
.returns("12345678\n")
|
||||||
|
|
||||||
run_command("stale_containers").tap do |output|
|
run_command("stale_containers").tap do |output|
|
||||||
@@ -251,11 +216,11 @@ class CliAppTest < CliTestCase
|
|||||||
|
|
||||||
test "stop stale_containers" do
|
test "stop stale_containers" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=destination=", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
||||||
.returns("12345678\n87654321\n")
|
.returns("12345678\n87654321\n")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("12345678\n")
|
.returns("12345678\n")
|
||||||
|
|
||||||
run_command("stale_containers", "--stop").tap do |output|
|
run_command("stale_containers", "--stop").tap do |output|
|
||||||
@@ -266,17 +231,15 @@ class CliAppTest < CliTestCase
|
|||||||
|
|
||||||
test "details" do
|
test "details" do
|
||||||
run_command("details").tap do |output|
|
run_command("details").tap do |output|
|
||||||
assert_match "docker ps --filter label=service=app --filter label=destination= --filter label=role=web", output
|
assert_match "docker ps --filter label=service=app --filter label=role=web", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove" do
|
test "remove" do
|
||||||
run_command("remove").tap do |output|
|
run_command("remove").tap do |output|
|
||||||
assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop", output
|
assert_match /#{Regexp.escape("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 stop")}/, output
|
||||||
assert_match "docker container prune --force --filter label=service=app", output
|
assert_match /#{Regexp.escape("docker container prune --force --filter label=service=app")}/, output
|
||||||
assert_match "docker image prune --all --force --filter label=service=app", output
|
assert_match /#{Regexp.escape("docker image prune --all --force --filter label=service=app")}/, output
|
||||||
assert_match "rm -r .kamal/apps/app on 1.1.1.1", output
|
|
||||||
assert_match "rm -r .kamal/proxy/apps-config/app on 1.1.1.1", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -298,86 +261,40 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove_app_directories" do
|
|
||||||
run_command("remove_app_directories").tap do |output|
|
|
||||||
assert_match "rm -r .kamal/apps/app on 1.1.1.1", output
|
|
||||||
assert_match "rm -r .kamal/proxy/apps-config/app on 1.1.1.1", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec" do
|
test "exec" do
|
||||||
run_command("exec", "ruby -v").tap do |output|
|
run_command("exec", "ruby -v").tap do |output|
|
||||||
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output
|
||||||
assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "exec without command fails" do
|
|
||||||
error = assert_raises(ArgumentError, "Exec requires a command to be specified") do
|
|
||||||
run_command("exec")
|
|
||||||
end
|
|
||||||
assert_equal "No command provided. You must specify a command to execute.", error.message
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec separate arguments" do
|
test "exec separate arguments" do
|
||||||
run_command("exec", "ruby", " -v").tap do |output|
|
run_command("exec", "ruby", " -v").tap do |output|
|
||||||
assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output
|
assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec detach" do
|
|
||||||
run_command("exec", "--detach", "ruby -v").tap do |output|
|
|
||||||
assert_match "docker run --detach --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec detach with reuse" do
|
|
||||||
assert_raises(ArgumentError, "Detach is not compatible with reuse") do
|
|
||||||
run_command("exec", "--detach", "--reuse", "ruby -v")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec detach with interactive" do
|
|
||||||
assert_raises(ArgumentError, "Detach is not compatible with interactive") do
|
|
||||||
run_command("exec", "--interactive", "--detach", "ruby -v")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "exec detach with interactive and reuse" do
|
|
||||||
assert_raises(ArgumentError, "Detach is not compatible with interactive or reuse") do
|
|
||||||
run_command("exec", "--interactive", "--detach", "--reuse", "ruby -v")
|
|
||||||
end
|
end
|
||||||
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=destination= --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=destination= --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
|
||||||
assert_match "docker exec app-web-999 ruby -v", output
|
assert_match "docker exec app-web-999 ruby -v", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "exec interactive" do
|
test "exec interactive" do
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:exec)
|
SSHKit::Backend::Abstract.any_instance.expects(:exec)
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v'")
|
.with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v'")
|
||||||
|
|
||||||
run_command("exec", "-i", "ruby -v").tap do |output|
|
run_command("exec", "-i", "ruby -v").tap do |output|
|
||||||
assert_hook_ran "pre-connect", output
|
|
||||||
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
|
||||||
assert_match "Get most recent version available as an image...", output
|
assert_match "Get most recent version available as an image...", output
|
||||||
assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output
|
assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "exec interactive with reuse" do
|
test "exec interactive with reuse" do
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:exec)
|
SSHKit::Backend::Abstract.any_instance.expects(:exec)
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'docker exec -it app-web-999 ruby -v'")
|
.with("ssh -t root@1.1.1.1 -p 22 'docker exec -it app-web-999 ruby -v'")
|
||||||
|
|
||||||
run_command("exec", "-i", "--reuse", "ruby -v").tap do |output|
|
run_command("exec", "-i", "--reuse", "ruby -v").tap do |output|
|
||||||
assert_hook_ran "pre-connect", output
|
|
||||||
assert_match "Get current version of running container...", output
|
assert_match "Get current version of running container...", output
|
||||||
assert_match "Running /usr/bin/env sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done on 1.1.1.1", output
|
assert_match "Running /usr/bin/env 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 on 1.1.1.1", output
|
||||||
assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output
|
assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -396,55 +313,46 @@ class CliAppTest < CliTestCase
|
|||||||
|
|
||||||
test "logs" do
|
test "logs" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
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=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1| xargs docker logs --timestamps --tail 10 2>&1'")
|
.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=destination= --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=destination= --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 --timestamps --tail 100 2>&1", run_command("logs")
|
||||||
|
|
||||||
assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --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 --timestamps 2>&1 | grep 'hey'", run_command("logs", "--grep", "hey")
|
||||||
|
|
||||||
assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --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")
|
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
|
end
|
||||||
|
|
||||||
test "logs with follow" do
|
test "logs with follow" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 10 --follow 2>&1'")
|
.with("ssh -t root@1.1.1.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 10 --follow 2>&1'")
|
||||||
|
|
||||||
assert_match "sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow")
|
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 10 --follow 2>&1", run_command("logs", "--follow")
|
||||||
end
|
|
||||||
|
|
||||||
test "logs with follow and container_id" do
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1'")
|
|
||||||
|
|
||||||
assert_match "echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow", "--container-id", "ID123")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with follow and grep" do
|
test "logs with follow and grep" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"hey\"'")
|
.with("ssh -t root@1.1.1.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 --follow 2>&1 | grep \"hey\"'")
|
||||||
|
|
||||||
assert_match "sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"hey\"", run_command("logs", "--follow", "--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 --follow 2>&1 | grep \"hey\"", run_command("logs", "--follow", "--grep", "hey")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with follow, grep and grep options" do
|
test "logs with follow, grep and grep options" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
SSHKit::Backend::Abstract.any_instance.stubs(:exec)
|
||||||
.with("ssh -t root@1.1.1.1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"hey\" -C 2'")
|
.with("ssh -t root@1.1.1.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 --follow 2>&1 | grep \"hey\" -C 2'")
|
||||||
|
|
||||||
assert_match "sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "--follow", "--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 --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "--follow", "--grep", "hey", "--grep-options", "-C 2")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "version" do
|
test "version" do
|
||||||
run_command("version").tap do |output|
|
run_command("version").tap do |output|
|
||||||
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", 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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "version through main" do
|
test "version through main" do
|
||||||
with_argv([ "app", "version", "-c", "test/fixtures/deploy_with_accessories.yml", "--hosts", "1.1.1.1" ]) do
|
stdouted { Kamal::Cli::Main.start([ "app", "version", "-c", "test/fixtures/deploy_with_accessories.yml", "--hosts", "1.1.1.1" ]) }.tap do |output|
|
||||||
stdouted { Kamal::Cli::Main.start }.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
|
||||||
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -489,24 +397,6 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "live" do
|
|
||||||
run_command("live").tap do |output|
|
|
||||||
assert_match "docker exec kamal-proxy kamal-proxy resume app-web on 1.1.1.1", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "maintenance" do
|
|
||||||
run_command("maintenance").tap do |output|
|
|
||||||
assert_match "docker exec kamal-proxy kamal-proxy stop app-web --drain-timeout=\"30s\" on 1.1.1.1", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "maintenance with options" do
|
|
||||||
run_command("maintenance", "--message", "Hello", "--drain_timeout", "10").tap do |output|
|
|
||||||
assert_match "docker exec kamal-proxy kamal-proxy stop app-web --drain-timeout=\"10s\" --message=\"Hello\" on 1.1.1.1", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command, config: :with_accessories, host: "1.1.1.1", allow_execute_error: false)
|
def run_command(*command, config: :with_accessories, host: "1.1.1.1", allow_execute_error: false)
|
||||||
stdouted do
|
stdouted do
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class CliBuildTest < CliTestCase
|
|||||||
test "push" do
|
test "push" do
|
||||||
with_build_directory do |build_directory|
|
with_build_directory do |build_directory|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
||||||
@@ -21,33 +22,11 @@ class CliBuildTest < CliTestCase
|
|||||||
.returns("")
|
.returns("")
|
||||||
|
|
||||||
run_command("push", "--verbose").tap do |output|
|
run_command("push", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-build", output
|
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 --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. 2>&1 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
|
|
||||||
|
|
||||||
test "push --output=docker" do
|
|
||||||
with_build_directory do |build_directory|
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:git, "-C", anything, :"rev-parse", :HEAD)
|
|
||||||
.returns(Kamal::Git.revision)
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
|
||||||
.with(:git, "-C", anything, :status, "--porcelain")
|
|
||||||
.returns("")
|
|
||||||
|
|
||||||
run_command("push", "--output=docker", "--verbose").tap do |output|
|
|
||||||
assert_hook_ran "pre-build", output
|
|
||||||
assert_match /Cloning repo into build directory/, output
|
|
||||||
assert_match /git -C #{Dir.tmpdir}\/kamal-clones\/app-#{pwd_sha} clone #{Dir.pwd}/, output
|
|
||||||
assert_match /docker --version && docker buildx version/, output
|
|
||||||
assert_match /docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -57,7 +36,6 @@ class CliBuildTest < CliTestCase
|
|||||||
stub_setup
|
stub_setup
|
||||||
|
|
||||||
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.stubs(:execute).with { |*args| args[0..1] == [ :docker, :login ] }
|
|
||||||
|
|
||||||
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, "--recurse-submodules")
|
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
|
||||||
@@ -71,7 +49,7 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:git, "-C", build_directory, :submodule, :update, "--init")
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(: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, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", "2>&1")
|
.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)
|
||||||
@@ -90,12 +68,13 @@ class CliBuildTest < CliTestCase
|
|||||||
|
|
||||||
test "push without clone" do
|
test "push without clone" do
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "build", subcommand: "push" }
|
||||||
|
|
||||||
run_command("push", "--verbose", fixture: :without_clone).tap do |output|
|
run_command("push", "--verbose", fixture: :without_clone).tap do |output|
|
||||||
assert_no_match /Cloning repo into build directory/, output
|
assert_no_match /Cloning repo into build directory/, output
|
||||||
assert_hook_ran "pre-build", output
|
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 --output=type=registry --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile . 2>&1 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
|
||||||
|
|
||||||
@@ -104,7 +83,6 @@ class CliBuildTest < CliTestCase
|
|||||||
stub_setup
|
stub_setup
|
||||||
|
|
||||||
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.stubs(:execute).with { |*args| args[0..1] == [ :docker, :login ] }
|
|
||||||
|
|
||||||
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, "--recurse-submodules")
|
.with(:git, "-C", "#{Dir.tmpdir}/kamal-clones/app-#{pwd_sha}", :clone, Dir.pwd, "--recurse-submodules")
|
||||||
@@ -141,9 +119,6 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
||||||
.with { |*args| args[0..1] == [ :docker, :login ] }
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||||
.with(:docker, :buildx, :rm, "kamal-local-docker-container")
|
.with(:docker, :buildx, :rm, "kamal-local-docker-container")
|
||||||
|
|
||||||
@@ -165,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, "--output=type=registry", "--platform", "linux/amd64", "--builder", "kamal-local-docker-container", "-t", "dhh/app:999", "-t", "dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".", "2>&1")
|
.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
|
||||||
@@ -180,7 +155,7 @@ class CliBuildTest < CliTestCase
|
|||||||
.raises(SSHKit::Command::Failed.new("no buildx"))
|
.raises(SSHKit::Command::Failed.new("no buildx"))
|
||||||
|
|
||||||
Kamal::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false)
|
Kamal::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false)
|
||||||
assert_raises(Kamal::Cli::DependencyError) { run_command("push") }
|
assert_raises(Kamal::Cli::Build::BuildError) { run_command("push") }
|
||||||
end
|
end
|
||||||
|
|
||||||
test "push pre-build hook failure" do
|
test "push pre-build hook failure" do
|
||||||
@@ -260,12 +235,6 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create cloud" do
|
|
||||||
run_command("create", fixture: :with_cloud_builder).tap do |output|
|
|
||||||
assert_match /docker buildx create --driver cloud example_org\/cloud_builder/, output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "create with error" do
|
test "create with error" do
|
||||||
stub_setup
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
@@ -283,12 +252,6 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove cloud" do
|
|
||||||
run_command("remove", fixture: :with_cloud_builder).tap do |output|
|
|
||||||
assert_match /docker buildx rm cloud-example_org-cloud_builder/, output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "details" do
|
test "details" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||||
.with(:docker, :context, :ls, "&&", :docker, :buildx, :ls)
|
.with(:docker, :context, :ls, "&&", :docker, :buildx, :ls)
|
||||||
@@ -300,33 +263,9 @@ class CliBuildTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "dev" do
|
|
||||||
with_build_directory do |build_directory|
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
|
|
||||||
run_command("dev", "--verbose").tap do |output|
|
|
||||||
assert_no_match(/Cloning repo into build directory/, output)
|
|
||||||
assert_match(/docker --version && docker buildx version/, output)
|
|
||||||
assert_match(/docker buildx build --output=type=docker --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "dev --output=local" do
|
|
||||||
with_build_directory do |build_directory|
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
|
|
||||||
run_command("dev", "--output=local", "--verbose").tap do |output|
|
|
||||||
assert_no_match(/Cloning repo into build directory/, output)
|
|
||||||
assert_match(/docker --version && docker buildx version/, output)
|
|
||||||
assert_match(/docker buildx build --output=type=local --platform linux\/amd64 --builder kamal-local-docker-container -t dhh\/app:999-dirty -t dhh\/app:latest-dirty --label service="app" --file Dockerfile \. 2>&1 as .*@localhost/, output)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command, fixture: :with_accessories)
|
def run_command(*command, fixture: :with_accessories)
|
||||||
stdouted { stderred { Kamal::Cli::Build.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) } }
|
stdouted { Kamal::Cli::Build.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_dependency_checks
|
def stub_dependency_checks
|
||||||
@@ -335,4 +274,17 @@ class CliBuildTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||||
.with { |*args| args[0..1] == [ :docker, :buildx ] }
|
.with { |*args| args[0..1] == [ :docker, :buildx ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_build_directory
|
||||||
|
build_directory = File.join Dir.tmpdir, "kamal-clones", "app-#{pwd_sha}", "kamal"
|
||||||
|
FileUtils.mkdir_p build_directory
|
||||||
|
FileUtils.touch File.join build_directory, "Dockerfile"
|
||||||
|
yield build_directory + "/"
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_rf build_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def pwd_sha
|
||||||
|
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,9 +40,8 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
|
.with(:docker, :buildx, :inspect, "kamal-local-docker-container")
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_hook_ran(hook, output, count: 1)
|
def assert_hook_ran(hook, output, version:, service_version:, hosts:, command:, subcommand: nil, runtime: false, secrets: false)
|
||||||
regexp = ([ "/usr/bin/env .kamal/hooks/#{hook}" ] * count).join(".*")
|
assert_match %r{usr/bin/env\s\.kamal/hooks/#{hook}}, output
|
||||||
assert_match /#{regexp}/m, output
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_argv(*argv)
|
def with_argv(*argv)
|
||||||
@@ -52,17 +51,4 @@ class CliTestCase < ActiveSupport::TestCase
|
|||||||
ensure
|
ensure
|
||||||
ARGV.replace(old_argv)
|
ARGV.replace(old_argv)
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_build_directory
|
|
||||||
build_directory = File.join Dir.tmpdir, "kamal-clones", "app-#{pwd_sha}", "kamal"
|
|
||||||
FileUtils.mkdir_p build_directory
|
|
||||||
FileUtils.touch File.join build_directory, "Dockerfile"
|
|
||||||
yield build_directory + "/"
|
|
||||||
ensure
|
|
||||||
FileUtils.rm_rf build_directory
|
|
||||||
end
|
|
||||||
|
|
||||||
def pwd_sha
|
|
||||||
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class CliMainTest < CliTestCase
|
|||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||||
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:deploy).with(boot_accessories: true)
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:deploy)
|
||||||
|
|
||||||
run_command("setup").tap do |output|
|
run_command("setup").tap do |output|
|
||||||
assert_match /Ensure Docker is installed.../, output
|
assert_match /Ensure Docker is installed.../, output
|
||||||
@@ -21,6 +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:server:bootstrap", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
||||||
# deploy
|
# deploy
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true))
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options)
|
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:proxy:boot", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
@@ -31,6 +33,7 @@ class CliMainTest < CliTestCase
|
|||||||
assert_match /Ensure Docker is installed.../, output
|
assert_match /Ensure Docker is installed.../, output
|
||||||
# deploy
|
# deploy
|
||||||
assert_match /Acquiring the deploy lock/, output
|
assert_match /Acquiring the deploy lock/, output
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
assert_match /Pull app image/, output
|
assert_match /Pull app image/, output
|
||||||
assert_match /Ensure kamal-proxy is running/, output
|
assert_match /Ensure kamal-proxy is running/, output
|
||||||
assert_match /Detect stale containers/, output
|
assert_match /Detect stale containers/, output
|
||||||
@@ -43,6 +46,7 @@ class CliMainTest < CliTestCase
|
|||||||
with_test_secrets("secrets" => "DB_PASSWORD=secret") do
|
with_test_secrets("secrets" => "DB_PASSWORD=secret") do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true }
|
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:build:deliver", [], invoke_options)
|
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:proxy:boot", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
@@ -50,15 +54,17 @@ class CliMainTest < CliTestCase
|
|||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
|
||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" }
|
||||||
|
|
||||||
run_command("deploy", "--verbose").tap do |output|
|
run_command("deploy", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-connect", output
|
assert_hook_ran "pre-connect", output, **hook_variables
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
assert_match /Build and push app image/, output
|
assert_match /Build and push app image/, output
|
||||||
assert_hook_ran "pre-deploy", output
|
assert_hook_ran "pre-deploy", output, **hook_variables, secrets: true
|
||||||
assert_match /Ensure kamal-proxy is running/, output
|
assert_match /Ensure kamal-proxy is running/, output
|
||||||
assert_match /Detect stale containers/, output
|
assert_match /Detect stale containers/, output
|
||||||
assert_match /Prune old containers and images/, output
|
assert_match /Prune old containers and images/, output
|
||||||
assert_hook_ran "post-deploy", output
|
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true, secrets: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -66,6 +72,7 @@ class CliMainTest < CliTestCase
|
|||||||
test "deploy with skip_push" do
|
test "deploy with skip_push" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||||
|
|
||||||
|
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:build:pull", [], invoke_options)
|
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:proxy:boot", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
@@ -74,6 +81,7 @@ class CliMainTest < CliTestCase
|
|||||||
|
|
||||||
run_command("deploy", "--skip_push").tap do |output|
|
run_command("deploy", "--skip_push").tap do |output|
|
||||||
assert_match /Acquiring the deploy lock/, output
|
assert_match /Acquiring the deploy lock/, output
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
assert_match /Pull app image/, output
|
assert_match /Pull app image/, output
|
||||||
assert_match /Ensure kamal-proxy is running/, output
|
assert_match /Ensure kamal-proxy is running/, output
|
||||||
assert_match /Detect stale containers/, output
|
assert_match /Detect stale containers/, output
|
||||||
@@ -116,32 +124,6 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deploy when inheriting lock" do
|
|
||||||
Thread.report_on_exception = false
|
|
||||||
|
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
|
||||||
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli: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))
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
|
|
||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
|
||||||
|
|
||||||
with_kamal_lock_env do
|
|
||||||
KAMAL.reset
|
|
||||||
run_command("deploy").tap do |output|
|
|
||||||
assert_no_match /Acquiring the deploy lock/, output
|
|
||||||
assert_match /Build and push app image/, output
|
|
||||||
assert_match /Ensure kamal-proxy is running/, output
|
|
||||||
assert_match /Detect stale containers/, output
|
|
||||||
assert_match /Prune old containers and images/, output
|
|
||||||
assert_no_match /Releasing the deploy lock/, output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "deploy error when locking" do
|
test "deploy error when locking" do
|
||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
|
|
||||||
@@ -173,11 +155,11 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deploy errors during outside section leave remote lock" do
|
test "deploy errors during outside section leave remove lock" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, :skip_local => false }
|
||||||
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke)
|
Kamal::Cli::Main.any_instance.expects(:invoke)
|
||||||
.with("kamal:cli:build:deliver", [], invoke_options)
|
.with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false))
|
||||||
.raises(RuntimeError)
|
.raises(RuntimeError)
|
||||||
|
|
||||||
assert_not KAMAL.holding_lock?
|
assert_not KAMAL.holding_lock?
|
||||||
@@ -190,6 +172,7 @@ class CliMainTest < CliTestCase
|
|||||||
test "deploy with skipped hooks" do
|
test "deploy with skipped hooks" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => true }
|
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:build:deliver", [], invoke_options)
|
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:proxy:boot", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
@@ -204,6 +187,7 @@ class CliMainTest < CliTestCase
|
|||||||
test "deploy with missing secrets" do
|
test "deploy with missing secrets" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false }
|
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:build:deliver", [], invoke_options)
|
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:proxy:boot", [], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
@@ -222,12 +206,14 @@ class CliMainTest < CliTestCase
|
|||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
|
||||||
|
hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "redeploy" }
|
||||||
|
|
||||||
run_command("redeploy", "--verbose").tap do |output|
|
run_command("redeploy", "--verbose").tap do |output|
|
||||||
assert_hook_ran "pre-connect", output
|
assert_hook_ran "pre-connect", output, **hook_variables
|
||||||
assert_match /Build and push app image/, output
|
assert_match /Build and push app image/, output
|
||||||
assert_hook_ran "pre-deploy", output
|
assert_hook_ran "pre-deploy", output, **hook_variables
|
||||||
assert_match /Running the pre-deploy hook.../, output
|
assert_match /Running the pre-deploy hook.../, output
|
||||||
assert_hook_ran "post-deploy", output
|
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -264,7 +250,7 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet")
|
||||||
.returns("version-to-rollback\n").at_least_once
|
.returns("version-to-rollback\n").at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=#{role} --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=destination= --filter label=role=#{role} --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-#{role}-}; done", raise_on_non_zero_exit: false)
|
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=#{role} --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=#{role} --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-#{role}-}; done", raise_on_non_zero_exit: false)
|
||||||
.returns("version-to-rollback\n").at_least_once
|
.returns("version-to-rollback\n").at_least_once
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -273,13 +259,14 @@ class CliMainTest < CliTestCase
|
|||||||
.returns("running").at_least_once # health check
|
.returns("running").at_least_once # health check
|
||||||
|
|
||||||
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
hook_variables = { version: 123, service_version: "app@123", hosts: "1.1.1.1,1.1.1.2,1.1.1.3,1.1.1.4", command: "rollback" }
|
||||||
|
|
||||||
run_command("rollback", "--verbose", "123", config_file: "deploy_with_accessories").tap do |output|
|
run_command("rollback", "--verbose", "123", config_file: "deploy_with_accessories").tap do |output|
|
||||||
assert_hook_ran "pre-deploy", output
|
assert_hook_ran "pre-deploy", output, **hook_variables
|
||||||
assert_match "docker tag dhh/app:123 dhh/app:latest", output
|
assert_match "docker tag dhh/app:123 dhh/app:latest", output
|
||||||
assert_match "docker run --detach --restart unless-stopped --name app-web-123", output
|
assert_match "docker run --detach --restart unless-stopped --name app-web-123", output
|
||||||
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
assert_match "docker container ls --all --filter name=^app-web-version-to-rollback$ --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
||||||
assert_hook_ran "post-deploy", output
|
assert_hook_ran "post-deploy", output, **hook_variables, runtime: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -293,7 +280,7 @@ class CliMainTest < CliTestCase
|
|||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet")
|
||||||
.returns("123").at_least_once
|
.returns("123").at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false)
|
.with(: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", raise_on_non_zero_exit: false)
|
||||||
.returns("").at_least_once
|
.returns("").at_least_once
|
||||||
|
|
||||||
run_command("rollback", "123").tap do |output|
|
run_command("rollback", "123").tap do |output|
|
||||||
@@ -473,7 +460,6 @@ class CliMainTest < CliTestCase
|
|||||||
|
|
||||||
test "run an alias for a console" do
|
test "run an alias for a console" do
|
||||||
run_command("console", config_file: "deploy_with_aliases").tap do |output|
|
run_command("console", config_file: "deploy_with_aliases").tap do |output|
|
||||||
assert_no_match "App Host: 1.1.1.4", output
|
|
||||||
assert_match "docker exec app-console-999 bin/console on 1.1.1.5", 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
|
assert_match "App Host: 1.1.1.5", output
|
||||||
end
|
end
|
||||||
@@ -500,33 +486,6 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "switch config file with an alias" do
|
|
||||||
with_config_files do
|
|
||||||
with_argv([ "other_config" ]) do
|
|
||||||
stdouted { Kamal::Cli::Main.start }.tap do |output|
|
|
||||||
assert_match ":service_with_version: app2-999", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "switch destination with an alias" do
|
|
||||||
with_config_files do
|
|
||||||
with_argv([ "other_destination_config" ]) do
|
|
||||||
stdouted { Kamal::Cli::Main.start }.tap do |output|
|
|
||||||
assert_match ":service_with_version: app3-999", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "run on primary via alias" do
|
|
||||||
run_command("primary_details", config_file: "deploy_with_aliases").tap do |output|
|
|
||||||
assert_match "App Host: 1.1.1.1", output
|
|
||||||
assert_no_match "App Host: 1.1.1.2", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "upgrade" do
|
test "upgrade" do
|
||||||
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_hooks" => false, "confirmed" => true, "rolling" => false }
|
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_hooks" => false, "confirmed" => true, "rolling" => false }
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:upgrade", [], invoke_options)
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:upgrade", [], invoke_options)
|
||||||
@@ -571,28 +530,7 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_config_files
|
|
||||||
Dir.mktmpdir do |tmpdir|
|
|
||||||
config_dir = File.join(tmpdir, "config")
|
|
||||||
FileUtils.mkdir_p(config_dir)
|
|
||||||
FileUtils.cp "test/fixtures/deploy.yml", config_dir
|
|
||||||
FileUtils.cp "test/fixtures/deploy2.yml", config_dir
|
|
||||||
FileUtils.cp "test/fixtures/deploy.elsewhere.yml", config_dir
|
|
||||||
|
|
||||||
Dir.chdir(tmpdir) do
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def assert_file(file, content)
|
def assert_file(file, content)
|
||||||
assert_match content, File.read(file)
|
assert_match content, File.read(file)
|
||||||
end
|
end
|
||||||
|
|
||||||
def with_kamal_lock_env
|
|
||||||
ENV["KAMAL_LOCK"] = "true"
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
ENV.delete("KAMAL_LOCK")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,26 +4,25 @@ class CliProxyTest < CliTestCase
|
|||||||
test "boot" do
|
test "boot" do
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match "docker login", output
|
assert_match "docker login", output
|
||||||
assert_match "mkdir -p .kamal/proxy/apps-config", output
|
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") #{KAMAL.config.proxy_image}", output
|
||||||
assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot old version" do
|
test "boot old version" do
|
||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'")
|
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
|
||||||
.returns("v0.0.1")
|
.returns("v0.0.1")
|
||||||
.at_least_once
|
.at_least_once
|
||||||
|
|
||||||
exception = assert_raises do
|
exception = assert_raises do
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match "docker login", output
|
assert_match "docker login", output
|
||||||
assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output
|
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") #{KAMAL.config.proxy_image}", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}"
|
assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
|
||||||
ensure
|
ensure
|
||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
end
|
end
|
||||||
@@ -31,13 +30,13 @@ class CliProxyTest < CliTestCase
|
|||||||
test "boot correct version" do
|
test "boot correct version" do
|
||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'")
|
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
|
||||||
.returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
.returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
|
||||||
.at_least_once
|
.at_least_once
|
||||||
|
|
||||||
run_command("boot").tap do |output|
|
run_command("boot").tap do |output|
|
||||||
assert_match "docker login", output
|
assert_match "docker login", output
|
||||||
assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output
|
assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") #{KAMAL.config.proxy_image}", output
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
Thread.report_on_exception = false
|
Thread.report_on_exception = false
|
||||||
@@ -56,15 +55,15 @@ class CliProxyTest < CliTestCase
|
|||||||
|
|
||||||
run_command("reboot", "-y").tap do |output|
|
run_command("reboot", "-y").tap do |output|
|
||||||
assert_match "docker container stop kamal-proxy on 1.1.1.1", output
|
assert_match "docker container stop kamal-proxy on 1.1.1.1", output
|
||||||
|
assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.1", output
|
||||||
assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output
|
assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.1", output
|
||||||
assert_match "mkdir -p .kamal/proxy/apps-config on 1.1.1.1", output
|
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") #{KAMAL.config.proxy_image} on 1.1.1.1", output
|
||||||
assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.1", output
|
|
||||||
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.1", output
|
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.1", output
|
||||||
|
|
||||||
assert_match "docker container stop kamal-proxy on 1.1.1.2", output
|
assert_match "docker container stop kamal-proxy on 1.1.1.2", output
|
||||||
|
assert_match "Running docker container stop traefik ; docker container prune --force --filter label=org.opencontainers.image.title=Traefik && docker image prune --all --force --filter label=org.opencontainers.image.title=Traefik on 1.1.1.2", output
|
||||||
assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.2", output
|
assert_match "docker container prune --force --filter label=org.opencontainers.image.title=kamal-proxy on 1.1.1.2", output
|
||||||
assert_match "mkdir -p .kamal/proxy/apps-config on 1.1.1.1", output
|
assert_match "docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") #{KAMAL.config.proxy_image} on 1.1.1.2", output
|
||||||
assert_match "echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config on 1.1.1.2", output
|
|
||||||
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.2", output
|
assert_match "docker exec kamal-proxy kamal-proxy deploy app-web --target=\"abcdefabcdef:80\" --deploy-timeout=\"6s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\" on 1.1.1.2", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -182,8 +181,8 @@ class CliProxyTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("12345678")
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("12345678")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'")
|
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
|
||||||
.returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
.returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
@@ -199,7 +198,7 @@ class CliProxyTest < CliTestCase
|
|||||||
assert_match "/usr/bin/env mkdir -p .kamal", output
|
assert_match "/usr/bin/env mkdir -p .kamal", output
|
||||||
assert_match "docker network create kamal", output
|
assert_match "docker network create kamal", output
|
||||||
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
assert_match "docker login -u [REDACTED] -p [REDACTED]", output
|
||||||
assert_match "docker container start kamal-proxy || echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", output
|
assert_match "docker container start kamal-proxy || docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}", output
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal", output
|
assert_match "/usr/bin/env mkdir -p .kamal", output
|
||||||
assert_match %r{docker rename app-web-latest app-web-latest_replaced_.*}, output
|
assert_match %r{docker rename app-web-latest app-web-latest_replaced_.*}, output
|
||||||
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/env/roles", output
|
assert_match "/usr/bin/env mkdir -p .kamal/apps/app/env/roles", output
|
||||||
@@ -221,8 +220,8 @@ class CliProxyTest < CliTestCase
|
|||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("12345678")
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture_with_info).returns("12345678")
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :awk, "-F:", "'{print $NF}'")
|
.with(:docker, :inspect, "kamal-proxy", "--format '{{.Config.Image}}'", "|", :cut, "-d:", "-f2")
|
||||||
.returns(Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
.returns(Kamal::Configuration::PROXY_MINIMUM_VERSION)
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
.with(:docker, :container, :ls, "--all", "--filter", "name=^app-workers-latest$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'")
|
||||||
@@ -241,10 +240,7 @@ class CliProxyTest < CliTestCase
|
|||||||
run_command("boot_config", "set").tap do |output|
|
run_command("boot_config", "set").tap do |output|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output
|
assert_match "Uploading \"--publish 80:80 --publish 443:443\" to .kamal/proxy/options on #{host}", output
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -253,34 +249,7 @@ class CliProxyTest < CliTestCase
|
|||||||
run_command("boot_config", "set", "--publish", "false").tap do |output|
|
run_command("boot_config", "set", "--publish", "false").tap do |output|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
||||||
assert_match "Uploading \"--log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output
|
assert_match "Uploading \"\" to .kamal/proxy/options on #{host}", output
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set custom max_size" do
|
|
||||||
run_command("boot_config", "set", "--log-max-size", "100m").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=100m\" to .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set no log max size" do
|
|
||||||
run_command("boot_config", "set", "--log-max-size=").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Uploading \"--publish 80:80 --publish 443:443\" to .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -289,117 +258,29 @@ class CliProxyTest < CliTestCase
|
|||||||
run_command("boot_config", "set", "--http-port", "8080", "--https-port", "8443").tap do |output|
|
run_command("boot_config", "set", "--http-port", "8080", "--https-port", "8443").tap do |output|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
||||||
assert_match "Uploading \"--publish 8080:80 --publish 8443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output
|
assert_match "Uploading \"--publish 8080:80 --publish 8443:443\" to .kamal/proxy/options on #{host}", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot_config set bind IP" do
|
|
||||||
run_command("boot_config", "set", "--publish-host-ip", "127.0.0.1").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Uploading \"--publish 127.0.0.1:80:80 --publish 127.0.0.1:443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set multiple bind IPs" do
|
|
||||||
run_command("boot_config", "set", "--publish-host-ip", "127.0.0.1", "--publish-host-ip", "::1").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Uploading \"--publish 127.0.0.1:80:80 --publish 127.0.0.1:443:443 --publish [::1]:80:80 --publish [::1]:443:443 --log-opt max-size=10m\" to .kamal/proxy/options on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set invalid bind IPs" do
|
|
||||||
exception = assert_raises do
|
|
||||||
run_command("boot_config", "set", "--publish-host-ip", "1.2.3.invalidIP", "--publish-host-ip", "::1")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_includes exception.message, "Invalid publish IP address: 1.2.3.invalidIP"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set docker options" do
|
test "boot_config set docker options" do
|
||||||
run_command("boot_config", "set", "--docker_options", "label=foo=bar", "add_host=thishost:thathost").tap do |output|
|
run_command("boot_config", "set", "--docker_options", "label=foo=bar", "add_host=thishost:thathost").tap do |output|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
||||||
assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --label=foo=bar --add_host=thishost:thathost\" to .kamal/proxy/options on #{host}", output
|
assert_match "Uploading \"--publish 80:80 --publish 443:443 --label=foo=bar --add_host=thishost:thathost\" to .kamal/proxy/options on #{host}", output
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set registry" do
|
|
||||||
run_command("boot_config", "set", "--registry", "myreg").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Uploading \"myreg/basecamp/kamal-proxy\" to .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set repository" do
|
|
||||||
run_command("boot_config", "set", "--repository", "myrepo").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Uploading \"myrepo/kamal-proxy\" to .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set image_version" do
|
|
||||||
run_command("boot_config", "set", "--image_version", "0.9.9").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Uploading \"0.9.9\" to .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set run_command" do
|
|
||||||
run_command("boot_config", "set", "--metrics_port", "9000", "--debug", "true").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output
|
|
||||||
assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --expose=9000\" to .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Running /usr/bin/env rm .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Uploading \"kamal-proxy run --debug --metrics-port \\\"9000\\\"\" to .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "boot_config set all" do
|
|
||||||
run_command("boot_config", "set", "--docker_options", "label=foo=bar", "--registry", "myreg", "--repository", "myrepo", "--image_version", "0.9.9", "--metrics_port", "9000", "--debug", "true").tap do |output|
|
|
||||||
%w[ 1.1.1.1 1.1.1.2 ].each do |host|
|
|
||||||
assert_match "Uploading \"--publish 80:80 --publish 443:443 --log-opt max-size=10m --expose=9000 --label=foo=bar\" to .kamal/proxy/options on #{host}", output
|
|
||||||
assert_match "Uploading \"myreg/myrepo/kamal-proxy\" to .kamal/proxy/image on #{host}", output
|
|
||||||
assert_match "Uploading \"0.9.9\" to .kamal/proxy/image_version on #{host}", output
|
|
||||||
assert_match "Uploading \"kamal-proxy run --debug --metrics-port \\\"9000\\\"\" to .kamal/proxy/run_command on #{host}", output
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "boot_config get" do
|
test "boot_config get" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info)
|
||||||
.with(:echo, "$(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\")")
|
.with(:cat, ".kamal/proxy/options", "||", :echo, "\"--publish 80:80 --publish 443:443\"")
|
||||||
.returns("--publish 80:80 --publish 8443:443 --label=foo=bar basecamp/kamal-proxy:v1.0.0")
|
.returns("--publish 80:80 --publish 8443:443 --label=foo=bar")
|
||||||
.twice
|
.twice
|
||||||
|
|
||||||
run_command("boot_config", "get").tap do |output|
|
run_command("boot_config", "get").tap do |output|
|
||||||
assert_match "Host 1.1.1.1: --publish 80:80 --publish 8443:443 --label=foo=bar basecamp/kamal-proxy:v1.0.0", output
|
assert_match "Host 1.1.1.1: --publish 80:80 --publish 8443:443 --label=foo=bar", output
|
||||||
assert_match "Host 1.1.1.2: --publish 80:80 --publish 8443:443 --label=foo=bar basecamp/kamal-proxy:v1.0.0", output
|
assert_match "Host 1.1.1.2: --publish 80:80 --publish 8443:443 --label=foo=bar", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -43,28 +43,6 @@ class CliRegistryTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "login with no docker" do
|
|
||||||
stub_setup
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
||||||
.raises(SSHKit::Command::Failed.new("command not found"))
|
|
||||||
|
|
||||||
assert_raises(Kamal::Cli::DependencyError) { run_command("login") }
|
|
||||||
end
|
|
||||||
|
|
||||||
test "allow remote login with no docker" do
|
|
||||||
stub_setup
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
||||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
|
||||||
.raises(SSHKit::Command::Failed.new("command not found"))
|
|
||||||
|
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
|
||||||
.with { |*args| args[0..1] == [ :docker, :login ] }
|
|
||||||
|
|
||||||
assert_nothing_raised { run_command("login", "--skip-local") }
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) }
|
||||||
|
|||||||
@@ -7,12 +7,6 @@ class CliSecretsTest < CliTestCase
|
|||||||
run_command("fetch", "foo", "bar", "baz", "--account", "myaccount", "--adapter", "test")
|
run_command("fetch", "foo", "bar", "baz", "--account", "myaccount", "--adapter", "test")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "fetch missing --acount" do
|
|
||||||
assert_equal \
|
|
||||||
"No value provided for required options '--account'",
|
|
||||||
run_command("fetch", "foo", "bar", "baz", "--adapter", "test")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "extract" do
|
test "extract" do
|
||||||
assert_equal "oof", run_command("extract", "foo", "{\"foo\":\"oof\", \"bar\":\"rab\", \"baz\":\"zab\"}")
|
assert_equal "oof", run_command("extract", "foo", "{\"foo\":\"oof\", \"bar\":\"rab\", \"baz\":\"zab\"}")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -104,6 +104,28 @@ class CommanderTest < ActiveSupport::TestCase
|
|||||||
assert_equal [ "web", "workers" ], @kamal.roles_on("1.1.1.1").map(&:name)
|
assert_equal [ "web", "workers" ], @kamal.roles_on("1.1.1.1").map(&:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "default group strategy" do
|
||||||
|
assert_empty @kamal.boot_strategy
|
||||||
|
end
|
||||||
|
|
||||||
|
test "specific limit group strategy" do
|
||||||
|
configure_with(:deploy_with_boot_strategy)
|
||||||
|
|
||||||
|
assert_equal({ in: :groups, limit: 3, wait: 2 }, @kamal.boot_strategy)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "percentage-based group strategy" do
|
||||||
|
configure_with(:deploy_with_percentage_boot_strategy)
|
||||||
|
|
||||||
|
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "percentage-based group strategy limit is at least 1" do
|
||||||
|
configure_with(:deploy_with_low_percentage_boot_strategy)
|
||||||
|
|
||||||
|
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||||
|
end
|
||||||
|
|
||||||
test "try to match the primary role from a list of specific roles" do
|
test "try to match the primary role from a list of specific roles" do
|
||||||
configure_with(:deploy_primary_web_role_override)
|
configure_with(:deploy_primary_web_role_override)
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
setup_test_secrets("secrets" => "MYSQL_ROOT_PASSWORD=secret123")
|
setup_test_secrets("secrets" => "MYSQL_ROOT_PASSWORD=secret123")
|
||||||
|
|
||||||
@config = {
|
@config = {
|
||||||
service: "app",
|
service: "app", image: "dhh/app", registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" },
|
||||||
image: "dhh/app",
|
|
||||||
registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" },
|
|
||||||
servers: [ "1.1.1.1" ],
|
servers: [ "1.1.1.1" ],
|
||||||
builder: { "arch" => "amd64" },
|
builder: { "arch" => "amd64" },
|
||||||
accessories: {
|
accessories: {
|
||||||
@@ -41,11 +39,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
"busybox" => {
|
"busybox" => {
|
||||||
"service" => "custom-busybox",
|
"service" => "custom-busybox",
|
||||||
"image" => "busybox:latest",
|
"image" => "busybox:latest",
|
||||||
"registry" => { "server" => "other.registry", "username" => "user", "password" => "pw" },
|
"host" => "1.1.1.7"
|
||||||
"host" => "1.1.1.7",
|
|
||||||
"proxy" => {
|
|
||||||
"host" => "busybox.example.com"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +59,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
new_command(:redis).run.join(" ")
|
new_command(:redis).run.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name custom-busybox --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --env-file .kamal/apps/app/env/accessories/busybox.env --label service=\"custom-busybox\" other.registry/busybox:latest",
|
"docker run --name custom-busybox --detach --restart unless-stopped --network kamal --log-opt max-size=\"10m\" --env-file .kamal/apps/app/env/accessories/busybox.env --label service=\"custom-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -73,18 +67,10 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name custom-busybox --detach --restart unless-stopped --network kamal --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/apps/app/env/accessories/busybox.env --label service=\"custom-busybox\" other.registry/busybox:latest",
|
"docker run --name custom-busybox --detach --restart unless-stopped --network kamal --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/apps/app/env/accessories/busybox.env --label service=\"custom-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run in custom network" do
|
|
||||||
@config[:accessories]["mysql"]["network"] = "custom"
|
|
||||||
|
|
||||||
assert_equal \
|
|
||||||
"docker run --name app-mysql --detach --restart unless-stopped --network custom --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --label service=\"app-mysql\" private.registry/mysql:8.0",
|
|
||||||
new_command(:mysql).run.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker container start app-mysql",
|
"docker container start app-mysql",
|
||||||
@@ -103,6 +89,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
new_command(:mysql).info.join(" ")
|
new_command(:mysql).info.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm --network kamal --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env private.registry/mysql:8.0 mysql -u root",
|
"docker run --rm --network kamal --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env private.registry/mysql:8.0 mysql -u root",
|
||||||
@@ -129,6 +116,8 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test "logs" do
|
test "logs" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker logs app-mysql --timestamps 2>&1",
|
"docker logs app-mysql --timestamps 2>&1",
|
||||||
@@ -169,18 +158,6 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
new_command(:mysql).remove_image.join(" ")
|
new_command(:mysql).remove_image.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deploy" do
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy deploy custom-busybox --target=\"172.1.0.2:80\" --host=\"busybox.example.com\" --deploy-timeout=\"30s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\"",
|
|
||||||
new_command(:busybox).deploy(target: "172.1.0.2").join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "remove" do
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy remove custom-busybox",
|
|
||||||
new_command(:busybox).remove.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_command(accessory)
|
def new_command(accessory)
|
||||||
Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory)
|
Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory)
|
||||||
|
|||||||
@@ -79,18 +79,18 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "stop" do
|
test "stop" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop",
|
"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 stop",
|
||||||
new_command.stop.join(" ")
|
new_command.stop.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "stop with custom drain timeout" do
|
test "stop with custom drain timeout" do
|
||||||
@config[:drain_timeout] = 20
|
@config[:drain_timeout] = 20
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop",
|
"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 stop",
|
||||||
new_command.stop.join(" ")
|
new_command.stop.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=workers --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=destination= --filter label=role=workers --filter status=running --filter status=restarting' | head -1 | xargs docker stop -t 20",
|
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=role=workers --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=workers --filter status=running --filter status=restarting' | head -1 | xargs docker stop -t 20",
|
||||||
new_command(role: "workers").stop.join(" ")
|
new_command(role: "workers").stop.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "info" do
|
test "info" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --filter label=service=app --filter label=destination= --filter label=role=web",
|
"docker ps --filter label=service=app --filter label=role=web",
|
||||||
new_command.info.join(" ")
|
new_command.info.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -135,14 +135,6 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
new_command.deploy(target: "172.1.0.2").join(" ")
|
new_command.deploy(target: "172.1.0.2").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deploy with SSL false" do
|
|
||||||
@config[:proxy] = { "ssl" => false }
|
|
||||||
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy deploy app-web --target=\"172.1.0.2:80\" --deploy-timeout=\"30s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\"",
|
|
||||||
new_command.deploy(target: "172.1.0.2").join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "remove" do
|
test "remove" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker exec kamal-proxy kamal-proxy remove app-web",
|
"docker exec kamal-proxy kamal-proxy remove app-web",
|
||||||
@@ -153,124 +145,100 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "logs" do
|
test "logs" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 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(" ")
|
new_command.logs.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with container_id" do
|
|
||||||
assert_equal \
|
|
||||||
"echo C137 | xargs docker logs --timestamps 2>&1",
|
|
||||||
new_command.logs(container_id: "C137").join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "logs with since" do
|
test "logs with since" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --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(" ")
|
new_command.logs(since: "5m").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with lines" do
|
test "logs with lines" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --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(" ")
|
new_command.logs(lines: "100").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with since and lines" do
|
test "logs with since and lines" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --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(" ")
|
new_command.logs(since: "5m", lines: "100").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with grep" do
|
test "logs with grep" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 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(" ")
|
new_command.logs(grep: "my-id").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with grep and grep options" do
|
test "logs with grep and grep options" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps 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(" ")
|
new_command.logs(grep: "my-id", grep_options: "-C 2").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with since, grep and grep options" do
|
test "logs with since, grep and grep options" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --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",
|
"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(" ")
|
new_command.logs(since: "5m", grep: "my-id", grep_options: "-C 2").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "logs with since and grep" do
|
test "logs with since and grep" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --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(" ")
|
new_command.logs(since: "5m", grep: "my-id").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "follow logs" do
|
test "follow logs" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1'",
|
"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 --follow 2>&1'",
|
||||||
new_command.follow_logs(host: "app-1")
|
new_command.follow_logs(host: "app-1")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"Completed\"'",
|
"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 --follow 2>&1 | grep \"Completed\"'",
|
||||||
new_command.follow_logs(host: "app-1", grep: "Completed")
|
new_command.follow_logs(host: "app-1", grep: "Completed")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"ssh -t root@app-1 -p 22 'echo ID321 | xargs docker logs --timestamps --follow 2>&1'",
|
"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'",
|
||||||
new_command.follow_logs(host: "app-1", container_id: "ID321")
|
|
||||||
|
|
||||||
assert_equal \
|
|
||||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 123 --follow 2>&1'",
|
|
||||||
new_command.follow_logs(host: "app-1", lines: 123)
|
new_command.follow_logs(host: "app-1", lines: 123)
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 123 --follow 2>&1 | grep \"Completed\"'",
|
"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")
|
new_command.follow_logs(host: "app-1", lines: 123, grep: "Completed")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --tail 123 --follow 2>&1 | grep \"Completed\"'",
|
"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")
|
new_command.follow_logs(host: "app-1", timestamps: false, lines: 123, grep: "Completed")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup",
|
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "execute in new container with logging" do
|
|
||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
|
||||||
|
|
||||||
assert_equal \
|
|
||||||
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" dhh/app:999 bin/rails db:setup",
|
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container with env" do
|
test "execute in new container with env" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --env foo=\"bar\" --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup",
|
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --env foo=\"bar\" dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new detached container" do
|
|
||||||
assert_equal \
|
|
||||||
"docker run --detach --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup",
|
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", detach: true, env: {}).join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "execute in new container with tags" do
|
test "execute in new container with tags" do
|
||||||
@config[:servers] = [ { "1.1.1.1" => "tag1" } ]
|
@config[:servers] = [ { "1.1.1.1" => "tag1" } ]
|
||||||
@config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } }
|
@config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup",
|
"docker run --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container with custom options" do
|
test "execute in new container with custom options" do
|
||||||
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup",
|
"docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup",
|
||||||
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -287,7 +255,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container over ssh" do
|
test "execute in new container over ssh" do
|
||||||
assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size="10m" dhh/app:999 bin/rails c},
|
assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails c},
|
||||||
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -295,13 +263,13 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:servers] = [ { "1.1.1.1" => "tag1" } ]
|
@config[:servers] = [ { "1.1.1.1" => "tag1" } ]
|
||||||
@config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } }
|
@config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } }
|
||||||
|
|
||||||
assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails c'",
|
assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails c'",
|
||||||
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container with custom options over ssh" do
|
test "execute in new container with custom options over ssh" do
|
||||||
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c},
|
assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c},
|
||||||
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {})
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -326,7 +294,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run over ssh with proxy" do
|
test "run over ssh with proxy" do
|
||||||
@config[:ssh] = { "proxy" => "2.2.2.2" }
|
@config[:ssh] = { "proxy" => "2.2.2.2" }
|
||||||
assert_equal "ssh -J root@2.2.2.2 -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -J 2.2.2.2 -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with proxy user" do
|
test "run over ssh with proxy user" do
|
||||||
@@ -336,17 +304,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run over ssh with custom user with proxy" do
|
test "run over ssh with custom user with proxy" do
|
||||||
@config[:ssh] = { "user" => "app", "proxy" => "2.2.2.2" }
|
@config[:ssh] = { "user" => "app", "proxy" => "2.2.2.2" }
|
||||||
assert_equal "ssh -J root@2.2.2.2 -t app@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -J 2.2.2.2 -t app@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
|
||||||
|
|
||||||
test "run over ssh with keys config" do
|
|
||||||
@config[:ssh] = { "keys" => [ "path_to_key.pem" ] }
|
|
||||||
assert_equal "ssh -i path_to_key.pem -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "run over ssh with keys config with keys_only" do
|
|
||||||
@config[:ssh] = { "keys" => [ "path_to_key.pem" ], "keys_only" => true }
|
|
||||||
assert_equal "ssh -i path_to_key.pem -o IdentitiesOnly=yes -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with proxy_command" do
|
test "run over ssh with proxy_command" do
|
||||||
@@ -356,7 +314,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "current_running_container_id" do
|
test "current_running_container_id" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -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",
|
||||||
new_command.current_running_container_id.join(" ")
|
new_command.current_running_container_id.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -375,23 +333,23 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "current_running_version" do
|
test "current_running_version" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done",
|
"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",
|
||||||
new_command.current_running_version.join(" ")
|
new_command.current_running_version.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "list_versions" do
|
test "list_versions" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --filter label=service=app --filter label=destination= --filter label=role=web --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done",
|
"docker ps --filter label=service=app --filter label=role=web --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done",
|
||||||
new_command.list_versions.join(" ")
|
new_command.list_versions.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done",
|
"docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done",
|
||||||
new_command.list_versions("--latest", statuses: [ :running, :restarting ]).join(" ")
|
new_command.list_versions("--latest", statuses: [ :running, :restarting ]).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "list_containers" do
|
test "list_containers" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker container ls --all --filter label=service=app --filter label=destination= --filter label=role=web",
|
"docker container ls --all --filter label=service=app --filter label=role=web",
|
||||||
new_command.list_containers.join(" ")
|
new_command.list_containers.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -404,7 +362,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "list_container_names" do
|
test "list_container_names" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker container ls --all --filter label=service=app --filter label=destination= --filter label=role=web --format '{{ .Names }}'",
|
"docker container ls --all --filter label=service=app --filter label=role=web --format '{{ .Names }}'",
|
||||||
new_command.list_container_names.join(" ")
|
new_command.list_container_names.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -423,7 +381,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "remove_containers" do
|
test "remove_containers" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker container prune --force --filter label=service=app --filter label=destination= --filter label=role=web",
|
"docker container prune --force --filter label=service=app --filter label=role=web",
|
||||||
new_command.remove_containers.join(" ")
|
new_command.remove_containers.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -442,14 +400,14 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "remove_images" do
|
test "remove_images" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker image prune --all --force --filter label=service=app",
|
"docker image prune --all --force --filter label=service=app --filter label=role=web",
|
||||||
new_command.remove_images.join(" ")
|
new_command.remove_images.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "remove_images with destination" do
|
test "remove_images with destination" do
|
||||||
@destination = "staging"
|
@destination = "staging"
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker image prune --all --force --filter label=service=app",
|
"docker image prune --all --force --filter label=service=app --filter label=destination=staging --filter label=role=web",
|
||||||
new_command.remove_images.join(" ")
|
new_command.remove_images.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -469,10 +427,10 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
test "extract assets" do
|
test "extract assets" do
|
||||||
assert_equal [
|
assert_equal [
|
||||||
:mkdir, "-p", ".kamal/apps/app/assets/extracted/web-999", "&&",
|
:mkdir, "-p", ".kamal/apps/app/assets/extracted/web-999", "&&",
|
||||||
:docker, :container, :rm, "app-web-assets", "2> /dev/null", "|| true", "&&",
|
:docker, :stop, "-t 1", "app-web-assets", "2> /dev/null", "|| true", "&&",
|
||||||
:docker, :container, :create, "--name", "app-web-assets", "dhh/app:999", "&&",
|
:docker, :run, "--name", "app-web-assets", "--detach", "--rm", "--entrypoint", "sleep", "dhh/app:999", "1000000", "&&",
|
||||||
:docker, :container, :cp, "-L", "app-web-assets:/public/assets/.", ".kamal/apps/app/assets/extracted/web-999", "&&",
|
:docker, :cp, "-L", "app-web-assets:/public/assets/.", ".kamal/apps/app/assets/extracted/web-999", "&&",
|
||||||
:docker, :container, :rm, "app-web-assets"
|
:docker, :stop, "-t 1", "app-web-assets"
|
||||||
], new_command(asset_path: "/public/assets").extract_assets
|
], new_command(asset_path: "/public/assets").extract_assets
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -497,30 +455,6 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
], new_command(asset_path: "/public/assets").clean_up_assets
|
], new_command(asset_path: "/public/assets").clean_up_assets
|
||||||
end
|
end
|
||||||
|
|
||||||
test "live" do
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy resume app-web",
|
|
||||||
new_command.live.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "maintenance" do
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy stop app-web",
|
|
||||||
new_command.maintenance.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "maintenance with options" do
|
|
||||||
assert_equal \
|
|
||||||
"docker exec kamal-proxy kamal-proxy stop app-web --drain-timeout=\"10s\" --message=\"Hi\"",
|
|
||||||
new_command.maintenance(drain_timeout: 10, message: "Hi").join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "remove_proxy_app_directory" do
|
|
||||||
assert_equal \
|
|
||||||
"rm -r .kamal/proxy/apps-config/app",
|
|
||||||
new_command.remove_proxy_app_directory.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_command(role: "web", host: "1.1.1.1", **additional_config)
|
def new_command(role: "web", host: "1.1.1.1", **additional_config)
|
||||||
config = Kamal::Configuration.new(@config.merge(additional_config), destination: @destination, version: "999")
|
config = Kamal::Configuration.new(@config.merge(additional_config), destination: @destination, version: "999")
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
assert_equal [
|
assert_equal [
|
||||||
:mkdir, "-p", ".kamal", "&&",
|
:mkdir, "-p", ".kamal", "&&",
|
||||||
:echo,
|
:echo,
|
||||||
"\"[#{@recorded_at}] [#{@performer}] app removed container\"",
|
"[#{@recorded_at}] [#{@performer}]",
|
||||||
|
"app removed container",
|
||||||
">>", ".kamal/app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], @auditor.record("app removed container")
|
], @auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
@@ -30,7 +31,8 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
assert_equal [
|
assert_equal [
|
||||||
:mkdir, "-p", ".kamal", "&&",
|
:mkdir, "-p", ".kamal", "&&",
|
||||||
:echo,
|
:echo,
|
||||||
"\"[#{@recorded_at}] [#{@performer}] [staging] app removed container\"",
|
"[#{@recorded_at}] [#{@performer}] [staging]",
|
||||||
|
"app removed container",
|
||||||
">>", ".kamal/app-staging-audit.log"
|
">>", ".kamal/app-staging-audit.log"
|
||||||
], auditor.record("app removed container")
|
], auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
@@ -41,7 +43,8 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
assert_equal [
|
assert_equal [
|
||||||
:mkdir, "-p", ".kamal", "&&",
|
:mkdir, "-p", ".kamal", "&&",
|
||||||
:echo,
|
:echo,
|
||||||
"\"[#{@recorded_at}] [#{@performer}] [web] app removed container\"",
|
"[#{@recorded_at}] [#{@performer}] [web]",
|
||||||
|
"app removed container",
|
||||||
">>", ".kamal/app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], auditor.record("app removed container")
|
], auditor.record("app removed container")
|
||||||
end
|
end
|
||||||
@@ -51,7 +54,8 @@ class CommandsAuditorTest < ActiveSupport::TestCase
|
|||||||
assert_equal [
|
assert_equal [
|
||||||
:mkdir, "-p", ".kamal", "&&",
|
:mkdir, "-p", ".kamal", "&&",
|
||||||
:echo,
|
:echo,
|
||||||
"\"[#{@recorded_at}] [#{@performer}] [value] app removed container\"",
|
"[#{@recorded_at}] [#{@performer}] [value]",
|
||||||
|
"app removed container",
|
||||||
">>", ".kamal/app-audit.log"
|
">>", ".kamal/app-audit.log"
|
||||||
], @auditor.record("app removed container", detail: "value")
|
], @auditor.record("app removed container", detail: "value")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||||
assert_equal "local", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "arch" => [ "amd64" ] })
|
builder = new_builder_command(builder: { "arch" => [ "amd64" ] })
|
||||||
assert_equal "local", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "cache" => { "type" => "gha" } })
|
||||||
assert_equal "local", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" } })
|
||||||
assert_equal "hybrid", builder.name
|
assert_equal "hybrid", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-hybrid-docker-container-ssh---app-127-0-0-1 -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" }, "local" => false })
|
builder = new_builder_command(builder: { "arch" => [ "amd64", "arm64" ], "remote" => "ssh://app@127.0.0.1", "cache" => { "type" => "gha" }, "local" => false })
|
||||||
assert_equal "remote", builder.name
|
assert_equal "remote", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64,linux/arm64 --builder kamal-remote-ssh---app-127-0-0-1 -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "arch" => [ "#{remote_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
||||||
assert_equal "remote", builder.name
|
assert_equal "remote", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/#{remote_arch} --builder kamal-remote-ssh---app-host -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -57,22 +57,14 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "remote" => "ssh://app@host", "cache" => { "type" => "gha" } })
|
||||||
assert_equal "local", builder.name
|
assert_equal "local", builder.name
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/#{local_arch} --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --cache-to type=gha --cache-from type=gha --label service=\"app\" --file Dockerfile . 2>&1",
|
"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(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "cloud builder" do
|
|
||||||
builder = new_builder_command(builder: { "arch" => [ "#{local_arch}" ], "driver" => "cloud docker-org-name/builder-name" })
|
|
||||||
assert_equal "cloud", builder.name
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --output=type=registry --platform linux/#{local_arch} --builder cloud-docker-org-name-builder-name -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile . 2>&1",
|
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "build args" do
|
test "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 \
|
||||||
"--label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -81,7 +73,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
FileUtils.touch("Dockerfile")
|
FileUtils.touch("Dockerfile")
|
||||||
builder = new_builder_command(builder: { "secrets" => [ "token_a", "token_b" ] })
|
builder = new_builder_command(builder: { "secrets" => [ "token_a", "token_b" ] })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"--label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"token_a\" --secret id=\"token_b\" --file Dockerfile",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -90,7 +82,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
Pathname.any_instance.expects(:exist?).returns(true).once
|
Pathname.any_instance.expects(:exist?).returns(true).once
|
||||||
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
|
builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"--label service=\"app\" --file Dockerfile.xyz",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -105,21 +97,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
test "build target" do
|
test "build target" do
|
||||||
builder = new_builder_command(builder: { "target" => "prod" })
|
builder = new_builder_command(builder: { "target" => "prod" })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"--label service=\"app\" --file Dockerfile --target prod",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --target prod",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
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 --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile .. 2>&1",
|
"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 "push with build args" do
|
test "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 --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --build-arg a=\"1\" --build-arg b=\"2\" --file Dockerfile . 2>&1",
|
"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
|
||||||
|
|
||||||
@@ -128,7 +120,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
FileUtils.touch("Dockerfile")
|
FileUtils.touch("Dockerfile")
|
||||||
builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] })
|
builder = new_builder_command(builder: { "secrets" => [ "a", "b" ] })
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --secret id=\"a\" --secret id=\"b\" --file Dockerfile . 2>&1",
|
"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
|
||||||
end
|
end
|
||||||
@@ -137,7 +129,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder = new_builder_command(builder: { "ssh" => "default=$SSH_AUTH_SOCK" })
|
builder = new_builder_command(builder: { "ssh" => "default=$SSH_AUTH_SOCK" })
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"--label service=\"app\" --file Dockerfile --ssh default=$SSH_AUTH_SOCK",
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --ssh default=$SSH_AUTH_SOCK",
|
||||||
builder.target.build_options.join(" ")
|
builder.target.build_options.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -148,35 +140,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
test "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 --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile ./foo 2>&1",
|
"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(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "push with provenance" do
|
|
||||||
builder = new_builder_command(builder: { "provenance" => "mode=max" })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max . 2>&1",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "push with provenance false" do
|
|
||||||
builder = new_builder_command(builder: { "provenance" => false })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false . 2>&1",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "push with sbom" do
|
|
||||||
builder = new_builder_command(builder: { "sbom" => true })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom true . 2>&1",
|
|
||||||
builder.push.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "push with sbom false" do
|
|
||||||
builder = new_builder_command(builder: { "sbom" => false })
|
|
||||||
assert_equal \
|
|
||||||
"docker buildx build --output=type=registry --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --sbom false . 2>&1",
|
|
||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -185,26 +149,15 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
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(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "clone path with spaces" do
|
|
||||||
command = new_builder_command
|
|
||||||
Kamal::Git.stubs(:root).returns("/absolute/path with spaces")
|
|
||||||
clone_command = command.clone.join(" ")
|
|
||||||
clone_reset_commands = command.clone_reset_steps.map { |a| a.join(" ") }
|
|
||||||
|
|
||||||
assert_match(%r{path\\ with\\ space}, clone_command)
|
|
||||||
assert_no_match(%r{path with spaces}, clone_command)
|
|
||||||
|
|
||||||
clone_reset_commands.each do |command|
|
|
||||||
assert_match(%r{path\\ with\\ space}, command)
|
|
||||||
assert_no_match(%r{path with spaces}, command)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_builder_command(additional_config = {})
|
def new_builder_command(additional_config = {})
|
||||||
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.deep_merge(additional_config), version: "123"))
|
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.deep_merge(additional_config), version: "123"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_directory
|
||||||
|
"#{Dir.tmpdir}/kamal-clones/app/kamal/"
|
||||||
|
end
|
||||||
|
|
||||||
def local_arch
|
def local_arch
|
||||||
Kamal::Utils.docker_arch
|
Kamal::Utils.docker_arch
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config",
|
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
|||||||
@config.delete(:proxy)
|
@config.delete(:proxy)
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"echo $(cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\") $(cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"):$(cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\") $(cat .kamal/proxy/run_command 2> /dev/null || echo \"\") | xargs docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy --volume $(pwd)/.kamal/proxy/apps-config:/home/kamal-proxy/.apps-config",
|
"docker run --name kamal-proxy --network kamal --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "version" do
|
test "version" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker inspect kamal-proxy --format '{{.Config.Image}}' | awk -F: '{print $NF}'",
|
"docker inspect kamal-proxy --format '{{.Config.Image}}' | cut -d: -f2",
|
||||||
new_command.version.join(" ")
|
new_command.version.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -111,28 +111,10 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
|||||||
new_command.ensure_proxy_directory.join(" ")
|
new_command.ensure_proxy_directory.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "read_boot_options" do
|
test "get_boot_options" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"cat .kamal/proxy/options 2> /dev/null || echo \"--publish 80:80 --publish 443:443 --log-opt max-size=10m\"",
|
"cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\"",
|
||||||
new_command.read_boot_options.join(" ")
|
new_command.get_boot_options.join(" ")
|
||||||
end
|
|
||||||
|
|
||||||
test "read_image" do
|
|
||||||
assert_equal \
|
|
||||||
"cat .kamal/proxy/image 2> /dev/null || echo \"basecamp/kamal-proxy\"",
|
|
||||||
new_command.read_image.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "read_image_version" do
|
|
||||||
assert_equal \
|
|
||||||
"cat .kamal/proxy/image_version 2> /dev/null || echo \"#{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}\"",
|
|
||||||
new_command.read_image_version.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "read_run_command" do
|
|
||||||
assert_equal \
|
|
||||||
"cat .kamal/proxy/run_command 2> /dev/null || echo \"\"",
|
|
||||||
new_command.read_run_command.join(" ")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "reset_boot_options" do
|
test "reset_boot_options" do
|
||||||
@@ -141,30 +123,6 @@ class CommandsProxyTest < ActiveSupport::TestCase
|
|||||||
new_command.reset_boot_options.join(" ")
|
new_command.reset_boot_options.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "reset_image" do
|
|
||||||
assert_equal \
|
|
||||||
"rm .kamal/proxy/image",
|
|
||||||
new_command.reset_image.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reset_image_version" do
|
|
||||||
assert_equal \
|
|
||||||
"rm .kamal/proxy/image_version",
|
|
||||||
new_command.reset_image_version.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "ensure_apps_config_directory" do
|
|
||||||
assert_equal \
|
|
||||||
"mkdir -p .kamal/proxy/apps-config",
|
|
||||||
new_command.ensure_apps_config_directory.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "reset_run_command" do
|
|
||||||
assert_equal \
|
|
||||||
"rm .kamal/proxy/run_command",
|
|
||||||
new_command.reset_run_command.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def new_command
|
def new_command
|
||||||
Kamal::Commands::Proxy.new(Kamal::Configuration.new(@config, version: "123"))
|
Kamal::Commands::Proxy.new(Kamal::Configuration.new(@config, version: "123"))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user