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,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
@@ -5,7 +5,7 @@ class Kamal::Configuration::Accessory
5
5
 
6
6
  delegate :argumentize, :optionize, to: Kamal::Utils
7
7
 
8
- attr_reader :name, :accessory_config, :env
8
+ attr_reader :name, :env, :proxy, :registry
9
9
 
10
10
  def initialize(name, config:)
11
11
  @name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
@@ -16,10 +16,11 @@ class Kamal::Configuration::Accessory
16
16
  context: "accessories/#{name}",
17
17
  with: Kamal::Configuration::Validator::Accessory
18
18
 
19
- @env = Kamal::Configuration::Env.new \
20
- config: accessory_config.fetch("env", {}),
21
- secrets: config.secrets,
22
- context: "accessories/#{name}/env"
19
+ ensure_valid_roles
20
+
21
+ @env = initialize_env
22
+ @proxy = initialize_proxy if running_proxy?
23
+ @registry = initialize_registry if accessory_config["registry"].present?
23
24
  end
24
25
 
25
26
  def service_name
@@ -27,11 +28,11 @@ class Kamal::Configuration::Accessory
27
28
  end
28
29
 
29
30
  def image
30
- accessory_config["image"]
31
+ [ registry&.server, accessory_config["image"] ].compact.join("/")
31
32
  end
32
33
 
33
34
  def hosts
34
- hosts_from_host || hosts_from_hosts || hosts_from_roles
35
+ hosts_from_host || hosts_from_hosts || hosts_from_roles || hosts_from_tags
35
36
  end
36
37
 
37
38
  def port
@@ -106,8 +107,34 @@ class Kamal::Configuration::Accessory
106
107
  accessory_config["cmd"]
107
108
  end
108
109
 
110
+ def running_proxy?
111
+ accessory_config["proxy"].present?
112
+ end
113
+
109
114
  private
110
- attr_accessor :config
115
+ attr_reader :config, :accessory_config
116
+
117
+ def initialize_env
118
+ Kamal::Configuration::Env.new \
119
+ config: accessory_config.fetch("env", {}),
120
+ secrets: config.secrets,
121
+ context: "accessories/#{name}/env"
122
+ end
123
+
124
+ def initialize_proxy
125
+ Kamal::Configuration::Proxy.new \
126
+ config: config,
127
+ proxy_config: accessory_config["proxy"],
128
+ context: "accessories/#{name}/proxy",
129
+ secrets: config.secrets
130
+ end
131
+
132
+ def initialize_registry
133
+ Kamal::Configuration::Registry.new \
134
+ config: accessory_config,
135
+ secrets: config.secrets,
136
+ context: "accessories/#{name}/registry"
137
+ end
111
138
 
112
139
  def default_labels
113
140
  { "service" => service_name }
@@ -129,7 +156,7 @@ class Kamal::Configuration::Accessory
129
156
  end
130
157
 
131
158
  def read_dynamic_file(local_file)
132
- StringIO.new(ERB.new(IO.read(local_file)).result)
159
+ StringIO.new(ERB.new(File.read(local_file)).result)
133
160
  end
134
161
 
135
162
  def expand_remote_file(remote_file)
@@ -175,12 +202,40 @@ class Kamal::Configuration::Accessory
175
202
  end
176
203
 
177
204
  def hosts_from_roles
178
- if accessory_config.key?("roles")
179
- accessory_config["roles"].flat_map { |role| config.role(role).hosts }
205
+ if accessory_config.key?("role")
206
+ config.role(accessory_config["role"])&.hosts
207
+ elsif accessory_config.key?("roles")
208
+ accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
209
+ end
210
+ end
211
+
212
+ def hosts_from_tags
213
+ if accessory_config.key?("tag")
214
+ extract_hosts_from_config_with_tag(accessory_config["tag"])
215
+ elsif accessory_config.key?("tags")
216
+ accessory_config["tags"].flat_map { |tag| extract_hosts_from_config_with_tag(tag) }
217
+ end
218
+ end
219
+
220
+ def extract_hosts_from_config_with_tag(tag)
221
+ if (servers_with_roles = config.raw_config.servers).is_a?(Hash)
222
+ servers_with_roles.flat_map do |role, servers_in_role|
223
+ servers_in_role.filter_map do |host|
224
+ host.keys.first if host.is_a?(Hash) && host.values.first.include?(tag)
225
+ end
226
+ end
180
227
  end
