kamal 1.3.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +38 -29
  3. data/lib/kamal/cli/app/boot.rb +67 -0
  4. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  5. data/lib/kamal/cli/app.rb +25 -67
  6. data/lib/kamal/cli/base.rb +23 -8
  7. data/lib/kamal/cli/env.rb +3 -5
  8. data/lib/kamal/cli/main.rb +7 -4
  9. data/lib/kamal/cli/prune.rb +6 -2
  10. data/lib/kamal/cli/server.rb +3 -1
  11. data/lib/kamal/cli/templates/deploy.yml +5 -1
  12. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +7 -0
  13. data/lib/kamal/cli/traefik.rb +16 -13
  14. data/lib/kamal/commander/specifics.rb +49 -0
  15. data/lib/kamal/commander.rb +9 -33
  16. data/lib/kamal/commands/accessory.rb +2 -2
  17. data/lib/kamal/commands/app/assets.rb +12 -12
  18. data/lib/kamal/commands/app/cord.rb +4 -4
  19. data/lib/kamal/commands/app/execution.rb +10 -8
  20. data/lib/kamal/commands/app/images.rb +1 -1
  21. data/lib/kamal/commands/app/logging.rb +2 -2
  22. data/lib/kamal/commands/app.rb +38 -18
  23. data/lib/kamal/commands/auditor.rb +1 -1
  24. data/lib/kamal/commands/base.rb +12 -0
  25. data/lib/kamal/commands/builder/base.rb +22 -5
  26. data/lib/kamal/commands/builder/multiarch.rb +17 -9
  27. data/lib/kamal/commands/builder/native/cached.rb +7 -6
  28. data/lib/kamal/commands/builder/native/remote.rb +9 -9
  29. data/lib/kamal/commands/builder/native.rb +8 -7
  30. data/lib/kamal/commands/docker.rb +10 -1
  31. data/lib/kamal/commands/healthcheck.rb +0 -1
  32. data/lib/kamal/commands/hook.rb +1 -1
  33. data/lib/kamal/commands/lock.rb +19 -9
  34. data/lib/kamal/commands/prune.rb +4 -4
  35. data/lib/kamal/commands/registry.rb +4 -1
  36. data/lib/kamal/commands/server.rb +1 -1
  37. data/lib/kamal/commands/traefik.rb +10 -16
  38. data/lib/kamal/configuration/accessory.rb +10 -20
  39. data/lib/kamal/configuration/boot.rb +1 -1
  40. data/lib/kamal/configuration/builder.rb +11 -3
  41. data/lib/kamal/configuration/env.rb +40 -0
  42. data/lib/kamal/configuration/role.rb +23 -40
  43. data/lib/kamal/configuration.rb +53 -21
  44. data/lib/kamal/env_file.rb +12 -15
  45. data/lib/kamal/sshkit_with_ext.rb +1 -0
  46. data/lib/kamal/utils.rb +7 -3
  47. data/lib/kamal/version.rb +1 -1
  48. data/lib/kamal.rb +1 -1
  49. metadata +8 -3
@@ -11,20 +11,23 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
11
11
 
12
12
  desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
13
13
  option :rolling, type: :boolean, default: false, desc: "Reboot traefik on hosts in sequence, rather than in parallel"
14
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
14
15
  def reboot
15
- mutating do
16
- host_groups = options[:rolling] ? KAMAL.traefik_hosts : [KAMAL.traefik_hosts]
17
- host_groups.each do |hosts|
18
- host_list = Array(hosts).join(",")
19
- run_hook "pre-traefik-reboot", hosts: host_list
20
- on(hosts) do
21
- execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
22
- execute *KAMAL.registry.login
23
- execute *KAMAL.traefik.stop
24
- execute *KAMAL.traefik.remove_container
25
- execute *KAMAL.traefik.run
16
+ confirming "This will cause a brief outage on each host. Are you sure?" do
17
+ mutating do
18
+ host_groups = options[:rolling] ? KAMAL.traefik_hosts : [ KAMAL.traefik_hosts ]
19
+ host_groups.each do |hosts|
20
+ host_list = Array(hosts).join(",")
21
+ run_hook "pre-traefik-reboot", hosts: host_list
22
+ on(hosts) do
23
+ execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
24
+ execute *KAMAL.registry.login
25
+ execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
26
+ execute *KAMAL.traefik.remove_container
27
+ execute *KAMAL.traefik.run
28
+ end
29
+ run_hook "post-traefik-reboot", hosts: host_list
26
30
  end
