dash 2.12.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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/dash +18 -0
  5. data/bin/kamal +18 -0
  6. data/lib/kamal/cli/accessory.rb +342 -0
  7. data/lib/kamal/cli/alias/command.rb +10 -0
  8. data/lib/kamal/cli/app/assets.rb +24 -0
  9. data/lib/kamal/cli/app/boot.rb +126 -0
  10. data/lib/kamal/cli/app/error_pages.rb +33 -0
  11. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  12. data/lib/kamal/cli/app.rb +368 -0
  13. data/lib/kamal/cli/base.rb +324 -0
  14. data/lib/kamal/cli/build/clone.rb +59 -0
  15. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  16. data/lib/kamal/cli/build.rb +242 -0
  17. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  18. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  19. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  20. data/lib/kamal/cli/lock.rb +34 -0
  21. data/lib/kamal/cli/main.rb +299 -0
  22. data/lib/kamal/cli/proxy.rb +419 -0
  23. data/lib/kamal/cli/prune.rb +34 -0
  24. data/lib/kamal/cli/registry.rb +49 -0
  25. data/lib/kamal/cli/secrets.rb +50 -0
  26. data/lib/kamal/cli/server.rb +70 -0
  27. data/lib/kamal/cli/templates/deploy.yml +102 -0
  28. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  31. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  35. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  36. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  37. data/lib/kamal/cli/templates/secrets +22 -0
  38. data/lib/kamal/cli.rb +9 -0
  39. data/lib/kamal/commander/specifics.rb +62 -0
  40. data/lib/kamal/commander.rb +230 -0
  41. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  42. data/lib/kamal/commands/accessory.rb +118 -0
  43. data/lib/kamal/commands/app/assets.rb +51 -0
  44. data/lib/kamal/commands/app/containers.rb +31 -0
  45. data/lib/kamal/commands/app/error_pages.rb +9 -0
  46. data/lib/kamal/commands/app/execution.rb +38 -0
  47. data/lib/kamal/commands/app/images.rb +13 -0
  48. data/lib/kamal/commands/app/logging.rb +28 -0
  49. data/lib/kamal/commands/app/proxy.rb +32 -0
  50. data/lib/kamal/commands/app.rb +125 -0
  51. data/lib/kamal/commands/auditor.rb +39 -0
  52. data/lib/kamal/commands/base.rb +147 -0
  53. data/lib/kamal/commands/builder/base.rb +143 -0
  54. data/lib/kamal/commands/builder/clone.rb +32 -0
  55. data/lib/kamal/commands/builder/cloud.rb +22 -0
  56. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  57. data/lib/kamal/commands/builder/local.rb +20 -0
  58. data/lib/kamal/commands/builder/pack.rb +46 -0
  59. data/lib/kamal/commands/builder/remote.rb +75 -0
  60. data/lib/kamal/commands/builder.rb +54 -0
  61. data/lib/kamal/commands/docker.rb +50 -0
  62. data/lib/kamal/commands/hook.rb +20 -0
  63. data/lib/kamal/commands/loadbalancer.rb +130 -0
  64. data/lib/kamal/commands/lock.rb +70 -0
  65. data/lib/kamal/commands/proxy.rb +150 -0
  66. data/lib/kamal/commands/prune.rb +38 -0
  67. data/lib/kamal/commands/registry.rb +38 -0
  68. data/lib/kamal/commands/server.rb +15 -0
  69. data/lib/kamal/commands.rb +2 -0
  70. data/lib/kamal/configuration/accessory.rb +280 -0
  71. data/lib/kamal/configuration/alias.rb +15 -0
  72. data/lib/kamal/configuration/boot.rb +29 -0
  73. data/lib/kamal/configuration/builder.rb +218 -0
  74. data/lib/kamal/configuration/docs/accessory.yml +160 -0
  75. data/lib/kamal/configuration/docs/alias.yml +29 -0
  76. data/lib/kamal/configuration/docs/boot.yml +21 -0
  77. data/lib/kamal/configuration/docs/builder.yml +132 -0
  78. data/lib/kamal/configuration/docs/configuration.yml +228 -0
  79. data/lib/kamal/configuration/docs/env.yml +118 -0
  80. data/lib/kamal/configuration/docs/logging.yml +21 -0
  81. data/lib/kamal/configuration/docs/output.yml +25 -0
  82. data/lib/kamal/configuration/docs/proxy.yml +207 -0
  83. data/lib/kamal/configuration/docs/registry.yml +64 -0
  84. data/lib/kamal/configuration/docs/role.yml +54 -0
  85. data/lib/kamal/configuration/docs/servers.yml +27 -0
  86. data/lib/kamal/configuration/docs/ssh.yml +81 -0
  87. data/lib/kamal/configuration/docs/sshkit.yml +31 -0
  88. data/lib/kamal/configuration/env/tag.rb +13 -0
  89. data/lib/kamal/configuration/env.rb +42 -0
  90. data/lib/kamal/configuration/loadbalancer.rb +34 -0
  91. data/lib/kamal/configuration/logging.rb +33 -0
  92. data/lib/kamal/configuration/output.rb +34 -0
  93. data/lib/kamal/configuration/proxy/boot.rb +124 -0
  94. data/lib/kamal/configuration/proxy/run.rb +152 -0
  95. data/lib/kamal/configuration/proxy.rb +156 -0
  96. data/lib/kamal/configuration/registry.rb +40 -0
  97. data/lib/kamal/configuration/role.rb +247 -0
  98. data/lib/kamal/configuration/servers.rb +25 -0
  99. data/lib/kamal/configuration/ssh.rb +76 -0
  100. data/lib/kamal/configuration/sshkit.rb +26 -0
  101. data/lib/kamal/configuration/validation.rb +27 -0
  102. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  103. data/lib/kamal/configuration/validator/alias.rb +15 -0
  104. data/lib/kamal/configuration/validator/builder.rb +15 -0
  105. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  106. data/lib/kamal/configuration/validator/env.rb +54 -0
  107. data/lib/kamal/configuration/validator/proxy.rb +47 -0
  108. data/lib/kamal/configuration/validator/registry.rb +27 -0
  109. data/lib/kamal/configuration/validator/role.rb +13 -0
  110. data/lib/kamal/configuration/validator/servers.rb +7 -0
  111. data/lib/kamal/configuration/validator.rb +251 -0
  112. data/lib/kamal/configuration/volume.rb +29 -0
  113. data/lib/kamal/configuration.rb +465 -0
  114. data/lib/kamal/docker.rb +30 -0
  115. data/lib/kamal/env_file.rb +44 -0
  116. data/lib/kamal/git.rb +37 -0
  117. data/lib/kamal/otel_shipper.rb +176 -0
  118. data/lib/kamal/output/base_logger.rb +29 -0
  119. data/lib/kamal/output/file_logger.rb +51 -0
  120. data/lib/kamal/output/formatter.rb +36 -0
  121. data/lib/kamal/output/otel_logger.rb +70 -0
  122. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
  123. data/lib/kamal/secrets/adapters/base.rb +33 -0
  124. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  125. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  126. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  127. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  128. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  129. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  130. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  131. data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
  132. data/lib/kamal/secrets/adapters/test.rb +16 -0
  133. data/lib/kamal/secrets/adapters.rb +16 -0
  134. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
  135. data/lib/kamal/secrets.rb +53 -0
  136. data/lib/kamal/sshkit_with_ext.rb +273 -0
  137. data/lib/kamal/tags.rb +40 -0
  138. data/lib/kamal/utils/sensitive.rb +20 -0
  139. data/lib/kamal/utils.rb +110 -0
  140. data/lib/kamal/version.rb +3 -0
  141. data/lib/kamal.rb +15 -0
  142. metadata +388 -0