181
228
  end
182
229
 
183
230
  def network
184
231
  accessory_config["network"] || DEFAULT_NETWORK
185
232
  end
233
+
234
+ def ensure_valid_roles
235
+ if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
236
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
237
+ elsif accessory_config["role"] && !config.role(accessory_config["role"])
238
+ raise Kamal::ConfigurationError, "accessories/#{name}: unknown role #{accessory_config["role"]}"
239
+ end
240
+ end
186
241
  end
@@ -53,10 +53,18 @@ class Kamal::Configuration::Builder
53
53
  !local_disabled? && (arches.empty? || local_arches.any?)
54
54
  end
55
55
 
56
+ def cloud?
57
+ driver.start_with? "cloud"
58
+ end
59
+
56
60
  def cached?
57
61
  !!builder_config["cache"]
58
62
  end
59
63
 
64
+ def pack?
65
+ !!builder_config["pack"]
66
+ end
67
+
60
68
  def args
61
69
  builder_config["args"] || {}
62
70
  end
@@ -81,6 +89,14 @@ class Kamal::Configuration::Builder
81
89
  builder_config.fetch("driver", "docker-container")
82
90
  end
83
91
 
92
+ def pack_builder
93
+ builder_config["pack"]["builder"] if pack?
94
+ end
95
+
96
+ def pack_buildpacks
97
+ builder_config["pack"]["buildpacks"] if pack?
98
+ end
99
+
84
100
  def local_disabled?
85
101
  builder_config["local"] == false
86
102
  end
@@ -115,6 +131,10 @@ class Kamal::Configuration::Builder
115
131
  builder_config["provenance"]
116
132
  end
117
133
 
134
+ def sbom
135
+ builder_config["sbom"]
136
+ end
137
+
118
138
  def git_clone?
119
139
  Kamal::Git.used? && builder_config["context"].nil?
120
140
  end
@@ -23,18 +23,41 @@ accessories:
23
23
 
24
24
  # Image
25
25
  #
26
- # The Docker image to use, prefix it with a registry if not using Docker Hub:
26
+ # The Docker image to use.
27
+ # Prefix it with its server when using root level registry different from Docker Hub.
28
+ # Define registry directly or via anchors when it differs from root level registry.
27
29
  image: mysql:8.0
28
30
 
31
+ # Registry
32
+ #
33
+ # By default accessories use Docker Hub registry.
34
+ # You can specify different registry per accessory with this option.
35
+ # Don't prefix image with this registry server.
36
+ # Use anchors if you need to set the same specific registry for several accessories.
37
+ #
38
+ # ```yml
39
+ # registry:
40
+ # <<: *specific-registry
41
+ # ```
42
+ #
43
+ # See kamal docs registry for more information:
44
+ registry:
45
+ ...
46
+
29
47
  # Accessory hosts
30
48
  #
31
- # Specify one of `host`, `hosts`, or `roles`:
49
+ # Specify one of `host`, `hosts`, `role`, `roles`, `tag` or `tags`:
32
50
  host: mysql-db1
33
51
  hosts:
34
52
  - mysql-db1
35
53
  - mysql-db2
54
+ role: mysql
36
55
  roles:
37
56
  - mysql
57
+ tag: writer
58
+ tags:
59
+ - writer
60
+ - reader
38
61
 
39
62
  # Custom command
40
63
  #
