kamal 2.6.1 → 2.8.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +15 -2
  3. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  4. data/lib/kamal/cli/app.rb +1 -0
  5. data/lib/kamal/cli/build.rb +33 -15
  6. data/lib/kamal/cli/main.rb +7 -2
  7. data/lib/kamal/cli/port_forwarding.rb +42 -0
  8. data/lib/kamal/cli/registry.rb +16 -8
  9. data/lib/kamal/cli/templates/deploy.yml +4 -3
  10. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +13 -1
  11. data/lib/kamal/cli/templates/secrets +1 -1
  12. data/lib/kamal/commander.rb +1 -1
  13. data/lib/kamal/commands/accessory.rb +8 -3
  14. data/lib/kamal/commands/app/execution.rb +2 -2
  15. data/lib/kamal/commands/app/proxy.rb +4 -0
  16. data/lib/kamal/commands/app.rb +4 -2
  17. data/lib/kamal/commands/base.rb +8 -0
  18. data/lib/kamal/commands/builder/base.rb +11 -1
  19. data/lib/kamal/commands/builder/local.rb +15 -2
  20. data/lib/kamal/commands/builder/pack.rb +46 -0
  21. data/lib/kamal/commands/builder/remote.rb +9 -1
  22. data/lib/kamal/commands/builder.rb +14 -2
  23. data/lib/kamal/commands/registry.rb +22 -0
  24. data/lib/kamal/configuration/accessory.rb +2 -1
  25. data/lib/kamal/configuration/builder.rb +12 -0
  26. data/lib/kamal/configuration/docs/builder.yml +13 -0
  27. data/lib/kamal/configuration/docs/proxy.yml +39 -0
  28. data/lib/kamal/configuration/proxy/boot.rb +8 -0
  29. data/lib/kamal/configuration/proxy.rb +52 -4
  30. data/lib/kamal/configuration/registry.rb +8 -0
  31. data/lib/kamal/configuration/role.rb +5 -3
  32. data/lib/kamal/configuration/validator/accessory.rb +2 -0
  33. data/lib/kamal/configuration/validator/builder.rb +2 -0
  34. data/lib/kamal/configuration/validator/proxy.rb +10 -0
  35. data/lib/kamal/configuration/validator/registry.rb +5 -3
  36. data/lib/kamal/configuration/validator/role.rb +1 -0
  37. data/lib/kamal/configuration/validator.rb +14 -0
  38. data/lib/kamal/configuration.rb +12 -3
  39. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +10 -16
  40. data/lib/kamal/secrets/adapters/one_password.rb +45 -11
  41. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  42. data/lib/kamal/version.rb +1 -1
  43. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2aa9cdc20d3b482b18687564bd8f45383a771d256153b69213923215ba94ebce
4
- data.tar.gz: b2b617c75e5b3324916f49f40e1ab1dac43186de0568000bb768eca2d21a38cf
3
+ metadata.gz: 80ba6d51041312c99d659a1bfaac13bfa0ed7d1e758bde65e5475c0e51d88238
4
+ data.tar.gz: f0cc94a905da2cfb5dcf4f5d9ead2194df1b0ed46fd3b8671a59bfc333b10fbf
5
5
  SHA512:
6
- metadata.gz: 9f6a7b11fd418414669e0477c9d3c3a30e9bbd39433ccc68e85df7bba9f97f96361168a1d780740321fdff1211ecd4c13262377759d25f88f37c2964d858789a
7
- data.tar.gz: bafc9d379a59dc332f3e3f0a079d76db96a433742b3b17eb72b3b90dd28e37067d9f6d8db6c18af29723020bbc0aa53e49bacb203bdba76f6cee839379f886d2
6
+ metadata.gz: e33ddb40f46e587364d9121bbd2f2d829beddeb956f4bf1d8399e5ac835285180914f51e1365bee96ec4b82a02c0e88df8ff513a68bf95e44a8dc7e0ce081509
7
+ data.tar.gz: 8a0b1a90bbacd96d072cbbe5cec3371e5f7fe6d213cfa5dac3203f6d4ef02fadbddd1e3458909f3051adcd9218d69d6edb5b193f86fca6fac28b49b00bd2dd91
@@ -24,11 +24,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
24
24
  directories(name)
