dash 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/dash +18 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +342 -0
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/assets.rb +24 -0
- data/lib/kamal/cli/app/boot.rb +126 -0
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +368 -0
- data/lib/kamal/cli/base.rb +324 -0
- data/lib/kamal/cli/build/clone.rb +59 -0
- data/lib/kamal/cli/build/port_forwarding.rb +66 -0
- data/lib/kamal/cli/build.rb +242 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +34 -0
- data/lib/kamal/cli/main.rb +299 -0
- data/lib/kamal/cli/proxy.rb +419 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +49 -0
- data/lib/kamal/cli/secrets.rb +50 -0
- data/lib/kamal/cli/server.rb +70 -0
- data/lib/kamal/cli/templates/deploy.yml +102 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +22 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +230 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +118 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +38 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +28 -0
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +125 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +147 -0
- data/lib/kamal/commands/builder/base.rb +143 -0
- data/lib/kamal/commands/builder/clone.rb +32 -0
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +20 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +75 -0
- data/lib/kamal/commands/builder.rb +54 -0
- data/lib/kamal/commands/docker.rb +50 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/loadbalancer.rb +130 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +150 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +38 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +280 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +29 -0
- data/lib/kamal/configuration/builder.rb +218 -0
- data/lib/kamal/configuration/docs/accessory.yml +160 -0
- data/lib/kamal/configuration/docs/alias.yml +29 -0
- data/lib/kamal/configuration/docs/boot.yml +21 -0
- data/lib/kamal/configuration/docs/builder.yml +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +228 -0
- data/lib/kamal/configuration/docs/env.yml +118 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/output.yml +25 -0
- data/lib/kamal/configuration/docs/proxy.yml +207 -0
- data/lib/kamal/configuration/docs/registry.yml +64 -0
- data/lib/kamal/configuration/docs/role.yml +54 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +81 -0
- data/lib/kamal/configuration/docs/sshkit.yml +31 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +42 -0
- data/lib/kamal/configuration/loadbalancer.rb +34 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/output.rb +34 -0
- data/lib/kamal/configuration/proxy/boot.rb +124 -0
- data/lib/kamal/configuration/proxy/run.rb +152 -0
- data/lib/kamal/configuration/proxy.rb +156 -0
- data/lib/kamal/configuration/registry.rb +40 -0
- data/lib/kamal/configuration/role.rb +247 -0
- data/lib/kamal/configuration/servers.rb +25 -0
- data/lib/kamal/configuration/ssh.rb +76 -0
- data/lib/kamal/configuration/sshkit.rb +26 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +13 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +15 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +47 -0
- data/lib/kamal/configuration/validator/registry.rb +27 -0
- data/lib/kamal/configuration/validator/role.rb +13 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +251 -0
- data/lib/kamal/configuration/volume.rb +29 -0
- data/lib/kamal/configuration.rb +465 -0
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +37 -0
- data/lib/kamal/otel_shipper.rb +176 -0
- data/lib/kamal/output/base_logger.rb +29 -0
- data/lib/kamal/output/file_logger.rb +51 -0
- data/lib/kamal/output/formatter.rb +36 -0
- data/lib/kamal/output/otel_logger.rb +70 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
- data/lib/kamal/secrets/adapters/base.rb +33 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
- data/lib/kamal/secrets/adapters/one_password.rb +104 -0
- data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
- data/lib/kamal/secrets/adapters/test.rb +16 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
- data/lib/kamal/secrets.rb +53 -0
- data/lib/kamal/sshkit_with_ext.rb +273 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +15 -0
- metadata +388 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Name of your application. Used to uniquely configure containers.
|
|
2
|
+
service: my-app
|
|
3
|
+
|
|
4
|
+
# Name of the container image.
|
|
5
|
+
image: my-user/my-app
|
|
6
|
+
|
|
7
|
+
# Deploy to these servers.
|
|
8
|
+
servers:
|
|
9
|
+
web:
|
|
10
|
+
- 192.168.0.1
|
|
11
|
+
# job:
|
|
12
|
+
# hosts:
|
|
13
|
+
# - 192.168.0.1
|
|
14
|
+
# cmd: bin/jobs
|
|
15
|
+
|
|
16
|
+
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
|
|
17
|
+
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
|
|
18
|
+
#
|
|
19
|
+
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
|
|
20
|
+
proxy:
|
|
21
|
+
ssl: true
|
|
22
|
+
host: app.example.com
|
|
23
|
+
# Proxy connects to your container on port 80 by default.
|
|
24
|
+
# app_port: 3000
|
|
25
|
+
|
|
26
|
+
# Credentials for your image host.
|
|
27
|
+
registry:
|
|
28
|
+
server: localhost:5555
|
|
29
|
+
# Specify the registry server, if you're not using Docker Hub
|
|
30
|
+
# server: registry.digitalocean.com / ghcr.io / ...
|
|
31
|
+
# username: my-user
|
|
32
|
+
|
|
33
|
+
# Always use an access token rather than real password (pulled from .kamal/secrets).
|
|
34
|
+
# password:
|
|
35
|
+
# - KAMAL_REGISTRY_PASSWORD
|
|
36
|
+
|
|
37
|
+
# Configure builder setup.
|
|
38
|
+
builder:
|
|
39
|
+
arch: amd64
|
|
40
|
+
# Pass in additional build args needed for your Dockerfile.
|
|
41
|
+
# args:
|
|
42
|
+
# RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
|
|
43
|
+
|
|
44
|
+
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
|
45
|
+
#
|
|
46
|
+
# env:
|
|
47
|
+
# clear:
|
|
48
|
+
# DB_HOST: 192.168.0.2
|
|
49
|
+
# secret:
|
|
50
|
+
# - RAILS_MASTER_KEY
|
|
51
|
+
|
|
52
|
+
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
|
53
|
+
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
|
|
54
|
+
#
|
|
55
|
+
# aliases:
|
|
56
|
+
# shell: app exec --interactive --reuse "bash"
|
|
57
|
+
|
|
58
|
+
# Use a different ssh user than root
|
|
59
|
+
#
|
|
60
|
+
# ssh:
|
|
61
|
+
# user: app
|
|
62
|
+
|
|
63
|
+
# Use a persistent storage volume.
|
|
64
|
+
#
|
|
65
|
+
# volumes:
|
|
66
|
+
# - "app_storage:/app/storage"
|
|
67
|
+
|
|
68
|
+
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
|
69
|
+
# hitting 404 on in-flight requests. Combines all files from new and old
|
|
70
|
+
# version inside the asset_path.
|
|
71
|
+
#
|
|
72
|
+
# asset_path: /app/public/assets
|
|
73
|
+
|
|
74
|
+
# Configure rolling deploys by setting a wait time between batches of restarts.
|
|
75
|
+
#
|
|
76
|
+
# boot:
|
|
77
|
+
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
|
78
|
+
# wait: 2
|
|
79
|
+
|
|
80
|
+
# Use accessory services (secrets come from .kamal/secrets).
|
|
81
|
+
#
|
|
82
|
+
# accessories:
|
|
83
|
+
# db:
|
|
84
|
+
# image: mysql:8.0
|
|
85
|
+
# host: 192.168.0.2
|
|
86
|
+
# port: 3306
|
|
87
|
+
# env:
|
|
88
|
+
# clear:
|
|
89
|
+
# MYSQL_ROOT_HOST: '%'
|
|
90
|
+
# secret:
|
|
91
|
+
# - MYSQL_ROOT_PASSWORD
|
|
92
|
+
# files:
|
|
93
|
+
# - config/mysql/production.cnf:/etc/mysql/my.cnf
|
|
94
|
+
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
|
|
95
|
+
# directories:
|
|
96
|
+
# - data:/var/lib/mysql
|
|
97
|
+
# redis:
|
|
98
|
+
# image: valkey/valkey:8
|
|
99
|
+
# host: 192.168.0.2
|
|
100
|
+
# port: 6379
|
|
101
|
+
# directories:
|
|
102
|
+
# - data:/data
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
|
|
3
|
+
# A sample post-deploy hook
|
|
4
|
+
#
|
|
5
|
+
# These environment variables are available:
|
|
6
|
+
# KAMAL_RECORDED_AT
|
|
7
|
+
# KAMAL_PERFORMER
|
|
8
|
+
# KAMAL_VERSION
|
|
9
|
+
# KAMAL_HOSTS
|
|
10
|
+
# KAMAL_ROLES (if set)
|
|
11
|
+
# KAMAL_DESTINATION (if set)
|
|
12
|
+
# KAMAL_RUNTIME
|
|
13
|
+
|
|
14
|
+
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
|
|
3
|
+
# A sample pre-build hook
|
|
4
|
+
#
|
|
5
|
+
# Checks:
|
|
6
|
+
# 1. We have a clean checkout
|
|
7
|
+
# 2. A remote is configured
|
|
8
|
+
# 3. The branch has been pushed to the remote
|
|
9
|
+
# 4. The version we are deploying matches the remote
|
|
10
|
+
#
|
|
11
|
+
# These environment variables are available:
|
|
12
|
+
# KAMAL_RECORDED_AT
|
|
13
|
+
# KAMAL_PERFORMER
|
|
14
|
+
# KAMAL_VERSION
|
|
15
|
+
# KAMAL_HOSTS
|
|
16
|
+
# KAMAL_ROLES (if set)
|
|
17
|
+
# KAMAL_DESTINATION (if set)
|
|
18
|
+
|
|
19
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
20
|
+
echo "Git checkout is not clean, aborting..." >&2
|
|
21
|
+
git status --porcelain >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
first_remote=$(git remote)
|
|
26
|
+
|
|
27
|
+
if [ -z "$first_remote" ]; then
|
|
28
|
+
echo "No git remote set, aborting..." >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
current_branch=$(git branch --show-current)
|
|
33
|
+
|
|
34
|
+
if [ -z "$current_branch" ]; then
|
|
35
|
+
echo "Not on a git branch, aborting..." >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
|
40
|
+
|
|
41
|
+
if [ -z "$remote_head" ]; then
|
|
42
|
+
echo "Branch not pushed to remote, aborting..." >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
|
47
|
+
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# A sample pre-connect check
|
|
4
|
+
#
|
|
5
|
+
# Warms DNS before connecting to hosts in parallel
|
|
6
|
+
#
|
|
7
|
+
# These environment variables are available:
|
|
8
|
+
# KAMAL_RECORDED_AT
|
|
9
|
+
# KAMAL_PERFORMER
|
|
10
|
+
# KAMAL_VERSION
|
|
11
|
+
# KAMAL_HOSTS
|
|
12
|
+
# KAMAL_ROLES (if set)
|
|
13
|
+
# KAMAL_DESTINATION (if set)
|
|
14
|
+
# KAMAL_RUNTIME
|
|
15
|
+
|
|
16
|
+
hosts = ENV["KAMAL_HOSTS"].split(",")
|
|
17
|
+
results = nil
|
|
18
|
+
max = 3
|
|
19
|
+
|
|
20
|
+
elapsed = Benchmark.realtime do
|
|
21
|
+
results = hosts.map do |host|
|
|
22
|
+
Thread.new do
|
|
23
|
+
tries = 1
|
|
24
|
+
|
|
25
|
+
begin
|
|
26
|
+
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
|
27
|
+
rescue SocketError
|
|
28
|
+
if tries < max
|
|
29
|
+
puts "Retrying DNS warmup: #{host}"
|
|
30
|
+
tries += 1
|
|
31
|
+
sleep rand
|
|
32
|
+
retry
|
|
33
|
+
else
|
|
34
|
+
puts "DNS warmup failed: #{host}"
|
|
35
|
+
host
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
tries
|
|
40
|
+
end
|
|
41
|
+
end.map(&:value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
retries = results.sum - hosts.size
|
|
45
|
+
nopes = results.count { |r| r == max }
|
|
46
|
+
|
|
47
|
+
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# A sample pre-deploy hook
|
|
4
|
+
#
|
|
5
|
+
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
|
|
6
|
+
#
|
|
7
|
+
# Fails unless the combined status is "success"
|
|
8
|
+
#
|
|
9
|
+
# These environment variables are available:
|
|
10
|
+
# KAMAL_RECORDED_AT
|
|
11
|
+
# KAMAL_PERFORMER
|
|
12
|
+
# KAMAL_VERSION
|
|
13
|
+
# KAMAL_HOSTS
|
|
14
|
+
# KAMAL_COMMAND
|
|
15
|
+
# KAMAL_SUBCOMMAND
|
|
16
|
+
# KAMAL_ROLES (if set)
|
|
17
|
+
# KAMAL_DESTINATION (if set)
|
|
18
|
+
|
|
19
|
+
# Only check the build status for production deployments
|
|
20
|
+
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
|
|
21
|
+
exit 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require "bundler/inline"
|
|
25
|
+
|
|
26
|
+
# true = install gems so this is fast on repeat invocations
|
|
27
|
+
gemfile(true, quiet: true) do
|
|
28
|
+
source "https://rubygems.org"
|
|
29
|
+
|
|
30
|
+
gem "octokit"
|
|
31
|
+
gem "faraday-retry"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
MAX_ATTEMPTS = 72
|
|
35
|
+
ATTEMPTS_GAP = 10
|
|
36
|
+
|
|
37
|
+
def exit_with_error(message)
|
|
38
|
+
$stderr.puts message
|
|
39
|
+
exit 1
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class GithubStatusChecks
|
|
43
|
+
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
|
44
|
+
|
|
45
|
+
def initialize
|
|
46
|
+
@remote_url = github_repo_from_remote_url
|
|
47
|
+
@git_sha = `git rev-parse HEAD`.strip
|
|
48
|
+
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
|
49
|
+
refresh!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def refresh!
|
|
53
|
+
@combined_status = github_client.combined_status(remote_url, git_sha)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def state
|
|
57
|
+
combined_status[:state]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def first_status_url
|
|
61
|
+
first_status = combined_status[:statuses].find { |status| status[:state] == state }
|
|
62
|
+
first_status && first_status[:target_url]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def complete_count
|
|
66
|
+
combined_status[:statuses].count { |status| status[:state] != "pending"}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def total_count
|
|
70
|
+
combined_status[:statuses].count
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def current_status
|
|
74
|
+
if total_count > 0
|
|
75
|
+
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
|
|
76
|
+
else
|
|
77
|
+
"Build not started..."
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
def github_repo_from_remote_url
|
|
83
|
+
url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
|
|
84
|
+
if url.start_with?("https://github.com/")
|
|
85
|
+
url.delete_prefix("https://github.com/")
|
|
86
|
+
elsif url.start_with?("git@github.com:")
|
|
87
|
+
url.delete_prefix("git@github.com:")
|
|
88
|
+
else
|
|
89
|
+
url
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
$stdout.sync = true
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
puts "Checking build status..."
|
|
99
|
+
|
|
100
|
+
attempts = 0
|
|
101
|
+
checks = GithubStatusChecks.new
|
|
102
|
+
|
|
103
|
+
loop do
|
|
104
|
+
case checks.state
|
|
105
|
+
when "success"
|
|
106
|
+
puts "Checks passed, see #{checks.first_status_url}"
|
|
107
|
+
exit 0
|
|
108
|
+
when "failure"
|
|
109
|
+
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
|
110
|
+
when "pending"
|
|
111
|
+
attempts += 1
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
|
115
|
+
|
|
116
|
+
puts checks.current_status
|
|
117
|
+
sleep(ATTEMPTS_GAP)
|
|
118
|
+
checks.refresh!
|
|
119
|
+
end
|
|
120
|
+
rescue Octokit::NotFound
|
|
121
|
+
exit_with_error "Build status could not be found"
|
|
122
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
|
|
2
|
+
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
|
|
3
|
+
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
|
|
4
|
+
#
|
|
5
|
+
# When deploying with destinations, shared secrets can go in .kamal/secrets-common and
|
|
6
|
+
# destination-specific secrets in .kamal/secrets.<destination>. This .kamal/secrets file is used
|
|
7
|
+
# only when no destination is selected.
|
|
8
|
+
|
|
9
|
+
# Option 1: Read secrets from the environment
|
|
10
|
+
# KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
|
|
11
|
+
|
|
12
|
+
# Option 2: Read secrets via a command
|
|
13
|
+
# RAILS_MASTER_KEY=$(cat config/master.key)
|
|
14
|
+
# KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
|
|
15
|
+
|
|
16
|
+
# Option 3: Read secrets via kamal secrets helpers
|
|
17
|
+
# These will handle logging in and fetching the secrets in as few calls as possible
|
|
18
|
+
# There are adapters for 1Password, LastPass + Bitwarden
|
|
19
|
+
#
|
|
20
|
+
# SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
|
|
21
|
+
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
|
|
22
|
+
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
|
data/lib/kamal/cli.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Kamal::Cli
|
|
2
|
+
class BootError < StandardError; end
|
|
3
|
+
class HookError < StandardError; end
|
|
4
|
+
class LockError < StandardError; end
|
|
5
|
+
class DependencyError < StandardError; end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# SSHKit uses instance eval, so we need a global const for ergonomics
|
|
9
|
+
KAMAL = Kamal::Commander.new
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class Kamal::Commander::Specifics
|
|
2
|
+
attr_reader :primary_host, :primary_role, :hosts, :roles
|
|
3
|
+
delegate :stable_sort!, to: Kamal::Utils
|
|
4
|
+
|
|
5
|
+
def initialize(config, specific_hosts, specific_roles)
|
|
6
|
+
@config, @specific_hosts, @specific_roles = config, specific_hosts, specific_roles
|
|
7
|
+
|
|
8
|
+
@roles, @hosts = specified_roles, specified_hosts
|
|
9
|
+
|
|
10
|
+
@primary_host = specific_hosts&.first || primary_specific_role&.primary_host || config.primary_host
|
|
11
|
+
@primary_role = primary_or_first_role(roles_on(primary_host))
|
|
12
|
+
|
|
13
|
+
stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
|
|
14
|
+
sort_primary_role_hosts_first!(hosts)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def roles_on(host)
|
|
18
|
+
roles.select { |role| role.hosts.include?(host.to_s) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def app_hosts
|
|
22
|
+
@app_hosts ||= sort_primary_role_hosts_first!(config.app_hosts & specified_hosts)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def proxy_hosts
|
|
26
|
+
config.proxy_hosts & specified_hosts
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def accessory_hosts
|
|
30
|
+
config.accessories.flat_map(&:hosts) & specified_hosts
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
attr_reader :config, :specific_hosts, :specific_roles
|
|
35
|
+
|
|
36
|
+
def primary_specific_role
|
|
37
|
+
primary_or_first_role(specific_roles) if specific_roles.present?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def primary_or_first_role(roles)
|
|
41
|
+
roles.detect { |role| role == config.primary_role } || roles.first
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def specified_roles
|
|
45
|
+
(specific_roles || config.roles) \
|
|
46
|
+
.select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def specified_hosts
|
|
50
|
+
specified_hosts = specific_hosts || config.all_hosts
|
|
51
|
+
|
|
52
|
+
if (specific_role_hosts = specific_roles&.flat_map(&:hosts)).present?
|
|
53
|
+
specified_hosts.select { |host| specific_role_hosts.include?(host) }
|
|
54
|
+
else
|
|
55
|
+
specified_hosts
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sort_primary_role_hosts_first!(hosts)
|
|
60
|
+
stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require "active_support/core_ext/enumerable"
|
|
2
|
+
require "active_support/core_ext/module/delegation"
|
|
3
|
+
require "active_support/core_ext/object/blank"
|
|
4
|
+
require "active_support/broadcast_logger"
|
|
5
|
+
require "active_support/notifications"
|
|
6
|
+
|
|
7
|
+
class Kamal::Commander
|
|
8
|
+
attr_accessor :verbosity, :holding_lock, :connected, :logging, :lock_wait, :lock_wait_timeout, :lock_wait_interval
|
|
9
|
+
attr_reader :specific_roles, :specific_hosts
|
|
10
|
+
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :proxy_hosts, :accessory_hosts, to: :specifics
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
reset
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset
|
|
17
|
+
self.verbosity = :info
|
|
18
|
+
self.holding_lock = ENV["KAMAL_LOCK"] == "true"
|
|
19
|
+
self.connected = false
|
|
20
|
+
self.logging = false
|
|
21
|
+
self.lock_wait = false
|
|
22
|
+
self.lock_wait_timeout = 900
|
|
23
|
+
self.lock_wait_interval = 15
|
|
24
|
+
@modify_depth = 0
|
|
25
|
+
@specifics = @specific_roles = @specific_hosts = nil
|
|
26
|
+
@config = @config_kwargs = nil
|
|
27
|
+
@output_logger = nil
|
|
28
|
+
@commands = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def config
|
|
32
|
+
@config ||= Kamal::Configuration.create_from(**@config_kwargs.to_h).tap do |config|
|
|
33
|
+
@config_kwargs = nil
|
|
34
|
+
configure_sshkit_with(config)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def configure(**kwargs)
|
|
39
|
+
@config, @config_kwargs = nil, kwargs
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def configured?
|
|
43
|
+
@config || @config_kwargs
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def specific_primary!
|
|
47
|
+
@specifics = nil
|
|
48
|
+
if specific_roles.present?
|
|
49
|
+
self.specific_hosts = [ specific_roles.first.primary_host ]
|
|
50
|
+
else
|
|
51
|
+
self.specific_hosts = [ config.primary_host ]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def specific_roles=(role_names)
|
|
56
|
+
@specifics = nil
|
|
57
|
+
@specific_roles = if role_names.present?
|
|
58
|
+
filtered = Kamal::Utils.filter_specific_items(role_names, config.roles)
|
|
59
|
+
raise ArgumentError, "No --roles match for #{role_names.join(',')}" if filtered.empty?
|
|
60
|
+
filtered
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def specific_hosts=(hosts)
|
|
65
|
+
@specifics = nil
|
|
66
|
+
@specific_hosts = if hosts.present?
|
|
67
|
+
filtered = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
|
|
68
|
+
raise ArgumentError, "No --hosts match for #{hosts.join(',')}" if filtered.empty?
|
|
69
|
+
filtered
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def with_specific_hosts(hosts)
|
|
74
|
+
original_hosts, self.specific_hosts = specific_hosts, hosts
|
|
75
|
+
yield
|
|
76
|
+
ensure
|
|
77
|
+
self.specific_hosts = original_hosts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def accessory_names
|
|
81
|
+
config.accessories&.collect(&:name) || []
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def app(role: nil, host: nil)
|
|
85
|
+
Kamal::Commands::App.new(config, role: role, host: host)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def accessory(name)
|
|
89
|
+
Kamal::Commands::Accessory.new(config, name: name)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def auditor(**details)
|
|
93
|
+
Kamal::Commands::Auditor.new(config, **details)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def builder
|
|
97
|
+
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def docker
|
|
101
|
+
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def hook
|
|
105
|
+
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def lock
|
|
109
|
+
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def proxy(host)
|
|
113
|
+
Kamal::Commands::Proxy.new(config, host: host)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def loadbalancer_config
|
|
117
|
+
@loadbalancer_config ||= Kamal::Configuration::Loadbalancer.new(config: config, proxy_config: config.proxy.proxy_config, secrets: config.secrets)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def loadbalancer
|
|
121
|
+
@commands[:loadbalancer] ||= Kamal::Commands::Loadbalancer.new(config, loadbalancer_config: loadbalancer_config)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def prune
|
|
125
|
+
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def registry
|
|
129
|
+
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def server
|
|
133
|
+
@commands[:server] ||= Kamal::Commands::Server.new(config)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def alias(name)
|
|
137
|
+
config.aliases[name]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def resolve_alias(name)
|
|
141
|
+
if @config
|
|
142
|
+
@config.aliases[name]&.command
|
|
143
|
+
else
|
|
144
|
+
raw_config = Kamal::Configuration.load_raw_config(**@config_kwargs.to_h.slice(:config_file, :destination))
|
|
145
|
+
raw_config[:aliases]&.dig(name)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def with_verbosity(level)
|
|
150
|
+
old_level = self.verbosity
|
|
151
|
+
|
|
152
|
+
self.verbosity = level
|
|
153
|
+
SSHKit.config.output_verbosity = level
|
|
154
|
+
|
|
155
|
+
yield
|
|
156
|
+
ensure
|
|
157
|
+
self.verbosity = old_level
|
|
158
|
+
SSHKit.config.output_verbosity = old_level
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def modify(command:, subcommand:)
|
|
162
|
+
@logging = true
|
|
163
|
+
if modify_started
|
|
164
|
+
ActiveSupport::Notifications.instrument("modify.kamal",
|
|
165
|
+
command: command, subcommand: subcommand, destination: config.destination, hosts: hosts) { yield }
|
|
166
|
+
else
|
|
167
|
+
yield
|
|
168
|
+
end
|
|
169
|
+
ensure
|
|
170
|
+
output_logger.close if modify_finished
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def log(line)
|
|
174
|
+
output_logger << "#{line}\n" if logging
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def holding_lock?
|
|
178
|
+
self.holding_lock
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def connected?
|
|
182
|
+
self.connected
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
def output_logger
|
|
187
|
+
@output_logger ||= ActiveSupport::BroadcastLogger.new
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def modify_started
|
|
191
|
+
@modify_depth += 1
|
|
192
|
+
@modify_depth == 1
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def modify_finished
|
|
196
|
+
@modify_depth -= 1
|
|
197
|
+
@modify_depth == 0
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Lazy setup of SSHKit
|
|
201
|
+
def configure_sshkit_with(config)
|
|
202
|
+
SSHKit::Backend::Netssh.pool.idle_timeout = config.sshkit.pool_idle_timeout
|
|
203
|
+
SSHKit::Backend::Netssh.configure do |sshkit|
|
|
204
|
+
sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts
|
|
205
|
+
sshkit.dns_retries = config.sshkit.dns_retries
|
|
206
|
+
sshkit.ssh_options = config.ssh.options
|
|
207
|
+
end
|
|
208
|
+
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
|
|
209
|
+
SSHKit.config.output_verbosity = verbosity
|
|
210
|
+
|
|
211
|
+
configure_output_with(config)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def configure_output_with(config)
|
|
215
|
+
return unless config.output.enabled?
|
|
216
|
+
|
|
217
|
+
config.output.loggers.each { |logger| output_logger.broadcast_to(logger) }
|
|
218
|
+
|
|
219
|
+
SSHKit.config.output = Kamal::Output::Formatter.new($stdout, output_logger)
|
|
220
|
+
|
|
221
|
+
at_exit { @output_logger&.close }
|
|
222
|
+
rescue => e
|
|
223
|
+
$stderr.puts "Output logger setup failed: #{e.class}: #{e.message}"
|
|
224
|
+
$stderr.puts e.backtrace.join("\n") if ENV["VERBOSE"]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def specifics
|
|
228
|
+
@specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles)
|
|
229
|
+
end
|
|
230
|
+
end
|