kamal 1.8.3 → 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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +92 -38
  4. data/lib/kamal/cli/alias/command.rb +10 -0
  5. data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
  6. data/lib/kamal/cli/app/boot.rb +23 -16
  7. data/lib/kamal/cli/app/error_pages.rb +33 -0
  8. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  9. data/lib/kamal/cli/app.rb +132 -30
  10. data/lib/kamal/cli/base.rb +57 -53
  11. data/lib/kamal/cli/build.rb +81 -38
  12. data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
  13. data/lib/kamal/cli/healthcheck/poller.rb +18 -39
  14. data/lib/kamal/cli/lock.rb +2 -3
  15. data/lib/kamal/cli/main.rb +60 -59
  16. data/lib/kamal/cli/proxy.rb +290 -0
  17. data/lib/kamal/cli/prune.rb +0 -1
  18. data/lib/kamal/cli/registry.rb +2 -0
  19. data/lib/kamal/cli/secrets.rb +49 -0
  20. data/lib/kamal/cli/server.rb +6 -5
  21. data/lib/kamal/cli/templates/deploy.yml +53 -53
  22. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +2 -12
  23. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  24. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  25. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  26. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +2 -0
  33. data/lib/kamal/commander/specifics.rb +19 -6
  34. data/lib/kamal/commander.rb +39 -32
  35. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  36. data/lib/kamal/commands/accessory.rb +19 -19
  37. data/lib/kamal/commands/app/assets.rb +10 -10
  38. data/lib/kamal/commands/app/containers.rb +2 -2
  39. data/lib/kamal/commands/app/error_pages.rb +9 -0
  40. data/lib/kamal/commands/app/execution.rb +7 -4
  41. data/lib/kamal/commands/app/images.rb +1 -1
  42. data/lib/kamal/commands/app/logging.rb +16 -6
  43. data/lib/kamal/commands/app/proxy.rb +32 -0
  44. data/lib/kamal/commands/app.rb +25 -24
  45. data/lib/kamal/commands/auditor.rb +12 -3
  46. data/lib/kamal/commands/base.rb +54 -8
  47. data/lib/kamal/commands/builder/base.rb +46 -16
  48. data/lib/kamal/commands/builder/clone.rb +16 -14
  49. data/lib/kamal/commands/builder/cloud.rb +22 -0
  50. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  51. data/lib/kamal/commands/builder/local.rb +14 -0
  52. data/lib/kamal/commands/builder/pack.rb +46 -0
  53. data/lib/kamal/commands/builder/remote.rb +63 -0
  54. data/lib/kamal/commands/builder.rb +21 -45
  55. data/lib/kamal/commands/docker.rb +4 -0
  56. data/lib/kamal/commands/hook.rb +8 -2
  57. data/lib/kamal/commands/lock.rb +2 -6
  58. data/lib/kamal/commands/proxy.rb +127 -0
  59. data/lib/kamal/commands/prune.rb +1 -9
  60. data/lib/kamal/commands/registry.rb +9 -7
  61. data/lib/kamal/commands/server.rb +11 -1
  62. data/lib/kamal/configuration/accessory.rb +89 -12
  63. data/lib/kamal/configuration/alias.rb +15 -0
  64. data/lib/kamal/configuration/builder.rb +73 -15
  65. data/lib/kamal/configuration/docs/accessory.yml +53 -15
  66. data/lib/kamal/configuration/docs/alias.yml +26 -0
  67. data/lib/kamal/configuration/docs/boot.yml +3 -3
  68. data/lib/kamal/configuration/docs/builder.yml +63 -38
  69. data/lib/kamal/configuration/docs/configuration.yml +62 -46
  70. data/lib/kamal/configuration/docs/env.yml +61 -17
  71. data/lib/kamal/configuration/docs/logging.yml +3 -3
  72. data/lib/kamal/configuration/docs/proxy.yml +168 -0
  73. data/lib/kamal/configuration/docs/registry.yml +20 -13
  74. data/lib/kamal/configuration/docs/role.yml +14 -13
  75. data/lib/kamal/configuration/docs/servers.yml +2 -2
  76. data/lib/kamal/configuration/docs/ssh.yml +23 -19
  77. data/lib/kamal/configuration/docs/sshkit.yml +4 -4
  78. data/lib/kamal/configuration/env/tag.rb +4 -3
  79. data/lib/kamal/configuration/env.rb +19 -17
  80. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  81. data/lib/kamal/configuration/proxy.rb +124 -0
  82. data/lib/kamal/configuration/registry.rb +7 -6
  83. data/lib/kamal/configuration/role.rb +69 -98
  84. data/lib/kamal/configuration/servers.rb +8 -1
  85. data/lib/kamal/configuration/validator/accessory.rb +6 -2
  86. data/lib/kamal/configuration/validator/alias.rb +15 -0
  87. data/lib/kamal/configuration/validator/builder.rb +6 -0
  88. data/lib/kamal/configuration/validator/proxy.rb +25 -0
  89. data/lib/kamal/configuration/validator/role.rb +3 -1
  90. data/lib/kamal/configuration/validator/servers.rb +1 -1
  91. data/lib/kamal/configuration/validator.rb +62 -24
  92. data/lib/kamal/configuration.rb +96 -50
  93. data/lib/kamal/docker.rb +30 -0
  94. data/lib/kamal/env_file.rb +7 -1
  95. data/lib/kamal/git.rb +10 -0
  96. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  97. data/lib/kamal/secrets/adapters/base.rb +33 -0
  98. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  99. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  100. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  101. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  102. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  103. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  104. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  105. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  106. data/lib/kamal/secrets/adapters/test.rb +14 -0
  107. data/lib/kamal/secrets/adapters.rb +16 -0
  108. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
  109. data/lib/kamal/secrets.rb +42 -0
  110. data/lib/kamal/sshkit_with_ext.rb +1 -0
  111. data/lib/kamal/utils.rb +30 -0
  112. data/lib/kamal/version.rb +1 -1
  113. data/lib/kamal.rb +3 -1
  114. metadata +63 -36
  115. data/lib/kamal/cli/env.rb +0 -54
  116. data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
  117. data/lib/kamal/cli/templates/sample_hooks/pre-traefik-reboot.sample +0 -3
  118. data/lib/kamal/cli/templates/template.env +0 -2
  119. data/lib/kamal/cli/traefik.rb +0 -122
  120. data/lib/kamal/commands/app/cord.rb +0 -22
  121. data/lib/kamal/commands/builder/multiarch/remote.rb +0 -65
  122. data/lib/kamal/commands/builder/multiarch.rb +0 -41
  123. data/lib/kamal/commands/builder/native/cached.rb +0 -25
  124. data/lib/kamal/commands/builder/native/remote.rb +0 -67
  125. data/lib/kamal/commands/builder/native.rb +0 -20
  126. data/lib/kamal/commands/traefik.rb +0 -85
  127. data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
  128. data/lib/kamal/configuration/docs/traefik.yml +0 -62
  129. data/lib/kamal/configuration/healthcheck.rb +0 -63
  130. data/lib/kamal/configuration/traefik.rb +0 -60