25
25
  upload(name)
26
26
 
27
- on(hosts) do
27
+ on(hosts) do |host|
28
28
  execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
29
29
  execute *accessory.ensure_env_directory
30
30
  upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
31
- execute *accessory.run
31
+ execute *accessory.run(host: host)
32
32
 
33
33
  if accessory.running_proxy?
34
34
  target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
@@ -77,6 +77,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
77
77
  KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
78
78
  else
79
79
  prepare(name)
80
+ pull_image(name)
80
81
  stop(name)
81
82
  remove_container(name)
82
83
  boot(name, prepare: false)
@@ -203,6 +204,18 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
203
204
  end
204
205
  end
205
206
 
207
+ desc "pull_image [NAME]", "Pull accessory image on host", hide: true
208
+ def pull_image(name)
209
+ with_lock do
210
+ with_accessory(name) do |accessory, hosts|
211
+ on(hosts) do
212
+ execute *KAMAL.auditor.record("Pull #{name} accessory image"), verbosity: :debug
213
+ execute *accessory.pull_image
214
+ end
215
+ end
216
+ end
217
+ end
218
+
206
219
  desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
207
220
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
208
221
  def remove(name)
@@ -0,0 +1,28 @@
1
+ class Kamal::Cli::App::SslCertificates
2
+ attr_reader :host, :role, :sshkit
3
+ delegate :execute, :info, :upload!, to: :sshkit
4
+
5
+ def initialize(host, role, sshkit)
6
+ @host = host
7
+ @role = role
8
+ @sshkit = sshkit
9
+ end
10
+
11
+ def run
12
+ if role.running_proxy? && role.proxy.custom_ssl_certificate?
13
+ info "Writing SSL certificates for #{role.name} on #{host}"
14
+ execute *app.create_ssl_directory
15
+ if cert_content = role.proxy.certificate_pem_content
16
+ upload!(StringIO.new(cert_content), role.proxy.host_tls_cert, mode: "0644")
17
+ end
18
+ if key_content = role.proxy.private_key_pem_content
19
+ upload!(StringIO.new(key_content), role.proxy.host_tls_key, mode: "0644")
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+ def app
26
+ @app ||= KAMAL.app(role: role, host: host)
27
+ end
28
+ end
data/lib/kamal/cli/app.rb CHANGED
@@ -12,6 +12,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
12
12
 
13
13
  KAMAL.roles_on(host).each do |role|
14
14
  Kamal::Cli::App::Assets.new(host, role, self).run
15
+ Kamal::Cli::App::SslCertificates.new(host, role, self).run
15
16
  end
16
17
  end
17
18
 
@@ -11,6 +11,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
11
11
 
12
12
  desc "push", "Build and push app image to registry"
13
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
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
14
15
  def push
15
16
  cli = self
16
17
 
@@ -19,7 +20,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
19
20
  pre_connect_if_required
20
21
 
21
22
  ensure_docker_installed
22
- login_to_registry_locally
23
+ login_to_registry_locally if KAMAL.builder.login_to_registry_locally?
23
24
 
24
25
  run_hook "pre-build"
25
26
 
@@ -56,10 +57,10 @@ class Kamal::Cli::Build < Kamal::Cli::Base
56
57
  end
57
58
 
58
59
  # Get the command here to ensure the Dir.chdir doesn't interfere with it
59
- push = KAMAL.builder.push(cli.options[:output])
60
+ push = KAMAL.builder.push(cli.options[:output], no_cache: cli.options[:no_cache])
60
61
 
61
62
  KAMAL.with_verbosity(:debug) do
62
- Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
63
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.builder.push_env }
63
64
  end
64
65
  end
65
66
  end
@@ -67,16 +68,18 @@ class Kamal::Cli::Build < Kamal::Cli::Base
67
68
 
68
69
  desc "pull", "Pull app image from registry onto servers"
69
70
  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)
71
+ login_to_registry_remotely unless KAMAL.registry.local?
72
+
73
+ forward_local_registry_port do
74
+ if (first_hosts = mirror_hosts).any?
75
+ #  Pull on a single host per mirror first to seed them
76
+ say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
77
+ pull_on_hosts(first_hosts)
78
+ say "Pulling image on remaining hosts...", :magenta
79
+ pull_on_hosts(KAMAL.app_hosts - first_hosts)
80
+ else
81
+ pull_on_hosts(KAMAL.app_hosts)
82
+ end
80
83
  end
