Class: Dash::Configuration

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

Defined Under Namespace

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

Constant Summary collapse

HOOKS_OUTPUT_LEVELS =
[ :quiet, :verbose ].freeze
LEGACY_RUN_DIRECTORY =

The pre-3b name of #run_directory. Referenced only by the one-shot host migration in Dash::Commands::Base; nothing reads paths under it.

".kamal"

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.



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
101
102
103
104
105
# File 'lib/dash/configuration.rb', line 58

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: Dash::Configuration::Validator::Configuration

  @secrets = Dash::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)
  @report = Report.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

#accessories ⇒ Object (readonly)

Returns the value of attribute accessories.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def accessories
  @accessories
end

#aliases ⇒ Object (readonly)

Returns the value of attribute aliases.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def aliases
  @aliases
end

#boot ⇒ Object (readonly)

Returns the value of attribute boot.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def boot
  @boot
end

#builder ⇒ Object (readonly)

Returns the value of attribute builder.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def builder
  @builder
end

#destination ⇒ Object (readonly)

Returns the value of attribute destination.



18
19
20
# File 'lib/dash/configuration.rb', line 18

def destination
  @destination
end

#env ⇒ Object (readonly)

Returns the value of attribute env.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def env
  @env
end

#logging ⇒ Object (readonly)

Returns the value of attribute logging.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def logging
  @logging
end

#output ⇒ Object (readonly)

Returns the value of attribute output.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def output
  @output
end

#proxy ⇒ Object (readonly)

Returns the value of attribute proxy.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def proxy
  @proxy
end

#proxy_boot ⇒ Object (readonly)

Returns the value of attribute proxy_boot.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def proxy_boot
  @proxy_boot
end

#raw_config ⇒ Object (readonly)

Returns the value of attribute raw_config.



18
19
20
# File 'lib/dash/configuration.rb', line 18

def raw_config
  @raw_config
end

#registry ⇒ Object (readonly)

Returns the value of attribute registry.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def registry
  @registry
end

#report ⇒ Object (readonly)

Returns the value of attribute report.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def report
  @report
end

#secrets ⇒ Object (readonly)

Returns the value of attribute secrets.



18
19
20
# File 'lib/dash/configuration.rb', line 18

def secrets
  @secrets
end

#servers ⇒ Object (readonly)

Returns the value of attribute servers.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def servers
  @servers
end

#ssh ⇒ Object (readonly)

Returns the value of attribute ssh.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def ssh
  @ssh
end

#sshkit ⇒ Object (readonly)

Returns the value of attribute sshkit.



19
20
21
# File 'lib/dash/configuration.rb', line 19

def sshkit
  @sshkit
end

Class Method Details

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



24
25
26
27
28
29
30
# File 'lib/dash/configuration.rb', line 24

def create_from(config_file:, destination: nil, version: nil)
  ENV["DASH_DESTINATION"] = 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



32
33
34
# File 'lib/dash/configuration.rb', line 32

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

Instance Method Details

#abbreviated_version ⇒ Object



141
142
143
144
145
146
147
148
149
150
# File 'lib/dash/configuration.rb', line 141

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

#absolute_image ⇒ Object



248
249
250
# File 'lib/dash/configuration.rb', line 248

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

#accessory(name) ⇒ Object



168
169
170
# File 'lib/dash/configuration.rb', line 168

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

#all_hosts ⇒ Object



172
173
174
# File 'lib/dash/configuration.rb', line 172

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

#allow_empty_roles? ⇒ Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/dash/configuration.rb', line 208

def allow_empty_roles?
  raw_config.allow_empty_roles
end

#app_directory ⇒ Object



314
315
316
# File 'lib/dash/configuration.rb', line 314

def app_directory
  File.join apps_directory, service_and_destination
end

#app_hosts ⇒ Object



192
193
194
# File 'lib/dash/configuration.rb', line 192

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

#apps_directory ⇒ Object



310
311
312
# File 'lib/dash/configuration.rb', line 310

def apps_directory
  File.join run_directory, "apps"
end

#asset_path ⇒ Object



334
335
336
# File 'lib/dash/configuration.rb', line 334

def asset_path
  raw_config.asset_path
end

#assets_directory ⇒ Object



322
323
324
# File 'lib/dash/configuration.rb', line 322

def assets_directory
  File.join app_directory, "assets"
end

#deploy_timeout ⇒ Object



288
289
290
# File 'lib/dash/configuration.rb', line 288

def deploy_timeout
  raw_config.deploy_timeout || 30
end

#drain_timeout ⇒ Object



292
293
294
# File 'lib/dash/configuration.rb', line 292

def drain_timeout
  raw_config.drain_timeout || 30
end

#env_directory ⇒ Object



318
319
320
# File 'lib/dash/configuration.rb', line 318

def env_directory
  File.join app_directory, "env"
end

#env_tag(name) ⇒ Object



350
351
352
# File 'lib/dash/configuration.rb', line 350

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

#env_tags ⇒ Object



342
343
344
345
346
347
348
# File 'lib/dash/configuration.rb', line 342

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_path ⇒ Object