@@ -1,10 +1,13 @@
1
1
  # Registry
2
2
  #
3
- # The default registry is Docker Hub, but you can change it using registry/server:
3
+ # The default registry is Docker Hub, but you can change it using `registry/server`.
4
4
  #
5
- # A reference to secret (in this case DOCKER_REGISTRY_TOKEN) will look up the secret
6
- # in the local environment.
7
-
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
+ #
9
+ # A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret
10
+ # in the local environment:
8
11
  registry:
9
12
  server: registry.digitalocean.com
10
13
  username:
@@ -13,28 +16,31 @@ registry:
13
16
  - DOCKER_REGISTRY_TOKEN
14
17
 
15
18
  # Using AWS ECR as the container registry
16
- # You will need to have the aws CLI installed locally for this to work.
17
- # AWS ECR’s access token is only valid for 12hrs. In order to not have to manually regenerate the token every time, you can use ERB in the deploy.yml file to shell out to the aws cli command, and obtain the token:
18
-
19
+ #
20
+ # You will need to have the AWS CLI installed locally for this to work.
21
+ # AWS ECR’s access token is only valid for 12 hours. In order to avoid having to manually regenerate the token every time, you can use ERB in the `deploy.yml` file to shell out to the AWS CLI command and obtain the token:
19
22
  registry:
20
23
  server: <your aws account id>.dkr.ecr.<your aws region id>.amazonaws.com
21
24
  username: AWS
22
25
  password: <%= %x(aws ecr get-login-password) %>
23
26
 
24
27
  # Using GCP Artifact Registry as the container registry
25
- # To sign into Artifact Registry, you would need to
28
+ #
29
+ # To sign into Artifact Registry, you need to
26
30
  # [create a service account](https://cloud.google.com/iam/docs/service-accounts-create#creating)