81
84
  end
82
85
 
@@ -119,6 +122,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
119
122
 
120
123
  desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
121
124
  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'."
125
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
122
126
  def dev
123
127
  cli = self
124
128
 
@@ -144,7 +148,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
144
148
 
145
149
  with_env(KAMAL.config.builder.secrets) do
146
150
  run_locally do
147
- build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
151
+ build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true, no_cache: cli.options[:no_cache])
148
152
  KAMAL.with_verbosity(:debug) do
149
153
  execute(*build)
150
154
  end
@@ -192,7 +196,11 @@ class Kamal::Cli::Build < Kamal::Cli::Base
192
196
 
193
197
  def login_to_registry_locally
194
198
  run_locally do
195
- execute *KAMAL.registry.login
199
+ if KAMAL.registry.local?
200
+ execute *KAMAL.registry.setup
201
+ else
202
+ execute *KAMAL.registry.login
203
+ end
196
204
  end
197
205
  end
198
206
 
@@ -201,4 +209,14 @@ class Kamal::Cli::Build < Kamal::Cli::Base
201
209
  execute *KAMAL.registry.login
202
210
  end
203
211
  end
212
+
213
+ def forward_local_registry_port(&block)
214
+ if KAMAL.config.registry.local?
215
+ Kamal::Cli::PortForwarding.
216
+ new(KAMAL.hosts, KAMAL.config.registry.local_port).
217
+ forward(&block)
218
+ else
219
+ yield
220
+ end
221
+ end
204
222
  end
@@ -1,6 +1,7 @@
1
1
  class Kamal::Cli::Main < Kamal::Cli::Base
2
2
  desc "setup", "Setup all accessories, push the env, and deploy app to servers"
3
3
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
4
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
4
5
  def setup
5
6
  print_runtime do
6
7
  with_lock do
@@ -16,6 +17,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
16
17
 
17
18
  desc "deploy", "Deploy app to servers"
18
19
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
20
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
19
21
  def deploy(boot_accessories: false)
20
22
  runtime = print_runtime do
21
23
  invoke_options = deploy_options
@@ -51,6 +53,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
51
53
 
52
54
  desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
53
55
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
56
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
54
57
  def redeploy
55
58
  runtime = print_runtime do
56
59
  invoke_options = deploy_options
@@ -182,7 +185,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
182
185
  invoke "kamal:cli:app:remove", [], options.without(:confirmed)
183
186
  invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
184
187
  invoke "kamal:cli:accessory:remove", [ "all" ], options
185
- invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
188
+ invoke "kamal:cli:registry:remove", [], options.without(:confirmed).merge(skip_local: true)
186
189
  end
187
190
  end
188
191
  end
@@ -272,6 +275,8 @@ class Kamal::Cli::Main < Kamal::Cli::Base
272
275
  end
273
276
 
274
277
  def deploy_options
275
- { "version" => KAMAL.config.version }.merge(options.without("skip_push"))
278
+ base_options = options.without("skip_push")
279
+ base_options = base_options.except("no_cache") unless base_options["no_cache"]
280
+ { "version" => KAMAL.config.version }.merge(base_options)
276
281
  end
277
282
  end
@@ -0,0 +1,42 @@
1
+ class Kamal::Cli::PortForwarding
2
+ attr_reader :hosts, :port
3
+
4
+ def initialize(hosts, port)
5
+ @hosts = hosts
6
+ @port = port
7
+ end
8
+
9
+ def forward
10
+ @done = false
11
+ forward_ports
12
+
13
+ yield
14
+ ensure
15
+ stop
16
+ end
17
+
18
+ private
19
+
20
+ def stop
21
+ @done = true
22
+ @threads.to_a.each(&:join)
23
+ end
24
+
25
+ def forward_ports
26
+ @threads = hosts.map do |host|
27
+ Thread.new do
28
+ Net::SSH.start(host, KAMAL.config.ssh.user, **{ proxy: KAMAL.config.ssh.proxy }.compact) do |ssh|
29
+ ssh.forward.remote(port, "localhost", port, "localhost")
30
+ ssh.loop(0.1) do
31
+ if @done
32
+ ssh.forward.cancel_remote(port, "localhost")
33
+ break
34
+ else
35
+ true
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,19 +1,27 @@
1
1
  class Kamal::Cli::Registry < Kamal::Cli::Base
