nocoffee-kamal 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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