kamal 1.8.3 → 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 +4 -4
- data/README.md +1 -1
- data/lib/kamal/cli/accessory.rb +92 -38
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
- data/lib/kamal/cli/app/boot.rb +23 -16
- 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 +132 -30
- data/lib/kamal/cli/base.rb +57 -53
- data/lib/kamal/cli/build.rb +81 -38
- data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +18 -39
- data/lib/kamal/cli/lock.rb +2 -3
- data/lib/kamal/cli/main.rb +60 -59
- data/lib/kamal/cli/proxy.rb +290 -0
- data/lib/kamal/cli/prune.rb +0 -1
- data/lib/kamal/cli/registry.rb +2 -0
- data/lib/kamal/cli/secrets.rb +49 -0
- data/lib/kamal/cli/server.rb +6 -5
- data/lib/kamal/cli/templates/deploy.yml +53 -53
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +2 -12
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
- 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 +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
- 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 +2 -0
- data/lib/kamal/commander/specifics.rb +19 -6
- data/lib/kamal/commander.rb +39 -32
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +19 -19
- data/lib/kamal/commands/app/assets.rb +10 -10
- data/lib/kamal/commands/app/containers.rb +2 -2
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +7 -4
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app/logging.rb +16 -6
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +25 -24
- data/lib/kamal/commands/auditor.rb +12 -3
- data/lib/kamal/commands/base.rb +54 -8
- data/lib/kamal/commands/builder/base.rb +46 -16
- data/lib/kamal/commands/builder/clone.rb +16 -14
- 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 +21 -45
- data/lib/kamal/commands/docker.rb +4 -0
- data/lib/kamal/commands/hook.rb +8 -2
- data/lib/kamal/commands/lock.rb +2 -6
- data/lib/kamal/commands/proxy.rb +127 -0
- data/lib/kamal/commands/prune.rb +1 -9
- data/lib/kamal/commands/registry.rb +9 -7
- data/lib/kamal/commands/server.rb +11 -1
- data/lib/kamal/configuration/accessory.rb +89 -12
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/builder.rb +73 -15
- data/lib/kamal/configuration/docs/accessory.yml +53 -15
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +3 -3
- data/lib/kamal/configuration/docs/builder.yml +63 -38
- data/lib/kamal/configuration/docs/configuration.yml +62 -46
- data/lib/kamal/configuration/docs/env.yml +61 -17
- data/lib/kamal/configuration/docs/logging.yml +3 -3
- data/lib/kamal/configuration/docs/proxy.yml +168 -0
- data/lib/kamal/configuration/docs/registry.yml +20 -13
- data/lib/kamal/configuration/docs/role.yml +14 -13
- data/lib/kamal/configuration/docs/servers.yml +2 -2
- data/lib/kamal/configuration/docs/ssh.yml +23 -19
- data/lib/kamal/configuration/docs/sshkit.yml +4 -4
- data/lib/kamal/configuration/env/tag.rb +4 -3
- data/lib/kamal/configuration/env.rb +19 -17
- data/lib/kamal/configuration/proxy/boot.rb +129 -0
- data/lib/kamal/configuration/proxy.rb +124 -0
- data/lib/kamal/configuration/registry.rb +7 -6
- data/lib/kamal/configuration/role.rb +69 -98
- data/lib/kamal/configuration/servers.rb +8 -1
- data/lib/kamal/configuration/validator/accessory.rb +6 -2
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +6 -0
- data/lib/kamal/configuration/validator/proxy.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +3 -1
- data/lib/kamal/configuration/validator/servers.rb +1 -1
- data/lib/kamal/configuration/validator.rb +62 -24
- data/lib/kamal/configuration.rb +96 -50
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +7 -1
- data/lib/kamal/git.rb +10 -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 +1 -0
- data/lib/kamal/utils.rb +30 -0
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +3 -1
- metadata +63 -36
- data/lib/kamal/cli/env.rb +0 -54
- data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
- data/lib/kamal/cli/templates/sample_hooks/pre-traefik-reboot.sample +0 -3
- data/lib/kamal/cli/templates/template.env +0 -2
- data/lib/kamal/cli/traefik.rb +0 -122
- data/lib/kamal/commands/app/cord.rb +0 -22
- data/lib/kamal/commands/builder/multiarch/remote.rb +0 -65
- data/lib/kamal/commands/builder/multiarch.rb +0 -41
- data/lib/kamal/commands/builder/native/cached.rb +0 -25
- data/lib/kamal/commands/builder/native/remote.rb +0 -67
- data/lib/kamal/commands/builder/native.rb +0 -20
- data/lib/kamal/commands/traefik.rb +0 -85
- data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
- data/lib/kamal/configuration/docs/traefik.yml +0 -62
- data/lib/kamal/configuration/healthcheck.rb +0 -63
- data/lib/kamal/configuration/traefik.rb +0 -60
@@ -0,0 +1,290 @@
|
|
1
|
+
class Kamal::Cli::Proxy < Kamal::Cli::Base
|
2
|
+
desc "boot", "Boot proxy on servers"
|
3
|
+
def boot
|
4
|
+
with_lock do
|
5
|
+
on(KAMAL.hosts) do |host|
|
6
|
+
execute *KAMAL.docker.create_network
|
7
|
+
rescue SSHKit::Command::Failed => e
|
8
|
+
raise unless e.message.include?("already exists")
|
9
|
+
end
|
10
|
+
|
11
|
+
on(KAMAL.proxy_hosts) do |host|
|
12
|
+
execute *KAMAL.registry.login
|
13
|
+
|
14
|
+
version = capture_with_info(*KAMAL.proxy.version).strip.presence
|
15
|
+
|
16
|
+
if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
|
17
|
+
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}"
|
18
|
+
end
|
19
|
+
execute *KAMAL.proxy.ensure_apps_config_directory
|
20
|
+
execute *KAMAL.proxy.start_or_run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
|
26
|
+
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
27
|
+
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
|
28
|
+
option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
|
29
|
+
option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host"
|
30
|
+
option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs"
|
31
|
+
option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image"
|
32
|
+
option :repository, type: :string, default: nil, desc: "Repository for the proxy image"
|
33
|
+
option :image_version, type: :string, default: nil, desc: "Version of the proxy to run"
|
34
|
+
option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on"
|
35
|
+
option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode"
|
36
|
+
option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
|
37
|
+
def boot_config(subcommand)
|
38
|
+
proxy_boot_config = KAMAL.config.proxy_boot
|
39
|
+
|
40
|
+
case subcommand
|
41
|
+
when "set"
|
42
|
+
boot_options = [
|
43
|
+
*(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
|
44
|
+
*(proxy_boot_config.logging_args(options[:log_max_size])),
|
45
|
+
*("--expose=#{options[:metrics_port]}" if options[:metrics_port]),
|
46
|
+
*options[:docker_options].map { |option| "--#{option}" }
|
47
|
+
]
|
48
|
+
|
49
|
+
image = [
|
50
|
+
options[:registry].presence,
|
51
|
+
options[:repository].presence || proxy_boot_config.repository_name,
|
52
|
+
proxy_boot_config.image_name
|
53
|
+
].compact.join("/")
|
54
|
+
|
55
|
+
image_version = options[:image_version]
|
56
|
+
|
57
|
+
run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
|
58
|
+
run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
|
59
|
+
|
60
|
+
on(KAMAL.proxy_hosts) do |host|
|
61
|
+
execute(*KAMAL.proxy.ensure_proxy_directory)
|
62
|
+
if boot_options != proxy_boot_config.default_boot_options
|
63
|
+
upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file
|
64
|
+
else
|
65
|
+
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
66
|
+
end
|
67
|
+
|
68
|
+
if image != proxy_boot_config.image_default
|
69
|
+
upload! StringIO.new(image), proxy_boot_config.image_file
|
70
|
+
else
|
71
|
+
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
72
|
+
end
|
73
|
+
|
74
|
+
if image_version
|
75
|
+
upload! StringIO.new(image_version), proxy_boot_config.image_version_file
|
76
|
+
else
|
77
|
+
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
78
|
+
end
|
79
|
+
|
80
|
+
if run_command
|
81
|
+
upload! StringIO.new(run_command), proxy_boot_config.run_command_file
|
82
|
+
else
|
83
|
+
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
when "get"
|
87
|
+
|
88
|
+
on(KAMAL.proxy_hosts) do |host|
|
89
|
+
puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}"
|
90
|
+
end
|
91
|
+
when "reset"
|
92
|
+
on(KAMAL.proxy_hosts) do |host|
|
93
|
+
execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
|
94
|
+
execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
|
95
|
+
execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
|
96
|
+
execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "reboot", "Reboot proxy on servers (stop container, remove container, start new container)"
|
104
|
+
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
105
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
106
|
+
def reboot
|
107
|
+
confirming "This will cause a brief outage on each host. Are you sure?" do
|
108
|
+
with_lock do
|
109
|
+
host_groups = options[:rolling] ? KAMAL.proxy_hosts : [ KAMAL.proxy_hosts ]
|
110
|
+
host_groups.each do |hosts|
|
111
|
+
host_list = Array(hosts).join(",")
|
112
|
+
run_hook "pre-proxy-reboot", hosts: host_list
|
113
|
+
on(hosts) do |host|
|
114
|
+
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
115
|
+
execute *KAMAL.registry.login
|
116
|
+
|
117
|
+
"Stopping and removing kamal-proxy on #{host}, if running..."
|
118
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
119
|
+
execute *KAMAL.proxy.remove_container
|
120
|
+
execute *KAMAL.proxy.ensure_apps_config_directory
|
121
|
+
|
122
|
+
execute *KAMAL.proxy.run
|
123
|
+
end
|
124
|
+
run_hook "post-proxy-reboot", hosts: host_list
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "upgrade", "Upgrade to kamal-proxy on servers (stop container, remove container, start new container, reboot app)", hide: true
|
131
|
+
option :rolling, type: :boolean, default: false, desc: "Reboot proxy on hosts in sequence, rather than in parallel"
|
132
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
133
|
+
def upgrade
|
134
|
+
invoke_options = { "version" => KAMAL.config.latest_tag }.merge(options)
|
135
|
+
|
136
|
+
confirming "This will cause a brief outage on each host. Are you sure?" do
|
137
|
+
host_groups = options[:rolling] ? KAMAL.hosts : [ KAMAL.hosts ]
|
138
|
+
host_groups.each do |hosts|
|
139
|
+
host_list = Array(hosts).join(",")
|
140
|
+
say "Upgrading proxy on #{host_list}...", :magenta
|
141
|
+
run_hook "pre-proxy-reboot", hosts: host_list
|
142
|
+
on(hosts) do |host|
|
143
|
+
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
144
|
+
execute *KAMAL.registry.login
|
145
|
+
|
146
|
+
"Stopping and removing Traefik on #{host}, if running..."
|
147
|
+
execute *KAMAL.proxy.cleanup_traefik
|
148
|
+
|
149
|
+
"Stopping and removing kamal-proxy on #{host}, if running..."
|
150
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
151
|
+
execute *KAMAL.proxy.remove_container
|
152
|
+
execute *KAMAL.proxy.remove_image
|
153
|
+
end
|
154
|
+
|
155
|
+
KAMAL.with_specific_hosts(hosts) do
|
156
|
+
invoke "kamal:cli:proxy:boot", [], invoke_options
|
157
|
+
reset_invocation(Kamal::Cli::Proxy)
|
158
|
+
invoke "kamal:cli:app:boot", [], invoke_options
|
159
|
+
reset_invocation(Kamal::Cli::App)
|
160
|
+
invoke "kamal:cli:prune:all", [], invoke_options
|
161
|
+
reset_invocation(Kamal::Cli::Prune)
|
162
|
+
end
|
163
|
+
|
164
|
+
run_hook "post-proxy-reboot", hosts: host_list
|
165
|
+
say "Upgraded proxy on #{host_list}", :magenta
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
desc "start", "Start existing proxy container on servers"
|
171
|
+
def start
|
172
|
+
with_lock do
|
173
|
+
on(KAMAL.proxy_hosts) do |host|
|
174
|
+
execute *KAMAL.auditor.record("Started proxy"), verbosity: :debug
|
175
|
+
execute *KAMAL.proxy.start
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
desc "stop", "Stop existing proxy container on servers"
|
181
|
+
def stop
|
182
|
+
with_lock do
|
183
|
+
on(KAMAL.proxy_hosts) do |host|
|
184
|
+
execute *KAMAL.auditor.record("Stopped proxy"), verbosity: :debug
|
185
|
+
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
desc "restart", "Restart existing proxy container on servers"
|
191
|
+
def restart
|
192
|
+
with_lock do
|
193
|
+
stop
|
194
|
+
start
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
desc "details", "Show details about proxy container from servers"
|
199
|
+
def details
|
200
|
+
on(KAMAL.proxy_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.proxy.info), type: "Proxy" }
|
201
|
+
end
|
202
|
+
|
203
|
+
desc "logs", "Show log lines from proxy on servers"
|
204
|
+
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
205
|
+
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
206
|
+
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
207
|
+
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
208
|
+
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
209
|
+
def logs
|
210
|
+
grep = options[:grep]
|
211
|
+
timestamps = !options[:skip_timestamps]
|
212
|
+
|
213
|
+
if options[:follow]
|
214
|
+
run_locally do
|
215
|
+
info "Following logs on #{KAMAL.primary_host}..."
|
216
|
+
info KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
217
|
+
exec KAMAL.proxy.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, grep: grep)
|
218
|
+
end
|
219
|
+
else
|
220
|
+
since = options[:since]
|
221
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
222
|
+
|
223
|
+
on(KAMAL.proxy_hosts) do |host|
|
224
|
+
puts_by_host host, capture(*KAMAL.proxy.logs(timestamps: timestamps, since: since, lines: lines, grep: grep)), type: "Proxy"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
desc "remove", "Remove proxy container and image from servers"
|
230
|
+
option :force, type: :boolean, default: false, desc: "Force removing proxy when apps are still installed"
|
231
|
+
def remove
|
232
|
+
with_lock do
|
233
|
+
if removal_allowed?(options[:force])
|
234
|
+
stop
|
235
|
+
remove_container
|
236
|
+
remove_image
|
237
|
+
remove_proxy_directory
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
desc "remove_container", "Remove proxy container from servers", hide: true
|
243
|
+
def remove_container
|
244
|
+
with_lock do
|
245
|
+
on(KAMAL.proxy_hosts) do
|
246
|
+
execute *KAMAL.auditor.record("Removed proxy container"), verbosity: :debug
|
247
|
+
execute *KAMAL.proxy.remove_container
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
desc "remove_image", "Remove proxy image from servers", hide: true
|
253
|
+
def remove_image
|
254
|
+
with_lock do
|
255
|
+
on(KAMAL.proxy_hosts) do
|
256
|
+
execute *KAMAL.auditor.record("Removed proxy image"), verbosity: :debug
|
257
|
+
execute *KAMAL.proxy.remove_image
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
desc "remove_proxy_directory", "Remove the proxy directory from servers", hide: true
|
263
|
+
def remove_proxy_directory
|
264
|
+
with_lock do
|
265
|
+
on(KAMAL.proxy_hosts) do
|
266
|
+
execute *KAMAL.proxy.remove_proxy_directory, raise_on_non_zero_exit: false
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
def removal_allowed?(force)
|
273
|
+
on(KAMAL.proxy_hosts) do |host|
|
274
|
+
app_count = capture_with_info(*KAMAL.server.app_directory_count).chomp.to_i
|
275
|
+
raise "The are other applications installed on #{host}" if app_count > 0
|
276
|
+
end
|
277
|
+
|
278
|
+
true
|
279
|
+
rescue SSHKit::Runner::ExecuteError => e
|
280
|
+
raise unless e.message.include?("The are other applications installed on")
|
281
|
+
|
282
|
+
if force
|
283
|
+
say "Forcing, so removing the proxy, even though other apps are installed", :magenta
|
284
|
+
else
|
285
|
+
say "Not removing the proxy, as other apps are installed, ignore this check with kamal proxy remove --force", :magenta
|
286
|
+
end
|
287
|
+
|
288
|
+
force
|
289
|
+
end
|
290
|
+
end
|
data/lib/kamal/cli/prune.rb
CHANGED
data/lib/kamal/cli/registry.rb
CHANGED
@@ -3,6 +3,8 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
|
|
3
3
|
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
4
4
|
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
5
5
|
def login
|
6
|
+
ensure_docker_installed unless options[:skip_local]
|
7
|
+
|
6
8
|
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
7
9
|
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
8
10
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Kamal::Cli::Secrets < Kamal::Cli::Base
|
2
|
+
desc "fetch [SECRETS...]", "Fetch secrets from a vault"
|
3
|
+
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
|
4
|
+
option :account, type: :string, required: false, desc: "The account identifier or username"
|
5
|
+
option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
|
6
|
+
option :inline, type: :boolean, required: false, hidden: true
|
7
|
+
def fetch(*secrets)
|
8
|
+
adapter = initialize_adapter(options[:adapter])
|
9
|
+
|
10
|
+
if adapter.requires_account? && options[:account].blank?
|
11
|
+
return puts "No value provided for required options '--account'"
|
12
|
+
end
|
13
|
+
|
14
|
+
results = adapter.fetch(secrets, **options.slice(:account, :from).symbolize_keys)
|
15
|
+
|
16
|
+
return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "extract", "Extract a single secret from the results of a fetch call"
|
20
|
+
option :inline, type: :boolean, required: false, hidden: true
|
21
|
+
def extract(name, secrets)
|
22
|
+
parsed_secrets = JSON.parse(secrets)
|
23
|
+
value = parsed_secrets[name] || parsed_secrets.find { |k, v| k.end_with?("/#{name}") }&.last
|
24
|
+
|
25
|
+
raise "Could not find secret #{name}" if value.nil?
|
26
|
+
|
27
|
+
return_or_puts value, inline: options[:inline]
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "print", "Print the secrets (for debugging)"
|
31
|
+
def print
|
32
|
+
KAMAL.config.secrets.to_h.each do |key, value|
|
33
|
+
puts "#{key}=#{value}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def initialize_adapter(adapter)
|
39
|
+
Kamal::Secrets::Adapters.lookup(adapter)
|
40
|
+
end
|
41
|
+
|
42
|
+
def return_or_puts(value, inline: nil)
|
43
|
+
if inline
|
44
|
+
value
|
45
|
+
else
|
46
|
+
puts value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/kamal/cli/server.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
class Kamal::Cli::Server < Kamal::Cli::Base
|
2
2
|
desc "exec", "Run a custom command on the server (use --help to show options)"
|
3
3
|
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
4
|
-
def exec(cmd)
|
5
|
-
|
4
|
+
def exec(*cmd)
|
5
|
+
pre_connect_if_required
|
6
|
+
|
7
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
8
|
+
hosts = KAMAL.hosts
|
6
9
|
|
7
10
|
case
|
8
11
|
when options[:interactive]
|
@@ -26,7 +29,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
26
29
|
with_lock do
|
27
30
|
missing = []
|
28
31
|
|
29
|
-
on(KAMAL.hosts
|
32
|
+
on(KAMAL.hosts) do |host|
|
30
33
|
unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
|
31
34
|
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
32
35
|
info "Missing Docker on #{host}. Installing…"
|
@@ -35,8 +38,6 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
35
38
|
missing << host
|
36
39
|
end
|
37
40
|
end
|
38
|
-
|
39
|
-
execute(*KAMAL.server.ensure_run_directory)
|
40
41
|
end
|
41
42
|
|
42
43
|
if missing.any?
|
@@ -2,11 +2,26 @@
|
|
2
2
|
service: my-app
|
3
3
|
|
4
4
|
# Name of the container image.
|
5
|
-
image: user/my-app
|
5
|
+
image: my-user/my-app
|
6
6
|
|
7
7
|
# Deploy to these servers.
|
8
8
|
servers:
|
9
|
-
|
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
|
10
25
|
|
11
26
|
# Credentials for your image host.
|
12
27
|
registry:
|
@@ -14,33 +29,55 @@ registry:
|
|
14
29
|
# server: registry.digitalocean.com / ghcr.io / ...
|
15
30
|
username: my-user
|
16
31
|
|
17
|
-
# Always use an access token rather than real password
|
32
|
+
# Always use an access token rather than real password (pulled from .kamal/secrets).
|
18
33
|
password:
|
19
34
|
- KAMAL_REGISTRY_PASSWORD
|
20
35
|
|
21
|
-
#
|
22
|
-
|
36
|
+
# Configure builder setup.
|
37
|
+
builder:
|
38
|
+
arch: amd64
|
39
|
+
# Pass in additional build args needed for your Dockerfile.
|
40
|
+
# args:
|
41
|
+
# RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
|
42
|
+
|
43
|
+
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
44
|
+
#
|
23
45
|
# env:
|
24
46
|
# clear:
|
25
47
|
# DB_HOST: 192.168.0.2
|
26
48
|
# secret:
|
27
49
|
# - RAILS_MASTER_KEY
|
28
50
|
|
51
|
+
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
52
|
+
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
|
53
|
+
#
|
54
|
+
# aliases:
|
55
|
+
# shell: app exec --interactive --reuse "bash"
|
56
|
+
|
29
57
|
# Use a different ssh user than root
|
58
|
+
#
|
30
59
|
# ssh:
|
31
60
|
# user: app
|
32
61
|
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# secrets:
|
38
|
-
# - GITHUB_TOKEN
|
39
|
-
# remote:
|
40
|
-
# arch: amd64
|
41
|
-
# host: ssh://app@192.168.0.1
|
62
|
+
# Use a persistent storage volume.
|
63
|
+
#
|
64
|
+
# volumes:
|
65
|
+
# - "app_storage:/app/storage"
|
42
66
|
|
43
|
-
#
|
67
|
+
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
68
|
+
# hitting 404 on in-flight requests. Combines all files from new and old
|
69
|
+
# version inside the asset_path.
|
70
|
+
#
|
71
|
+
# asset_path: /app/public/assets
|
72
|
+
|
73
|
+
# Configure rolling deploys by setting a wait time between batches of restarts.
|
74
|
+
#
|
75
|
+
# boot:
|
76
|
+
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
77
|
+
# wait: 2
|
78
|
+
|
79
|
+
# Use accessory services (secrets come from .kamal/secrets).
|
80
|
+
#
|
44
81
|
# accessories:
|
45
82
|
# db:
|
46
83
|
# image: mysql:8.0
|
@@ -57,45 +94,8 @@ registry:
|
|
57
94
|
# directories:
|
58
95
|
# - data:/var/lib/mysql
|
59
96
|
# redis:
|
60
|
-
# image:
|
97
|
+
# image: valkey/valkey:8
|
61
98
|
# host: 192.168.0.2
|
62
99
|
# port: 6379
|
63
100
|
# directories:
|
64
101
|
# - data:/data
|
65
|
-
|
66
|
-
# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
|
67
|
-
# traefik:
|
68
|
-
# args:
|
69
|
-
# accesslog: true
|
70
|
-
# accesslog.format: json
|
71
|
-
|
72
|
-
# Configure a custom healthcheck (default is /up on port 3000)
|
73
|
-
# healthcheck:
|
74
|
-
# path: /healthz
|
75
|
-
# port: 4000
|
76
|
-
|
77
|
-
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
78
|
-
# hitting 404 on in-flight requests. Combines all files from new and old
|
79
|
-
# version inside the asset_path.
|
80
|
-
#
|
81
|
-
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
|
82
|
-
# See https://github.com/basecamp/kamal/issues/626 for details
|
83
|
-
#
|
84
|
-
# asset_path: /rails/public/assets
|
85
|
-
|
86
|
-
# Configure rolling deploys by setting a wait time between batches of restarts.
|
87
|
-
# boot:
|
88
|
-
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
89
|
-
# wait: 2
|
90
|
-
|
91
|
-
# Configure the role used to determine the primary_host. This host takes
|
92
|
-
# deploy locks, runs health checks during the deploy, and follow logs, etc.
|
93
|
-
#
|
94
|
-
# Caution: there's no support for role renaming yet, so be careful to cleanup
|
95
|
-
# the previous role on the deployed hosts.
|
96
|
-
# primary_role: web
|
97
|
-
|
98
|
-
# Controls if we abort when see a role with no hosts. Disabling this may be
|
99
|
-
# useful for more complex deploy configurations.
|
100
|
-
#
|
101
|
-
# allow_empty_roles: false
|
@@ -1,13 +1,3 @@
|
|
1
|
-
#!/
|
1
|
+
#!/bin/sh
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
# Sets up a Docker network on defined hosts which can then be used by the application’s containers
|
6
|
-
|
7
|
-
hosts = ENV["KAMAL_HOSTS"].split(",")
|
8
|
-
|
9
|
-
hosts.each do |ip|
|
10
|
-
destination = "root@#{ip}"
|
11
|
-
puts "Creating a Docker network \"kamal\" on #{destination}"
|
12
|
-
`ssh #{destination} docker network create kamal`
|
13
|
-
end
|
3
|
+
echo "Docker set up on $KAMAL_HOSTS..."
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# KAMAL_HOSTS
|
14
14
|
# KAMAL_COMMAND
|
15
15
|
# KAMAL_SUBCOMMAND
|
16
|
-
#
|
16
|
+
# KAMAL_ROLES (if set)
|
17
17
|
# KAMAL_DESTINATION (if set)
|
18
18
|
|
19
19
|
# Only check the build status for production deployments
|
@@ -43,7 +43,7 @@ class GithubStatusChecks
|
|
43
43
|
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
44
44
|
|
45
45
|
def initialize
|
46
|
-
@remote_url =
|
46
|
+
@remote_url = github_repo_from_remote_url
|
47
47
|
@git_sha = `git rev-parse HEAD`.strip
|
48
48
|
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
49
49
|
refresh!
|
@@ -77,16 +77,29 @@ class GithubStatusChecks
|
|
77
77
|
"Build not started..."
|
78
78
|
end
|
79
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
|
80
92
|
end
|
81
93
|
|
82
94
|
|
83
95
|
$stdout.sync = true
|
84
96
|
|
85
|
-
puts "Checking build status..."
|
86
|
-
attempts = 0
|
87
|
-
checks = GithubStatusChecks.new
|
88
|
-
|
89
97
|
begin
|
98
|
+
puts "Checking build status..."
|
99
|
+
|
100
|
+
attempts = 0
|
101
|
+
checks = GithubStatusChecks.new
|
102
|
+
|
90
103
|
loop do
|
91
104
|
case checks.state
|
92
105
|
when "success"
|
@@ -0,0 +1,17 @@
|
|
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
|
+
# Option 1: Read secrets from the environment
|
6
|
+
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
|
7
|
+
|
8
|
+
# Option 2: Read secrets via a command
|
9
|
+
# RAILS_MASTER_KEY=$(cat config/master.key)
|
10
|
+
|
11
|
+
# Option 3: Read secrets via kamal secrets helpers
|
12
|
+
# These will handle logging in and fetching the secrets in as few calls as possible
|
13
|
+
# There are adapters for 1Password, LastPass + Bitwarden
|
14
|
+
#
|
15
|
+
# SECRETS=$(kamal secrets fetch --adapter 1password --account my-account --from MyVault/MyItem KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY)
|
16
|
+
# KAMAL_REGISTRY_PASSWORD=$(kamal secrets extract KAMAL_REGISTRY_PASSWORD $SECRETS)
|
17
|
+
# RAILS_MASTER_KEY=$(kamal secrets extract RAILS_MASTER_KEY $SECRETS)
|
data/lib/kamal/cli.rb
CHANGED