2
- desc "login", "Log in to registry locally and remotely"
2
+ desc "setup", "Setup local registry or log in to remote registry locally and remotely"
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
- def login
5
+ def setup
6
6
  ensure_docker_installed unless options[:skip_local]
7
7
 
8
- run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
9
- on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
8
+ if KAMAL.registry.local?
9
+ run_locally { execute *KAMAL.registry.setup } unless options[:skip_local]
10
+ else
11
+ run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
12
+ on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
13
+ end
10
14
  end
11
15
 
12
- desc "logout", "Log out of registry locally and remotely"
16
+ desc "remove", "Remove local registry or log out of remote registry locally and remotely"
13
17
  option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
14
18
  option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
15
- def logout
16
- run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
17
- on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
19
+ def remove
20
+ if KAMAL.registry.local?
21
+ run_locally { execute *KAMAL.registry.remove, raise_on_non_zero_exit: false } unless options[:skip_local]
22
+ else
23
+ run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
24
+ on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
25
+ end
18
26
  end
19
27
  end
@@ -25,13 +25,14 @@ proxy:
25
25
 
26
26
  # Credentials for your image host.
27
27
  registry:
28
+ server: localhost:5555
28
29
  # Specify the registry server, if you're not using Docker Hub
29
30
  # server: registry.digitalocean.com / ghcr.io / ...
30
- username: my-user
31
+ # username: my-user
31
32
 
32
33
  # Always use an access token rather than real password (pulled from .kamal/secrets).
33
- password:
34
- - KAMAL_REGISTRY_PASSWORD
34
+ # password:
35
+ # - KAMAL_REGISTRY_PASSWORD
35
36
 
36
37
  # Configure builder setup.
37
38
  builder:
@@ -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 = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
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,6 +77,18 @@ 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
 
@@ -3,7 +3,7 @@
3
3
  # password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
4
4
 
5
5
  # Option 1: Read secrets from the environment
6
- KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
6
+ # KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
7
7
 
8
8
  # Option 2: Read secrets via a command
9
9
  # RAILS_MASTER_KEY=$(cat config/master.key)
@@ -21,7 +21,7 @@ class Kamal::Commander
21
21
  end
22
22
 
23
23
  def config
24
- @config ||= Kamal::Configuration.create_from(**@config_kwargs).tap do |config|
24
+ @config ||= Kamal::Configuration.create_from(**@config_kwargs.to_h).tap do |config|
25
25
  @config_kwargs = nil
26
26
  configure_sshkit_with(config)
27
27
  end
@@ -12,7 +12,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
12
12
  @accessory_config = config.accessory(name)
13
13
  end
14
14
 
15
- def run
15
+ def run(host: nil)
16
16
  docker :run,
17
17
  "--name", service_name,
18
18
  "--detach",
@@ -20,6 +20,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
20
20
  *network_args,
21
21
  *config.logging_args,
22
22
  *publish_args,
23
+ *([ "--env", "KAMAL_HOST=\"#{host}\"" ] if host),
23
24
  *env_args,
24
25
  *volume_args,
25
26
  *label_args,
@@ -55,14 +56,14 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
55
56
 
56
57
  def execute_in_existing_container(*command, interactive: false)
57
58
  docker :exec,
58
- ("-it" if interactive),
59
+ (docker_interactive_args if interactive),
59
60
  service_name,
60
61
  *command
61
62
  end
62
63
 
63
64
  def execute_in_new_container(*command, interactive: false)
64
65
  docker :run,
65
- ("-it" if interactive),
66
+ (docker_interactive_args if interactive),
66
67
  "--rm",
67
68
  *network_args,
68
69
  *env_args,
@@ -89,6 +90,10 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
89
90
  end
90
91
  end
91
92
 
93
+ def pull_image
94
+ docker :image, :pull, image
95
+ end
96
+
92
97
  def remove_service_directory