@@ -43,8 +66,8 @@ accessories:
43
66
 
44
67
  # Port mappings
45
68
  #
46
- # See https://docs.docker.com/network/, and especially note the warning about the security
47
- # implications of exposing ports publicly.
69
+ # See [https://docs.docker.com/network/](https://docs.docker.com/network/), and
70
+ # especially note the warning about the security implications of exposing ports publicly.
48
71
  port: "127.0.0.1:3306:3306"
49
72
 
50
73
  # Labels
@@ -98,3 +121,8 @@ accessories:
98
121
  # Defaults to kamal:
99
122
  network: custom
100
123
 
124
+ # Proxy
125
+ #
126
+ # You can run your accessory behind the Kamal proxy. See kamal docs proxy for more information
127
+ proxy:
128
+ ...
@@ -5,12 +5,12 @@
5
5
  # For example, for a Rails app, you might open a console with:
6
6
  #
7
7
  # ```shell
8
- # kamal app exec -i -r console "rails console"
8
+ # kamal app exec -i --reuse "bin/rails console"
9
9
  # ```
10
10
  #
11
11
  # By defining an alias, like this:
12
12
  aliases:
13
- console: app exec -r console -i "rails console"
13
+ console: app exec -i --reuse "bin/rails console"
14
14
  # You can now open the console with:
15
15
  #
16
16
  # ```shell
@@ -31,6 +31,19 @@ builder:
31
31
  # Defaults to true:
32
32
  local: true
33
33
 
34
+ # Buildpack configuration
35
+ #
36
+ # The build configuration for using pack to build a Cloud Native Buildpack image.
37
+ #
38
+ # For additional buildpack customization options you can create a project descriptor
39
+ # file(project.toml) that the Pack CLI will automatically use.
40
+ # See https://buildpacks.io/docs/for-app-developers/how-to/build-inputs/use-project-toml/ for more information.
41
+ pack:
42
+ builder: heroku/builder:24
43
+ buildpacks:
44
+ - heroku/ruby
45
+ - heroku/procfile
46
+
34
47
  # Builder cache
35
48
  #
36
49
  # The type must be either 'gha' or 'registry'.
@@ -102,9 +115,18 @@ builder:
102
115
  #
103
116
  # The build driver to use, defaults to `docker-container`:
104
117
  driver: docker
118
+ #
119
+ # If you want to use Docker Build Cloud (https://www.docker.com/products/build-cloud/), you can set the driver to:
120
+ driver: cloud org-name/builder-name
105
121
 
106
122
  # Provenance
107
123
  #
108
124
  # It is used to configure provenance attestations for the build result.
109
125
  # The value can also be a boolean to enable or disable provenance attestations.
110
126
  provenance: mode=max
127
+
128
+ # SBOM (Software Bill of Materials)
129
+ #
130
+ # It is used to configure SBOM generation for the build result.
131
+ # The value can also be a boolean to enable or disable SBOM generation.
132
+ sbom: true
@@ -82,6 +82,12 @@ asset_path: /path/to/assets
82
82
  # See https://kamal-deploy.org/docs/hooks for more information:
83
83
  hooks_path: /user_home/kamal/hooks
84
84
 
85
+ # Error pages
86
+ #
87
+ # A directory relative to the app root to find error pages for the proxy to serve.
88
+ # Any files in the format 4xx.html or 5xx.html will be copied to the hosts.
89
+ error_pages_path: public
90
+
85
91
  # Require destinations
86
92
  #
87
93
  # Whether deployments require a destination to be specified, defaults to `false`:
@@ -51,6 +51,37 @@ env:
51
51
  secret:
52
52
  - DB_PASSWORD
53
53
 