27
31
  # and [set up roles and permissions](https://cloud.google.com/artifact-registry/docs/access-control#permissions).
28
- # Normally, assigning a roles/artifactregistry.writer role should be sufficient.
32
+ # Normally, assigning the `roles/artifactregistry.writer` role should be sufficient.
29
33
  #
30
- # Once the service account is ready, you need to generate and download a JSON key, base64 encode it and add to .env:
34
+ # Once the service account is ready, you need to generate and download a JSON key and base64 encode it:
31
35
  #
32
36
  # ```shell
33
- # echo "KAMAL_REGISTRY_PASSWORD=$(base64 -i /path/to/key.json)" | tr -d "\\n" >> .env
37
+ # base64 -i /path/to/key.json | tr -d "\\n"
34
38
  # ```
35
- # Use the env variable as password along with _json_key_base64 as username.
39
+ #
40
+ # You'll then need to set the `KAMAL_REGISTRY_PASSWORD` secret to that value.
41
+ #
42
+ # Use the environment variable as the password along with `_json_key_base64` as the username.
36
43
  # Here’s the final configuration:
37
-
38
44
  registry:
39
45
  server: <your registry region>-docker.pkg.dev
40
46
  username: _json_key_base64
@@ -44,6 +50,7 @@ registry:
44
50
  # Validating the configuration
45
51
  #
46
52
  # You can validate the configuration by running:
53
+ #
47
54
  # ```shell
48
55
  # kamal registry login
49
56
  # ```
@@ -1,22 +1,21 @@
1
1
  # Roles
2
2
  #
3
3
  # Roles are used to configure different types of servers in the deployment.
4
- # The most common use for this is to run a web servers and job servers.
4
+ # The most common use for this is to run web servers and job servers.
5
5
  #
6
6
  # Kamal expects there to be a `web` role, unless you set a different `primary_role`
7
7
  # in the root configuration.
8
8
 
9
9
  # Role configuration
10
10
  #
11
- # Roles are specified under the servers key
11
+ # Roles are specified under the servers key:
12
12
  servers:
13
13
 
14
14
  # Simple role configuration
15
15
  #
16
+ # This can be a list of hosts if you don't need custom configuration for the role.
16
17
  #
17
- # This can be a list of hosts, if you don't need custom configuration for the role.
18
- #
19
- # You can set tags on the hosts for custom env variables (see kamal docs env)
18
+ # You can set tags on the hosts for custom env variables (see kamal docs env):
20
19
  web:
21
20
  - 172.1.0.1
22
21
  - 172.1.0.2: experiment1
@@ -24,29 +23,31 @@ servers:
24
23
 
25
24
  # Custom role configuration
26
25
  #
27
- # When there are other options to set, the list of hosts goes under the `hosts` key
26
+ # When there are other options to set, the list of hosts goes under the `hosts` key.
27
+ #
28
+ # By default, only the primary role uses a proxy.
29
+ #
30
+ # For other roles, you can set it to `proxy: true` to enable it and inherit the root proxy
31
+ # configuration or provide a map of options to override the root configuration.
28
32
  #
29
- # By default only the primary role uses Traefik, but you can set `traefik` to change
30
- # it.
33
+ # For the primary role, you can set `proxy: false` to disable the proxy.
31
34
  #
32
- # You can also set a custom cmd to run in the container, and overwrite other settings
35
+ # You can also set a custom `cmd` to run in the container and overwrite other settings
33
36
  # from the root configuration.
34
37
  workers:
35
38
  hosts:
36
39
  - 172.1.0.3
37
40
  - 172.1.0.4: experiment1
38
- traefik: true
39
41
  cmd: "bin/jobs"
40
42
  options:
41
43
  memory: 2g
42
44
  cpus: 4
43
- healthcheck:
44
- ...
45
45
  logging:
46
46
  ...
47
+ proxy:
48
+ ...
47
49
  labels:
48
50
  my-label: workers
49
51
  env:
50
52
  ...
51
53
  asset_path: /public
52
-
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Servers are split into different roles, with each role having its own configuration.
4
4
  #
5
- # For simpler deployments though where all servers are identical, you can just specify a list of servers
5
+ # For simpler deployments, though, where all servers are identical, you can just specify a list of servers.
6
6
  # They will be implicitly assigned to the `web` role.
7
7
  servers:
