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