338
339
340
# File 'lib/dash/configuration.rb', line 338

def error_pages_path
  raw_config.error_pages_path
end

#hooks_output_for(hook) ⇒ Object



354
355
356
357
358
359
360
361
# File 'lib/dash/configuration.rb', line 354

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_path ⇒ Object



326
327
328
# File 'lib/dash/configuration.rb', line 326

def hooks_path
  raw_config.hooks_path || Dash::ProjectDirectory.join("hooks")
end

#host_accessories(host) ⇒ Object



188
189
190
# File 'lib/dash/configuration.rb', line 188

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

#host_roles(host) ⇒ Object



176
177
178
# File 'lib/dash/configuration.rb', line 176

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

#image ⇒ Object



232
233
234
235
236
237
# File 'lib/dash/configuration.rb', line 232

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

  name
end

#latest_image ⇒ Object



252
253
254
# File 'lib/dash/configuration.rb', line 252

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

#latest_tag ⇒ Object



256
257
258
# File 'lib/dash/configuration.rb', line 256

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

#load_balancing? ⇒ Boolean

Returns:

  • (Boolean)


216
217
218
# File 'lib/dash/configuration.rb', line 216

def load_balancing?
  proxy&.load_balancing?
end

#logging_args ⇒ Object



280
281
282
# File 'lib/dash/configuration.rb', line 280

def logging_args
  logging.args
end

#minimum_version ⇒ Object



152
153
154
# File 'lib/dash/configuration.rb', line 152

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)


184
185
186
# File 'lib/dash/configuration.rb', line 184

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

#primary_host ⇒ Object



196
197
198
# File 'lib/dash/configuration.rb', line 196

def primary_host
  primary_role&.primary_host
end

#primary_role ⇒ Object



204
205
206
# File 'lib/dash/configuration.rb', line 204

def primary_role
  role(primary_role_name)
end

#primary_role_name ⇒ Object



200
201
202
# File 'lib/dash/configuration.rb', line 200

def primary_role_name
  raw_config.primary_role || "web"
end

#proxy_accessories ⇒ Object



224
225
226
# File 'lib/dash/configuration.rb', line 224

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

#proxy_hosts ⇒ Object



228
229
230
# File 'lib/dash/configuration.rb', line 228

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

#proxy_role_names ⇒ Object



220
221
222
# File 'lib/dash/configuration.rb', line 220

def proxy_role_names
  proxy_roles.flat_map(&:name)
end

#proxy_roles ⇒ Object



212
213
214
# File 'lib/dash/configuration.rb', line 212

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

#proxy_run(host) ⇒ Object



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

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

#readiness_delay ⇒ Object



284
285
286
# File 'lib/dash/configuration.rb', line 284

def readiness_delay
  raw_config.readiness_delay || 7
end

#repository ⇒ Object



244
245
246
# File 'lib/dash/configuration.rb', line 244

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

#require_destination? ⇒ Boolean

Returns:

  • (Boolean)


264
265
266
# File 'lib/dash/configuration.rb', line 264

def require_destination?
  raw_config.require_destination
end

#retain_containers ⇒ Object



268
269
270
# File 'lib/dash/configuration.rb', line 268

def retain_containers
  raw_config.retain_containers || 5
end

#role(name) ⇒ Object



164
165
166
# File 'lib/dash/configuration.rb', line 164

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

#roles ⇒ Object



160
161
162
# File 'lib/dash/configuration.rb', line 160

def roles
  servers.roles
end

#run_directory ⇒ Object

Where dash keeps its per-host state - app env and assets, the proxy's options/image/run_command files and apps-config bind mount, loadbalancer service claims, deploy locks and the audit log - under the SSH user's home. Renamed from .kamal in stage 3b of the server artifact rename (zoolutions/dash#118); hosts still on the old name are migrated in place by Dash::Commands::Base#ensure_run_directory.



306
307
308
# File 'lib/dash/configuration.rb', line 306

def run_directory
  ".dash"
end

#secrets_path ⇒ Object



330
331
332
# File 'lib/dash/configuration.rb', line 330

def secrets_path
  raw_config.secrets_path || Dash::ProjectDirectory.join("secrets")
end

#service_and_destination ⇒ Object



156
157
158
# File 'lib/dash/configuration.rb', line 156

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

#service_with_version ⇒ Object



260
261
262
# File 'lib/dash/configuration.rb', line 260

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

#stop_timeout ⇒ Object



296
297
298
# File 'lib/dash/configuration.rb', line 296

def stop_timeout
  raw_config.stop_timeout
end

#to_h ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/dash/configuration.rb', line 363

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.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/dash/configuration.rb', line 110

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

#version ⇒ Object



137
138
139
# File 'lib/dash/configuration.rb', line 137

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

#version=(version) ⇒ Object



133
134
135
# File 'lib/dash/configuration.rb', line 133

def version=(version)
  @declared_version = version
end

#volume_args ⇒ Object



272
273
274
275
276
277
278
# File 'lib/dash/configuration.rb', line 272

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