8
8
  - 172.0.0.1
@@ -19,7 +19,7 @@ servers:
19
19
 
20
20
  # Roles
21
21
  #
22
- # For more complex deployments (e.g. if you are running job hosts), you can specify roles, and configure each separately (see kamal docs role)
22
+ # For more complex deployments (e.g., if you are running job hosts), you can specify roles and configure each separately (see kamal docs role):
23
23
  servers:
24
24
  web:
25
25
  ...
@@ -1,9 +1,9 @@
1
1
  # SSH configuration
2
2
  #
3
- # Kamal uses SSH to connect run commands on your hosts.
4
- # By default it will attempt to connect to the root user on port 22
3
+ # Kamal uses SSH to connect and run commands on your hosts.
4
+ # By default, it will attempt to connect to the root user on port 22.
5
5
  #
6
- # If you are using non-root user, you may need to bootstrap your servers manually, before using them with Kamal. On Ubuntu, you’d do:
6
+ # If you are using a non-root user, you may need to bootstrap your servers manually before using them with Kamal. On Ubuntu, you’d do:
7
7
  #
8
8
  # ```shell
9
9
  # sudo apt update
@@ -12,7 +12,6 @@
12
12
  # sudo usermod -a -G docker app
13
13
  # ```
14
14
 
15
-
16
15
  # SSH options
17
16
  #
18
17
  # The options are specified under the ssh key in the configuration file.
@@ -20,47 +19,52 @@ ssh:
20
19
 
21
20
  # The SSH user
22
21
  #
23
- # Defaults to `root`
24
- #
22
+ # Defaults to `root`:
25
23
  user: app
26
24
 
27
25
  # The SSH port
28
26
  #
29
- # Defaults to 22
27
+ # Defaults to 22:
30
28
  port: "2222"
31
29
 
32
30
  # Proxy host
33
31
  #
34
- # Specified in the form <host> or <user>@<host>
32
+ # Specified in the form <host> or <user>@<host>:
35
33
  proxy: root@proxy-host
36
34
 
37
35
  # Proxy command
38
36
  #
39
- # A custom proxy command, required for older versions of SSH
37
+ # A custom proxy command, required for older versions of SSH:
40
38
  proxy_command: "ssh -W %h:%p user@proxy"
41
39
 
42
40
  # Log level
43
41
  #
44
- # Defaults to `fatal`. Set this to debug if you are having
45
- # SSH connection issues.
42
+ # Defaults to `fatal`. Set this to `debug` if you are having SSH connection issues.
46
43
  log_level: debug
47
44
 
48
- # Keys Only
45
+ # Keys only
49
46
  #
50
- # Set to true to use only private keys from keys and key_data parameters,
51
- # even if ssh-agent offers more identities. This option is intended for
52
- # situations where ssh-agent offers many different identites or you have
53
- # a need to overwrite all identites and force a single one.
47
+ # Set to `true` to use only private keys from the `keys` and `key_data` parameters,
48
+ # even if ssh-agent offers more identities. This option is intended for
49
+ # situations where ssh-agent offers many different identities or you
50
+ # need to overwrite all identities and force a single one.
54
51
  keys_only: false
55
52
 
56
53
  # Keys
57
54
  #
58
- # An array of file names of private keys to use for publickey
59
- # and hostbased authentication
55
+ # An array of file names of private keys to use for public key
56
+ # and host-based authentication:
60
57
  keys: [ "~/.ssh/id.pem" ]
61
58
 
62
- # Key Data
59
+ # Key data
63
60
  #
64
61
  # An array of strings, with each element of the array being
65
62
  # a raw private key in PEM format.
66
63
  key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]
64
+
65
+ # Config
66
+ #
67
+ # Set to true to load the default OpenSSH config files (~/.ssh/config,
68
+ # /etc/ssh_config), to false ignore config files, or to a file path
69
+ # (or array of paths) to load specific configuration. Defaults to true.
70
+ config: true
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # [SSHKit](https://github.com/capistrano/sshkit) is the SSH toolkit used by Kamal.
4
4
  #
5
- # The default settings should be sufficient for most use cases, but
6
- # when connecting to a large number of hosts you may need to adjust
5
+ # The default, settings should be sufficient for most use cases, but
6
+ # when connecting to a large number of hosts, you may need to adjust.
7
7
 
8
8
  # SSHKit options
9
9
  #
@@ -13,11 +13,11 @@ sshkit:
13
13
  # Max concurrent starts
