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,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