kamal 2.3.0 → 2.7.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +42 -16
  3. data/lib/kamal/cli/alias/command.rb +1 -0
  4. data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
  5. data/lib/kamal/cli/app/boot.rb +3 -2
  6. data/lib/kamal/cli/app/error_pages.rb +33 -0
  7. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  8. data/lib/kamal/cli/app.rb +94 -29
  9. data/lib/kamal/cli/base.rb +29 -4
  10. data/lib/kamal/cli/build.rb +60 -18
  11. data/lib/kamal/cli/main.rb +8 -10
  12. data/lib/kamal/cli/proxy.rb +58 -25
  13. data/lib/kamal/cli/registry.rb +2 -0
  14. data/lib/kamal/cli/secrets.rb +9 -3
  15. data/lib/kamal/cli/server.rb +4 -2
  16. data/lib/kamal/cli/templates/deploy.yml +6 -3
  17. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  18. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  19. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  20. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  21. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
  22. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
  23. data/lib/kamal/cli.rb +1 -0
  24. data/lib/kamal/commander/specifics.rb +9 -1
  25. data/lib/kamal/commander.rb +18 -27
  26. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  27. data/lib/kamal/commands/accessory.rb +9 -9
  28. data/lib/kamal/commands/app/assets.rb +4 -4
  29. data/lib/kamal/commands/app/containers.rb +2 -2
  30. data/lib/kamal/commands/app/error_pages.rb +9 -0
  31. data/lib/kamal/commands/app/execution.rb +6 -4
  32. data/lib/kamal/commands/app/images.rb +1 -1
  33. data/lib/kamal/commands/app/logging.rb +14 -4
  34. data/lib/kamal/commands/app/proxy.rb +17 -1
  35. data/lib/kamal/commands/app.rb +19 -10
  36. data/lib/kamal/commands/auditor.rb +11 -5
  37. data/lib/kamal/commands/base.rb +37 -1
  38. data/lib/kamal/commands/builder/base.rb +20 -7
  39. data/lib/kamal/commands/builder/cloud.rb +22 -0
  40. data/lib/kamal/commands/builder/pack.rb +46 -0
  41. data/lib/kamal/commands/builder.rb +11 -19
  42. data/lib/kamal/commands/proxy.rb +55 -15
  43. data/lib/kamal/commands/registry.rb +9 -7
  44. data/lib/kamal/configuration/accessory.rb +66 -11
  45. data/lib/kamal/configuration/builder.rb +20 -0
  46. data/lib/kamal/configuration/docs/accessory.yml +32 -4
  47. data/lib/kamal/configuration/docs/alias.yml +2 -2
  48. data/lib/kamal/configuration/docs/builder.yml +22 -0
  49. data/lib/kamal/configuration/docs/configuration.yml +6 -0
  50. data/lib/kamal/configuration/docs/env.yml +31 -0
  51. data/lib/kamal/configuration/docs/proxy.yml +78 -15
  52. data/lib/kamal/configuration/docs/registry.yml +4 -0
  53. data/lib/kamal/configuration/env.rb +13 -4
  54. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  55. data/lib/kamal/configuration/proxy.rb +67 -5
  56. data/lib/kamal/configuration/registry.rb +6 -6
  57. data/lib/kamal/configuration/role.rb +11 -9
  58. data/lib/kamal/configuration/servers.rb +8 -1
  59. data/lib/kamal/configuration/validator/accessory.rb +6 -2
  60. data/lib/kamal/configuration/validator/builder.rb +2 -0
  61. data/lib/kamal/configuration/validator/proxy.rb +10 -0
  62. data/lib/kamal/configuration/validator/role.rb +3 -1
  63. data/lib/kamal/configuration/validator/servers.rb +1 -1
  64. data/lib/kamal/configuration/validator.rb +21 -1
  65. data/lib/kamal/configuration.rb +36 -57
  66. data/lib/kamal/docker.rb +30 -0
  67. data/lib/kamal/git.rb +10 -0
  68. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  69. data/lib/kamal/secrets/adapters/base.rb +13 -3
  70. data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
  71. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  72. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  73. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  74. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  75. data/lib/kamal/secrets/adapters/last_pass.rb +3 -2
  76. data/lib/kamal/secrets/adapters/one_password.rb +47 -13
  77. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  78. data/lib/kamal/secrets/adapters/test.rb +2 -2
  79. data/lib/kamal/secrets/adapters.rb +2 -0
  80. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +2 -1
  81. data/lib/kamal/secrets.rb +1 -1
  82. data/lib/kamal/version.rb +1 -1
  83. metadata +22 -10