14
14
  #
15
15
  # Creating SSH connections concurrently can be an issue when deploying to many servers.
16
- # By default Kamal will limit concurrent connection starts to 30 at a time.
16
+ # By default, Kamal will limit concurrent connection starts to 30 at a time.
17
17
  max_concurrent_starts: 10
18
18
 
19
19
  # Pool idle timeout
20
20
  #
21
21
  # Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
22
- # re-connection storms after an idle period, like building an image or waiting for CI.
22
+ # re-connection storms after an idle period, such as building an image or waiting for CI.
23
23
  pool_idle_timeout: 300
@@ -1,12 +1,13 @@
1
1
  class Kamal::Configuration::Env::Tag
2
- attr_reader :name, :config
2
+ attr_reader :name, :config, :secrets
3
3
 
4
- def initialize(name, config:)
4
+ def initialize(name, config:, secrets:)
5
5
  @name = name
6
6
  @config = config
7
+ @secrets = secrets
7
8
  end
8
9
 
9
10
  def env
10
- Kamal::Configuration::Env.new(config: config)
11
+ Kamal::Configuration::Env.new(config: config, secrets: secrets)
11
12
  end
12
13
  end
@@ -1,36 +1,38 @@
1
1
  class Kamal::Configuration::Env
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- attr_reader :secrets_keys, :clear, :secrets_file, :context
4
+ attr_reader :context, :clear, :secret_keys
5
5
  delegate :argumentize, to: Kamal::Utils
6
6
 
7
- def initialize(config:, secrets_file: nil, context: "env")
7
+ def initialize(config:, secrets:, context: "env")
8
8
  @clear = config.fetch("clear", config.key?("secret") || config.key?("tags") ? {} : config)
9
- @secrets_keys = config.fetch("secret", [])
10
- @secrets_file = secrets_file
9
+ @secrets = secrets
10
+ @secret_keys = config.fetch("secret", [])
11
11
  @context = context
12
12
  validate! config, context: context, with: Kamal::Configuration::Validator::Env
13
13
  end
14
14
 
15
- def args
16
- [ "--env-file", secrets_file, *argumentize("--env", clear) ]
15
+ def clear_args
16
+ argumentize("--env", clear)
17
17
  end
18
18
 
19
19
  def secrets_io
20
- StringIO.new(Kamal::EnvFile.new(secrets).to_s)
21
- end
22
-
23
- def secrets
24
- @secrets ||= secrets_keys.to_h { |key| [ key, ENV.fetch(key) ] }
25
- end
26
-
27
- def secrets_directory
28
- File.dirname(secrets_file)
20
+ Kamal::EnvFile.new(aliased_secrets).to_io
29
21
  end
30
22
 
31
23
  def merge(other)
32
24
  self.class.new \
