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, 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
# 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)
  @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.



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

def accessories
  @accessories
end

#aliasesObject (readonly)

Returns the value of attribute aliases.



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

def aliases
  @aliases
end

#bootObject (readonly)

Returns the value of attribute boot.



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

def boot
  @boot
end

#builderObject (readonly)

Returns the value of attribute builder.



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

def builder
  @builder
end

#destinationObject (readonly)

Returns the value of attribute destination.



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

def destination
  @destination
end

#envObject (readonly)

Returns the value of attribute env.



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

def env
  @env
end

#loggingObject (readonly)

Returns the value of attribute logging.



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

def logging
  @logging
end

#outputObject (readonly)

Returns the value of attribute output.



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

def output
  @output
end

#proxyObject (readonly)

Returns the value of attribute proxy.



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

def proxy
  @proxy
end

#proxy_bootObject (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_configObject (readonly)

Returns the value of attribute raw_config.



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

def raw_config
  @raw_config
end

#registryObject (readonly)

Returns the value of attribute registry.



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

def registry
  @registry
end

#secretsObject (readonly)

Returns the value of attribute secrets.



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

def secrets
  @secrets
end

#serversObject (readonly)

Returns the value of attribute servers.



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

def servers
  @servers
end

#sshObject (readonly)

Returns the value of attribute ssh.



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

def ssh
  @ssh
end

#sshkitObject (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_versionObject



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

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

#absolute_imageObject



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

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

#accessory(name) ⇒ Object



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

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

#all_hostsObject



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

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

#allow_empty_roles?Boolean

Returns:

  • (Boolean)


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

def allow_empty_roles?
  raw_config.allow_empty_roles
end

#app_directoryObject



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

def app_directory
  File.join apps_directory, service_and_destination
end

#app_hostsObject



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

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

#apps_directoryObject



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

def apps_directory
  File.join run_directory, "apps"
end

#asset_pathObject



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

def asset_path
  raw_config.asset_path
end

#assets_directoryObject



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

def assets_directory
  File.join app_directory, "assets"
end

#deploy_timeoutObject



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

def deploy_timeout
  raw_config.deploy_timeout || 30
end

#drain_timeoutObject



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

def drain_timeout
  raw_config.drain_timeout || 30
end

#env_directoryObject



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

def env_directory
  File.join app_directory, "env"
end

#env_tag(name) ⇒ Object



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

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

#env_tagsObject



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

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



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

def error_pages_path
  raw_config.error_pages_path
end

#hooks_output_for(hook) ⇒ Object



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

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



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

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

#host_accessories(host) ⇒ Object



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

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

#host_roles(host) ⇒ Object



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

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

#imageObject



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

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

  name
end

#latest_imageObject



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

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

#latest_tagObject



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

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

#load_balancing?Boolean

Returns:

  • (Boolean)


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

def load_balancing?
  proxy&.load_balancing?
end

#logging_argsObject



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

def logging_args
  logging.args
end

#minimum_versionObject



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

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)


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

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

#primary_hostObject



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

def primary_host
  primary_role&.primary_host
end

#primary_roleObject



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

def primary_role
  role(primary_role_name)
end

#primary_role_nameObject



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

def primary_role_name
  raw_config.primary_role || "web"
end

#proxy_accessoriesObject



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

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

#proxy_hostsObject



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

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

#proxy_role_namesObject



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

def proxy_role_names
  proxy_roles.flat_map(&:name)
end

#proxy_rolesObject



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

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

#proxy_run(host) ⇒ Object



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

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

#readiness_delayObject



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

def readiness_delay
  raw_config.readiness_delay || 7
end

#repositoryObject



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

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

#require_destination?Boolean

Returns:

  • (Boolean)


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

def require_destination?
  raw_config.require_destination
end

#retain_containersObject



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

def retain_containers
  raw_config.retain_containers || 5
end

#role(name) ⇒ Object



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

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

#rolesObject



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

def roles
  servers.roles
end

#run_directoryObject

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.



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

def run_directory
  ".dash"
end

#secrets_pathObject



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

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

#service_and_destinationObject



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

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

#service_with_versionObject



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

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

#stop_timeoutObject



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

def stop_timeout
  raw_config.stop_timeout
end

#to_hObject



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

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.



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

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



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

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

#version=(version) ⇒ Object



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

def version=(version)
  @declared_version = version
end

#volume_argsObject



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

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