kamal 2.4.0 → 2.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +1 -1
  3. data/lib/kamal/cli/alias/command.rb +1 -0
  4. data/lib/kamal/cli/app.rb +15 -3
  5. data/lib/kamal/cli/base.rb +16 -1
  6. data/lib/kamal/cli/build.rb +36 -14
  7. data/lib/kamal/cli/main.rb +4 -3
  8. data/lib/kamal/cli/proxy.rb +2 -4
  9. data/lib/kamal/cli/registry.rb +2 -0
  10. data/lib/kamal/cli/templates/deploy.yml +2 -2
  11. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  12. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  13. data/lib/kamal/cli.rb +1 -0
  14. data/lib/kamal/commander.rb +16 -25
  15. data/lib/kamal/commands/accessory.rb +1 -5
  16. data/lib/kamal/commands/app/assets.rb +4 -4
  17. data/lib/kamal/commands/base.rb +14 -0
  18. data/lib/kamal/commands/builder/base.rb +12 -5
  19. data/lib/kamal/commands/builder/cloud.rb +22 -0
  20. data/lib/kamal/commands/builder.rb +6 -20
  21. data/lib/kamal/commands/registry.rb +9 -7
  22. data/lib/kamal/configuration/accessory.rb +36 -19
  23. data/lib/kamal/configuration/builder.rb +4 -0
  24. data/lib/kamal/configuration/docs/accessory.yml +20 -1
  25. data/lib/kamal/configuration/docs/builder.yml +3 -0
  26. data/lib/kamal/configuration/registry.rb +6 -6
  27. data/lib/kamal/configuration/role.rb +6 -6
  28. data/lib/kamal/configuration/validator/role.rb +1 -1
  29. data/lib/kamal/configuration.rb +29 -12
  30. data/lib/kamal/docker.rb +30 -0
  31. data/lib/kamal/git.rb +10 -0
  32. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +12 -4
  33. data/lib/kamal/secrets/adapters/base.rb +5 -2
  34. data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
  35. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +72 -0
  36. data/lib/kamal/secrets/adapters/doppler.rb +15 -11
  37. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  38. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  39. data/lib/kamal/secrets/adapters/last_pass.rb +3 -2
  40. data/lib/kamal/secrets/adapters/one_password.rb +2 -2
  41. data/lib/kamal/secrets/adapters/test.rb +2 -2
  42. data/lib/kamal/secrets/adapters.rb +2 -0
  43. data/lib/kamal/version.rb +1 -1
  44. metadata +10 -4
  45. data/lib/kamal/secrets/adapters/test_optional_account.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 246d570a9a6f85698245aa14237986648b39f5e32750bd6f76fd401f96e2398a
4
- data.tar.gz: 02a6b0c3aff13021ee86381e5c10bd4f5b7708d8f9c13c92e03ddd46ed5fa9d5
3
+ metadata.gz: '01499d423cd415dea520fe78d0e5f17b0d67d3ef2d241e56abb84e2deaeb3f65'
4
+ data.tar.gz: 826b542e67f360b9d019d886a454fff796b73cda57b5e201edc2981e5059a1dd
5
5
  SHA512:
6
- metadata.gz: 31365397457be212570677a8dfa4fd3aac1701a4f251bc989100f4ea043edc0f29265d92c9bdde432b323ceadb62c7ccc491c8f6bc9c88b49d0835d193447f83
7
- data.tar.gz: cc6f0cbce224c0234bbaba0ed5bf78001cbb9e888a969154e2728b07b36e8fb6e7a24fa1db2b5a52159f49de5fcee66d28de816ba5b9a8f95285a0e75f08a090
6
+ metadata.gz: bbc7aa2632e0e5810666cb6d93ce698461fd3290dd5813c11bb88dd9faa4d6f7f015140c24a928b588df34316b814aec6ce710cbb4a28aa19713cc3420c15ae3
7
+ data.tar.gz: 2c818511055983038c9cbc9674a6f9c122de98296e27bf47441b02f3f9474176416e07cf1928bfc5b333648e51bb9ff4d857249173df82127e2ed29a124a97a7
@@ -292,7 +292,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
292
292
  def prepare(name)
293
293
  with_accessory(name) do |accessory, hosts|
294
294
  on(hosts) do
295
- execute *KAMAL.registry.login
295
+ execute *KAMAL.registry.login(registry_config: accessory.registry)
296
296
  execute *KAMAL.docker.create_network