33
- config: { "clear" => clear.merge(other.clear), "secret" => secrets_keys | other.secrets_keys },
34
- secrets_file: secrets_file || other.secrets_file
25
+ config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
26
+ secrets: @secrets
35
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
36
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
@@ -0,0 +1,124 @@
1
+ class Kamal::Configuration::Proxy
2
+ include Kamal::Configuration::Validation
3
+
4
+ DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
5
+ CONTAINER_NAME = "kamal-proxy"
6
+
7
+ delegate :argumentize, :optionize, to: Kamal::Utils
8
+
9
+ attr_reader :config, :proxy_config, :role_name, :secrets
10
+
11
+ def initialize(config:, proxy_config:, role_name: nil, secrets:, context: "proxy")
12
+ @config = config
13
+ @proxy_config = proxy_config
14
+ @proxy_config = {} if @proxy_config.nil?
15
+ @role_name = role_name
16
+ @secrets = secrets
17
+ validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
18
+ end
19
+
20
+ def app_port
21
+ proxy_config.fetch("app_port", 80)
22
+ end
23
+
24
+ def ssl?
25
+ proxy_config.fetch("ssl", false)
26
+ end
27
+
28
+ def hosts
29
+ proxy_config["hosts"] || proxy_config["host"]&.split(",") || []
30
+ end
31
+
32
+ def custom_ssl_certificate?
33
+ ssl = proxy_config["ssl"]
34
+ return false unless ssl.is_a?(Hash)
35
+ ssl["certificate_pem"].present? && ssl["private_key_pem"].present?
36
+ end
37
+
38
+ def certificate_pem_content
39
+ ssl = proxy_config["ssl"]
40
+ return nil unless ssl.is_a?(Hash)
41
+ secrets[ssl["certificate_pem"]]
42
+ end
43
+
44
+ def private_key_pem_content
45
+ ssl = proxy_config["ssl"]
46
+ return nil unless ssl.is_a?(Hash)
47
+ secrets[ssl["private_key_pem"]]
48
+ end
49
+
50
+ def host_tls_cert
51
+ tls_path(config.proxy_boot.tls_directory, "cert.pem")
52
+ end
53
+
54
+ def host_tls_key
55
+ tls_path(config.proxy_boot.tls_directory, "key.pem")
56
+ end
57
+
58
+ def container_tls_cert
59
+ tls_path(config.proxy_boot.tls_container_directory, "cert.pem")
60
+ end
61
+
62
+ def container_tls_key
63
+ tls_path(config.proxy_boot.tls_container_directory, "key.pem") if custom_ssl_certificate?
64
+ end
65
+
66
+ def deploy_options
67
+ {
68
+ host: hosts,
69
+ tls: ssl? ? true : nil,
70
+ "tls-certificate-path": container_tls_cert,
71
+ "tls-private-key-path": container_tls_key,
72
+ "deploy-timeout": seconds_duration(config.deploy_timeout),
73
+ "drain-timeout": seconds_duration(config.drain_timeout),
74
+ "health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
75
+ "health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
76
+ "health-check-path": proxy_config.dig("healthcheck", "path"),
77
+ "target-timeout": seconds_duration(proxy_config["response_timeout"]),
78
+ "buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
79
+ "buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
80
+ "buffer-memory": proxy_config.dig("buffering", "memory"),
81
+ "max-request-body": proxy_config.dig("buffering", "max_request_body"),
82
+ "max-response-body": proxy_config.dig("buffering", "max_response_body"),
83
+ "path-prefix": proxy_config.dig("path_prefix"),
84
+ "strip-path-prefix": proxy_config.dig("strip_path_prefix"),
85
+ "forward-headers": proxy_config.dig("forward_headers"),
86
+ "tls-redirect": proxy_config.dig("ssl_redirect"),
87
+ "log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
88
+ "log-response-header": proxy_config.dig("logging", "response_headers"),
89
+ "error-pages": error_pages
90
+ }.compact
91
+ end
92
+
93
+ def deploy_command_args(target:)
94
+ optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
95
+ end
96
+
97
+ def stop_options(drain_timeout: nil, message: nil)
98
+ {
99
+ "drain-timeout": seconds_duration(drain_timeout),
100
+ message: message
101
+ }.compact
102
+ end
103
+
104
+ def stop_command_args(**options)
105
+ optionize stop_options(**options), with: "="
106
+ end
107
+
108
+ def merge(other)
109
+ self.class.new config: config, proxy_config: other.proxy_config.deep_merge(proxy_config), role_name: role_name, secrets: secrets
110
+ end
111
+
112
+ private
113
+ def tls_path(directory, filename)
114
+ File.join([ directory, role_name, filename ].compact) if custom_ssl_certificate?
115
+ end
116
+
117
+ def seconds_duration(value)
118
+ value ? "#{value}s" : nil
119
+ end
120
+
121
+ def error_pages
122
+ File.join config.proxy_boot.error_pages_container_directory, config.version if config.error_pages_path
123
+ end
124
+ end
@@ -1,11 +1,10 @@
1
1
  class Kamal::Configuration::Registry
2
2
  include Kamal::Configuration::Validation
3
3
 
4
- attr_reader :registry_config
5
-
6
- def initialize(config:)
7
- @registry_config = config.raw_config.registry || {}
8
- validate! registry_config, with: Kamal::Configuration::Validator::Registry
4
+ def initialize(config:, secrets:, context: "registry")
5
+ @registry_config = config["registry"] || {}
6
+ @secrets = secrets
7
+ validate! registry_config, context: context, with: Kamal::Configuration::Validator::Registry
9
8
  end
10
9
 
11
10
  def server
@@ -21,9 +20,11 @@ class Kamal::Configuration::Registry
21
20
  end
22
21
 
23
22
  private
23
+ attr_reader :registry_config, :secrets
24
+
24
25
  def lookup(key)
25
26
  if registry_config[key].is_a?(Array)
26
- ENV.fetch(registry_config[key].first).dup
27
+ secrets[registry_config[key].first]
27
28
  else
28
29
  registry_config[key]
29
30
  end