54
+ # Aliased secrets
55
+ #
56
+ # You can also alias secrets to other secrets using a `:` separator.
57
+ #
58
+ # This is useful when the ENV name is different from the secret name. For example, if you have two
59
+ # places where you need to define the ENV variable `DB_PASSWORD`, but the value is different depending
60
+ # on the context.
61
+ #
62
+ # ```shell
63
+ # SECRETS=$(kamal secrets fetch ...)
64
+ #
65
+ # MAIN_DB_PASSWORD=$(kamal secrets extract MAIN_DB_PASSWORD $SECRETS)
66
+ # SECONDARY_DB_PASSWORD=$(kamal secrets extract SECONDARY_DB_PASSWORD $SECRETS)
67
+ # ```
68
+ env:
69
+ secret:
70
+ - DB_PASSWORD:MAIN_DB_PASSWORD
71
+ tags:
72
+ secondary_db:
73
+ secret:
74
+ - DB_PASSWORD:SECONDARY_DB_PASSWORD
75
+ accessories:
76
+ main_db_accessory:
77
+ env:
78
+ secret:
79
+ - DB_PASSWORD:MAIN_DB_PASSWORD
80
+ secondary_db_accessory:
81
+ env:
82
+ secret:
83
+ - DB_PASSWORD:SECONDARY_DB_PASSWORD
84
+
54
85
  # Tags
55
86
  #
56
87
  # Tags are used to add extra env variables to specific hosts.
@@ -10,11 +10,6 @@
10
10
  # They are application-specific, so they are not shared when multiple applications
11
11
  # run on the same proxy.
12
12
  #
13
- # The proxy is enabled by default on the primary role but can be disabled by
14
- # setting `proxy: false`.
15
- #
16
- # It is disabled by default on all other roles but can be enabled by setting
17
- # `proxy: true` or providing a proxy configuration.
18
13
  proxy:
19
14
 
20
15
  # Hosts
@@ -46,14 +41,65 @@ proxy:
46
41
  # The host value must point to the server we are deploying to, and port 443 must be
47
42
  # open for the Let's Encrypt challenge to succeed.
48
43
  #
44
+ # If you set `ssl` to `true`, `kamal-proxy` will stop forwarding headers to your app,
45
+ # unless you explicitly set `forward_headers: true`
46
+ #
49
47
  # Defaults to `false`:
50
- ssl: true
48
+ ssl: ...
49
+
50
+ # Custom SSL certificate
51
+ #
52
+ # In some cases, using Let's Encrypt for automatic certificate management is not an
53
+ # option, for example if you are running from host than one host. Or you may already
54
+ # have SSL certificates issued by a different Certificate Authority (CA).
55
+ # Kamal supports loading custom SSL certificates
56
+ # directly from secrets.
57
+ #
58
+ # Examples:
59
+ # ssl: true # Enable SSL with Let's Encrypt
60
+ # ssl: false # Disable SSL
61
+ # ssl: # Enable custom SSL
62
+ # certificate_pem: CERTIFICATE_PEM
63
+ # private_key_pem: PRIVATE_KEY_PEM
64
+ #
65
+ # ### Notes
66
+ # - If the certificate or key is missing or invalid, kamal-proxy will fail to start.
67
+ # - Always handle SSL certificates and private keys securely. Avoid hard-coding them in deploy.yml files or source control.
68
+ # - For automated certificate management, consider using the built-in Let's Encrypt integration instead.
69
+
70
+ # SSL redirect
71
+ #
72
+ # By default, kamal-proxy will redirect all HTTP requests to HTTPS when SSL is enabled.
73
+ # If you prefer that HTTP traffic is passed through to your application (along with
74
+ # HTTPS traffic), you can disable this redirect by setting `ssl_redirect: false`:
75
+ ssl_redirect: false
76
+
77
+ # Forward headers
78
+ #
79
+ # Whether to forward the `X-Forwarded-For` and `X-Forwarded-Proto` headers.
80
+ #
81
+ # If you are behind a trusted proxy, you can set this to `true` to forward the headers.
82
+ #
83
+ # By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and
84
+ # will forward them if it is set to `false`.
85
+ forward_headers: true
51
86
 
