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,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,15 @@
1
+ class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
2
+ def validate!
3
+ unless config.nil?
4
+ super
5
+
6
+ if config["host"].blank? && config["hosts"].blank? && config["ssl"]
7
+ error "Must set a host to enable automatic SSL"
8
+ end
9
+
10
+ if (config.keys & [ "host", "hosts" ]).size > 1
11
+ error "Specify one of 'host' or 'hosts', not both"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
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
+ error "is required" unless value.present?
19
+
20
+ unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
21
+ error "should be a string or an array with one string (for secret lookup)"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
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! "servers", config
7
+ else
8
+ super
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class Kamal::Configuration::Validator::Servers < Kamal::Configuration::Validator
2
+ def validate!
3
+ validate_type! config, Array, Hash
4
+
5
+ validate_servers! config if config.is_a?(Array)
6
+ end
7
+ end
@@ -0,0 +1,171 @@
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 == "hosts"
31
+ validate_servers! value
32
+ elsif example_value.is_a?(Array)
33
+ if key == "arch"
34
+ validate_array_of_or_type! value, example_value.first.class
35
+ else
36
+ validate_array_of! value, example_value.first.class
37
+ end
38
+ elsif example_value.is_a?(Hash)
39
+ case key.to_s
40
+ when "options", "args"
41
+ validate_type! value, Hash
42
+ when "labels"
43
+ validate_hash_of! value, example_value.first[1].class
44
+ else
45
+ validate_against_example! value, example_value
46
+ end
47
+ else
48
+ validate_type! value, example_value.class
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ def valid_type?(value, type)
57
+ value.is_a?(type) ||
58
+ (type == String && stringish?(value)) ||
59
+ (boolean?(type) && boolean?(value.class))
60
+ end
61
+
62
+ def type_description(type)
63
+ if type == Integer || type == Array
64
+ "an #{type.name.downcase}"
65
+ elsif type == TrueClass || type == FalseClass
66
+ "a boolean"
67
+ else
68
+ "a #{type.name.downcase}"
69
+ end
70
+ end
71
+
72
+ def boolean?(type)
73
+ type == TrueClass || type == FalseClass
74
+ end
75
+
76
+ def stringish?(value)
77
+ value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
78
+ end
79
+
80
+ def validate_array_of_or_type!(value, type)
81
+ if value.is_a?(Array)
82
+ validate_array_of! value, type
83
+ else
84
+ validate_type! value, type
85
+ end
86
+ rescue Kamal::ConfigurationError
87
+ type_error(Array, type)
88
+ end
89
+
90
+ def validate_array_of!(array, type)
91
+ validate_type! array, Array
92
+
93
+ array.each_with_index do |value, index|
94
+ with_context(index) do
95
+ validate_type! value, type
96
+ end
97
+ end
98
+ end
99
+
100
+ def validate_hash_of!(hash, type)
101
+ validate_type! hash, Hash
102
+
103
+ hash.each do |key, value|
104
+ with_context(key) do
105
+ validate_type! value, type
106
+ end
107
+ end
108
+ end
109
+
110
+ def validate_servers!(servers)
111
+ validate_type! servers, Array
112
+
113
+ servers.each_with_index do |server, index|
114
+ with_context(index) do
115
+ validate_type! server, String, Hash
116
+
117
+ if server.is_a?(Hash)
118
+ error "multiple hosts found" unless server.size == 1
119
+ host, tags = server.first
120
+
121
+ with_context(host) do
122
+ validate_type! tags, String, Array
123
+ validate_array_of! tags, String if tags.is_a?(Array)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def validate_type!(value, *types)
131
+ type_error(*types) unless types.any? { |type| valid_type?(value, type) }
132
+ end
133
+
134
+ def error(message)
135
+ raise Kamal::ConfigurationError, "#{error_context}#{message}"
136
+ end
137
+
138
+ def type_error(*expected_types)
139
+ error "should be #{expected_types.map { |type| type_description(type) }.join(" or ")}"
140
+ end
141
+
142
+ def unknown_keys_error(unknown_keys)
143
+ error "unknown #{"key".pluralize(unknown_keys.count)}: #{unknown_keys.join(", ")}"
144
+ end
145
+
146
+ def error_context
147
+ "#{context}: " if context.present?
148
+ end
149
+
150
+ def with_context(context)
151
+ old_context = @context
152
+ @context = [ @context, context ].select(&:present?).join("/")
153
+ yield
154
+ ensure
155
+ @context = old_context
156
+ end
157
+
158
+ def allow_extensions?
159
+ false
160
+ end
161
+
162
+ def extension?(key)
163
+ key.to_s.start_with?("x-")
164
+ end
165
+
166
+ def check_unknown_keys!(config, example)
167
+ unknown_keys = config.keys - example.keys
168
+ unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
169
+ unknown_keys_error unknown_keys if unknown_keys.present?
170
+ end
171
+ end
@@ -0,0 +1,22 @@
1
+ class Kamal::Configuration::Volume
2
+ attr_reader :host_path, :container_path
3
+ delegate :argumentize, to: Kamal::Utils
4
+
5
+ def initialize(host_path:, container_path:)
6
+ @host_path = host_path
7
+ @container_path = container_path
8
+ end
9
+
10
+ def docker_args
11
+ argumentize "--volume", "#{host_path_for_docker_volume}:#{container_path}"
12
+ end
13
+
14
+ private
15
+ def host_path_for_docker_volume
16
+ if Pathname.new(host_path).absolute?
17
+ host_path
18
+ else
19
+ File.join "$(pwd)", host_path
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,393 @@
1
+ require "active_support/ordered_options"
2
+ require "active_support/core_ext/string/inquiry"
3
+ require "active_support/core_ext/module/delegation"
4
+ require "active_support/core_ext/hash/keys"
5
+ require "erb"
6
+ require "net/ssh/proxy/jump"
7
+
8
+ class Kamal::Configuration
9
+ delegate :service, :image, :labels, :hooks_path, to: :raw_config, allow_nil: true
10
+ delegate :argumentize, :optionize, to: Kamal::Utils
11
+
12
+ attr_reader :destination, :raw_config, :secrets
13
+ attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :servers, :ssh, :sshkit, :registry
14
+
15
+ include Validation
16
+
17
+ PROXY_MINIMUM_VERSION = "v0.8.2.6"
18
+ PROXY_HTTP_PORT = 80
19
+ PROXY_HTTPS_PORT = 443
20
+ PROXY_LOG_MAX_SIZE = "10m"
21
+
22
+ class << self
23
+ def create_from(config_file:, destination: nil, version: nil)
24
+ ENV["KAMAL_DESTINATION"] = destination
25
+
26
+ raw_config = load_config_files(config_file, *destination_config_file(config_file, destination))
27
+
28
+ new raw_config, destination: destination, version: version
29
+ end
30
+
31
+ private
32
+ def load_config_files(*files)
33
+ files.inject({}) { |config, file| config.deep_merge! load_config_file(file) }
34
+ end
35
+
36
+ def load_config_file(file)
37
+ if file.exist?
38
+ # Newer Psych doesn't load aliases by default
39
+ load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
40
+ YAML.send(load_method, ERB.new(IO.read(file)).result).symbolize_keys
41
+ else
42
+ raise "Configuration file not found in #{file}"
43
+ end
44
+ end
45
+
46
+ def destination_config_file(base_config_file, destination)
47
+ base_config_file.sub_ext(".#{destination}.yml") if destination
48
+ end
49
+ end
50
+
51
+ def initialize(raw_config, destination: nil, version: nil, validate: true)
52
+ @raw_config = ActiveSupport::InheritableOptions.new(raw_config)
53
+ @destination = destination
54
+ @declared_version = version
55
+
56
+ validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration
57
+
58
+ @secrets = Kamal::Secrets.new(destination: destination)
59
+
60
+ # Eager load config to validate it, these are first as they have dependencies later on
61
+ @servers = Servers.new(config: self)
62
+ @registry = Registry.new(config: self)
63
+
64
+ @accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
65
+ @aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
66
+ @boot = Boot.new(config: self)
67
+ @builder = Builder.new(config: self)
68
+ @env = Env.new(config: @raw_config.env || {}, secrets: secrets)
69
+
70
+ @logging = Logging.new(logging_config: @raw_config.logging)
71
+ @proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy || {})
72
+ @ssh = Ssh.new(config: self)
73
+ @sshkit = Sshkit.new(config: self)
74
+
75
+ ensure_destination_if_required
76
+ ensure_required_keys_present
77
+ ensure_valid_kamal_version
78
+ ensure_retain_containers_valid
79
+ ensure_valid_service_name
80
+ ensure_no_traefik_reboot_hooks
81
+ ensure_one_host_for_ssl_roles
82
+ ensure_unique_hosts_for_ssl_roles
83
+ end
84
+
85
+
86
+ def version=(version)
87
+ @declared_version = version
88
+ end
89
+
90
+ def version
91
+ @declared_version.presence || ENV["VERSION"] || git_version
92
+ end
93
+
94
+ def abbreviated_version
95
+ if version
96
+ # Don't abbreviate <sha>_uncommitted_<etc>
97
+ if version.include?("_")
98
+ version
99
+ else
100
+ version[0...7]
101
+ end
102
+ end
103
+ end
104
+
105
+ def minimum_version
106
+ raw_config.minimum_version
107
+ end
108
+
109
+
110
+ def roles
111
+ servers.roles
112
+ end
113
+
114
+ def role(name)
115
+ roles.detect { |r| r.name == name.to_s }
116
+ end
117
+
118
+ def accessory(name)
119
+ accessories.detect { |a| a.name == name.to_s }
120
+ end
121
+
122
+
123
+ def all_hosts
124
+ (roles + accessories).flat_map(&:hosts).uniq
125
+ end
126
+
127
+ def primary_host
128
+ primary_role&.primary_host
129
+ end
130
+
131
+ def primary_role_name
132
+ raw_config.primary_role || "web"
133
+ end
134
+
135
+ def primary_role
136
+ role(primary_role_name)
137
+ end
138
+
139
+ def allow_empty_roles?
140
+ raw_config.allow_empty_roles
141
+ end
142
+
143
+ def proxy_roles
144
+ roles.select(&:running_proxy?)
145
+ end
146
+
147
+ def proxy_role_names
148
+ proxy_roles.flat_map(&:name)
149
+ end
150
+
151
+ def proxy_hosts
152
+ proxy_roles.flat_map(&:hosts).uniq
153
+ end
154
+
155
+ def repository
156
+ [ registry.server, image ].compact.join("/")
157
+ end
158
+
159
+ def absolute_image
160
+ "#{repository}:#{version}"
161
+ end
162
+
163
+ def latest_image
164
+ "#{repository}:#{latest_tag}"
165
+ end
166
+
167
+ def latest_tag
168
+ [ "latest", *destination ].join("-")
169
+ end
170
+
171
+ def service_with_version
172
+ "#{service}-#{version}"
173
+ end
174
+
175
+ def require_destination?
176
+ raw_config.require_destination
177
+ end
178
+
179
+ def retain_containers
180
+ raw_config.retain_containers || 5
181
+ end
182
+
183
+
184
+ def volume_args
185
+ if raw_config.volumes.present?
186
+ argumentize "--volume", raw_config.volumes
187
+ else
188
+ []
189
+ end
190
+ end
191
+
192
+ def logging_args
193
+ logging.args
194
+ end
195
+
196
+
197
+ def readiness_delay
198
+ raw_config.readiness_delay || 7
199
+ end
200
+
201
+ def deploy_timeout
202
+ raw_config.deploy_timeout || 30
203
+ end
204
+
205
+ def drain_timeout
206
+ raw_config.drain_timeout || 30
207
+ end
208
+
209
+
210
+ def run_directory
211
+ ".kamal"
212
+ end
213
+
214
+ def apps_directory
215
+ File.join run_directory, "apps"
216
+ end
217
+
218
+ def app_directory
219
+ File.join apps_directory, [ service, destination ].compact.join("-")
220
+ end
221
+
222
+ def env_directory
223
+ File.join app_directory, "env"
224
+ end
225
+
226
+ def assets_directory
227
+ File.join app_directory, "assets"
228
+ end
229
+
230
+
231
+ def hooks_path
232
+ raw_config.hooks_path || ".kamal/hooks"
233
+ end
234
+
235
+ def asset_path
236
+ raw_config.asset_path
237
+ end
238
+
239
+
240
+ def env_tags
241
+ @env_tags ||= if (tags = raw_config.env["tags"])
242
+ tags.collect { |name, config| Env::Tag.new(name, config: config, secrets: secrets) }
243
+ else
244
+ []
245
+ end
246
+ end
247
+
248
+ def env_tag(name)
249
+ env_tags.detect { |t| t.name == name.to_s }
250
+ end
251
+
252
+ def proxy_publish_args(http_port, https_port)
253
+ argumentize "--publish", [ "#{http_port}:#{PROXY_HTTP_PORT}", "#{https_port}:#{PROXY_HTTPS_PORT}" ]
254
+ end
255
+
256
+ def proxy_logging_args(max_size)
257
+ argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
258
+ end
259
+
260
+ def proxy_options_default
261
+ [ *proxy_publish_args(PROXY_HTTP_PORT, PROXY_HTTPS_PORT), *proxy_logging_args(PROXY_LOG_MAX_SIZE) ]
262
+ end
263
+
264
+ def proxy_image
265
+ "nocoffeehq/kamal-proxy:#{PROXY_MINIMUM_VERSION}"
266
+ end
267
+
268
+ def proxy_container_name
269
+ "kamal-proxy"
270
+ end
271
+
272
+ def proxy_directory
273
+ File.join run_directory, "proxy"
274
+ end
275
+
276
+ def proxy_options_file
277
+ File.join proxy_directory, "options"
278
+ end
279
+
280
+
281
+ def to_h
282
+ {
283
+ roles: role_names,
284
+ hosts: all_hosts,
285
+ primary_host: primary_host,
286
+ version: version,
287
+ repository: repository,
288
+ absolute_image: absolute_image,
289
+ service_with_version: service_with_version,
290
+ volume_args: volume_args,
291
+ ssh_options: ssh.to_h,
292
+ sshkit: sshkit.to_h,
293
+ builder: builder.to_h,
294
+ accessories: raw_config.accessories,
295
+ logging: logging_args
296
+ }.compact
297
+ end
298
+
299
+ private
300
+ # Will raise ArgumentError if any required config keys are missing
301
+ def ensure_destination_if_required
302
+ if require_destination? && destination.nil?
303
+ raise ArgumentError, "You must specify a destination"
304
+ end
305
+
306
+ true
307
+ end
308
+
309
+ def ensure_required_keys_present
310
+ %i[ service image registry servers ].each do |key|
311
+ raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
312
+ end
313
+
314
+ unless role(primary_role_name).present?
315
+ raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
316
+ end
317
+
318
+ if primary_role.hosts.empty?
319
+ raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
320
+ end
321
+
322
+ unless allow_empty_roles?
323
+ roles.each do |role|
324
+ if role.hosts.empty?
325
+ raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
326
+ end
327
+ end
328
+ end
329
+
330
+ true
331
+ end
332
+
333
+ def ensure_valid_service_name
334
+ raise Kamal::ConfigurationError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
335
+
336
+ true
337
+ end
338
+
339
+ def ensure_valid_kamal_version
340
+ if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
341
+ raise Kamal::ConfigurationError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
342
+ end
343
+
344
+ true
345
+ end
346
+
347
+ def ensure_retain_containers_valid
348
+ raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1
349
+
350
+ true
351
+ end
352
+
353
+ def ensure_no_traefik_reboot_hooks
354
+ hooks = %w[ pre-traefik-reboot post-traefik-reboot ].select { |hook_file| File.exist?(File.join(hooks_path, hook_file)) }
355
+
356
+ if hooks.any?
357
+ raise Kamal::ConfigurationError, "Found #{hooks.join(", ")}, these should be renamed to (pre|post)-proxy-reboot"
358
+ end
359
+
360
+ true
361
+ end
362
+
363
+ def ensure_one_host_for_ssl_roles
364
+ roles.each(&:ensure_one_host_for_ssl)
365
+
366
+ true
367
+ end
368
+
369
+ def ensure_unique_hosts_for_ssl_roles
370
+ hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts }
371
+ duplicates = hosts.tally.filter_map { |host, count| host if count > 1 }
372
+
373
+ raise Kamal::ConfigurationError, "Different roles can't share the same host for SSL: #{duplicates.join(", ")}" if duplicates.any?
374
+
375
+ true
376
+ end
377
+
378
+ def role_names
379
+ raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
380
+ end
381
+
382
+ def git_version
383
+ @git_version ||=
384
+ if Kamal::Git.used?
385
+ if Kamal::Git.uncommitted_changes.present? && !builder.git_clone?
386
+ uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
387
+ end
388
+ [ Kamal::Git.revision, uncommitted_suffix ].compact.join
389
+ else
390
+ raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
391
+ end
392
+ end
393
+ end