Class: Kamal::Configuration

Inherits:
Object
  • Object
show all
Includes:
Validation
Defined in:
lib/kamal/configuration.rb

Defined Under Namespace

Modules: Validation Classes: Accessory, Alias, Boot, Builder, Env, Loadbalancer, Logging, Output, Proxy, Registry, Role, Servers, Ssh, Sshkit, Validator, Volume

Constant Summary collapse

HOOKS_OUTPUT_LEVELS =
[ :quiet, :verbose ].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Validation

#validate!, #validation_yml

Constructor Details

#initialize(raw_config, destination: nil, version: nil, validate: true) ⇒ Configuration

Returns a new instance of Configuration.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/kamal/configuration.rb', line 54

def initialize(raw_config, destination: nil, version: nil, validate: true)
  @raw_config = ActiveSupport::InheritableOptions.new(raw_config)
  @destination = destination
  @declared_version = version

  validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration

  @secrets = Kamal::Secrets.new(destination: destination, secrets_path: secrets_path)

  # Eager load config to validate it, these are first as they have dependencies later on
  @servers = Servers.new(config: self)
  @registry = Registry.new(config: @raw_config, secrets: secrets)

  @accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
  @aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
  @boot = Boot.new(config: self)
  @builder = Builder.new(config: self)
  @env = Env.new(config: @raw_config.env || {}, secrets: secrets)

  @logging = Logging.new(logging_config: @raw_config.logging)
  @output = Output.new(config: self)
  @proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy, secrets: secrets)
  @proxy_boot = Proxy::Boot.new(config: self)
  @ssh = Ssh.new(config: self)
  @sshkit = Sshkit.new(config: self)

  ensure_destination_if_required
  ensure_required_keys_present
  ensure_valid_kamal_version
  ensure_retain_containers_valid
  ensure_valid_service_name
  ensure_no_traefik_reboot_hooks
  ensure_one_host_for_ssl_roles
  ensure_unique_hosts_for_ssl_roles
  ensure_local_registry_remote_builder_has_ssh_url
  ensure_no_conflicting_proxy_runs
  ensure_valid_loadbalancer
  ensure_valid_hooks_output!
  ensure_unproxied_roles_are_readiness_gated
  ensure_role_boot_can_pace_its_hosts
  ensure_boot_wait_paces_something
  ensure_rate_limit_can_identify_clients
  ensure_intercepted_errors_have_pages
  ensure_sleep_has_a_docker_socket
  ensure_proxy_protocol_names_its_peers
  ensure_max_idle_conns_meaningful
end

Instance Attribute Details

#accessoriesObject (readonly)

Returns the value of attribute accessories.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def accessories
  @accessories
end

#aliasesObject (readonly)

Returns the value of attribute aliases.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def aliases
  @aliases
end

#bootObject (readonly)

Returns the value of attribute boot.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def boot
  @boot
end

#builderObject (readonly)

Returns the value of attribute builder.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def builder
  @builder
end

#destinationObject (readonly)

Returns the value of attribute destination.



14
15
16
# File 'lib/kamal/configuration.rb', line 14

def destination
  @destination
end

#envObject (readonly)

Returns the value of attribute env.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def env
  @env
end

#loggingObject (readonly)

Returns the value of attribute logging.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def logging
  @logging
end

#outputObject (readonly)

Returns the value of attribute output.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def output
  @output
end

#proxyObject (readonly)

Returns the value of attribute proxy.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def proxy
  @proxy
end

#proxy_bootObject (readonly)

Returns the value of attribute proxy_boot.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def proxy_boot
  @proxy_boot
end

#raw_configObject (readonly)

Returns the value of attribute raw_config.



14
15
16
# File 'lib/kamal/configuration.rb', line 14

def raw_config
  @raw_config
end

#registryObject (readonly)

Returns the value of attribute registry.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def registry
  @registry
end

#secretsObject (readonly)

Returns the value of attribute secrets.



14
15
16
# File 'lib/kamal/configuration.rb', line 14

def secrets
  @secrets
end

#serversObject (readonly)

Returns the value of attribute servers.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def servers
  @servers
end

#sshObject (readonly)

Returns the value of attribute ssh.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def ssh
  @ssh
end

#sshkitObject (readonly)

Returns the value of attribute sshkit.



15
16
17
# File 'lib/kamal/configuration.rb', line 15

def sshkit
  @sshkit
end

Class Method Details

.create_from(config_file:, destination: nil, version: nil) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/kamal/configuration.rb', line 20

def create_from(config_file:, destination: nil, version: nil)
  ENV["KAMAL_DESTINATION"] = destination

  raw_config = load_raw_config(config_file: config_file, destination: destination)

  new raw_config, destination: destination, version: version
end

.load_raw_config(config_file:, destination: nil) ⇒ Object



28
29
30
# File 'lib/kamal/configuration.rb', line 28

def load_raw_config(config_file:, destination: nil)
  load_config_files(config_file, *destination_config_file(config_file, destination))
end

Instance Method Details

#abbreviated_versionObject