27
- run_hook "post-traefik-reboot", hosts: host_list
28
31
  end
29
32
  end
30
33
  end
@@ -44,7 +47,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
44
47
  mutating do
45
48
  on(KAMAL.traefik_hosts) do
46
49
  execute *KAMAL.auditor.record("Stopped traefik"), verbosity: :debug
47
- execute *KAMAL.traefik.stop
50
+ execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
48
51
  end
49
52
  end
50
53
  end
@@ -0,0 +1,49 @@
1
+ class Kamal::Commander::Specifics
2
+ attr_reader :primary_host, :primary_role, :hosts, :roles
3
+ delegate :stable_sort!, to: Kamal::Utils
4
+
5
+ def initialize(config, specific_hosts, specific_roles)
6
+ @config, @specific_hosts, @specific_roles = config, specific_hosts, specific_roles
7
+
8
+ @roles, @hosts = specified_roles, specified_hosts
9
+
10
+ @primary_host = specific_hosts&.first || primary_specific_role&.primary_host || config.primary_host
11
+ @primary_role = primary_or_first_role(roles_on(primary_host))
12
+
13
+ stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
14
+ stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
15
+ end
16
+
17
+ def roles_on(host)
18
+ roles.select { |role| role.hosts.include?(host.to_s) }
19
+ end
20
+
21
+ def traefik_hosts
22
+ specific_hosts || config.traefik_hosts
23
+ end
24
+
25
+ def accessory_hosts
26
+ specific_hosts || config.accessories.flat_map(&:hosts)
27
+ end
28
+
29
+ private
30
+ attr_reader :config, :specific_hosts, :specific_roles
31
+
32
+ def primary_specific_role
33
+ primary_or_first_role(specific_roles) if specific_roles.present?
34
+ end
35
+
36
+ def primary_or_first_role(roles)
37
+ roles.detect { |role| role == config.primary_role } || roles.first
38
+ end
39
+
40
+ def specified_roles
41
+ (specific_roles || config.roles) \
42
+ .select { |role| ((specific_hosts || config.all_hosts) & role.hosts).any? }
43
+ end
44
+
45
+ def specified_hosts
46
+ (specific_hosts || config.all_hosts) \
47
+ .select { |host| (specific_roles || config.roles).flat_map(&:hosts).include?(host) }
48
+ end
49
+ end
@@ -3,11 +3,13 @@ require "active_support/core_ext/module/delegation"
3
3
 
4
4
  class Kamal::Commander
5
5
  attr_accessor :verbosity, :holding_lock, :hold_lock_on_error
6
+ delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :traefik_hosts, :accessory_hosts, to: :specifics
6
7
 
7
8
  def initialize
8
9
  self.verbosity = :info
9
10
  self.holding_lock = false
10
11
  self.hold_lock_on_error = false
12
+ @specifics = nil
11
13
  end
12
14
 
13
15
  def config
@@ -24,10 +26,12 @@ class Kamal::Commander
24
26
  attr_reader :specific_roles, :specific_hosts
25
27
 
26
28
  def specific_primary!
29
+ @specifics = nil
27
30
  self.specific_hosts = [ config.primary_host ]
28
31
  end
29
32
 
30
33
  def specific_roles=(role_names)
34
+ @specifics = nil
31
35
  if role_names.present?
32
36
  @specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles)
33
37
 
@@ -40,6 +44,7 @@ class Kamal::Commander
40
44
  end
41
45
 
42
46
  def specific_hosts=(hosts)
47
+ @specifics = nil
43
48
  if hosts.present?
44
49
  @specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
45
50
 
@@ -51,39 +56,6 @@ class Kamal::Commander
51
56
  end
52
57
  end
53
58
 
