nocoffee-kamal 2.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +287 -0
- data/lib/kamal/cli/alias/command.rb +9 -0
- data/lib/kamal/cli/app/boot.rb +125 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +335 -0
- data/lib/kamal/cli/base.rb +198 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +162 -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 +45 -0
- data/lib/kamal/cli/main.rb +279 -0
- data/lib/kamal/cli/proxy.rb +257 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +17 -0
- data/lib/kamal/cli/secrets.rb +43 -0
- data/lib/kamal/cli/server.rb +48 -0
- data/lib/kamal/cli/templates/deploy.yml +98 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.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-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 +109 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +8 -0
- data/lib/kamal/commander/specifics.rb +54 -0
- data/lib/kamal/commander.rb +176 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/execution.rb +30 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +18 -0
- data/lib/kamal/commands/app/proxy.rb +16 -0
- data/lib/kamal/commands/app.rb +115 -0
- data/lib/kamal/commands/auditor.rb +33 -0
- data/lib/kamal/commands/base.rb +98 -0
- data/lib/kamal/commands/builder/base.rb +111 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +56 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +87 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +14 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +186 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +191 -0
- data/lib/kamal/configuration/docs/accessory.yml +100 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +110 -0
- data/lib/kamal/configuration/docs/configuration.yml +178 -0
- data/lib/kamal/configuration/docs/env.yml +85 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +110 -0
- data/lib/kamal/configuration/docs/registry.yml +52 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +29 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy.rb +63 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +220 -0
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +13 -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 +15 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +171 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +393 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +27 -0
- data/lib/kamal/secrets/adapters/base.rb +23 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
- data/lib/kamal/secrets/adapters/one_password.rb +70 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +14 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -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 +14 -0
- metadata +349 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
class Kamal::Cli::Build < Kamal::Cli::Base
|
4
|
+
class BuildError < StandardError; end
|
5
|
+
|
6
|
+
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
7
|
+
def deliver
|
8
|
+
push
|
9
|
+
pull
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "push", "Build and push app image to registry"
|
13
|
+
def push
|
14
|
+
cli = self
|
15
|
+
|
16
|
+
verify_local_dependencies
|
17
|
+
run_hook "pre-build"
|
18
|
+
|
19
|
+
uncommitted_changes = Kamal::Git.uncommitted_changes
|
20
|
+
|
21
|
+
if KAMAL.config.builder.git_clone?
|
22
|
+
if uncommitted_changes.present?
|
23
|
+
say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
|
24
|
+
end
|
25
|
+
|
26
|
+
run_locally do
|
27
|
+
Clone.new(self).prepare
|
28
|
+
end
|
29
|
+
elsif uncommitted_changes.present?
|
30
|
+
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
31
|
+
end
|
32
|
+
|
33
|
+
with_env(KAMAL.config.builder.secrets) do
|
34
|
+
run_locally do
|
35
|
+
begin
|
36
|
+
execute *KAMAL.builder.inspect_builder
|
37
|
+
rescue SSHKit::Command::Failed => e
|
38
|
+
if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
|
39
|
+
warn "Missing compatible builder, so creating a new one first"
|
40
|
+
begin
|
41
|
+
cli.remove
|
42
|
+
rescue SSHKit::Command::Failed
|
43
|
+
raise unless e.message =~ /(context not found|no builder|does not exist)/
|
44
|
+
end
|
45
|
+
cli.create
|
46
|
+
else
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
52
|
+
push = KAMAL.builder.push
|
53
|
+
|
54
|
+
KAMAL.with_verbosity(:debug) do
|
55
|
+
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "pull", "Pull app image from registry onto servers"
|
62
|
+
def pull
|
63
|
+
if (first_hosts = mirror_hosts).any?
|
64
|
+
# Pull on a single host per mirror first to seed them
|
65
|
+
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
|
66
|
+
pull_on_hosts(first_hosts)
|
67
|
+
say "Pulling image on remaining hosts...", :magenta
|
68
|
+
pull_on_hosts(KAMAL.hosts - first_hosts)
|
69
|
+
else
|
70
|
+
pull_on_hosts(KAMAL.hosts)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "create", "Create a build setup"
|
75
|
+
def create
|
76
|
+
if (remote_host = KAMAL.config.builder.remote)
|
77
|
+
connect_to_remote_host(remote_host)
|
78
|
+
end
|
79
|
+
|
80
|
+
run_locally do
|
81
|
+
begin
|
82
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
83
|
+
execute *KAMAL.builder.create
|
84
|
+
rescue SSHKit::Command::Failed => e
|
85
|
+
if e.message =~ /stderr=(.*)/
|
86
|
+
error "Couldn't create remote builder: #{$1}"
|
87
|
+
false
|
88
|
+
else
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "remove", "Remove build setup"
|
96
|
+
def remove
|
97
|
+
run_locally do
|
98
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
99
|
+
execute *KAMAL.builder.remove
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "details", "Show build setup"
|
104
|
+
def details
|
105
|
+
run_locally do
|
106
|
+
puts "Builder: #{KAMAL.builder.name}"
|
107
|
+
puts capture(*KAMAL.builder.info)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def verify_local_dependencies
|
113
|
+
run_locally do
|
114
|
+
begin
|
115
|
+
execute *KAMAL.builder.ensure_local_dependencies_installed
|
116
|
+
rescue SSHKit::Command::Failed => e
|
117
|
+
build_error = e.message =~ /command not found/ ?
|
118
|
+
"Docker is not installed locally" :
|
119
|
+
"Docker buildx plugin is not installed locally"
|
120
|
+
|
121
|
+
raise BuildError, build_error
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def connect_to_remote_host(remote_host)
|
127
|
+
remote_uri = URI.parse(remote_host)
|
128
|
+
if remote_uri.scheme == "ssh"
|
129
|
+
host = SSHKit::Host.new(
|
130
|
+
hostname: remote_uri.host,
|
131
|
+
ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
|
132
|
+
)
|
133
|
+
on(host, options) do
|
134
|
+
execute "true"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def mirror_hosts
|
140
|
+
if KAMAL.hosts.many?
|
141
|
+
mirror_hosts = Concurrent::Hash.new
|
142
|
+
on(KAMAL.hosts) do |host|
|
143
|
+
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
|
144
|
+
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
|
145
|
+
rescue SSHKit::Command::Failed => e
|
146
|
+
raise unless e.message =~ /error calling index: reflect: slice index out of range/
|
147
|
+
end
|
148
|
+
mirror_hosts.values
|
149
|
+
else
|
150
|
+
[]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def pull_on_hosts(hosts)
|
155
|
+
on(hosts) do
|
156
|
+
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
157
|
+
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
158
|
+
execute *KAMAL.builder.pull
|
159
|
+
execute *KAMAL.builder.validate_image
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "concurrent/ivar"
|
2
|
+
|
3
|
+
class Kamal::Cli::Healthcheck::Barrier
|
4
|
+
def initialize
|
5
|
+
@ivar = Concurrent::IVar.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def close
|
9
|
+
set(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
def open
|
13
|
+
set(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def wait
|
17
|
+
unless opened?
|
18
|
+
raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def opened?
|
24
|
+
@ivar.value
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(value)
|
28
|
+
@ivar.set(value)
|
29
|
+
true
|
30
|
+
rescue Concurrent::MultipleAssignmentError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Kamal::Cli::Healthcheck::Poller
|
2
|
+
extend self
|
3
|
+
|
4
|
+
def wait_for_healthy(role, &block)
|
5
|
+
attempt = 1
|
6
|
+
timeout_at = Time.now + KAMAL.config.deploy_timeout
|
7
|
+
readiness_delay = KAMAL.config.readiness_delay
|
8
|
+
|
9
|
+
begin
|
10
|
+
status = block.call
|
11
|
+
|
12
|
+
if status == "running"
|
13
|
+
# Wait for the readiness delay and confirm it is still running
|
14
|
+
if readiness_delay > 0
|
15
|
+
info "Container is running, waiting for readiness delay of #{readiness_delay} seconds"
|
16
|
+
sleep readiness_delay
|
17
|
+
status = block.call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
unless %w[ running healthy ].include?(status)
|
22
|
+
raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
|
23
|
+
end
|
24
|
+
rescue Kamal::Cli::Healthcheck::Error => e
|
25
|
+
time_left = timeout_at - Time.now
|
26
|
+
if time_left > 0
|
27
|
+
sleep [ attempt, time_left ].min
|
28
|
+
attempt += 1
|
29
|
+
retry
|
30
|
+
else
|
31
|
+
raise
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
info "Container is healthy!"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def info(message)
|
40
|
+
SSHKit.config.output.info(message)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Kamal::Cli::Lock < Kamal::Cli::Base
|
2
|
+
desc "status", "Report lock status"
|
3
|
+
def status
|
4
|
+
handle_missing_lock do
|
5
|
+
on(KAMAL.primary_host) do
|
6
|
+
puts capture_with_debug(*KAMAL.lock.status)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "acquire", "Acquire the deploy lock"
|
12
|
+
option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
|
13
|
+
def acquire
|
14
|
+
message = options[:message]
|
15
|
+
ensure_run_directory
|
16
|
+
|
17
|
+
raise_if_locked do
|
18
|
+
on(KAMAL.primary_host) do
|
19
|
+
execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
|
20
|
+
end
|
21
|
+
say "Acquired the deploy lock"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "release", "Release the deploy lock"
|
26
|
+
def release
|
27
|
+
handle_missing_lock do
|
28
|
+
on(KAMAL.primary_host) do
|
29
|
+
execute *KAMAL.lock.release, verbosity: :debug
|
30
|
+
end
|
31
|
+
say "Released the deploy lock"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def handle_missing_lock
|
37
|
+
yield
|
38
|
+
rescue SSHKit::Runner::ExecuteError => e
|
39
|
+
if e.message =~ /No such file or directory/
|
40
|
+
say "There is no deploy lock"
|
41
|
+
else
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,279 @@
|
|
1
|
+
class Kamal::Cli::Main < Kamal::Cli::Base
|
2
|
+
desc "setup", "Setup all accessories, push the env, and deploy app to servers"
|
3
|
+
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
4
|
+
def setup
|
5
|
+
print_runtime do
|
6
|
+
with_lock do
|
7
|
+
invoke_options = deploy_options
|
8
|
+
|
9
|
+
say "Ensure Docker is installed...", :magenta
|
10
|
+
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
11
|
+
|
12
|
+
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
|
13
|
+
deploy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "deploy", "Deploy app to servers"
|
19
|
+
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
20
|
+
def deploy
|
21
|
+
runtime = print_runtime do
|
22
|
+
invoke_options = deploy_options
|
23
|
+
|
24
|
+
say "Log into image registry...", :magenta
|
25
|
+
invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
|
26
|
+
|
27
|
+
if options[:skip_push]
|
28
|
+
say "Pull app image...", :magenta
|
29
|
+
invoke "kamal:cli:build:pull", [], invoke_options
|
30
|
+
else
|
31
|
+
say "Build and push app image...", :magenta
|
32
|
+
invoke "kamal:cli:build:deliver", [], invoke_options
|
33
|
+
end
|
34
|
+
|
35
|
+
with_lock do
|
36
|
+
run_hook "pre-deploy", secrets: true
|
37
|
+
|
38
|
+
say "Ensure kamal-proxy is running...", :magenta
|
39
|
+
invoke "kamal:cli:proxy:boot", [], invoke_options
|
40
|
+
|
41
|
+
say "Detect stale containers...", :magenta
|
42
|
+
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
43
|
+
|
44
|
+
invoke "kamal:cli:app:boot", [], invoke_options
|
45
|
+
|
46
|
+
say "Prune old containers and images...", :magenta
|
47
|
+
invoke "kamal:cli:prune:all", [], invoke_options
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
|
55
|
+
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
56
|
+
def redeploy
|
57
|
+
runtime = print_runtime do
|
58
|
+
invoke_options = deploy_options
|
59
|
+
|
60
|
+
if options[:skip_push]
|
61
|
+
say "Pull app image...", :magenta
|
62
|
+
invoke "kamal:cli:build:pull", [], invoke_options
|
63
|
+
else
|
64
|
+
say "Build and push app image...", :magenta
|
65
|
+
invoke "kamal:cli:build:deliver", [], invoke_options
|
66
|
+
end
|
67
|
+
|
68
|
+
with_lock do
|
69
|
+
run_hook "pre-deploy", secrets: true
|
70
|
+
|
71
|
+
say "Detect stale containers...", :magenta
|
72
|
+
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
73
|
+
|
74
|
+
invoke "kamal:cli:app:boot", [], invoke_options
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "rollback [VERSION]", "Rollback app to VERSION"
|
82
|
+
def rollback(version)
|
83
|
+
rolled_back = false
|
84
|
+
runtime = print_runtime do
|
85
|
+
with_lock do
|
86
|
+
invoke_options = deploy_options
|
87
|
+
|
88
|
+
KAMAL.config.version = version
|
89
|
+
old_version = nil
|
90
|
+
|
91
|
+
if container_available?(version)
|
92
|
+
run_hook "pre-deploy", secrets: true
|
93
|
+
|
94
|
+
invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
|
95
|
+
rolled_back = true
|
96
|
+
else
|
97
|
+
say "The app version '#{version}' is not available as a container (use 'kamal app containers' for available versions)", :red
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s if rolled_back
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "details", "Show details about all containers"
|
106
|
+
def details
|
107
|
+
invoke "kamal:cli:proxy:details"
|
108
|
+
invoke "kamal:cli:app:details"
|
109
|
+
invoke "kamal:cli:accessory:details", [ "all" ]
|
110
|
+
end
|
111
|
+
|
112
|
+
desc "audit", "Show audit log from servers"
|
113
|
+
def audit
|
114
|
+
on(KAMAL.hosts) do |host|
|
115
|
+
puts_by_host host, capture_with_info(*KAMAL.auditor.reveal)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
desc "config", "Show combined config (including secrets!)"
|
120
|
+
def config
|
121
|
+
run_locally do
|
122
|
+
puts Kamal::Utils.redacted(KAMAL.config.to_h).to_yaml
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
desc "docs [SECTION]", "Show Kamal configuration documentation"
|
127
|
+
def docs(section = nil)
|
128
|
+
case section
|
129
|
+
when NilClass
|
130
|
+
puts Kamal::Configuration.validation_doc
|
131
|
+
else
|
132
|
+
puts Kamal::Configuration.const_get(section.titlecase.to_sym).validation_doc
|
133
|
+
end
|
134
|
+
rescue NameError
|
135
|
+
puts "No documentation found for #{section}"
|
136
|
+
end
|
137
|
+
|
138
|
+
desc "init", "Create config stub in config/deploy.yml and secrets stub in .kamal"
|
139
|
+
option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
|
140
|
+
def init
|
141
|
+
require "fileutils"
|
142
|
+
|
143
|
+
if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
|
144
|
+
puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
|
145
|
+
else
|
146
|
+
FileUtils.mkdir_p deploy_file.dirname
|
147
|
+
FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
|
148
|
+
puts "Created configuration file in config/deploy.yml"
|
149
|
+
end
|
150
|
+
|
151
|
+
unless (secrets_file = Pathname.new(File.expand_path(".kamal/secrets"))).exist?
|
152
|
+
FileUtils.mkdir_p secrets_file.dirname
|
153
|
+
FileUtils.cp_r Pathname.new(File.expand_path("templates/secrets", __dir__)), secrets_file
|
154
|
+
puts "Created .kamal/secrets file"
|
155
|
+
end
|
156
|
+
|
157
|
+
unless (hooks_dir = Pathname.new(File.expand_path(".kamal/hooks"))).exist?
|
158
|
+
hooks_dir.mkpath
|
159
|
+
Pathname.new(File.expand_path("templates/sample_hooks", __dir__)).each_child do |sample_hook|
|
160
|
+
FileUtils.cp sample_hook, hooks_dir, preserve: true
|
161
|
+
end
|
162
|
+
puts "Created sample hooks in .kamal/hooks"
|
163
|
+
end
|
164
|
+
|
165
|
+
if options[:bundle]
|
166
|
+
if (binstub = Pathname.new(File.expand_path("bin/kamal"))).exist?
|
167
|
+
puts "Binstub already exists in bin/kamal (remove first to create a new one)"
|
168
|
+
else
|
169
|
+
puts "Adding Kamal to Gemfile and bundle..."
|
170
|
+
run_locally do
|
171
|
+
execute :bundle, :add, :kamal
|
172
|
+
execute :bundle, :binstubs, :kamal
|
173
|
+
end
|
174
|
+
puts "Created binstub file in bin/kamal"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
desc "remove", "Remove kamal-proxy, app, accessories, and registry session from servers"
|
180
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
181
|
+
def remove
|
182
|
+
confirming "This will remove all containers and images. Are you sure?" do
|
183
|
+
with_lock do
|
184
|
+
invoke "kamal:cli:app:remove", [], options.without(:confirmed)
|
185
|
+
invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
|
186
|
+
invoke "kamal:cli:accessory:remove", [ "all" ], options
|
187
|
+
invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
desc "upgrade", "Upgrade from Kamal 1.x to 2.0"
|
193
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
194
|
+
option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
|
195
|
+
def upgrade
|
196
|
+
confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
|
197
|
+
with_lock do
|
198
|
+
if options[:rolling]
|
199
|
+
(KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
|
200
|
+
KAMAL.with_specific_hosts(host) do
|
201
|
+
say "Upgrading #{host}...", :magenta
|
202
|
+
if KAMAL.hosts.include?(host)
|
203
|
+
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
|
204
|
+
reset_invocation(Kamal::Cli::Proxy)
|
205
|
+
end
|
206
|
+
if KAMAL.accessory_hosts.include?(host)
|
207
|
+
invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
|
208
|
+
reset_invocation(Kamal::Cli::Accessory)
|
209
|
+
end
|
210
|
+
say "Upgraded #{host}", :magenta
|
211
|
+
end
|
212
|
+
end
|
213
|
+
else
|
214
|
+
say "Upgrading all hosts...", :magenta
|
215
|
+
invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true)
|
216
|
+
invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true)
|
217
|
+
say "Upgraded all hosts", :magenta
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
desc "version", "Show Kamal version"
|
224
|
+
def version
|
225
|
+
puts Kamal::VERSION
|
226
|
+
end
|
227
|
+
|
228
|
+
desc "accessory", "Manage accessories (db/redis/search)"
|
229
|
+
subcommand "accessory", Kamal::Cli::Accessory
|
230
|
+
|
231
|
+
desc "app", "Manage application"
|
232
|
+
subcommand "app", Kamal::Cli::App
|
233
|
+
|
234
|
+
desc "build", "Build application image"
|
235
|
+
subcommand "build", Kamal::Cli::Build
|
236
|
+
|
237
|
+
desc "lock", "Manage the deploy lock"
|
238
|
+
subcommand "lock", Kamal::Cli::Lock
|
239
|
+
|
240
|
+
desc "proxy", "Manage kamal-proxy"
|
241
|
+
subcommand "proxy", Kamal::Cli::Proxy
|
242
|
+
|
243
|
+
desc "prune", "Prune old application images and containers"
|
244
|
+
subcommand "prune", Kamal::Cli::Prune
|
245
|
+
|
246
|
+
desc "registry", "Login and -out of the image registry"
|
247
|
+
subcommand "registry", Kamal::Cli::Registry
|
248
|
+
|
249
|
+
desc "secrets", "Helpers for extracting secrets"
|
250
|
+
subcommand "secrets", Kamal::Cli::Secrets
|
251
|
+
|
252
|
+
desc "server", "Bootstrap servers with curl and Docker"
|
253
|
+
subcommand "server", Kamal::Cli::Server
|
254
|
+
|
255
|
+
private
|
256
|
+
def container_available?(version)
|
257
|
+
begin
|
258
|
+
on(KAMAL.hosts) do
|
259
|
+
KAMAL.roles_on(host).each do |role|
|
260
|
+
container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
|
261
|
+
raise "Container not found" unless container_id.present?
|
262
|
+
end
|
263
|
+
end
|
264
|
+
rescue SSHKit::Runner::ExecuteError, SSHKit::Runner::MultipleExecuteError => e
|
265
|
+
if e.message =~ /Container not found/
|
266
|
+
say "Error looking for container version #{version}: #{e.message}"
|
267
|
+
return false
|
268
|
+
else
|
269
|
+
raise
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
true
|
274
|
+
end
|
275
|
+
|
276
|
+
def deploy_options
|
277
|
+
{ "version" => KAMAL.config.version }.merge(options.without("skip_push"))
|
278
|
+
end
|
279
|
+
end
|