kamal 2.3.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +24 -9
- data/lib/kamal/cli/alias/command.rb +1 -0
- data/lib/kamal/cli/app/boot.rb +2 -2
- data/lib/kamal/cli/app.rb +28 -8
- data/lib/kamal/cli/base.rb +16 -1
- data/lib/kamal/cli/build.rb +36 -14
- data/lib/kamal/cli/main.rb +4 -3
- data/lib/kamal/cli/proxy.rb +2 -4
- data/lib/kamal/cli/registry.rb +2 -0
- data/lib/kamal/cli/secrets.rb +9 -3
- data/lib/kamal/cli/templates/deploy.yml +6 -3
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli.rb +1 -0
- data/lib/kamal/commander.rb +16 -25
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +4 -4
- data/lib/kamal/commands/app/assets.rb +4 -4
- data/lib/kamal/commands/app/containers.rb +2 -2
- data/lib/kamal/commands/app/execution.rb +4 -2
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app/logging.rb +14 -4
- data/lib/kamal/commands/app.rb +15 -7
- data/lib/kamal/commands/base.rb +25 -1
- data/lib/kamal/commands/builder/base.rb +17 -6
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder.rb +6 -20
- data/lib/kamal/commands/registry.rb +9 -7
- data/lib/kamal/configuration/accessory.rb +41 -9
- data/lib/kamal/configuration/builder.rb +8 -0
- data/lib/kamal/configuration/docs/accessory.yml +26 -3
- data/lib/kamal/configuration/docs/alias.yml +2 -2
- data/lib/kamal/configuration/docs/builder.yml +9 -0
- data/lib/kamal/configuration/docs/proxy.yml +13 -10
- data/lib/kamal/configuration/docs/registry.yml +4 -0
- data/lib/kamal/configuration/registry.rb +6 -6
- data/lib/kamal/configuration/role.rb +6 -6
- data/lib/kamal/configuration/validator/role.rb +1 -1
- data/lib/kamal/configuration.rb +31 -14
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/git.rb +10 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +50 -0
- data/lib/kamal/secrets/adapters/base.rb +13 -3
- data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +72 -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 +3 -2
- data/lib/kamal/secrets/adapters/one_password.rb +2 -2
- data/lib/kamal/secrets/adapters/test.rb +2 -2
- data/lib/kamal/secrets/adapters.rb +2 -0
- data/lib/kamal/secrets.rb +1 -1
- data/lib/kamal/version.rb +1 -1
- metadata +13 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '01499d423cd415dea520fe78d0e5f17b0d67d3ef2d241e56abb84e2deaeb3f65'
|
4
|
+
data.tar.gz: 826b542e67f360b9d019d886a454fff796b73cda57b5e201edc2981e5059a1dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbc7aa2632e0e5810666cb6d93ce698461fd3290dd5813c11bb88dd9faa4d6f7f015140c24a928b588df34316b814aec6ce710cbb4a28aa19713cc3420c15ae3
|
7
|
+
data.tar.gz: 2c818511055983038c9cbc9674a6f9c122de98296e27bf47441b02f3f9474176416e07cf1928bfc5b333648e51bb9ff4d857249173df82127e2ed29a124a97a7
|
data/lib/kamal/cli/accessory.rb
CHANGED
@@ -18,6 +18,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
18
18
|
execute *accessory.ensure_env_directory
|
19
19
|
upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
|
20
20
|
execute *accessory.run
|
21
|
+
|
22
|
+
if accessory.running_proxy?
|
23
|
+
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
24
|
+
execute *accessory.deploy(target: target)
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -75,6 +80,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
75
80
|
on(hosts) do
|
76
81
|
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
77
82
|
execute *accessory.start
|
83
|
+
if accessory.running_proxy?
|
84
|
+
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
85
|
+
execute *accessory.deploy(target: target)
|
86
|
+
end
|
78
87
|
end
|
79
88
|
end
|
80
89
|
end
|
@@ -87,6 +96,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
87
96
|
on(hosts) do
|
88
97
|
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
89
98
|
execute *accessory.stop, raise_on_non_zero_exit: false
|
99
|
+
|
100
|
+
if accessory.running_proxy?
|
101
|
+
target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
|
102
|
+
execute *accessory.remove if target
|
103
|
+
end
|
90
104
|
end
|
91
105
|
end
|
92
106
|
end
|
@@ -112,14 +126,15 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
112
126
|
end
|
113
127
|
end
|
114
128
|
|
115
|
-
desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
|
129
|
+
desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
|
116
130
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
117
131
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
118
|
-
def exec(name, cmd)
|
132
|
+
def exec(name, *cmd)
|
133
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
119
134
|
with_accessory(name) do |accessory, hosts|
|
120
135
|
case
|
121
136
|
when options[:interactive] && options[:reuse]
|
122
|
-
say "Launching interactive command
|
137
|
+
say "Launching interactive command via SSH from existing container...", :magenta
|
123
138
|
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
124
139
|
|
125
140
|
when options[:interactive]
|
@@ -128,16 +143,16 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
128
143
|
|
129
144
|
when options[:reuse]
|
130
145
|
say "Launching command from existing container...", :magenta
|
131
|
-
on(hosts) do
|
146
|
+
on(hosts) do |host|
|
132
147
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
133
|
-
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
148
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
|
134
149
|
end
|
135
150
|
|
136
151
|
else
|
137
152
|
say "Launching command from new container...", :magenta
|
138
|
-
on(hosts) do
|
153
|
+
on(hosts) do |host|
|
139
154
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
140
|
-
capture_with_info(*accessory.execute_in_new_container(cmd))
|
155
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
|
141
156
|
end
|
142
157
|
end
|
143
158
|
end
|
@@ -147,7 +162,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
147
162
|
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
148
163
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
149
164
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
150
|
-
option :grep_options,
|
165
|
+
option :grep_options, desc: "Additional options supplied to grep"
|
151
166
|
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
152
167
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
153
168
|
def logs(name)
|
@@ -277,7 +292,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
277
292
|
def prepare(name)
|
278
293
|
with_accessory(name) do |accessory, hosts|
|
279
294
|
on(hosts) do
|
280
|
-
execute *KAMAL.registry.login
|
295
|
+
execute *KAMAL.registry.login(registry_config: accessory.registry)
|
281
296
|
execute *KAMAL.docker.create_network
|
282
297
|
rescue SSHKit::Command::Failed => e
|
283
298
|
raise unless e.message.include?("already exists")
|
data/lib/kamal/cli/app/boot.rb
CHANGED
@@ -45,7 +45,7 @@ class Kamal::Cli::App::Boot
|
|
45
45
|
|
46
46
|
def start_new_version
|
47
47
|
audit "Booted app version #{version}"
|
48
|
-
hostname = "#{host.to_s[0...51].
|
48
|
+
hostname = "#{host.to_s[0...51].chomp(".")}-#{SecureRandom.hex(6)}"
|
49
49
|
|
50
50
|
execute *app.ensure_env_directory
|
51
51
|
upload! role.secrets_io(host), role.secrets_path, mode: "0600"
|
@@ -91,7 +91,7 @@ class Kamal::Cli::App::Boot
|
|
91
91
|
if barrier.close
|
92
92
|
info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
|
93
93
|
begin
|
94
|
-
error capture_with_info(*app.logs(
|
94
|
+
error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
|
95
95
|
error capture_with_info(*app.container_health_log(version: version))
|
96
96
|
rescue SSHKit::Command::Failed
|
97
97
|
error "Could not fetch logs for #{version}"
|
data/lib/kamal/cli/app.rb
CHANGED
@@ -16,10 +16,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
16
16
|
# Primary hosts and roles are returned first, so they can open the barrier
|
17
17
|
barrier = Kamal::Cli::Healthcheck::Barrier.new
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
host_boot_groups.each do |hosts|
|
20
|
+
host_list = Array(hosts).join(",")
|
21
|
+
run_hook "pre-app-boot", hosts: host_list
|
22
|
+
|
23
|
+
on(hosts) do |host|
|
24
|
+
KAMAL.roles_on(host).each do |role|
|
25
|
+
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
|
26
|
+
end
|
22
27
|
end
|
28
|
+
|
29
|
+
run_hook "post-app-boot", hosts: host_list
|
30
|
+
sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
|
23
31
|
end
|
24
32
|
|
25
33
|
# Tag once the app booted on all hosts
|
@@ -94,9 +102,15 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
94
102
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
95
103
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
96
104
|
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
105
|
+
option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
|
97
106
|
def exec(*cmd)
|
107
|
+
if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
|
108
|
+
raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
|
109
|
+
end
|
110
|
+
|
98
111
|
cmd = Kamal::Utils.join_commands(cmd)
|
99
112
|
env = options[:env]
|
113
|
+
detach = options[:detach]
|
100
114
|
case
|
101
115
|
when options[:interactive] && options[:reuse]
|
102
116
|
say "Get current version of running container...", :magenta unless options[:version]
|
@@ -138,7 +152,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
138
152
|
|
139
153
|
roles.each do |role|
|
140
154
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
141
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env))
|
155
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
|
142
156
|
end
|
143
157
|
end
|
144
158
|
end
|
@@ -186,15 +200,17 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
186
200
|
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
187
201
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
188
202
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
189
|
-
option :grep_options,
|
203
|
+
option :grep_options, desc: "Additional options supplied to grep"
|
190
204
|
option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
|
191
205
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
206
|
+
option :container_id, desc: "Docker container ID to fetch logs"
|
192
207
|
def logs
|
193
208
|
# FIXME: Catch when app containers aren't running
|
194
209
|
|
195
210
|
grep = options[:grep]
|
196
211
|
grep_options = options[:grep_options]
|
197
212
|
since = options[:since]
|
213
|
+
container_id = options[:container_id]
|
198
214
|
timestamps = !options[:skip_timestamps]
|
199
215
|
|
200
216
|
if options[:follow]
|
@@ -207,8 +223,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
207
223
|
role = KAMAL.roles_on(KAMAL.primary_host).first
|
208
224
|
|
209
225
|
app = KAMAL.app(role: role, host: host)
|
210
|
-
info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
211
|
-
exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
226
|
+
info app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
227
|
+
exec app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
212
228
|
end
|
213
229
|
else
|
214
230
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
@@ -218,7 +234,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
218
234
|
|
219
235
|
roles.each do |role|
|
220
236
|
begin
|
221
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
237
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
222
238
|
rescue SSHKit::Command::Failed
|
223
239
|
puts_by_host host, "Nothing found"
|
224
240
|
end
|
@@ -332,4 +348,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
332
348
|
yield
|
333
349
|
end
|
334
350
|
end
|
351
|
+
|
352
|
+
def host_boot_groups
|
353
|
+
KAMAL.config.boot.limit ? KAMAL.hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.hosts ]
|
354
|
+
end
|
335
355
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
@@ -5,7 +5,7 @@ module Kamal::Cli
|
|
5
5
|
class Base < Thor
|
6
6
|
include SSHKit::DSL
|
7
7
|
|
8
|
-
def self.exit_on_failure?()
|
8
|
+
def self.exit_on_failure?() true end
|
9
9
|
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
|
10
10
|
|
11
11
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
@@ -30,6 +30,7 @@ module Kamal::Cli
|
|
30
30
|
else
|
31
31
|
super
|
32
32
|
end
|
33
|
+
|
33
34
|
initialize_commander unless KAMAL.configured?
|
34
35
|
end
|
35
36
|
|
@@ -194,5 +195,19 @@ module Kamal::Cli
|
|
194
195
|
ENV.clear
|
195
196
|
ENV.update(current_env)
|
196
197
|
end
|
198
|
+
|
199
|
+
def ensure_docker_installed
|
200
|
+
run_locally do
|
201
|
+
begin
|
202
|
+
execute *KAMAL.builder.ensure_docker_installed
|
203
|
+
rescue SSHKit::Command::Failed => e
|
204
|
+
error = e.message =~ /command not found/ ?
|
205
|
+
"Docker is not installed locally" :
|
206
|
+
"Docker buildx plugin is not installed locally"
|
207
|
+
|
208
|
+
raise DependencyError, error
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
197
212
|
end
|
198
213
|
end
|
data/lib/kamal/cli/build.rb
CHANGED
@@ -5,15 +5,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
5
5
|
|
6
6
|
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
7
7
|
def deliver
|
8
|
-
push
|
9
|
-
pull
|
8
|
+
invoke :push
|
9
|
+
invoke :pull
|
10
10
|
end
|
11
11
|
|
12
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'."
|
13
14
|
def push
|
14
15
|
cli = self
|
15
16
|
|
16
|
-
|
17
|
+
ensure_docker_installed
|
17
18
|
run_hook "pre-build"
|
18
19
|
|
19
20
|
uncommitted_changes = Kamal::Git.uncommitted_changes
|
@@ -49,7 +50,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
49
50
|
end
|
50
51
|
|
51
52
|
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
52
|
-
push = KAMAL.builder.push
|
53
|
+
push = KAMAL.builder.push(cli.options[:output])
|
53
54
|
|
54
55
|
KAMAL.with_verbosity(:debug) do
|
55
56
|
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
@@ -108,21 +109,42 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
112
|
+
desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
|
113
|
+
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'."
|
114
|
+
def dev
|
115
|
+
cli = self
|
116
|
+
|
117
|
+
ensure_docker_installed
|
118
|
+
|
119
|
+
docker_included_files = Set.new(Kamal::Docker.included_files)
|
120
|
+
git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
|
121
|
+
git_untracked_files = Set.new(Kamal::Git.untracked_files)
|
122
|
+
|
123
|
+
docker_uncommitted_files = docker_included_files & git_uncommitted_files
|
124
|
+
if docker_uncommitted_files.any?
|
125
|
+
say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
|
126
|
+
docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
|
127
|
+
say
|
128
|
+
end
|
129
|
+
|
130
|
+
docker_untracked_files = docker_included_files & git_untracked_files
|
131
|
+
if docker_untracked_files.any?
|
132
|
+
say "WARNING: Untracked files will be present in the dev container:", :yellow
|
133
|
+
docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
|
134
|
+
say
|
135
|
+
end
|
120
136
|
|
121
|
-
|
137
|
+
with_env(KAMAL.config.builder.secrets) do
|
138
|
+
run_locally do
|
139
|
+
build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
|
140
|
+
KAMAL.with_verbosity(:debug) do
|
141
|
+
execute(*build)
|
122
142
|
end
|
123
143
|
end
|
124
144
|
end
|
145
|
+
end
|
125
146
|
|
147
|
+
private
|
126
148
|
def connect_to_remote_host(remote_host)
|
127
149
|
remote_uri = URI.parse(remote_host)
|
128
150
|
if remote_uri.scheme == "ssh"
|
data/lib/kamal/cli/main.rb
CHANGED
@@ -9,15 +9,14 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
9
9
|
say "Ensure Docker is installed...", :magenta
|
10
10
|
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
11
11
|
|
12
|
-
|
13
|
-
deploy
|
12
|
+
deploy(boot_accessories: true)
|
14
13
|
end
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
17
|
desc "deploy", "Deploy app to servers"
|
19
18
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
20
|
-
def deploy
|
19
|
+
def deploy(boot_accessories: false)
|
21
20
|
runtime = print_runtime do
|
22
21
|
invoke_options = deploy_options
|
23
22
|
|
@@ -38,6 +37,8 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
38
37
|
say "Ensure kamal-proxy is running...", :magenta
|
39
38
|
invoke "kamal:cli:proxy:boot", [], invoke_options
|
40
39
|
|
40
|
+
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
|
41
|
+
|
41
42
|
say "Detect stale containers...", :magenta
|
42
43
|
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
43
44
|
|
data/lib/kamal/cli/proxy.rb
CHANGED
@@ -23,6 +23,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
23
23
|
|
24
24
|
desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
|
25
25
|
option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
|
26
|
+
option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
|
26
27
|
option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host"
|
27
28
|
option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host"
|
28
29
|
option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs"
|
@@ -31,7 +32,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
31
32
|
case subcommand
|
32
33
|
when "set"
|
33
34
|
boot_options = [
|
34
|
-
*(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port]) if options[:publish]),
|
35
|
+
*(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
|
35
36
|
*(KAMAL.config.proxy_logging_args(options[:log_max_size])),
|
36
37
|
*options[:docker_options].map { |option| "--#{option}" }
|
37
38
|
]
|
@@ -67,9 +68,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
|
67
68
|
execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
|
68
69
|
execute *KAMAL.registry.login
|
69
70
|
|
70
|
-
"Stopping and removing Traefik on #{host}, if running..."
|
71
|
-
execute *KAMAL.proxy.cleanup_traefik
|
72
|
-
|
73
71
|
"Stopping and removing kamal-proxy on #{host}, if running..."
|
74
72
|
execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
|
75
73
|
execute *KAMAL.proxy.remove_container
|
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
|
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
|
data/lib/kamal/cli/secrets.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
class Kamal::Cli::Secrets < Kamal::Cli::Base
|
2
2
|
desc "fetch [SECRETS...]", "Fetch secrets from a vault"
|
3
3
|
option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
|
4
|
-
option :account, type: :string, required:
|
4
|
+
option :account, type: :string, required: false, desc: "The account identifier or username"
|
5
5
|
option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
|
6
6
|
option :inline, type: :boolean, required: false, hidden: true
|
7
7
|
def fetch(*secrets)
|
8
|
-
|
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)
|
9
15
|
|
10
16
|
return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
|
11
17
|
end
|
@@ -29,7 +35,7 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
|
|
29
35
|
end
|
30
36
|
|
31
37
|
private
|
32
|
-
def
|
38
|
+
def initialize_adapter(adapter)
|
33
39
|
Kamal::Secrets::Adapters.lookup(adapter)
|
34
40
|
end
|
35
41
|
|
@@ -16,8 +16,8 @@ servers:
|
|
16
16
|
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
|
17
17
|
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
|
18
18
|
#
|
19
|
-
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
|
20
|
-
proxy:
|
19
|
+
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
|
20
|
+
proxy:
|
21
21
|
ssl: true
|
22
22
|
host: app.example.com
|
23
23
|
# Proxy connects to your container on port 80 by default.
|
@@ -36,6 +36,9 @@ registry:
|
|
36
36
|
# Configure builder setup.
|
37
37
|
builder:
|
38
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}" %>
|
39
42
|
|
40
43
|
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
41
44
|
#
|
@@ -46,7 +49,7 @@ builder:
|
|
46
49
|
# - RAILS_MASTER_KEY
|
47
50
|
|
48
51
|
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
|
49
|
-
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
|
52
|
+
# "bin/kamal app logs -r job" will tail logs from the first server in the job section.
|
50
53
|
#
|
51
54
|
# aliases:
|
52
55
|
# shell: app exec --interactive --reuse "bash"
|
data/lib/kamal/cli.rb
CHANGED
data/lib/kamal/commander.rb
CHANGED
@@ -4,13 +4,20 @@ require "active_support/core_ext/object/blank"
|
|
4
4
|
|
5
5
|
class Kamal::Commander
|
6
6
|
attr_accessor :verbosity, :holding_lock, :connected
|
7
|
+
attr_reader :specific_roles, :specific_hosts
|
7
8
|
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
|
8
9
|
|
9
10
|
def initialize
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset
|
10
15
|
self.verbosity = :info
|
11
16
|
self.holding_lock = false
|
12
17
|
self.connected = false
|
13
|
-
@specifics = nil
|
18
|
+
@specifics = @specific_roles = @specific_hosts = nil
|
19
|
+
@config = @config_kwargs = nil
|
20
|
+
@commands = {}
|
14
21
|
end
|
15
22
|
|
16
23
|
def config
|
@@ -28,8 +35,6 @@ class Kamal::Commander
|
|
28
35
|
@config || @config_kwargs
|
29
36
|
end
|
30
37
|
|
31
|
-
attr_reader :specific_roles, :specific_hosts
|
32
|
-
|
33
38
|
def specific_primary!
|
34
39
|
@specifics = nil
|
35
40
|
if specific_roles.present?
|
@@ -76,11 +81,6 @@ class Kamal::Commander
|
|
76
81
|
config.accessories&.collect(&:name) || []
|
77
82
|
end
|
78
83
|
|
79
|
-
def accessories_on(host)
|
80
|
-
config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&:name)
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
84
|
def app(role: nil, host: nil)
|
85
85
|
Kamal::Commands::App.new(config, role: role, host: host)
|
86
86
|
end
|
@@ -94,42 +94,41 @@ class Kamal::Commander
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def builder
|
97
|
-
@builder ||= Kamal::Commands::Builder.new(config)
|
97
|
+
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
|
98
98
|
end
|
99
99
|
|
100
100
|
def docker
|
101
|
-
@docker ||= Kamal::Commands::Docker.new(config)
|
101
|
+
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
|
102
102
|
end
|
103
103
|
|
104
104
|
def hook
|
105
|
-
@hook ||= Kamal::Commands::Hook.new(config)
|
105
|
+
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
|
106
106
|
end
|
107
107
|
|
108
108
|
def lock
|
109
|
-
@lock ||= Kamal::Commands::Lock.new(config)
|
109
|
+
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
|
110
110
|
end
|
111
111
|
|
112
112
|
def proxy
|
113
|
-
@proxy ||= Kamal::Commands::Proxy.new(config)
|
113
|
+
@commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
|
114
114
|
end
|
115
115
|
|
116
116
|
def prune
|
117
|
-
@prune ||= Kamal::Commands::Prune.new(config)
|
117
|
+
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
|
118
118
|
end
|
119
119
|
|
120
120
|
def registry
|
121
|
-
@registry ||= Kamal::Commands::Registry.new(config)
|
121
|
+
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
|
122
122
|
end
|
123
123
|
|
124
124
|
def server
|
125
|
-
@server ||= Kamal::Commands::Server.new(config)
|
125
|
+
@commands[:server] ||= Kamal::Commands::Server.new(config)
|
126
126
|
end
|
127
127
|
|
128
128
|
def alias(name)
|
129
129
|
config.aliases[name]
|
130
130
|
end
|
131
131
|
|
132
|
-
|
133
132
|
def with_verbosity(level)
|
134
133
|
old_level = self.verbosity
|
135
134
|
|
@@ -142,14 +141,6 @@ class Kamal::Commander
|
|
142
141
|
SSHKit.config.output_verbosity = old_level
|
143
142
|
end
|
144
143
|
|
145
|
-
def boot_strategy
|
146
|
-
if config.boot.limit.present?
|
147
|
-
{ in: :groups, limit: config.boot.limit, wait: config.boot.wait }
|
148
|
-
else
|
149
|
-
{}
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
144
|
def holding_lock?
|
154
145
|
self.holding_lock
|
155
146
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Kamal::Commands::Accessory::Proxy
|
2
|
+
delegate :proxy_container_name, to: :config
|
3
|
+
|
4
|
+
def deploy(target:)
|
5
|
+
proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove
|
9
|
+
proxy_exec :remove, service_name
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def proxy_exec(*command)
|
14
|
+
docker :exec, proxy_container_name, "kamal-proxy", *command
|
15
|
+
end
|
16
|
+
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
2
|
+
include Proxy
|
3
|
+
|
2
4
|
attr_reader :accessory_config
|
3
5
|
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
4
6
|
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
|
5
|
-
:secrets_io, :secrets_path, :env_directory,
|
7
|
+
:secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
|
6
8
|
to: :accessory_config
|
9
|
+
delegate :proxy_container_name, to: :config
|
7
10
|
|
8
11
|
def initialize(config, name:)
|
9
12
|
super(config)
|
@@ -38,7 +41,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|
38
41
|
docker :ps, *service_filter
|
39
42
|
end
|
40
43
|
|
41
|
-
|
42
44
|
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
43
45
|
pipe \
|
44
46
|
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
@@ -52,7 +54,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|
52
54
|
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
57
|
def execute_in_existing_container(*command, interactive: false)
|
57
58
|
docker :exec,
|
58
59
|
("-it" if interactive),
|
@@ -83,7 +84,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|
83
84
|
super command, host: hosts.first
|
84
85
|
end
|
85
86
|
|
86
|
-
|
87
87
|
def ensure_local_file_present(local_file)
|
88
88
|
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
89
89
|
raise "Missing file: #{local_file}"
|