54
- def primary_host
55
- # Given a list of specific roles, make an effort to match up with the primary_role
56
- specific_hosts&.first || specific_roles&.detect { |role| role.name == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host
57
- end
58
-
59
- def primary_role
60
- roles_on(primary_host).first
61
- end
62
-
63
- def roles
64
- (specific_roles || config.roles).select do |role|
65
- ((specific_hosts || config.all_hosts) & role.hosts).any?
66
- end
67
- end
68
-
69
- def hosts
70
- (specific_hosts || config.all_hosts).select do |host|
71
- (specific_roles || config.roles).flat_map(&:hosts).include?(host)
72
- end
73
- end
74
-
75
- def roles_on(host)
76
- roles.select { |role| role.hosts.include?(host.to_s) }.map(&:name)
77
- end
78
-
79
- def traefik_hosts
80
- specific_hosts || config.traefik_hosts
81
- end
82
-
83
- def accessory_hosts
84
- specific_hosts || config.accessories.flat_map(&:hosts)
85
- end
86
-
87
59
  def accessory_names
88
60
  config.accessories&.collect(&:name) || []
89
61
  end
@@ -181,4 +153,8 @@ class Kamal::Commander
181
153
  SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
182
154
  SSHKit.config.output_verbosity = verbosity
183
155
  end
156
+
157
+ def specifics
158
+ @specifics ||= Kamal::Commander::Specifics.new(config, specific_hosts, specific_roles)
159
+ end
184
160
  end
@@ -99,11 +99,11 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
99
99
  end
100
100
 
101
101
  def make_env_directory
102
- make_directory accessory_config.host_env_directory
102
+ make_directory accessory_config.env.secrets_directory
103
103
  end
104
104
 
105
105
  def remove_env_file
106
- [:rm, "-f", accessory_config.host_env_file_path]
106
+ [ :rm, "-f", accessory_config.env.secrets_file ]
107
107
  end
108
108
 
109
109
  private
@@ -1,23 +1,23 @@
1
1
  module Kamal::Commands::App::Assets
2
2
  def extract_assets
3
- asset_container = "#{role_config.container_prefix}-assets"
3
+ asset_container = "#{role.container_prefix}-assets"
4
4
 
5
5
  combine \
6
- make_directory(role_config.asset_extracted_path),
7
- [*docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true"],
8
- docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep 1000000"),
9
- docker(:cp, "-L", "#{asset_container}:#{role_config.asset_path}/.", role_config.asset_extracted_path),
6
+ make_directory(role.asset_extracted_path),
7
+ [ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ],
8
+ docker(:run, "--name", asset_container, "--detach", "--rm", config.absolute_image, "sleep 1000000"),
9
+ docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_path),
10
10
  docker(:stop, "-t 1", asset_container),
11
11
  by: "&&"
12
12
  end
13
13
 
14
14
  def sync_asset_volumes(old_version: nil)
15
- new_extracted_path, new_volume_path = role_config.asset_extracted_path(config.version), role_config.asset_volume.host_path
15
+ new_extracted_path, new_volume_path = role.asset_extracted_path(config.version), role.asset_volume.host_path
16
16
  if old_version.present?
17
- old_extracted_path, old_volume_path = role_config.asset_extracted_path(old_version), role_config.asset_volume(old_version).host_path
17
+ old_extracted_path, old_volume_path = role.asset_extracted_path(old_version), role.asset_volume(old_version).host_path
18
18
  end
19
19
 
20
- commands = [make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path)]
20
+ commands = [ make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path) ]
21
21
 
22
22
  if old_version.present?
23
23
  commands << copy_contents(new_extracted_path, old_volume_path, continue_on_error: true)
@@ -29,8 +29,8 @@ module Kamal::Commands::App::Assets
29
29
 
30
30
  def clean_up_assets
31
31
  chain \
32
- find_and_remove_older_siblings(role_config.asset_extracted_path),
33
- find_and_remove_older_siblings(role_config.asset_volume_path)
32
+ find_and_remove_older_siblings(role.asset_extracted_path),
33
+ find_and_remove_older_siblings(role.asset_volume_path)
34
34
  end
35
35
 
36
36
  private
@@ -39,13 +39,13 @@ module Kamal::Commands::App::Assets
39
39
  :find,
40
40
  Pathname.new(path).dirname.to_s,
41
41
  "-maxdepth 1",
42
- "-name", "'#{role_config.container_prefix}-*'",
42
+ "-name", "'#{role.container_prefix}-*'",
43
43
  "!", "-name", Pathname.new(path).basename.to_s,
44
44
  "-exec rm -rf \"{}\" +"
45
45
  ]
46
46
  end
47
47
 
48
48
  def copy_contents(source, destination, continue_on_error: false)
49
- [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error)]
49
+ [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error) ]
50
50
  end
51
51
  end
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Cord
2
2
  def cord(version:)
3
3
  pipe \