@@ -1,8 +1,10 @@
1
1
  class Kamal::Commands::Accessory < Kamal::Commands::Base
2
+ include Proxy
3
+
2
4
  attr_reader :accessory_config
3
5
  delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
4
6
  :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
5
- :secrets_io, :secrets_path, :env_directory,
7
+ :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
6
8
  to: :accessory_config
7
9
 
8
10
  def initialize(config, name:)
@@ -10,7 +12,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
10
12
  @accessory_config = config.accessory(name)
11
13
  end
12
14
 
13
- def run
15
+ def run(host: nil)
14
16
  docker :run,
15
17
  "--name", service_name,
16
18
  "--detach",
@@ -18,6 +20,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
18
20
  *network_args,
19
21
  *config.logging_args,
20
22
  *publish_args,
23
+ *([ "--env", "KAMAL_HOST=\"#{host}\"" ] if host),
21
24
  *env_args,
22
25
  *volume_args,
23
26
  *label_args,
@@ -34,11 +37,10 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
34
37
  docker :container, :stop, service_name
35
38
  end
36
39
 
37
- def info
38
- docker :ps, *service_filter
40
+ def info(all: false, quiet: false)
41
+ docker :ps, *("-a" if all), *("-q" if quiet), *service_filter
39
42
  end
40
43
 
41
-
42
44
  def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
43
45
  pipe \
44
46
  docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
