nocoffee-kamal 2.3.0.1

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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  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 +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
@@ -0,0 +1,70 @@
1
+ # SSH configuration
2
+ #
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
+ #
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
+ #
8
+ # ```shell
9
+ # sudo apt update
10
+ # sudo apt upgrade -y
11
+ # sudo apt install -y docker.io curl git
12
+ # sudo usermod -a -G docker app
13
+ # ```
14
+
15
+ # SSH options
16
+ #
17
+ # The options are specified under the ssh key in the configuration file.
18
+ ssh:
19
+
20
+ # The SSH user
21
+ #
22
+ # Defaults to `root`:
23
+ user: app
24
+
25
+ # The SSH port
26
+ #
27
+ # Defaults to 22:
28
+ port: "2222"
29
+
30
+ # Proxy host
31
+ #
32
+ # Specified in the form <host> or <user>@<host>:
33
+ proxy: root@proxy-host
34
+
35
+ # Proxy command
36
+ #
37
+ # A custom proxy command, required for older versions of SSH:
38
+ proxy_command: "ssh -W %h:%p user@proxy"
39
+
40
+ # Log level
41
+ #
42
+ # Defaults to `fatal`. Set this to `debug` if you are having SSH connection issues.
43
+ log_level: debug
44
+
45
+ # Keys only
46
+ #
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.
51
+ keys_only: false
52
+
53
+ # Keys
54
+ #
55
+ # An array of file names of private keys to use for public key
56
+ # and host-based authentication:
57
+ keys: [ "~/.ssh/id.pem" ]
58
+
59
+ # Key data
60
+ #
61
+ # An array of strings, with each element of the array being
62
+ # a raw private key in PEM format.
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
@@ -0,0 +1,23 @@
1
+ # SSHKit
2
+ #
3
+ # [SSHKit](https://github.com/capistrano/sshkit) is the SSH toolkit used by Kamal.
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.
7
+
8
+ # SSHKit options
9
+ #
10
+ # The options are specified under the sshkit key in the configuration file.
11
+ sshkit:
12
+
13
+ # Max concurrent starts
14
+ #
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.
17
+ max_concurrent_starts: 10
18
+
19
+ # Pool idle timeout
20
+ #
21
+ # Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
22
+ # re-connection storms after an idle period, such as building an image or waiting for CI.
23
+ pool_idle_timeout: 300
@@ -0,0 +1,13 @@
1
+ class Kamal::Configuration::Env::Tag
2
+ attr_reader :name, :config, :secrets
3
+
4
+ def initialize(name, config:, secrets:)
5
+ @name = name
6
+ @config = config
7
+ @secrets = secrets
8
+ end
9
+
10
+ def env
11
+ Kamal::Configuration::Env.new(config: config, secrets: secrets)
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ class Kamal::Configuration::Env
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :context, :secrets
5
+ attr_reader :clear, :secret_keys
6
+ delegate :argumentize, to: Kamal::Utils
7
+
8
+ def initialize(config:, secrets:, context: "env")
9
+ @clear = config.fetch("clear", config.key?("secret") || config.key?("tags") ? {} : config)
10
+ @secrets = secrets
11
+ @secret_keys = config.fetch("secret", [])
12
+ @context = context
13
+ validate! config, context: context, with: Kamal::Configuration::Validator::Env
14
+ end
15
+
16
+ def clear_args
17
+ argumentize("--env", clear)
18
+ end
19
+
20
+ def secrets_io
21
+ Kamal::EnvFile.new(secret_keys.to_h { |key| [ key, secrets[key] ] }).to_io
22
+ end
23
+
24
+ def merge(other)
25
+ self.class.new \
26
+ config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
27
+ secrets: secrets
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ class Kamal::Configuration::Logging
2
+ delegate :optionize, :argumentize, to: Kamal::Utils
3
+
4
+ include Kamal::Configuration::Validation
5
+
6
+ attr_reader :logging_config
7
+
8
+ def initialize(logging_config:, context: "logging")
9
+ @logging_config = logging_config || {}
10
+ validate! @logging_config, context: context
11
+ end
12
+
13
+ def driver
14
+ logging_config["driver"]
15
+ end
16
+
17
+ def options
18
+ logging_config.fetch("options", {})
19
+ end
20
+
21
+ def merge(other)
22
+ self.class.new logging_config: logging_config.deep_merge(other.logging_config)
23
+ end
24
+
25
+ def args
26
+ if driver.present? || options.present?
27
+ optionize({ "log-driver" => driver }.compact) +
28
+ argumentize("--log-opt", options)
29
+ else
30
+ argumentize("--log-opt", { "max-size" => "10m" })
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
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
10
+
11
+ def initialize(config:, proxy_config:, context: "proxy")
12
+ @config = config
13
+ @proxy_config = proxy_config
14
+ validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
15
+ end
16
+
17
+ def app_port
18
+ proxy_config.fetch("app_port", 80)
19
+ end
20
+
21
+ def ssl?
22
+ proxy_config.fetch("ssl", false)
23
+ end
24
+
25
+ def hosts
26
+ proxy_config["hosts"] || proxy_config["host"]&.split(",") || []
27
+ end
28
+
29
+ def deploy_options
30
+ {
31
+ host: hosts,
32
+ tls: proxy_config["ssl"].presence,
33
+ "tls-on-demand-url": proxy_config["tls_on_demand_url"],
34
+ "deploy-timeout": seconds_duration(config.deploy_timeout),
35
+ "drain-timeout": seconds_duration(config.drain_timeout),
36
+ "health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
37
+ "health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
38
+ "health-check-path": proxy_config.dig("healthcheck", "path"),
39
+ "target-timeout": seconds_duration(proxy_config["response_timeout"]),
40
+ "buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
41
+ "buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
42
+ "buffer-memory": proxy_config.dig("buffering", "memory"),
43
+ "max-request-body": proxy_config.dig("buffering", "max_request_body"),
44
+ "max-response-body": proxy_config.dig("buffering", "max_response_body"),
45
+ "forward-headers": proxy_config.dig("forward_headers"),
46
+ "log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
47
+ "log-response-header": proxy_config.dig("logging", "response_headers")
48
+ }.compact
49
+ end
50
+
51
+ def deploy_command_args(target:)
52
+ optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
53
+ end
54
+
55
+ def merge(other)
56
+ self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
57
+ end
58
+
59
+ private
60
+ def seconds_duration(value)
61
+ value ? "#{value}s" : nil
62
+ end
63
+ end
@@ -0,0 +1,32 @@
1
+ class Kamal::Configuration::Registry
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :registry_config, :secrets
5
+
6
+ def initialize(config:)
7
+ @registry_config = config.raw_config.registry || {}
8
+ @secrets = config.secrets
9
+ validate! registry_config, with: Kamal::Configuration::Validator::Registry
10
+ end
11
+
12
+ def server
13
+ registry_config["server"]
14
+ end
15
+
16
+ def username
17
+ lookup("username")
18
+ end
19
+
20
+ def password
21
+ lookup("password")
22
+ end
23
+
24
+ private
25
+ def lookup(key)
26
+ if registry_config[key].is_a?(Array)
27
+ secrets[registry_config[key].first]
28
+ else
29
+ registry_config[key]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,220 @@
1
+ class Kamal::Configuration::Role
2
+ include Kamal::Configuration::Validation
3
+
4
+ delegate :argumentize, :optionize, to: Kamal::Utils
5
+
6
+ attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_proxy
7
+
8
+ alias to_s name
9
+
10
+ def initialize(name, config:)
11
+ @name, @config = name.inquiry, config
12
+ validate! \
13
+ specializations,
14
+ example: validation_yml["servers"]["workers"],
15
+ context: "servers/#{name}",
16
+ with: Kamal::Configuration::Validator::Role
17
+
18
+ @specialized_env = Kamal::Configuration::Env.new \
19
+ config: specializations.fetch("env", {}),
20
+ secrets: config.secrets,
21
+ context: "servers/#{name}/env"
22
+
23
+ @specialized_logging = Kamal::Configuration::Logging.new \
24
+ logging_config: specializations.fetch("logging", {}),
25
+ context: "servers/#{name}/logging"
26
+
27
+ initialize_specialized_proxy
28
+ end
29
+
30
+ def primary_host
31
+ hosts.first
32
+ end
33
+
34
+ def hosts
35
+ tagged_hosts.keys
36
+ end
37
+
38
+ def env_tags(host)
39
+ tagged_hosts.fetch(host).collect { |tag| config.env_tag(tag) }
40
+ end
41
+
42
+ def cmd
43
+ specializations["cmd"]
44
+ end
45
+
46
+ def option_args
47
+ if args = specializations["options"]
48
+ optionize args
49
+ else
50
+ []
51
+ end
52
+ end
53
+
54
+ def labels
55
+ default_labels.merge(custom_labels)
56
+ end
57
+
58
+ def label_args
59
+ argumentize "--label", labels
60
+ end
61
+
62
+ def logging_args
63
+ logging.args
64
+ end
65
+
66
+ def logging
67
+ @logging ||= config.logging.merge(specialized_logging)
68
+ end
69
+
70
+ def proxy
71
+ @proxy ||= config.proxy.merge(specialized_proxy) if running_proxy?
72
+ end
73
+
74
+ def running_proxy?
75
+ @running_proxy
76
+ end
77
+
78
+ def ssl?
79
+ running_proxy? && proxy.ssl?
80
+ end
81
+
82
+ def stop_args
83
+ # When deploying with the proxy, kamal-proxy will drain request before returning so we don't need to wait.
84
+ timeout = running_proxy? ? nil : config.drain_timeout
85
+
86
+ [ *argumentize("-t", timeout) ]
87
+ end
88
+
89
+ def env(host)
90
+ @envs ||= {}
91
+ @envs[host] ||= [ config.env, specialized_env, *env_tags(host).map(&:env) ].reduce(:merge)
92
+ end
93
+
94
+ def env_args(host)
95
+ [ *env(host).clear_args, *argumentize("--env-file", secrets_path) ]
96
+ end
97
+
98
+ def env_directory
99
+ File.join(config.env_directory, "roles")
100
+ end
101
+
102
+ def secrets_io(host)
103
+ env(host).secrets_io
104
+ end
105
+
106
+ def secrets_path
107
+ File.join(config.env_directory, "roles", "#{name}.env")
108
+ end
109
+
110
+ def asset_volume_args
111
+ asset_volume&.docker_args
112
+ end
113
+
114
+
115
+ def primary?
116
+ name == @config.primary_role_name
117
+ end
118
+
119
+
120
+ def container_name(version = nil)
121
+ [ container_prefix, version || config.version ].compact.join("-")
122
+ end
123
+
124
+ def container_prefix
125
+ [ config.service, name, config.destination ].compact.join("-")
126
+ end
127
+
128
+
129
+ def asset_path
130
+ specializations["asset_path"] || config.asset_path
131
+ end
132
+
133
+ def assets?
134
+ asset_path.present? && running_proxy?
135
+ end
136
+
137
+ def asset_volume(version = config.version)
138
+ if assets?
139
+ Kamal::Configuration::Volume.new \
140
+ host_path: asset_volume_directory(version), container_path: asset_path
141
+ end
142
+ end
143
+
144
+ def asset_extracted_directory(version = config.version)
145
+ File.join config.assets_directory, "extracted", [ name, version ].join("-")
146
+ end
147
+
148
+ def asset_volume_directory(version = config.version)
149
+ File.join config.assets_directory, "volumes", [ name, version ].join("-")
150
+ end
151
+
152
+ def ensure_one_host_for_ssl
153
+ if running_proxy? && proxy.ssl? && hosts.size > 1
154
+ raise Kamal::ConfigurationError, "SSL is only supported on a single server, found #{hosts.size} servers for role #{name}"
155
+ end
156
+ end
157
+
158
+ private
159
+ def initialize_specialized_proxy
160
+ proxy_specializations = specializations["proxy"]
161
+
162
+ if primary?
163
+ # only false means no proxy for non-primary roles
164
+ @running_proxy = proxy_specializations != false
165
+ else
166
+ # false and nil both mean no proxy for non-primary roles
167
+ @running_proxy = !!proxy_specializations
168
+ end
169
+
170
+ if running_proxy?
171
+ proxy_config = proxy_specializations == true || proxy_specializations.nil? ? {} : proxy_specializations
172
+
173
+ @specialized_proxy = Kamal::Configuration::Proxy.new \
174
+ config: config,
175
+ proxy_config: proxy_config,
176
+ context: "servers/#{name}/proxy"
177
+ end
178
+ end
179
+
180
+ def tagged_hosts
181
+ {}.tap do |tagged_hosts|
182
+ extract_hosts_from_config.map do |host_config|
183
+ if host_config.is_a?(Hash)
184
+ host, tags = host_config.first
185
+ tagged_hosts[host] = Array(tags)
186
+ elsif host_config.is_a?(String)
187
+ tagged_hosts[host_config] = []
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ def extract_hosts_from_config
194
+ if config.raw_config.servers.is_a?(Array)
195
+ config.raw_config.servers
196
+ else
197
+ servers = config.raw_config.servers[name]
198
+ servers.is_a?(Array) ? servers : Array(servers["hosts"])
199
+ end
200
+ end
201
+
202
+ def default_labels
203
+ { "service" => config.service, "role" => name, "destination" => config.destination }
204
+ end
205
+
206
+ def specializations
207
+ if config.raw_config.servers.is_a?(Array) || config.raw_config.servers[name].is_a?(Array)
208
+ {}
209
+ else
210
+ config.raw_config.servers[name]
211
+ end
212
+ end
213
+
214
+ def custom_labels
215
+ Hash.new.tap do |labels|
216
+ labels.merge!(config.labels) if config.labels.present?
217
+ labels.merge!(specializations["labels"]) if specializations["labels"].present?
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,18 @@
1
+ class Kamal::Configuration::Servers
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :config, :servers_config, :roles
5
+
6
+ def initialize(config:)
7
+ @config = config
8
+ @servers_config = config.raw_config.servers
9
+ validate! servers_config, with: Kamal::Configuration::Validator::Servers
10
+
11
+ @roles = role_names.map { |role_name| Kamal::Configuration::Role.new role_name, config: config }
12
+ end
13
+
14
+ private
15
+ def role_names
16
+ servers_config.is_a?(Array) ? [ "web" ] : servers_config.keys.sort
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ class Kamal::Configuration::Ssh
2
+ LOGGER = ::Logger.new(STDERR)
3
+
4
+ include Kamal::Configuration::Validation
5
+
6
+ attr_reader :ssh_config
7
+
8
+ def initialize(config:)
9
+ @ssh_config = config.raw_config.ssh || {}
10
+ validate! ssh_config
11
+ end
12
+
13
+ def user
14
+ ssh_config.fetch("user", "root")
15
+ end
16
+
17
+ def port
18
+ ssh_config.fetch("port", 22)
19
+ end
20
+
21
+ def proxy
22
+ if (proxy = ssh_config["proxy"])
23
+ Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
24
+ elsif (proxy_command = ssh_config["proxy_command"])
25
+ Net::SSH::Proxy::Command.new(proxy_command)
26
+ end
27
+ end
28
+
29
+ def keys_only
30
+ ssh_config["keys_only"]
31
+ end
32
+
33
+ def keys
34
+ ssh_config["keys"]
35
+ end
36
+
37
+ def key_data
38
+ ssh_config["key_data"]
39
+ end
40
+
41
+ def options
42
+ { user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
43
+ end
44
+
45
+ def to_h
46
+ options.except(:logger).merge(log_level: log_level)
47
+ end
48
+
49
+ private
50
+ def logger
51
+ LOGGER.tap { |logger| logger.level = log_level }
52
+ end
53
+
54
+ def log_level
55
+ ssh_config.fetch("log_level", :fatal)
56
+ end
57
+ end
@@ -0,0 +1,22 @@
1
+ class Kamal::Configuration::Sshkit
2
+ include Kamal::Configuration::Validation
3
+
4
+ attr_reader :sshkit_config
5
+
6
+ def initialize(config:)
7
+ @sshkit_config = config.raw_config.sshkit || {}
8
+ validate! sshkit_config
9
+ end
10
+
11
+ def max_concurrent_starts
12
+ sshkit_config.fetch("max_concurrent_starts", 30)
13
+ end
14
+
15
+ def pool_idle_timeout
16
+ sshkit_config.fetch("pool_idle_timeout", 900)
17
+ end
18
+
19
+ def to_h
20
+ sshkit_config
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require "yaml"
2
+ require "active_support/inflector"
3
+
4
+ module Kamal::Configuration::Validation
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def validation_doc
9
+ @validation_doc ||= File.read(File.join(File.dirname(__FILE__), "docs", "#{validation_config_key}.yml"))
10
+ end
11
+
12
+ def validation_config_key
13
+ @validation_config_key ||= name.demodulize.underscore
14
+ end
15
+ end
16
+
17
+ def validate!(config, example: nil, context: nil, with: Kamal::Configuration::Validator)
18
+ context ||= self.class.validation_config_key
19
+ example ||= validation_yml[self.class.validation_config_key]
20
+
21
+ with.new(config, example: example, context: context).validate!
22
+ end
23
+
24
+ def validation_yml
25
+ @validation_yml ||= YAML.load(self.class.validation_doc)
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ class Kamal::Configuration::Validator::Accessory < Kamal::Configuration::Validator
2
+ def validate!
3
+ super
4
+
5
+ if (config.keys & [ "host", "hosts", "roles" ]).size != 1
6
+ error "specify one of `host`, `hosts` or `roles`"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ class Kamal::Configuration::Validator::Alias < Kamal::Configuration::Validator
2
+ def validate!
3
+ super
4
+
5
+ name = context.delete_prefix("aliases/")
6
+
7
+ if name !~ /\A[a-z0-9_-]+\z/
8
+ error "Invalid alias name: '#{name}'. Must only contain lowercase letters, alphanumeric, hyphens and underscores."
9
+ end
10
+
11
+ if Kamal::Cli::Main.commands.include?(name)
12
+ error "Alias '#{name}' conflicts with a built-in command."
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
2
+ def validate!
3
+ super
4
+
5
+ if config["cache"] && config["cache"]["type"]
6
+ error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
7
+ end
8
+
9
+ error "Builder arch not set" unless config["arch"].present?
10
+
11
+ error "Cannot disable local builds, no remote is set" if config["local"] == false && config["remote"].blank?
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ class Kamal::Configuration::Validator::Configuration < Kamal::Configuration::Validator
2
+ private
3
+ def allow_extensions?
4
+ true
5
+ end
6
+ end