@@ -0,0 +1,54 @@
1
+ class Kamal::Configuration::Validator::Env < Kamal::Configuration::Validator
2
+ SPECIAL_KEYS = [ "clear", "secret", "tags" ]
3
+
4
+ def validate!
5
+ if known_keys.any?
6
+ validate_complex_env!
7
+ else
8
+ validate_simple_env!
9
+ end
10
+ end
11
+
12
+ private
13
+ def validate_simple_env!
14
+ validate_hash_of!(config, String)
15
+ end
16
+
17
+ def validate_complex_env!
18
+ unknown_keys_error unknown_keys if unknown_keys.any?
19
+
20
+ with_context("clear") { validate_hash_of!(config["clear"], String) } if config.key?("clear")
21
+ with_context("secret") { validate_array_of!(config["secret"], String) } if config.key?("secret")
22
+ validate_tags! if config.key?("tags")
23
+ end
24
+
25
+ def known_keys
26
+ @known_keys ||= config.keys & SPECIAL_KEYS
27
+ end
28
+
29
+ def unknown_keys
30
+ @unknown_keys ||= config.keys - SPECIAL_KEYS
31
+ end
32
+
33
+ def validate_tags!
34
+ if context == "env"
35
+ with_context("tags") do
36
+ validate_type! config["tags"], Hash
37
+
38
+ config["tags"].each do |tag, value|
39
+ with_context(tag) do
40
+ validate_type! value, Hash
41
+
42
+ Kamal::Configuration::Validator::Env.new(
43
+ value,
44
+ example: example["tags"].values[1],
45
+ context: context
46
+ ).validate!
47
+ end
48
+ end
49
+ end
50
+ else
51
+ error "tags are only allowed in the root env"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
2
+ def validate!
3
+ unless config.nil?
4
+ super
5
+
6
+ # Skip SSL host validation when a loadbalancer is present
7
+ # since SSL is disabled when using a loadbalancer
8
+ if config["host"].blank? && config["hosts"].blank? && config["ssl"] && config["loadbalancer"].blank?
9
+ error "Must set a host to enable automatic SSL"
10
+ end
11
+
12
+ if (config.keys & [ "host", "hosts" ]).size > 1
13
+ error "Specify one of 'host' or 'hosts', not both"
14
+ end
15
+
16
+ if config["ssl"].is_a?(Hash)
17
+ if config["ssl"]["certificate_pem"].present? && config["ssl"]["private_key_pem"].blank?
18
+ error "Missing private_key_pem setting (required when certificate_pem is present)"
19
+ end
20
+
21
+ if config["ssl"]["private_key_pem"].present? && config["ssl"]["certificate_pem"].blank?
22
+ error "Missing certificate_pem setting (required when private_key_pem is present)"
23
+ end
24
+ end
25
+
26
+ if run_config = config["run"]
27
+ if run_config["bind_ips"].present?
28
+ ensure_valid_bind_ips(config["bind_ips"])
29
+ end
30
+
31
+ if run_config["publish"] == false
32
+ if run_config["bind_ips"].present? || run_config["http_port"].present? || run_config["https_port"].present?
33
+ error "Cannot set http_port, https_port or bind_ips when publish is false"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+ def ensure_valid_bind_ips(bind_ips)
42
+ bind_ips.present? && bind_ips.each do |ip|
43
+ next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
44
+ error "Invalid publish IP address: #{ip}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validator
2
+ STRING_OR_ONE_ITEM_ARRAY_KEYS = [ "username", "password" ]
3
+
4
+ def validate!
5
+ validate_against_example! \
6
+ config.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS),
7
+ example.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS)
8
+
9
+ validate_string_or_one_item_array! "username"
10
+ validate_string_or_one_item_array! "password"
11
+ end
12
+
13
+ private
14
+ def validate_string_or_one_item_array!(key)
15
+ with_context(key) do
16
+ value = config[key]
17
+
18
+ unless config["server"]&.match?("^localhost[:$]")
19
+ error "is required" unless value.present?
20
+
21
+ unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
22
+ error "should be a string or an array with one string (for secret lookup)"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ class Kamal::Configuration::Validator::Role < Kamal::Configuration::Validator
2
+ def validate!
3
+ validate_type! config, Array, Hash
4
+
5
+ if config.is_a?(Array)
6
+ validate_servers!(config)
7
+ else
8
+ super
9
+ validate_labels!(config["labels"])
10
+ validate_docker_options!(config["options"])
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class Kamal::Configuration::Validator::Servers < Kamal::Configuration::Validator
2
+ def validate!
3
+ validate_type! config, Array, Hash, NilClass
4
+
5
+ validate_servers! config if config.is_a?(Array)
6
+ end
7
+ end
@@ -0,0 +1,251 @@
1
+ class Kamal::Configuration::Validator
2
+ attr_reader :config, :example, :context
3
+
4
+ def initialize(config, example:, context:)
5
+ @config = config
6
+ @example = example
7
+ @context = context
8
+ end
9
+
10
+ def validate!
11
+ validate_against_example! config, example
12
+ end
13
+
14
+ private
15
+ def validate_against_example!(validation_config, example)
16
+ validate_type! validation_config, example.class
17
+
18
+ if example.class == Hash
19
+ check_unknown_keys! validation_config, example
20
+
21
+ validation_config.each do |key, value|
22
+ next if extension?(key)
23
+ with_context(key) do
24
+ example_value = example[key]
25
+
26
+ if example_value == "..."
27
+ unless key.to_s == "proxy" && boolean?(value.class)
28
+ validate_type! value, *(Array if key == :servers), Hash
29
+ end
30
+ elsif key.to_s == "ssl"
31
+ validate_type! value, TrueClass, FalseClass, Hash
32
+ elsif key.to_s == "hooks_output"
33
+ validate_hooks_output!(value)
34
+ elsif key == "hosts"
35
+ validate_servers! value
36
+ elsif example_value.is_a?(Array)
37
+ if key == "arch"
38
+ validate_array_of_or_type! value, example_value.first.class
39
+ elsif key.to_s == "config"
40
+ validate_ssh_config!(value)
41
+ elsif key.to_s == "files" || key.to_s == "directories"
42
+ validate_paths!(value)
43
+ else
44
+ validate_array_of! value, example_value.first.class
45
+ end
46
+ elsif example_value.is_a?(Hash)
47
+ case key.to_s
48
+ when "options", "args"
49
+ validate_type! value, Hash
50
+ when "labels"
51
+ validate_hash_of! value, example_value.first[1].class
52
+ else
53
+ validate_against_example! value, example_value
54
+ end
55
+ else
56
+ validate_type! value, example_value.class
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+
64
+ def valid_type?(value, type)
65
+ value.is_a?(type) ||
66
+ (type == String && stringish?(value)) ||
67
+ (boolean?(type) && boolean?(value.class))
68
+ end
69
+
70
+ def type_description(type)
71
+ if type == Integer || type == Array
72
+ "an #{type.name.downcase}"
73
+ elsif type == TrueClass || type == FalseClass
74
+ "a boolean"
75
+ else
76
+ "a #{type.name.downcase}"
77
+ end
78
+ end
79
+
80
+ def boolean?(type)
81
+ type == TrueClass || type == FalseClass
82
+ end
83
+
84
+ def stringish?(value)
85
+ value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
86
+ end
87
+
88
+ def validate_array_of_or_type!(value, type)
89
+ if value.is_a?(Array)
90
+ validate_array_of! value, type
91
+ else
92
+ validate_type! value, type
93
+ end
94
+ rescue Kamal::ConfigurationError
95
+ type_error(Array, type)
96
+ end
97
+
98
+ def validate_array_of!(array, type)
99
+ validate_type! array, Array
100
+
101
+ array.each_with_index do |value, index|
102
+ with_context(index) do
103
+ validate_type! value, type
104
+ end
105
+ end
106
+ end
107
+
108
+ def validate_hash_of!(hash, type)
109
+ validate_type! hash, Hash
110
+
111
+ hash.each do |key, value|
112
+ with_context(key) do
113
+ validate_type! value, type
114
+ end
115
+ end
116
+ end
117
+
118
+ def validate_servers!(servers)
119
+ validate_type! servers, Array
120
+
121
+ servers.each_with_index do |server, index|
122
+ with_context(index) do
123
+ validate_type! server, String, Hash
124
+
125
+ if server.is_a?(Hash)
126
+ error "multiple hosts found" unless server.size == 1
127
+ host, tags = server.first
128
+
129
+ with_context(host) do
130
+ validate_type! tags, String, Array
131
+ validate_array_of! tags, String if tags.is_a?(Array)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ def validate_ssh_config!(config)
139
+ if config.is_a?(Array)
140
+ validate_array_of! config, String
141
+ elsif boolean?(config.class) || config.is_a?(String)
142
+ # Booleans and Strings are allowed
143
+ else
144
+ type_error(TrueClass, FalseClass, String, Array)
145
+ end
146
+ end
147
+
148
+ def validate_paths!(paths)
149
+ validate_type! paths, Array
150
+
151
+ paths.each_with_index do |path, index|
152
+ with_context(index) do
153
+ validate_type! path, String, Hash
154
+
155
+ if path.is_a?(Hash)
156
+ %w[local remote mode owner options].each do |key|
157
+ with_context(key) do
158
+ validate_type! path[key], String if path.key?(key)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def validate_hooks_output!(value)
167
+ # hooks_output can be either a symbol/string (global) or a hash (per-hook)
168
+ if value.is_a?(Hash)
169
+ value.each do |hook, level|
170
+ with_context(hook) do
171
+ validate_type! level, String, Symbol
172
+ end
173
+ end
174
+ else
175
+ validate_type! value, String, Symbol
176
+ end
177
+ end
178
+
179
+ def validate_type!(value, *types)
180
+ type_error(*types) unless types.any? { |type| valid_type?(value, type) }
181
+ end
182
+
183
+ def error(message)
184
+ raise Kamal::ConfigurationError, "#{error_context}#{message}"
185
+ end
186
+
187
+ def type_error(*expected_types)
188
+ descriptions = expected_types.map { |type| type_description(type) }.uniq
189
+ error "should be #{descriptions.join(" or ")}"
190
+ end
191
+
192
+ def unknown_keys_error(unknown_keys)
193
+ error "unknown #{"key".pluralize(unknown_keys.count)}: #{unknown_keys.join(", ")}"
194
+ end
195
+
196
+ def error_context
197
+ "#{context}: " if context.present?
198
+ end
199
+
200
+ def with_context(context)
201
+ old_context = @context
202
+ @context = [ @context, context ].select(&:present?).join("/")
203
+ yield
204
+ ensure
205
+ @context = old_context
206
+ end
207
+
208
+ def allow_extensions?
209
+ false
210
+ end
211
+
212
+ def extension?(key)
213
+ key.to_s.start_with?("x-")
214
+ end
215
+
216
+ def check_unknown_keys!(config, example)
217
+ unknown_keys = config.keys - example.keys
218
+ unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
219
+ unknown_keys_error unknown_keys if unknown_keys.present?
220
+ end
221
+
222
+ def validate_labels!(labels)
223
+ return true if labels.blank?
224
+
225
+ with_context("labels") do
226
+ labels.each do |key, _|
227
+ with_context(key) do
228
+ error "invalid label. destination, role, and service are reserved labels" if %w[destination role service].include?(key)
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ def validate_docker_options!(options)
235
+ if restart_policy = options&.find { |key, _| key.to_s == "restart" }
236
+ validate_restart_policy!(restart_policy.last)
237
+ end
238
+ end
239
+
240
+ def validate_restart_policy!(restart_policy)
241
+ with_context("options/restart") do
242
+ unless restart_policy.is_a?(String)
243
+ error %(should be a string. Use "no" to disable restarts)
244
+ end
245
+
246
+ unless restart_policy.match?(/\A(?:no|always|unless-stopped|on-failure(?::\d+)?)\z/)
247
+ error "should be no, always, unless-stopped, on-failure, or on-failure:N"
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,29 @@
1
+ class Kamal::Configuration::Volume
2
+ attr_reader :host_path, :container_path, :options
3
+ delegate :argumentize, to: Kamal::Utils
4
+
5
+ def initialize(host_path:, container_path:, options: nil)
6
+ @host_path = host_path
7
+ @container_path = container_path
8
+ @options = options
9
+ end
10
+
11
+ def docker_args
12
+ argumentize "--volume", docker_args_string
13
+ end
14
+
15
+ def docker_args_string
16
+ volume_string = "#{host_path_for_docker_volume}:#{container_path}"
17
+ volume_string += ":#{options}" if options.present?
18
+ volume_string
19
+ end
20
+
21
+ private
22
+ def host_path_for_docker_volume
23
+ if Pathname.new(host_path).absolute?
24
+ host_path
25
+ else
26
+ "$PWD/#{host_path}"
27
+ end
28
+ end
29
+ end