93
98
  [ :rm, "-rf", service_name ]
94
99
  end
@@ -1,7 +1,7 @@
1
1
  module Kamal::Commands::App::Execution
2
2
  def execute_in_existing_container(*command, interactive: false, env:)
3
3
  docker :exec,
4
- ("-it" if interactive),
4
+ (docker_interactive_args if interactive),
5
5
  *argumentize("--env", env),
6
6
  container_name,
7
7
  *command
@@ -9,7 +9,7 @@ module Kamal::Commands::App::Execution
9
9
 
10
10
  def execute_in_new_container(*command, interactive: false, detach: false, env:)
11
11
  docker :run,
12
- ("-it" if interactive),
12
+ (docker_interactive_args if interactive),
13
13
  ("--detach" if detach),
14
14
  ("--rm" unless detach),
15
15
  "--network", "kamal",
@@ -21,6 +21,10 @@ module Kamal::Commands::App::Proxy
21
21
  remove_directory config.proxy_boot.app_directory
22
22
  end
23
23
 
24
+ def create_ssl_directory
25
+ make_directory(File.join(config.proxy_boot.tls_directory, role.name))
26
+ end
27
+
24
28
  private
25
29
  def proxy_exec(*command)
26
30
  docker :exec, proxy_container_name, "kamal-proxy", *command
@@ -20,8 +20,10 @@ class Kamal::Commands::App < Kamal::Commands::Base
20
20
  "--name", container_name,
21
21
  "--network", "kamal",
22
22
  *([ "--hostname", hostname ] if hostname),
23
- "-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
24
- "-e", "KAMAL_VERSION=\"#{config.version}\"",
23
+ "--env", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
24
+ "--env", "KAMAL_VERSION=\"#{config.version}\"",
25
+ "--env", "KAMAL_HOST=\"#{host}\"",
26
+ "--env", "KAMAL_DESTINATION=\"#{config.destination}\"",
25
27
  *role.env_args(host),
26
28
  *role.logging_args,
27
29
  *config.volume_args,
@@ -84,6 +84,10 @@ module Kamal::Commands
84
84
  args.compact.unshift :docker
85
85
  end
86
86
 
87
+ def pack(*args)
88
+ args.compact.unshift :pack
89
+ end
90
+
87
91
  def git(*args, path: nil)
88
92
  [ :git, *([ "-C", path ] if path), *args.compact ]
89
93
  end
@@ -122,5 +126,9 @@ module Kamal::Commands
122
126
  def ensure_local_buildx_installed
123
127
  docker :buildx, "version"
124
128
  end
129
+
130
+ def docker_interactive_args
131
+ STDIN.isatty ? "-it" : "-i"
132
+ end
125
133
  end
126
134
  end
@@ -6,6 +6,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
6
6
  delegate :argumentize, to: Kamal::Utils
7
7
  delegate \
8
8
  :args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
9
+ :pack?, :pack_builder, :pack_buildpacks,
9
10
  :cache_from, :cache_to, :ssh, :provenance, :sbom, :driver, :docker_driver?,
10
11
  to: :builder_config
11
12
 
@@ -13,13 +14,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
13
14
  docker :image, :rm, "--force", config.absolute_image
14
15
  end
15
16
 
16
- def push(export_action = "registry", tag_as_dirty: false)
17
+ def push(export_action = "registry", tag_as_dirty: false, no_cache: false)
17
18
  docker :buildx, :build,
18
19
  "--output=type=#{export_action}",
19
20
  *platform_options(arches),
20
21
  *([ "--builder", builder_name ] unless docker_driver?),
21
22
  *build_tag_options(tag_as_dirty: tag_as_dirty),
22
23
  *build_options,
24
+ *([ "--no-cache" ] if no_cache),
23
25
  build_context,
24
26
  "2>&1"
25
27
  end
@@ -59,6 +61,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
59
61
  docker(:info, "--format '{{index .RegistryConfig.Mirrors 0}}'")
60
62
  end
61
63
 
64
+ def login_to_registry_locally?
65
+ true
66
+ end
67
+
68
+ def push_env
69
+ {}
70
+ end
71
+
62
72
  private
63
73
  def build_tag_names(tag_as_dirty: false)
64
74
  tag_names = [ config.absolute_image, config.latest_image ]