52
87
  # Response timeout
53
88
  #
54
89
  # How long to wait for requests to complete before timing out, defaults to 30 seconds:
55
90
  response_timeout: 10
56
91
 
92
+ # Path-based routing
93
+ #
94
+ # For applications that split their traffic to different services based on the request path,
95
+ # you can use path-based routing to mount services under different path prefixes.
96
+ path_prefix: '/api'
97
+ # By default, the path prefix will be stripped from the request before it is forwarded upstream.
98
+ # So in the example above, a request to /api/users/123 will be forwarded to web-1 as /users/123.
99
+ # To instead forward the request with the original path (including the prefix),
100
+ # specify --strip-path-prefix=false
101
+ strip_path_prefix: false
102
+
57
103
  # Healthcheck
58
104
  #
59
105
  # When deploying, the proxy will by default hit `/up` once every second until we hit
@@ -94,12 +140,29 @@ proxy:
94
140
  - X-Request-ID
95
141
  - X-Request-Start
96
142
 
97
- # Forward headers
98
- #
99
- # Whether to forward the `X-Forwarded-For` and `X-Forwarded-Proto` headers.
100
- #
101
- # If you are behind a trusted proxy, you can set this to `true` to forward the headers.
102
- #
103
- # By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and
104
- # will forward them if it is set to `false`.
105
- forward_headers: true
143
+ # Enabling/disabling the proxy on roles
144
+ #
145
+ # The proxy is enabled by default on the primary role but can be disabled by
146
+ # setting `proxy: false` in the primary role's configuration.
147
+ #
148
+ # ```yaml
149
+ # servers:
150
+ # web:
151
+ # hosts:
152
+ # - ...
153
+ # proxy: false
154
+ # ```
155
+ #
156
+ # It is disabled by default on all other roles but can be enabled by setting
157
+ # `proxy: true` or providing a proxy configuration for that role.
158
+ #
159
+ # ```yaml
160
+ # servers:
161
+ # web:
162
+ # hosts:
163
+ # - ...
164
+ # web2:
165
+ # hosts:
166
+ # - ...
167
+ # proxy: true
168
+ # ```
@@ -2,6 +2,10 @@
2
2
  #
3
3
  # The default registry is Docker Hub, but you can change it using `registry/server`.
4
4
  #
5
+ # By default, Docker Hub creates public repositories. To avoid making your images public,
6
+ # set up a private repository before deploying, or change the default repository privacy
7
+ # settings to private in your [Docker Hub settings](https://hub.docker.com/repository-settings/default-privacy).
8
+ #
5
9
  # A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret
6
10
  # in the local environment:
7
11
  registry:
@@ -1,8 +1,7 @@
1
1
  class Kamal::Configuration::Env
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- attr_reader :context, :secrets
5
- attr_reader :clear, :secret_keys
4
+ attr_reader :context, :clear, :secret_keys
6
5
  delegate :argumentize, to: Kamal::Utils
7
6
 
8
7
  def initialize(config:, secrets:, context: "env")
@@ -18,12 +17,22 @@ class Kamal::Configuration::Env
18
17
  end
19
18
 
20
19
  def secrets_io
21
- Kamal::EnvFile.new(secret_keys.to_h { |key| [ key, secrets[key] ] }).to_io
20
+ Kamal::EnvFile.new(aliased_secrets).to_io
22
21
  end
23
22
 
24
23
  def merge(other)
25
24
  self.class.new \
26
25
  config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
27
- secrets: secrets
26
+ secrets: @secrets
28
27
  end
28
+
29
+ private
30
+ def aliased_secrets
31
+ secret_keys.to_h { |key| extract_alias(key) }.transform_values { |secret_key| @secrets[secret_key] }
32
+ end
33
+
34
+ def extract_alias(key)
35
+ key_name, key_aliased_to = key.split(":", 2)
36
+ [ key_name, key_aliased_to || key_name ]
37
+ end
29
38
  end
