kamal 2.8.1 → 2.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd8b2329effde7405d2a1b8d60f394d1da81faaf5e8bcd6980c83f8bb590f988
4
- data.tar.gz: 5543d21fbe88acf86a24fcf1f44dff30e0aaf410b3ead0293b0f005b15fa2cbb
3
+ metadata.gz: ee017d64694e5a35507b9cfc9693e6a6c5fc876e0cfb731d58f0de7dba281f41
4
+ data.tar.gz: bcbc84a444f0aa5efb08fd0ab63ea87d5892821f04e9c6ac51d938d78e2547a5
5
5
  SHA512:
6
- metadata.gz: bdae637114b1ad0630d5b1bbc0e76be5ebb3817d370d1f3f5a4222024f13fa3546a24806ad812e9d6d14735c2be23ed6956702723e75cc27c5ec34ebe87d72f9
7
- data.tar.gz: 8db206acbeb707f3a0d3267720b9d248cb2566517d340a61072777e0fdbbf237701d42e203d2d96b21fb7c04596896d6039c4ba9d59eb1515d47de0f85148248
6
+ metadata.gz: b285597e99a5d2d90a324e9be64332a71afd66269ac27c8550eebf692758cac14535a14164058fd246d0001fd3b371382ed0b4e4bc9a75e19f8d42e40b4d9365
7
+ data.tar.gz: fca4902b9da04bab86d087b0582ed956e2ee174259e47f1a491a4b68c928dc16a501cb0b137d443daeda25c00fd99bcf0f234ce54bf85da7b61002ce90402de0
@@ -1,5 +1,3 @@
1
- require "uri"
2
-
3
1
  class Kamal::Cli::Build::Clone
4
2
  attr_reader :sshkit
5
3
  delegate :info, :error, :execute, :capture_with_info, to: :sshkit
@@ -0,0 +1,66 @@
1
+ require "concurrent/atomic/count_down_latch"
2
+
3
+ class Kamal::Cli::Build::PortForwarding
4
+ attr_reader :hosts, :port, :ssh_options
5
+
6
+ def initialize(hosts, port, **ssh_options)
7
+ @hosts = hosts
8
+ @port = port
9
+ @ssh_options = ssh_options
10
+ end
11
+
12
+ def forward
13
+ @done = false
14
+ forward_ports
15
+
16
+ yield
17
+ ensure
18
+ stop
19
+ end
20
+
21
+ private
22
+ def stop
23
+ @done = true
24
+ @threads.to_a.each(&:join)
25
+ end
26
+
27
+ def forward_ports
28
+ ready = Concurrent::CountDownLatch.new(hosts.size)
29
+
30
+ @threads = hosts.map do |host|
31
+ Thread.new do
32
+ begin
33
+ Net::SSH.start(host, ssh_options[:user], **ssh_options.except(:user)) do |ssh|
34
+ ssh.forward.remote(port, "localhost", port, "127.0.0.1") do |remote_port, bind_address|
35
+ if remote_port == :error
36
+ raise "Failed to establish port forward on #{host}"
37
+ else
38
+ ready.count_down
39
+ end
40
+ end
41
+
42
+ ssh.loop(0.1) do
43
+ if @done
44
+ ssh.forward.cancel_remote(port, "127.0.0.1")
45
+ break
46
+ else
47
+ true
48
+ end
49
+ end
50
+ end
51
+ rescue Exception => e
52
+ error "Error setting up port forwarding to #{host}: #{e.class}: #{e.message}"
53
+ error e.backtrace.join("\n")
54
+
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ raise "Timed out waiting for port forwarding to be established" unless ready.wait(30)
61
+ end
62
+
63
+ def error(message)
64
+ SSHKit.config.output.error(message)
65
+ end
66
+ end
@@ -1,5 +1,3 @@
1
- require "uri"
2
-
3
1
  class Kamal::Cli::Build < Kamal::Cli::Base
4
2
  class BuildError < StandardError; end
5
3
 
@@ -38,29 +36,31 @@ class Kamal::Cli::Build < Kamal::Cli::Base
38
36
  say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