297
297
  rescue SSHKit::Command::Failed => e
298
298
  raise unless e.message.include?("already exists")
@@ -1,6 +1,7 @@
1
1
  class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
2
  def run(instance, args = [])
3
3
  if (_alias = KAMAL.config.aliases[name])
4
+ KAMAL.reset
4
5
  Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
6
  else
6
7
  super
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
- on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
20
- KAMAL.roles_on(host).each do |role|
21
- Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
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
@@ -340,4 +348,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
340
348
  yield
341
349
  end
342
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
343
355
  end
@@ -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?() false end
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
@@ -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
- verify_local_dependencies
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
- private
112
- def verify_local_dependencies
113
- run_locally do
114
- begin
115
- execute *KAMAL.builder.ensure_local_dependencies_installed
116
- rescue SSHKit::Command::Failed => e
117
- build_error = e.message =~ /command not found/ ?
118
- "Docker is not installed locally" :
119
- "Docker buildx plugin is not installed locally"
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
- raise BuildError, build_error
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"
@@ -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
- invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
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
 
@@ -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
@@ -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
@@ -38,7 +38,7 @@ builder:
38
38
  arch: amd64
39
39
  # Pass in additional build args needed for your Dockerfile.
40
40
  # args:
41
- # RUBY_VERSION: <%= File.read('.ruby-version').strip %>
41
+ # RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
42
42
 
43
43
  # Inject ENV variables into containers (secrets come from .kamal/secrets).
44
44
  #
@@ -49,7 +49,7 @@ builder:
49
49
  # - RAILS_MASTER_KEY
50
50
 
51
51
  # Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
52
- # "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.
53
53
  #
54
54
  # aliases:
55
55
  # shell: app exec --interactive --reuse "bash"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
data/lib/kamal/cli.rb CHANGED
@@ -2,6 +2,7 @@ module Kamal::Cli
2
2
  class BootError < StandardError; end
3
3
  class HookError < StandardError; end
4
4
  class LockError < StandardError; end
5
+ class DependencyError < StandardError; end
5
6
  end
6
7
 
7
8
  # SSHKit uses instance eval, so we need a global const for ergonomics
@@ -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
@@ -4,11 +4,10 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
4
4
  attr_reader :accessory_config
5
5
  delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
6
6
  :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
7
- :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?,
7
+ :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
8
8
  to: :accessory_config
9
9
  delegate :proxy_container_name, to: :config
10
10
 
11
-
12
11
  def initialize(config, name:)
13
12
  super(config)
14
13
  @accessory_config = config.accessory(name)
@@ -42,7 +41,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
42
41
  docker :ps, *service_filter
43
42
  end
44
43
 
45
-
46
44
  def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
47
45
  pipe \
48
46
  docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