4
4
  docker(:inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", container_name(version)),
5
- [:awk, "'$2 == \"#{role_config.cord_volume.container_path}\" {print $1}'"]
5
+ [ :awk, "'$2 == \"#{role.cord_volume.container_path}\" {print $1}'" ]
6
6
  end
7
7
 
8
8
  def tie_cord(cord)
@@ -12,11 +12,11 @@ module Kamal::Commands::App::Cord
12
12
  def cut_cord(cord)
13
13
  remove_directory(cord)
14
14
  end
15
-
16
- private
15
+
16
+ private
17
17
  def create_empty_file(file)
18
18
  chain \
19
19
  make_directory_for(file),
20
- [:touch, file]
20
+ [ :touch, file ]
21
21
  end
22
22
  end
@@ -1,27 +1,29 @@
1
1
  module Kamal::Commands::App::Execution
2
- def execute_in_existing_container(*command, interactive: false)
2
+ def execute_in_existing_container(*command, interactive: false, env:)
3
3
  docker :exec,
4
4
  ("-it" if interactive),
5
+ *argumentize("--env", env),
5
6
  container_name,
6
7
  *command
7
8
  end
8
9
 
9
- def execute_in_new_container(*command, interactive: false)
10
+ def execute_in_new_container(*command, interactive: false, env:)
10
11
  docker :run,
11
12
  ("-it" if interactive),
12
13
  "--rm",
13
- *role_config&.env_args,
14
+ *role&.env_args,
15
+ *argumentize("--env", env),
14
16
  *config.volume_args,
15
- *role_config&.option_args,
17
+ *role&.option_args,
16
18
  config.absolute_image,
17
19
  *command
18
20
  end
19
21
 
20
- def execute_in_existing_container_over_ssh(*command, host:)
21
- run_over_ssh execute_in_existing_container(*command, interactive: true), host: host
22
+ def execute_in_existing_container_over_ssh(*command, host:, env:)
23
+ run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
22
24
  end
23
25
 
24
- def execute_in_new_container_over_ssh(*command, host:)
25
- run_over_ssh execute_in_new_container(*command, interactive: true), host: host
26
+ def execute_in_new_container_over_ssh(*command, host:, env:)
27
+ run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
26
28
  end
27
29
  end
@@ -7,7 +7,7 @@ module Kamal::Commands::App::Images
7
7
  docker :image, :prune, "--all", "--force", *filter_args
8
8
  end
9
9
 
10
- def tag_current_image_as_latest
10
+ def tag_latest_image
11
11
  docker :tag, config.absolute_image, config.latest_image
12
12
  end
13
13
  end
@@ -6,11 +6,11 @@ module Kamal::Commands::App::Logging
6
6
  ("grep '#{grep}'" if grep)
7
7
  end
8
8
 
9
- def follow_logs(host:, grep: nil)
9
+ def follow_logs(host:, lines: nil, grep: nil)
10
10
  run_over_ssh \
11
11
  pipe(
12
12
  current_running_container_id,
13
- "xargs docker logs --timestamps --tail 10 --follow 2>&1",
13
+ "xargs docker logs --timestamps#{" --tail #{lines}" if lines} --follow 2>&1",
14
14
  (%(grep "#{grep}") if grep)
15
15
  ),
16
16
  host: host
@@ -3,12 +3,11 @@ class Kamal::Commands::App < Kamal::Commands::Base
3
3
 
4
4
  ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
5
5
 
6
- attr_reader :role, :role_config
6
+ attr_reader :role, :role
7
7
 
8
8
  def initialize(config, role: nil)
9
9
  super(config)
10
10
  @role = role
11
- @role_config = config.role(self.role)
12
11
  end
13
12
 
14
13
  def run(hostname: nil)
@@ -16,18 +15,18 @@ class Kamal::Commands::App < Kamal::Commands::Base
16
15
  "--detach",
17
16
  "--restart unless-stopped",
18
17
  "--name", container_name,
19
- *(["--hostname", hostname] if hostname),
18
+ *([ "--hostname", hostname ] if hostname),
20
19
  "-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
21
20
  "-e", "KAMAL_VERSION=\"#{config.version}\"",
22
- *role_config.env_args,
23
- *role_config.health_check_args,
24
- *config.logging_args,
21
+ *role.env_args,
22
+ *role.health_check_args,
23
+ *role.logging_args,
25
24
  *config.volume_args,
26
- *role_config.asset_volume_args,
27
- *role_config.label_args,
28
- *role_config.option_args,
25
+ *role.asset_volume_args,
26
+ *role.label_args,
27
+ *role.option_args,
29
28
  config.absolute_image,
30
- role_config.cmd
29
+ role.cmd
31
30
  end
32
31
 
33
32
  def start
@@ -50,7 +49,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
50
49
 
51
50
 
52
51
  def current_running_container_id
53
- docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest"
52
+ current_running_container(format: "--quiet")
54
53
  end
55
54
 
56
55
  def container_id_for_version(version, only_running: false)
@@ -58,36 +57,57 @@ class Kamal::Commands::App < Kamal::Commands::Base
58
57
  end
59
58
 
60
59
  def current_running_version
61
- list_versions("--latest", statuses: ACTIVE_DOCKER_STATUSES)
60
+ pipe \
61
+ current_running_container(format: "--format '{{.Names}}'"),
62
+ extract_version_from_name
62
63
  end
63
64
 
64
65
  def list_versions(*docker_args, statuses: nil)
65
66
  pipe \
66
67
  docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
67
- %(while read line; do echo ${line##{role_config.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA"
68
+ extract_version_from_name
68
69
  end
69
70
 
70
71
 
71
72
  def make_env_directory
72
- make_directory role_config.host_env_directory
73
+ make_directory role.env.secrets_directory
73
74
  end
74
75
 
75
76
  def remove_env_file
76
- [ :rm, "-f", role_config.host_env_file_path ]
77
+ [ :rm, "-f", role.env.secrets_file ]
77
78
  end
78
79
 
79
80
 
80
81
  private
81
82
  def container_name(version = nil)
82
- [ role_config.container_prefix, version || config.version ].compact.join("-")
83
+ [ role.container_prefix, version || config.version ].compact.join("-")
84
+ end
85
+
86
+ def latest_image_id
87
+ docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'"
88
+ end
89
+
90
+ def current_running_container(format:)
91
+ pipe \
92
+ shell(chain(latest_image_container(format: format), latest_container(format: format))),
93
+ [ :head, "-1" ]
94
+ end
95
+
96
+ def latest_image_container(format:)
97
+ latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ]
98
+ end
99
+
100
+ def latest_container(format:, filters: nil)
101
+ docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
83
102
  end
84
103
 
85
104
  def filter_args(statuses: nil)
86
105
  argumentize "--filter", filters(statuses: statuses)
87
106
  end
88
107
 
89
- def service_role_dest
90
- [ config.service, role, config.destination ].compact.join("-")
108
+ def extract_version_from_name
109
+ # Extract SHA from "service-role-dest-SHA"
110
+ %(while read line; do echo ${line##{role.container_prefix}-}; done)
91
111
  end
92
112
 
93
113
  def filters(statuses: nil)
@@ -21,7 +21,7 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
21
21
  def audit_log_file
22
22
  file = [ config.service, config.destination, "audit.log" ].compact.join("-")
23
23
 
24
- "#{config.run_directory}/#{file}"
24
+ File.join(config.run_directory, file)
25
25
  end
26
26
 
27
27
  def audit_tags(**details)
@@ -62,14 +62,26 @@ module Kamal::Commands
62
62
  combine *commands, by: ">"
63
63
  end
64
64
 
65
+ def any(*commands)
66
+ combine *commands, by: "||"
67
+ end
68
+
65
69
  def xargs(command)
66
70
  [ :xargs, command ].flatten
67
71
  end
68
72
 
73
+ def shell(command)
74
+ [ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\\\''")}'" ]
75
+ end
76
+
69
77
  def docker(*args)
70
78
  args.compact.unshift :docker
71
79
  end
72
80
 
81
+ def git(*args)
82
+ args.compact.unshift :git
83
+ end
84
+
73
85
  def tags(**details)
74
86
  Kamal::Tags.from_config(config, **details)
75
87
  end
@@ -3,7 +3,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
3
3
  class BuilderError < StandardError; end
4
4
 
5
5
  delegate :argumentize, to: Kamal::Utils
6
- delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, to: :builder_config
6
+ delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, :git_archive?, to: :builder_config
7
7
 
8
8
  def clean
9
9
  docker :image, :rm, "--force", config.absolute_image
@@ -13,8 +13,18 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
13
13
  docker :pull, config.absolute_image
14
14
  end
15
15
 
16
+ def push
17
+ if git_archive?
18
+ pipe \
19
+ git(:archive, "--format=tar", :HEAD),
20
+ build_and_push
21
+ else
22
+ build_and_push
23
+ end
24
+ end
25
+
16
26
  def build_options
17
- [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile ]
27
+ [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_ssh ]
18
28
  end
19
29
 
20
30
  def build_context
@@ -24,7 +34,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
24
34
  def validate_image
25
35
  pipe \
26
36
  docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
27
- [:grep, "-x", config.service, "||", "(echo \"Image #{config.absolute_image} is missing the `service` label\" && exit 1)"]
37
+ any(
38
+ [ :grep, "-x", config.service ],
39
+ "(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"
40
+ )
28
41
  end
29
42
 
30
43
 
@@ -35,8 +48,8 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
35
48
 
36
49
  def build_cache
37
50
  if cache_to && cache_from
38
- ["--cache-to", cache_to,
39
- "--cache-from", cache_from]
51
+ [ "--cache-to", cache_to,
52
+ "--cache-from", cache_from ]
40
53
  end
41
54
  end
42
55
 
@@ -60,6 +73,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
60
73
  end
61
74
  end
62
75
 
76
+ def build_ssh
77
+ argumentize "--ssh", ssh if ssh.present?
78
+ end
79
+
63
80
  def builder_config
64
81
  config.builder
65
82
  end
@@ -7,15 +7,6 @@ class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
7
7
  docker :buildx, :rm, builder_name
8
8
  end
9
9
 
10
- def push
11
- docker :buildx, :build,
12
- "--push",
13
- "--platform", "linux/amd64,linux/arm64",
14
- "--builder", builder_name,
15
- *build_options,
16
- build_context
17
- end
18
-
19
10
  def info
20
11
  combine \
21
12
  docker(:context, :ls),
@@ -26,4 +17,21 @@ class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
26
17
  def builder_name
27
18
  "kamal-#{config.service}-multiarch"
28
19
  end
20
+
21
+ def platform_names
22
+ if local_arch
23
+ "linux/#{local_arch}"
24
+ else
25
+ "linux/amd64,linux/arm64"
26
+ end
27
+ end
28
+
29
+ def build_and_push
30
+ docker :buildx, :build,
31
+ "--push",
32
+ "--platform", platform_names,
33
+ "--builder", builder_name,
34
+ *build_options,
35
+ build_context
36
+ end
29
37
  end
@@ -7,10 +7,11 @@ class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Nativ
7
7
  docker :buildx, :rm, builder_name
8
8
  end
9
9
 
10
- def push
11
- docker :buildx, :build,
12
- "--push",
13
- *build_options,
14
- build_context
15
- end
10
+ private
11
+ def build_and_push
12
+ docker :buildx, :build,
13
+ "--push",
14
+ *build_options,
15
+ build_context
16
+ end
16
17
  end
@@ -11,15 +11,6 @@ class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Nativ
11
11
  remove_buildx
12
12
  end
13
13
 
14
- def push
15
- docker :buildx, :build,
16
- "--push",
17
- "--platform", platform,
18
- "--builder", builder_name,
19
- *build_options,
20
- build_context
21
- end
22
-
23
14
  def info
24
15
  chain \
25
16
  docker(:context, :ls),
@@ -56,4 +47,13 @@ class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Nativ
56
47
  def remove_buildx
57
48
  docker :buildx, :rm, builder_name
58
49
  end
50
+
51
+ def build_and_push
52
+ docker :buildx, :build,
53
+ "--push",
54
+ "--platform", platform,
55
+ "--builder", builder_name,
56
+ *build_options,
57
+ build_context
58
+ end
59
59
  end
@@ -7,14 +7,15 @@ class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
7
7
  # No-op on native without cache
8
8
  end
9
9
 
10
- def push
11
- combine \
12
- docker(:build, *build_options, build_context),
13
- docker(:push, config.absolute_image),
14
- docker(:push, config.latest_image)
15
- end
16
-
17
10
  def info
18
11
  # No-op on native
19
12
  end
13
+
14
+ private
15
+ def build_and_push
16
+ combine \
17
+ docker(:build, *build_options, build_context),
18
+ docker(:push, config.absolute_image),
19
+ docker(:push, config.latest_image)
20
+ end
20
21
  end