@@ -1,6 +1,15 @@
1
1
  class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
2
2
  def create
3
- docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver?
3
+ return if docker_driver?
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
4
13
  end
5
14
 
6
15
  def remove
@@ -9,6 +18,10 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
9
18
 
10
19
  private
11
20
  def builder_name
12
- "kamal-local-#{driver}"
21
+ if KAMAL.registry.local?
22
+ "kamal-local-registry-#{driver}"
23
+ else
24
+ "kamal-local-#{driver}"
25
+ end
13
26
  end
14
27
  end
@@ -0,0 +1,46 @@
1
+ class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
2
+ def push(export_action = "registry", tag_as_dirty: false, no_cache: false)
3
+ combine \
4
+ build(tag_as_dirty: tag_as_dirty, no_cache: no_cache),
5
+ export(export_action)
6
+ end
7
+
8
+ def remove;end
9
+
10
+ def info
11
+ pack :builder, :inspect, pack_builder
12
+ end
13
+ alias_method :inspect_builder, :info
14
+
15
+ private
16
+ def build(tag_as_dirty: false, no_cache: false)
17
+ pack(:build,
18
+ config.repository,
19
+ "--platform", platform,
20
+ "--creation-time", "now",
21
+ "--builder", pack_builder,
22
+ buildpacks,
23
+ *build_tag_options(tag_as_dirty: tag_as_dirty),
24
+ *([ "--clear-cache" ] if no_cache),
25
+ "--env", "BP_IMAGE_LABELS=service=#{config.service}",
26
+ *argumentize("--env", args),
27
+ *argumentize("--env", secrets, sensitive: true),
28
+ "--path", build_context)
29
+ end
30
+
31
+ def export(export_action)
32
+ return unless export_action == "registry"
33
+
34
+ combine \
35
+ docker(:push, config.absolute_image),
36
+ docker(:push, config.latest_image)
37
+ end
38
+
39
+ def platform
40
+ "linux/#{local_arches.first}"
41
+ end
42
+
43
+ def buildpacks
44
+ (pack_buildpacks << "paketo-buildpacks/image-labels").map { |buildpack| [ "--buildpack", buildpack ] }
45
+ end
46
+ end
@@ -19,11 +19,19 @@ class Kamal::Commands::Builder::Remote < Kamal::Commands::Builder::Base
19
19
 
20
20
  def inspect_builder
21
21
  combine \
22
- combine inspect_buildx, inspect_remote_context,
22
+ combine(inspect_buildx, inspect_remote_context),
23
23
  [ "(echo no compatible builder && exit 1)" ],
24
24
  by: "||"
25
25
  end
26
26
 
27
+ def login_to_registry_locally?
28
+ false
29
+ end
30
+
31
+ def push_env
32
+ { "BUILDKIT_NO_CLIENT_TOKEN" => "1" }
33
+ end
34
+
27
35
  private
28
36
  def builder_name
29
37
  "kamal-remote-#{remote.gsub(/[^a-z0-9_-]/, "-")}"
@@ -1,8 +1,14 @@
1
1
  require "active_support/core_ext/string/filters"
2
2
 
3
3
  class Kamal::Commands::Builder < Kamal::Commands::Base
4
- delegate :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
5
- delegate :local?, :remote?, :cloud?, to: "config.builder"
4
+ delegate \
5
+ :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder,
6
+ :validate_image, :first_mirror, :login_to_registry_locally?, :push_env,
7
+ to: :target
8
+
9
+ delegate \
10
+ :local?, :remote?, :pack?, :cloud?,
11
+ to: "config.builder"
6
12
 
7
13
  include Clone
8
14
 
@@ -17,6 +23,8 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
17
23
  else
18
24
  remote
19
25
  end
26
+ elsif pack?
27
+ pack
20
28
  elsif cloud?
21
29
  cloud
22
30
  else
@@ -36,6 +44,10 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
36
44
  @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
37
45
  end
38
46
 
47
+ def pack
48
+ @pack ||= Kamal::Commands::Builder::Pack.new(config)
49
+ end
50
+
39
51
  def cloud
40
52
  @cloud ||= Kamal::Commands::Builder::Cloud.new(config)
41
53
  end