39
37
  end
40
38
 
41
- with_env(KAMAL.config.builder.secrets) do
42
- run_locally do
43
- begin
44
- execute *KAMAL.builder.inspect_builder
45
- rescue SSHKit::Command::Failed => e
46
- if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
47
- warn "Missing compatible builder, so creating a new one first"
48
- begin
49
- cli.remove
50
- rescue SSHKit::Command::Failed
51
- raise unless e.message =~ /(context not found|no builder|does not exist)/
39
+ forward_local_registry_port_for_remote_builder do
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
52
55
  end
53
- cli.create
54
- else
55
- raise
56
56
  end
57
- end
58
57
 
59
- # Get the command here to ensure the Dir.chdir doesn't interfere with it
60
- push = KAMAL.builder.push(cli.options[:output], no_cache: cli.options[:no_cache])
58
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
59
+ push = KAMAL.builder.push(cli.options[:output], no_cache: cli.options[:no_cache])
61
60
 
62
- KAMAL.with_verbosity(:debug) do
63
- Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.builder.push_env }
61
+ KAMAL.with_verbosity(:debug) do
62
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.builder.push_env }
63
+ end
64
64
  end
65
65
  end
66
66
  end
@@ -70,7 +70,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
70
70
  def pull
71
71
  login_to_registry_remotely unless KAMAL.registry.local?
72
72
 
73
- forward_local_registry_port do
73
+ forward_local_registry_port(KAMAL.hosts, **KAMAL.config.ssh.options) do
74
74
  if (first_hosts = mirror_hosts).any?
75
75
  #  Pull on a single host per mirror first to seed them
76
76
  say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
@@ -210,13 +210,30 @@ class Kamal::Cli::Build < Kamal::Cli::Base
210
210
  end
211
211
  end
212
212
 
213
- def forward_local_registry_port(&block)
213
+ def forward_local_registry_port_for_remote_builder(&block)
214
+ if KAMAL.builder.remote?
215
+ remote_uri = URI(KAMAL.config.builder.remote)
216
+ forward_local_registry_port([ remote_uri.host ], **remote_builder_ssh_options(remote_uri), &block)
217
+ else
218
+ yield
219
+ end
220
+ end
221
+
222
+ def forward_local_registry_port(hosts, **ssh_options, &block)
214
223
  if KAMAL.config.registry.local?
215
- Kamal::Cli::PortForwarding.
216
- new(KAMAL.hosts, KAMAL.config.registry.local_port).
217
- forward(&block)
224
+ say "Setting up local registry port forwarding to #{hosts.join(', ')}..."
225
+ PortForwarding.new(hosts, KAMAL.config.registry.local_port, **ssh_options).forward(&block)
218
226
  else
219
227
  yield
220
228
  end
221
229
  end
230
+
231
+ def remote_builder_ssh_options(remote_uri)
232
+ { user: remote_uri.user,
233
+ port: remote_uri.port,
234
+ keepalive: KAMAL.config.ssh.options[:keepalive],
235
+ keepalive_interval: KAMAL.config.ssh.options[:keepalive_interval],
236
+ logger: KAMAL.config.ssh.options[:logger]
237
+ }.compact
238
+ end
222
239
  end
@@ -24,4 +24,26 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
24
24
  on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
25
25
  end
26
26
  end
27
+
28
+ desc "login", "Log in to remote registry locally and remotely"
29
+ option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
30
+ option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
31
+ def login
32
+ if KAMAL.registry.local?
33
+ raise "Cannot use login command with a local registry. Use `kamal registry setup` instead."
34
+ end
35
+
36
+ setup
37
+ end
38
+
39
+ desc "logout", "Log out of remote registry locally and remotely"
40
+ option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
41
+ option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
42
+ def logout
43
+ if KAMAL.registry.local?
44
+ raise "Cannot use logout command with a local registry. Use `kamal registry remove` instead."
45
+ end
46
+
47
+ remove
48
+ end
27
49
  end
@@ -127,6 +127,16 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
127
127
  config.builder
128
128
  end
129
129
 