136
137
138
139
140
141
142
143
144
145
# File 'lib/kamal/configuration.rb', line 136

def abbreviated_version
  if version
    # Don't abbreviate <sha>_uncommitted_<etc>
    if version.include?("_")
      version
    else
      version[0...7]
    end
  end
end

#absolute_imageObject



243
244
245
# File 'lib/kamal/configuration.rb', line 243

def absolute_image
  "#{repository}:#{version}"
end

#accessory(name) ⇒ Object



163
164
165
# File 'lib/kamal/configuration.rb', line 163

def accessory(name)
  accessories.detect { |a| a.name == name.to_s }
end

#all_hostsObject



167
168
169
# File 'lib/kamal/configuration.rb', line 167

def all_hosts
  (roles + accessories).flat_map(&:hosts).uniq
end

#allow_empty_roles?Boolean

Returns:

  • (Boolean)


203
204
205
# File 'lib/kamal/configuration.rb', line 203

def allow_empty_roles?
  raw_config.allow_empty_roles
end

#app_directoryObject



303
304
305
# File 'lib/kamal/configuration.rb', line 303

def app_directory
  File.join apps_directory, service_and_destination
end

#app_hostsObject



187
188
189
# File 'lib/kamal/configuration.rb', line 187

def app_hosts
  roles.flat_map(&:hosts).uniq
end

#apps_directoryObject



299
300
301
# File 'lib/kamal/configuration.rb', line 299

def apps_directory
  File.join run_directory, "apps"
end

#asset_pathObject



323
324
325
# File 'lib/kamal/configuration.rb', line 323

def asset_path
  raw_config.asset_path
end

#assets_directoryObject



311
312
313
# File 'lib/kamal/configuration.rb', line 311

def assets_directory
  File.join app_directory, "assets"
end

#deploy_timeoutObject



283
284
285
# File 'lib/kamal/configuration.rb', line 283

def deploy_timeout
  raw_config.deploy_timeout || 30
end

#drain_timeoutObject



287
288
289
# File 'lib/kamal/configuration.rb', line 287

def drain_timeout
  raw_config.drain_timeout || 30
end

#env_directoryObject



307
308
309
# File 'lib/kamal/configuration.rb', line 307

def env_directory
  File.join app_directory, "env"
end

#env_tag(name) ⇒ Object



339
340
341
# File 'lib/kamal/configuration.rb', line 339

def env_tag(name)
  env_tags.detect { |t| t.name == name.to_s }
end

#env_tagsObject



331
332
333
334
335
336
337
# File 'lib/kamal/configuration.rb', line 331

def env_tags
  @env_tags ||= if (tags = raw_config.env["tags"])
    tags.collect { |name, config| Env::Tag.new(name, config: config, secrets: secrets) }
  else
    []
  end
end

#error_pages_pathObject



327
328
329
# File 'lib/kamal/configuration.rb', line 327

def error_pages_path
  raw_config.error_pages_path
end

#hooks_output_for(hook) ⇒ Object



343
344
345
346
347
348
349
350
# File 'lib/kamal/configuration.rb', line 343

def hooks_output_for(hook)
  case raw_config.hooks_output
  when Symbol, String
    raw_config.hooks_output.to_sym
  when Hash
    raw_config.hooks_output[hook]&.to_sym
  end
end

#hooks_pathObject



315
316
317
# File 'lib/kamal/configuration.rb', line 315

def hooks_path
  raw_config.hooks_path || ".kamal/hooks"
end

#host_accessories(host) ⇒ Object



183
184
185
# File 'lib/kamal/configuration.rb', line 183

def host_accessories(host)
  accessories.select { |accessory| accessory.hosts.include?(host) }
end

#host_roles(host) ⇒ Object



171
172
173
# File 'lib/kamal/configuration.rb', line 171

def host_roles(host)
  roles.select { |role| role.hosts.include?(host) }
end

#imageObject



227
228
229
230
231
232
# File 'lib/kamal/configuration.rb', line 227

def image
  name = raw_config&.image.presence
  name ||= raw_config&.service if registry.local?

  name
end

#latest_imageObject



247
248
249
# File 'lib/kamal/configuration.rb', line 247

def latest_image
  "#{repository}:#{latest_tag}"
end

#latest_tagObject



251
252
253
# File 'lib/kamal/configuration.rb', line 251

def latest_tag
  [ "latest", *destination ].join("-")
end

#load_balancing?Boolean

Returns:

  • (Boolean)


211
212
213
# File 'lib/kamal/configuration.rb', line 211

def load_balancing?
  proxy&.load_balancing?
end

#logging_argsObject



275
276
277
# File 'lib/kamal/configuration.rb', line 275

def logging_args
  logging.args
end

#minimum_versionObject



147
148
149
# File 'lib/kamal/configuration.rb', line 147

def minimum_version
  raw_config.minimum_version
end

#parallel_roles?Boolean

Whether app commands iterate role-first. A role pacing its own hosts needs that: the host-first branch of on_roles has no per-role runner to hand boot options to. Setting boot on a role only overrides an unset parallel_roles — an explicit false conflicts and is rejected in ensure_role_boot_can_pace_its_hosts.

