kamal 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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