@@ -52,17 +54,16 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
52
54
  (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
53
55
  end
54
56
 
55
-
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,
@@ -83,7 +84,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
83
84
  super command, host: hosts.first
84
85
  end
85
86
 
86
-
87
87
  def ensure_local_file_present(local_file)
88
88
  if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
89
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
 
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Containers
2
2
  DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
3
3
 
4
4
  def list_containers
5
- docker :container, :ls, "--all", *filter_args
5
+ docker :container, :ls, "--all", *container_filter_args
6
6
  end
7
7
 
8
8
  def list_container_names
@@ -20,7 +20,7 @@ module Kamal::Commands::App::Containers
20
20
  end
21
21
 
22
22
  def remove_containers
23
- docker :container, :prune, "--force", *filter_args
23
+ docker :container, :prune, "--force", *container_filter_args
24
24
  end
25
25
 
26
26
  def container_health_log(version:)
@@ -0,0 +1,9 @@
1
+ module Kamal::Commands::App::ErrorPages
2
+ def create_error_pages_directory
3
+ make_directory(config.proxy_boot.error_pages_directory)
4
+ end
5
+
6
+ def clean_up_error_pages
7
+ [ :find, config.proxy_boot.error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ]
8
+ end
9
+ end
@@ -1,19 +1,21 @@
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
8
8
  end
9
9
 
10
- def execute_in_new_container(*command, interactive: false, env:)
10
+ def execute_in_new_container(*command, interactive: false, detach: false, env:)
11
11
  docker :run,
12
- ("-it" if interactive),
13
- "--rm",
12
+ (docker_interactive_args if interactive),
13
+ ("--detach" if detach),
14
+ ("--rm" unless detach),
14
15
  "--network", "kamal",
15
16
  *role&.env_args(host),
16
17
  *argumentize("--env", env),
18
+ *role.logging_args,
17
19
  *config.volume_args,
18
20
  *role&.option_args,
19
21
  config.absolute_image,
@@ -4,7 +4,7 @@ module Kamal::Commands::App::Images
4
4
  end
5
5
 
6
6
  def remove_images
7
- docker :image, :prune, "--all", "--force", *filter_args
7
+ docker :image, :prune, "--all", "--force", *image_filter_args
8
8
  end
9
9
 
10
10
  def tag_latest_image
@@ -1,18 +1,28 @@
1
1
  module Kamal::Commands::App::Logging
2
- def logs(version: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
2
+ def logs(container_id: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
3
3
  pipe \
4
- version ? container_id_for_version(version) : current_running_container_id,
4
+ container_id_command(container_id),
5
5
  "xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
6
6
  ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
7
7
  end
8
8
 
9
- def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil)
9
+ def follow_logs(host:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil)
10
10
  run_over_ssh \
11
11
  pipe(
12
- current_running_container_id,
12
+ container_id_command(container_id),
13
13
  "xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1",
14
14
  (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
15
15
  ),
16
16
  host: host
17
17
  end
18
+
19
+ private
20
+
21
+ def container_id_command(container_id)
22
+ case container_id
23
+ when Array then container_id
24
+ when String, Symbol then "echo #{container_id}"
25
+ else current_running_container_id
26
+ end
27
+ end
18
28
  end
@@ -1,5 +1,5 @@
1
1
  module Kamal::Commands::App::Proxy
2
- delegate :proxy_container_name, to: :config
2
+ delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
3
3
 
4
4
  def deploy(target:)
5
5
  proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
@@ -9,6 +9,22 @@ module Kamal::Commands::App::Proxy
9
9
  proxy_exec :remove, role.container_prefix
10
10
  end
11
11
 
12
+ def live
13
+ proxy_exec :resume, role.container_prefix
14
+ end
15
+
16
+ def maintenance(**options)
17
+ proxy_exec :stop, role.container_prefix, *role.proxy.stop_command_args(**options)
18
+ end
19
+
20
+ def remove_proxy_app_directory
21
+ remove_directory config.proxy_boot.app_directory
22
+ end
23
+
24
+ def create_ssl_directory
25
+ make_directory(File.join(config.proxy_boot.tls_directory, role.name))
26
+ end
27
+
12
28
  private
13
29
  def proxy_exec(*command)
14
30
  docker :exec, proxy_container_name, "kamal-proxy", *command
@@ -1,5 +1,5 @@
1
1
  class Kamal::Commands::App < Kamal::Commands::Base
2
- include Assets, Containers, Execution, Images, Logging, Proxy
2
+ include Assets, Containers, ErrorPages, Execution, Images, Logging, Proxy
3
3
 
4
4
  ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
5
5
 
@@ -20,8 +20,9 @@ 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}\"",
25
26
  *role.env_args(host),
26
27
  *role.logging_args,
27
28
  *config.volume_args,
@@ -47,7 +48,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
47
48
  end
48
49
 
49
50
  def info
50
- docker :ps, *filter_args
51
+ docker :ps, *container_filter_args
51
52
  end
52
53
 
53
54
 
@@ -67,7 +68,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
67
68
 
68
69
  def list_versions(*docker_args, statuses: nil)
69
70
  pipe \
70
- docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
71
+ docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
71
72
  extract_version_from_name
72
73
  end
73
74
 
@@ -91,11 +92,15 @@ class Kamal::Commands::App < Kamal::Commands::Base
91
92
  end
92
93
 
93
94
  def latest_container(format:, filters: nil)
94
- docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
95
+ docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
95
96
  end
96
97
 
97
- def filter_args(statuses: nil)
98
- argumentize "--filter", filters(statuses: statuses)
98
+ def container_filter_args(statuses: nil)
99
+ argumentize "--filter", container_filters(statuses: statuses)
100
+ end
101
+
102
+ def image_filter_args
103
+ argumentize "--filter", image_filters
99
104
  end
100
105
 
101
106
  def extract_version_from_name
@@ -103,13 +108,17 @@ class Kamal::Commands::App < Kamal::Commands::Base
103
108
  %(while read line; do echo ${line##{role.container_prefix}-}; done)
104
109
  end
105
110
 
106
- def filters(statuses: nil)
111
+ def container_filters(statuses: nil)
107
112
  [ "label=service=#{config.service}" ].tap do |filters|
108
- filters << "label=destination=#{config.destination}" if config.destination
113
+ filters << "label=destination=#{config.destination}"
109
114
  filters << "label=role=#{role}" if role
110
115
  statuses&.each do |status|
111
116
  filters << "status=#{status}"
112
117
  end
113
118
  end
114
119
  end
120
+
121
+ def image_filters
122
+ [ "label=service=#{config.service}" ]
123
+ end
115
124
  end
@@ -1,5 +1,6 @@
1
1
  class Kamal::Commands::Auditor < Kamal::Commands::Base
2
2
  attr_reader :details
3
+ delegate :escape_shell_value, to: Kamal::Utils
3
4
 
4
5
  def initialize(config, **details)
5
6
  super(config)
@@ -9,11 +10,8 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
9
10
  # Runs remotely
10
11
  def record(line, **details)
11
12
  combine \
12
- [ :mkdir, "-p", config.run_directory ],
13
- append(
14
- [ :echo, audit_tags(**details).except(:version, :service_version, :service).to_s, line ],
15
- audit_log_file
16
- )
13
+ make_run_directory,
14
+ append([ :echo, escape_shell_value(audit_line(line, **details)) ], audit_log_file)
17
15
  end
18
16
 
19
17
  def reveal
@@ -30,4 +28,12 @@ class Kamal::Commands::Auditor < Kamal::Commands::Base
30
28
  def audit_tags(**details)
31
29
  tags(**self.details, **details)
32
30
  end
31
+
32
+ def make_run_directory
33
+ [ :mkdir, "-p", config.run_directory ]
34
+ end
35
+
36
+ def audit_line(line, **details)
37
+ "#{audit_tags(**details).except(:version, :service_version, :service)} #{line}"
38
+ end
33
39
  end
@@ -11,7 +11,7 @@ module Kamal::Commands
11
11
  end
12
12
 
13
13
  def run_over_ssh(*command, host:)
14
- "ssh#{ssh_proxy_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
14
+ "ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
15
15
  end
16
16
 
17
17
  def container_id_for(container_name:, only_running: false)
@@ -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
@@ -62,6 +68,10 @@ module Kamal::Commands
62
68
  combine *commands, by: "||"
63
69
  end
64
70
 
71
+ def substitute(*commands)
72
+ "\$\(#{commands.join(" ")}\)"
73
+ end
74
+
65
75
  def xargs(command)
66
76
  [ :xargs, command ].flatten
67
77
  end
@@ -74,6 +84,10 @@ module Kamal::Commands
74
84
  args.compact.unshift :docker
75
85
  end
76
86
 
87
+ def pack(*args)
88
+ args.compact.unshift :pack
89
+ end
90
+
77
91
  def git(*args, path: nil)
78
92
  [ :git, *([ "-C", path ] if path), *args.compact ]
79
93
  end
@@ -94,5 +108,27 @@ module Kamal::Commands
94
108
  " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
95
109
  end
96
110
  end
111
+
112
+ def ssh_keys_args
113
+ "#{ ssh_keys.join("") if ssh_keys}" + "#{" -o IdentitiesOnly=yes" if config.ssh&.keys_only}"
114
+ end
115
+
116
+ def ssh_keys
117
+ config.ssh.keys&.map do |key|
118
+ " -i #{key}"
119
+ end
120
+ end
121
+
122
+ def ensure_local_docker_installed
123
+ docker "--version"
124
+ end
125
+
126
+ def ensure_local_buildx_installed
127
+ docker :buildx, "version"
128
+ end
129
+
130
+ def docker_interactive_args
131
+ STDIN.isatty ? "-it" : "-i"
132
+ end
97
133
  end
98
134
  end
@@ -6,20 +6,23 @@ 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
- :cache_from, :cache_to, :ssh, :provenance, :driver, :docker_driver?,
9
+ :pack?, :pack_builder, :pack_buildpacks,
10
+ :cache_from, :cache_to, :ssh, :provenance, :sbom, :driver, :docker_driver?,
10
11
  to: :builder_config
11
12
 
12
13
  def clean
13
14
  docker :image, :rm, "--force", config.absolute_image
14
15
  end
15
16
 
16
- def push
17
+ def push(export_action = "registry", tag_as_dirty: false)
17
18
  docker :buildx, :build,
18
- "--push",
19
+ "--output=type=#{export_action}",
19
20
  *platform_options(arches),
20
21
  *([ "--builder", builder_name ] unless docker_driver?),
22
+ *build_tag_options(tag_as_dirty: tag_as_dirty),
21
23
  *build_options,
22
- build_context
24
+ build_context,
25
+ "2>&1"
23
26
  end
24
27
 
25
28
  def pull
@@ -37,7 +40,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
37
40
  end
38
41
 
39
42
  def build_options
40
- [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance ]
43
+ [ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
41
44
  end
42
45
 
43
46
  def build_context
@@ -58,8 +61,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
58
61
  end
59
62
 
60
63
  private
61
- def build_tags
62
- [ "-t", config.absolute_image, "-t", config.latest_image ]
64
+ def build_tag_names(tag_as_dirty: false)
65
+ tag_names = [ config.absolute_image, config.latest_image ]
66
+ tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
67
+ tag_names
68
+ end
69
+
70
+ def build_tag_options(tag_as_dirty: false)
71
+ build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
63
72
  end
64
73
 
65
74
  def build_cache
@@ -101,6 +110,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
101
110
  argumentize "--provenance", provenance unless provenance.nil?
102
111
  end
103
112
 
113
+ def builder_sbom
114
+ argumentize "--sbom", sbom unless sbom.nil?
115
+ end
116
+
104
117
  def builder_config
105
118
  config.builder
106
119
  end
@@ -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
@@ -0,0 +1,46 @@
1
+ class Kamal::Commands::Builder::Pack < Kamal::Commands::Builder::Base
2
+ def push(export_action = "registry")
3
+ combine \
4
+ build,
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
17
+ pack(:build,
18
+ config.repository,
19
+ "--platform", platform,
20
+ "--creation-time", "now",
21
+ "--builder", pack_builder,
22
+ buildpacks,
23
+ "-t", config.absolute_image,
24
+ "-t", config.latest_image,
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
@@ -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?, :pack?, :cloud?, to: "config.builder"
6
6
 
7
7
  include Clone
8
8
 
@@ -17,6 +17,10 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
17
17
  else
18
18
  remote
19
19
  end
20
+ elsif pack?
21
+ pack
22
+ elsif cloud?
23
+ cloud
20
24
  else
21
25
  local
22
26
  end
@@ -34,23 +38,11 @@ class Kamal::Commands::Builder < Kamal::Commands::Base
34
38
  @hybrid ||= Kamal::Commands::Builder::Hybrid.new(config)
35
39
  end
36
40
 
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
41
+ def pack
42
+ @pack ||= Kamal::Commands::Builder::Pack.new(config)
46
43
  end
47
44
 
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
45
+ def cloud
46
+ @cloud ||= Kamal::Commands::Builder::Cloud.new(config)
47
+ end
56
48
  end
@@ -2,14 +2,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
2
2
  delegate :argumentize, :optionize, to: Kamal::Utils
3
3
 
4
4
  def run
5
- docker :run,
6
- "--name", container_name,
7
- "--network", "kamal",
8
- "--detach",
9
- "--restart", "unless-stopped",
10
- "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
11
- "\$\(#{get_boot_options.join(" ")}\)",
12
- config.proxy_image
5
+ pipe boot_config, xargs(docker_run)
13
6
  end
14
7
 
15
8
  def start
@@ -31,7 +24,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
31
24
  def version
32
25
  pipe \
33
26
  docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
34
- [ :cut, "-d:", "-f2" ]
27
+ [ :awk, "-F:", "'{print \$NF}'" ]
35
28
  end
36
29
 
37
30
  def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
@@ -65,23 +58,70 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base
65
58
  end
66
59
 
67
60
  def ensure_proxy_directory
68
- make_directory config.proxy_directory
61
+ make_directory config.proxy_boot.host_directory
69
62
  end
70
63
 
71
64
  def remove_proxy_directory
72
- remove_directory config.proxy_directory
65
+ remove_directory config.proxy_boot.host_directory
73
66
  end
74
67
 
75
- def get_boot_options
76
- combine [ :cat, config.proxy_options_file ], [ :echo, "\"#{config.proxy_options_default.join(" ")}\"" ], by: "||"
68
+ def ensure_apps_config_directory
69
+ make_directory config.proxy_boot.apps_directory
70
+ end
71
+
72
+ def boot_config
73
+ [ :echo, "#{substitute(read_boot_options)} #{substitute(read_image)}:#{substitute(read_image_version)} #{substitute(read_run_command)}" ]
74
+ end
75
+
76
+ def read_boot_options
77
+ read_file(config.proxy_boot.options_file, default: config.proxy_boot.default_boot_options.join(" "))
78
+ end
79
+
80
+ def read_image
81
+ read_file(config.proxy_boot.image_file, default: config.proxy_boot.image_default)
82
+ end
83
+
84
+ def read_image_version
85
+ read_file(config.proxy_boot.image_version_file, default: Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
86
+ end
87
+
88
+ def read_run_command
89
+ read_file(config.proxy_boot.run_command_file)
77
90
  end
78
91
 
79
92
  def reset_boot_options
80
- remove_file config.proxy_options_file
93
+ remove_file config.proxy_boot.options_file
94
+ end
95
+
96
+ def reset_image
97
+ remove_file config.proxy_boot.image_file
98
+ end
99
+
100
+ def reset_image_version
101
+ remove_file config.proxy_boot.image_version_file
102
+ end
103
+
104
+ def reset_run_command
105
+ remove_file config.proxy_boot.run_command_file
81
106
  end
82
107
 
83
108
  private
84
109
  def container_name
85
- config.proxy_container_name
110
+ config.proxy_boot.container_name
111
+ end
112
+
113
+ def read_file(file, default: nil)
114
+ combine [ :cat, file, "2>", "/dev/null" ], [ :echo, "\"#{default}\"" ], by: "||"
115
+ end
116
+
117
+ def docker_run
118
+ docker \
119
+ :run,
120
+ "--name", container_name,
121
+ "--network", "kamal",
122
+ "--detach",
123
+ "--restart", "unless-stopped",
124
+ "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
125
+ *config.proxy_boot.apps_volume.docker_args
86
126
  end
87
127
  end