@@ -0,0 +1,129 @@
1
+ class Kamal::Configuration::Proxy::Boot
2
+ MINIMUM_VERSION = "v0.9.0"
3
+ DEFAULT_HTTP_PORT = 80
4
+ DEFAULT_HTTPS_PORT = 443
5
+ DEFAULT_LOG_MAX_SIZE = "10m"
6
+
7
+ attr_reader :config
8
+ delegate :argumentize, :optionize, to: Kamal::Utils
9
+
10
+ def initialize(config:)
11
+ @config = config
12
+ end
13
+
14
+ def publish_args(http_port, https_port, bind_ips = nil)
15
+ ensure_valid_bind_ips(bind_ips)
16
+
17
+ (bind_ips || [ nil ]).map do |bind_ip|
18
+ bind_ip = format_bind_ip(bind_ip)
19
+ publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
20
+ publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
21
+
22
+ argumentize "--publish", [ publish_http, publish_https ]
23
+ end.join(" ")
24
+ end
25
+
26
+ def logging_args(max_size)
27
+ argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
28
+ end
29
+
30
+ def default_boot_options
31
+ [
32
+ *(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)),
33
+ *(logging_args(DEFAULT_LOG_MAX_SIZE))
34
+ ]
35
+ end
36
+
37
+ def repository_name
38
+ "basecamp"
39
+ end
40
+
41
+ def image_name
42
+ "kamal-proxy"
43
+ end
44
+
45
+ def image_default
46
+ "#{repository_name}/#{image_name}"
47
+ end
48
+
49
+ def container_name
50
+ "kamal-proxy"
51
+ end
52
+
53
+ def host_directory
54
+ File.join config.run_directory, "proxy"
55
+ end
56
+
57
+ def options_file
58
+ File.join host_directory, "options"
59
+ end
60
+
61
+ def image_file
62
+ File.join host_directory, "image"
63
+ end
64
+
65
+ def image_version_file
66
+ File.join host_directory, "image_version"
67
+ end
68
+
69
+ def run_command_file
70
+ File.join host_directory, "run_command"
71
+ end
72
+
73
+ def apps_directory
74
+ File.join host_directory, "apps-config"
75
+ end
76
+
77
+ def apps_container_directory
78
+ "/home/kamal-proxy/.apps-config"
79
+ end
80
+
81
+ def apps_volume
82
+ Kamal::Configuration::Volume.new \
83
+ host_path: apps_directory,
84
+ container_path: apps_container_directory
85
+ end
86
+
87
+ def app_directory
88
+ File.join apps_directory, config.service_and_destination
89
+ end
90
+
91
+ def app_container_directory
92
+ File.join apps_container_directory, config.service_and_destination
93
+ end
94
+
95
+ def error_pages_directory
96
+ File.join app_directory, "error_pages"
97
+ end
98
+
99
+ def error_pages_container_directory
100
+ File.join app_container_directory, "error_pages"
101
+ end
102
+
103
+ def tls_directory
104
+ File.join app_directory, "tls"
105
+ end
106
+
107
+ def tls_container_directory
108
+ File.join app_container_directory, "tls"
109
+ end
110
+
111
+ private
112
+ def ensure_valid_bind_ips(bind_ips)
113
+ bind_ips.present? && bind_ips.each do |ip|
114
+ next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
115
+ raise ArgumentError, "Invalid publish IP address: #{ip}"
116
+ end
117
+
118
+ true
119
+ end
120
+
121
+ def format_bind_ip(ip)
122
+ # Ensure IPv6 address inside square brackets - e.g. [::1]
123
+ if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/
124
+ "[#{ip}]"
125
+ else
126
+ ip
127
+ end
128
+ end
129
+ end