@@ -56,7 +54,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
56
54
  (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
57
55
  end
58
56
 
59
-
60
57
  def execute_in_existing_container(*command, interactive: false)
61
58
  docker :exec,
62
59
  ("-it" if interactive),
@@ -87,7 +84,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
87
84
  super command, host: hosts.first
88
85
  end
89
86
 
90
-
91
87
  def ensure_local_file_present(local_file)
92
88
  if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
93
89
  raise "Missing file: #{local_file}"
@@ -4,10 +4,10 @@ module Kamal::Commands::App::Assets
4
4
 
5
5
  combine \
6
6
  make_directory(role.asset_extracted_directory),
7
- [ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ],
8
- docker(:run, "--name", asset_container, "--detach", "--rm", "--entrypoint", "sleep", config.absolute_image, "1000000"),
9
- docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
10
- docker(:stop, "-t 1", asset_container),
7
+ [ *docker(:container, :rm, asset_container, "2> /dev/null"), "|| true" ],
8
+ docker(:container, :create, "--name", asset_container, config.absolute_image),
9
+ docker(:container, :cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
10
+ docker(:container, :rm, asset_container),
11
11
  by: "&&"
12
12
  end
13
13
 
@@ -34,6 +34,12 @@ module Kamal::Commands
34
34
  [ :rm, path ]
35
35
  end
36
36
 
37
+ def ensure_docker_installed
38
+ combine \
39
+ ensure_local_docker_installed,
40
+ ensure_local_buildx_installed
41
+ end
42
+
37
43
  private
38
44
  def combine(*commands, by: "&&")
39
45
  commands
@@ -104,5 +110,13 @@ module Kamal::Commands
104
110
  " -i #{key}"
105
111
  end
106
112
  end
113
+
114
+ def ensure_local_docker_installed
115
+ docker "--version"
116
+ end
117
+
118
+ def ensure_local_buildx_installed
119
+ docker :buildx, "version"
120
+ end
107
121
  end
108
122
  end
@@ -13,11 +13,12 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
13
13
  docker :image, :rm, "--force", config.absolute_image
14
14
  end
15
15
 
16
- def push
16
+ def push(export_action = "registry", tag_as_dirty: false)
17
17
  docker :buildx, :build,
18
- "--push",
18
+ "--output=type=#{export_action}",
19
19
  *platform_options(arches),
20
20
  *([ "--builder", builder_name ] unless docker_driver?),
21
+ *build_tag_options(tag_as_dirty: tag_as_dirty),
21
22
  *build_options,
22
23
  build_context
23
24
  end
@@ -37,7 +38,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
37
38
  end
38
39
 
39
40
  def build_options
40
- [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
41
+ [ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
41
42
  end
42
43
 
43
44
  def build_context
@@ -58,8 +59,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
58
59
  end
59
60
 
60
61
  private
61
- def build_tags
62
- [ "-t", config.absolute_image, "-t", config.latest_image ]
62
+ def build_tag_names(tag_as_dirty: false)
63
+ tag_names = [ config.absolute_image, config.latest_image ]
64
+ tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
65
+ tag_names
66
+ end
67
+
68
+ def build_tag_options(tag_as_dirty: false)
69
+ build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
63
70
  end
64
71
 
65
72
  def build_cache
@@ -0,0 +1,22 @@
1
+ class Kamal::Commands::Builder::Cloud < Kamal::Commands::Builder::Base
2
+ # Expects `driver` to be of format "cloud docker-org-name/builder-name"
3
+
4
+ def create
5
+ docker :buildx, :create, "--driver", driver
6
+ end
7
+
8
+ def remove
9
+ docker :buildx, :rm, builder_name
10
+ end
11
+
12
+ private
13
+ def builder_name
14
+ driver.gsub(/[ \/]/, "-")
15
+ end
16
+
17
+ def inspect_buildx
18
+ pipe \
19
+ docker(:buildx, :inspect, builder_name),
20
+ grep("-q", "Endpoint:.*cloud://.*")
21
+ end
22
+ end
@@ -1,8 +1,8 @@
1
1
  require "active_support/core_ext/string/filters"
2
2
 
3
3
  class Kamal::Commands::Builder < Kamal::Commands::Base
4
- delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
5
- delegate :local?, :remote?, to: "config.builder"
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"
6
6
 
7
7
  include Clone
8
8
 
@@ -17,6 +17,8 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
17
17
  else
18
18
  remote
19
19
  end
20
+ elsif cloud?
21
+ cloud
20
22
  else
21
23
  local
22
24
  end
@@ -34,23 +36,7 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
34
36
  @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
35
37
  end
36
38
 
37
-
38
- def ensure_local_dependencies_installed
39
- if name.native?
40
- ensure_local_docker_installed
41
- else
42
- combine \
43
- ensure_local_docker_installed,
44
- ensure_local_buildx_installed
45
- end
39
+ def cloud
40
+ @cloud ||= Kamal::Commands::Builder::Cloud.new(config)
46
41
  end
47
-
48
- private
49
- def ensure_local_docker_installed
50
- docker "--version"
51
- end
52
-
53
- def ensure_local_buildx_installed
54
- docker :buildx, "version"
55
- end
56
42
  end
@@ -1,14 +1,16 @@
1
1
  class Kamal::Commands::Registry < Kamal::Commands::Base
2
- delegate :registry, to: :config
2
+ def login(registry_config: nil)
3
+ registry_config ||= config.registry
3
4
 
4
- def login
5
5
  docker :login,
6
- registry.server,
7
- "-u", sensitive(Kamal::Utils.escape_shell_value(registry.username)),
8
- "-p", sensitive(Kamal::Utils.escape_shell_value(registry.password))
6
+ registry_config.server,
7
+ "-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
8
+ "-p", sensitive(Kamal::Utils.escape_shell_value(registry_config.password))
9
9
  end
10
10
 
11
- def logout
12
- docker :logout, registry.server
11
+ def logout(registry_config: nil)
12
+ registry_config ||= config.registry
13
+
14
+ docker :logout, registry_config.server
13
15
  end
14
16
  end