Returns:

  • (Boolean)


179
180
181
# File 'lib/kamal/configuration.rb', line 179

def parallel_roles?
  !!(boot.parallel_roles || roles.any?(&:boot))
end

#primary_hostObject



191
192
193
# File 'lib/kamal/configuration.rb', line 191

def primary_host
  primary_role&.primary_host
end

#primary_roleObject



199
200
201
# File 'lib/kamal/configuration.rb', line 199

def primary_role
  role(primary_role_name)
end

#primary_role_nameObject



195
196
197
# File 'lib/kamal/configuration.rb', line 195

def primary_role_name
  raw_config.primary_role || "web"
end

#proxy_accessoriesObject



219
220
221
# File 'lib/kamal/configuration.rb', line 219

def proxy_accessories
  accessories.select(&:running_proxy?)
end

#proxy_hostsObject



223
224
225
# File 'lib/kamal/configuration.rb', line 223

def proxy_hosts
  (proxy_roles.flat_map(&:hosts) + proxy_accessories.flat_map(&:hosts)).uniq
end

#proxy_role_namesObject



215
216
217
# File 'lib/kamal/configuration.rb', line 215

def proxy_role_names
  proxy_roles.flat_map(&:name)
end

#proxy_rolesObject



207
208
209
# File 'lib/kamal/configuration.rb', line 207

def proxy_roles
  roles.select(&:running_proxy?)
end

#proxy_run(host) ⇒ Object



234
235
236
237
# File 'lib/kamal/configuration.rb', line 234

def proxy_run(host)
  # We validate that all the config are identical for a host
  proxy_runs(host.to_s).first
end

#readiness_delayObject



279
280
281
# File 'lib/kamal/configuration.rb', line 279

def readiness_delay
  raw_config.readiness_delay || 7
end

#repositoryObject



239
240
241
# File 'lib/kamal/configuration.rb', line 239

def repository
  [ registry.server, image ].compact.join("/")
end

#require_destination?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/kamal/configuration.rb', line 259

def require_destination?
  raw_config.require_destination
end

#retain_containersObject



263
264
265
# File 'lib/kamal/configuration.rb', line 263

def retain_containers
  raw_config.retain_containers || 5
end

#role(name) ⇒ Object



159
160
161
# File 'lib/kamal/configuration.rb', line 159

def role(name)
  roles.detect { |r| r.name == name.to_s }
end

#rolesObject



155
156
157
# File 'lib/kamal/configuration.rb', line 155

def roles
  servers.roles
end

#run_directoryObject



295
296
297
# File 'lib/kamal/configuration.rb', line 295

def run_directory
  ".kamal"
end

#secrets_pathObject



319
320
321
# File 'lib/kamal/configuration.rb', line 319

def secrets_path
  raw_config.secrets_path || ".kamal/secrets"
end

#service_and_destinationObject



151
152
153
# File 'lib/kamal/configuration.rb', line 151

def service_and_destination
  [ service, destination ].compact.join("-")
end

#service_with_versionObject



255
256
257
# File 'lib/kamal/configuration.rb', line 255

def service_with_version
  "#{service}-#{version}"
end

#stop_timeoutObject



291
292
293
# File 'lib/kamal/configuration.rb', line 291

def stop_timeout
  raw_config.stop_timeout
end

#to_hObject



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/kamal/configuration.rb', line 352

def to_h
  {
    roles: role_names,
    hosts: all_hosts,
    primary_host: primary_host,
    version: version,
    repository: repository,
    absolute_image: absolute_image,
    service_with_version: service_with_version,
    volume_args: volume_args,
    ssh_options: ssh.to_h,
    sshkit: sshkit.to_h,
    builder: builder.to_h,
    accessories: raw_config.accessories,
    logging: logging_args
  }.compact
end

#validate_secrets!(include_accessories: false) ⇒ Object

Resolves every secret the deploy will need so a missing secret fails fast, before any registry login or SSH connection. Secret adapters run here instead of mid-deploy — same work, done earlier.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/kamal/configuration.rb', line 105

def validate_secrets!(include_accessories: false)
  registry.username
  registry.password
  builder.secrets

  roles.each do |role|
    role.secrets_io(role.hosts.first) if role.hosts.any?

    if role.running_proxy?
      role.proxy.run&.secrets_io

      if role.proxy.custom_ssl_certificate?
        role.proxy.certificate_pem_content
        role.proxy.private_key_pem_content
      end
    end
  end

  accessories.each(&:secrets_io) if include_accessories

  true
end

#versionObject



132
133
134
# File 'lib/kamal/configuration.rb', line 132

def version
  @declared_version.presence || ENV["VERSION"] || git_version
end

#version=(version) ⇒ Object



128
129
130
# File 'lib/kamal/configuration.rb', line 128

def version=(version)
  @declared_version = version
end

#volume_argsObject



267
268
269
270
271
272
273
# File 'lib/kamal/configuration.rb', line 267

def volume_args
  if raw_config.volumes.present?
    argumentize "--volume", raw_config.volumes
  else
    []
  end
end