130
+ def registry_config
131
+ config.registry
132
+ end
133
+
134
+ def driver_options
135
+ if registry_config.local?
136
+ [ "--driver-opt", "network=host" ]
137
+ end
138
+ end
139
+
130
140
  def platform_options(arches)
131
141
  argumentize "--platform", arches.map { |arch| "linux/#{arch}" }.join(",") if arches.any?
132
142
  end
@@ -8,14 +8,14 @@ class Kamal::Commands::Builder::Hybrid < Kamal::Commands::Builder::Remote
8
8
 
9
9
  private
10
10
  def builder_name
11
- "kamal-hybrid-#{driver}-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
11
+ "kamal-hybrid-#{driver}-#{remote_builder_name_suffix}"
12
12
  end
13
13
 
14
14
  def create_local_buildx
15
- docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}"
15
+ docker :buildx, :create, *platform_options(local_arches), "--name", builder_name, "--driver=#{driver}", *driver_options
16
16
  end
17
17
 
18
18
  def append_remote_buildx
19
- docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, remote_context_name
19
+ docker :buildx, :create, *platform_options(remote_arches), "--append", "--name", builder_name, *driver_options, remote_context_name
20
20
  end
21
21
  end
@@ -2,14 +2,7 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
2
2
  def create
3
3
  return if docker_driver?
4
4
 
5
- options =
6
- if KAMAL.registry.local?
7
- "--driver=#{driver} --driver-opt network=host"
8
- else
9
- "--driver=#{driver}"
10
- end
11
-
12
- docker :buildx, :create, "--name", builder_name, options
5
+ docker :buildx, :create, "--name", builder_name, "--driver=#{driver}", *driver_options
13
6
  end
14
7
 
15
8
  def remove
@@ -18,7 +11,7 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
18
11
 
19
12
  private
20
13
  def builder_name
21
- if KAMAL.registry.local?
14
+ if registry_config.local?
22
15
  "kamal-local-registry-#{driver}"
23
16
  else
24
17
  "kamal-local-#{driver}"
@@ -34,13 +34,17 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
34
34
 
35
35
  private
36
36
  def builder_name
37
- "kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
37
+ "kamal-remote-#{remote_builder_name_suffix}"
38
38
  end
39
39
 
40
40
  def remote_context_name
41
41
  "#{builder_name}-context"
42
42
  end
43
43
 
44
+ def remote_builder_name_suffix
45
+ "#{remote.gsub(/[^a-z0-9_-]/, "-")}#{registry_config.local? ? "-local-registry" : "" }"
46
+ end
47
+
44
48
  def inspect_buildx
45
49
  pipe \
46
50
  docker(:buildx, :inspect, builder_name),
@@ -62,7 +66,7 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
62
66
  end
63
67
 
64
68
  def create_buildx
65
- docker :buildx, :create, "--name", builder_name, remote_context_name
69
+ docker :buildx, :create, "--name", builder_name, *driver_options, remote_context_name
66
70
  end
67
71
 
68
72
  def remove_buildx
@@ -1,19 +1,27 @@
1
1
  # Registry
2
2
  #
3
3
  # The default registry is Docker Hub, but you can change it using `registry/server`.
4
+
5
+ # Using a local container registry
6
+ #
7
+ # If the registry server starts with `localhost`, Kamal will start a local Docker registry
8
+ # on that port and push the app image to it.
9
+ registry:
10
+ server: localhost:5555
11
+
12
+ # Using Docker Hub as the container registry
4
13
  #
5
14
  # By default, Docker Hub creates public repositories. To avoid making your images public,
6
15
  # set up a private repository before deploying, or change the default repository privacy
7
16
  # settings to private in your [Docker Hub settings](https://hub.docker.com/repository-settings/default-privacy).
8
17
  #
9
- # A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret
18
+ # A reference to a secret (in this case, `KAMAL_REGISTRY_PASSWORD`) will look up the secret
10
19
  # in the local environment:
11
20
  registry:
12
- server: registry.digitalocean.com
13
21
  username:
14
- - DOCKER_REGISTRY_TOKEN
22
+ - <your docker hub username>
15
23
  password:
16
- - DOCKER_REGISTRY_TOKEN
24
+ - KAMAL_REGISTRY_PASSWORD
17
25
 
18
26
  # Using AWS ECR as the container registry
19
27
  #
@@ -76,6 +76,7 @@ class Kamal::Configuration
76
76
  ensure_no_traefik_reboot_hooks
77
77
  ensure_one_host_for_ssl_roles
78
78
  ensure_unique_hosts_for_ssl_roles
79
+ ensure_local_registry_remote_builder_has_ssh_url
79
80
  end
80
81
 
81
82
  def version=(version)
@@ -363,6 +364,16 @@ class Kamal::Configuration
363
364
  true
364
365
  end
365
366
 
367
+ def ensure_local_registry_remote_builder_has_ssh_url
368
+ if registry.local? && builder.remote?
369
+ unless URI(builder.remote).scheme == "ssh"
370
+ raise Kamal::ConfigurationError, "Local registry with remote builder requires an SSH URL (e.g., ssh://user@host)"
371
+ end
372
+ end
373
+
374
+ true
375
+ end
376
+
366
377
  def role_names
367
378
  raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
368
379
  end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "2.8.1"
2
+ VERSION = "2.8.2"
3
3
  end
data/lib/kamal.rb CHANGED
@@ -7,6 +7,7 @@ require "zeitwerk"
7
7
  require "yaml"
8
8
  require "tmpdir"
9
9
  require "pathname"
10
+ require "uri"
10
11
 
11
12
  loader = Zeitwerk::Loader.for_gem
12
13
  loader.ignore(File.join(__dir__, "kamal", "sshkit_with_ext.rb"))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.1
4
+ version: 2.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -224,12 +224,12 @@ files:
224
224
  - lib/kamal/cli/base.rb
225
225
  - lib/kamal/cli/build.rb
226
226
  - lib/kamal/cli/build/clone.rb
227
+ - lib/kamal/cli/build/port_forwarding.rb
227
228
  - lib/kamal/cli/healthcheck/barrier.rb
228
229
  - lib/kamal/cli/healthcheck/error.rb
229
230
  - lib/kamal/cli/healthcheck/poller.rb
230
231
  - lib/kamal/cli/lock.rb
231
232
  - lib/kamal/cli/main.rb
232
- - lib/kamal/cli/port_forwarding.rb
233
233
  - lib/kamal/cli/proxy.rb
234
234
  - lib/kamal/cli/prune.rb
235
235
  - lib/kamal/cli/registry.rb
@@ -1,55 +0,0 @@
1
- require "concurrent/atomic/count_down_latch"
2
-
3
- class Kamal::Cli::PortForwarding
4
- attr_reader :hosts, :port
5
-
6
- def initialize(hosts, port)
7
- @hosts = hosts
8
- @port = port
9
- end
10
-
11
- def forward
12
- @done = false
13
- forward_ports
14
-
15
- yield
16
- ensure
17
- stop
18
- end
19
-
20
- private
21
-
22
- def stop
23
- @done = true
24
- @threads.to_a.each(&:join)
25
- end
26
-
27
- def forward_ports
28
- ready = Concurrent::CountDownLatch.new(hosts.size)
29
-
30
- @threads = hosts.map do |host|
31
- Thread.new do
32
- Net::SSH.start(host, KAMAL.config.ssh.user, **{ proxy: KAMAL.config.ssh.proxy }.compact) do |ssh|
33
- ssh.forward.remote(port, "localhost", port, "127.0.0.1") do |remote_port, bind_address|
34
- if remote_port == :error
35
- raise "Failed to establish port forward on #{host}"
36
- else
37
- ready.count_down
38
- end
39
- end
40
-
41
- ssh.loop(0.1) do
42
- if @done
43
- ssh.forward.cancel_remote(port, "127.0.0.1")
44
- break
45
- else
46
- true
47
- end
48
- end
49
- end
50
- end
51
- end
52
-
53
- raise "Timed out waiting for port forwarding to be established" unless ready.wait(10)
54
- end
55
- end