Class: Kamal::Dev::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/kamal/dev/config.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, validate: false) ⇒ Config

Returns a new instance of Config.



14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/kamal/dev/config.rb', line 14

def initialize(config, validate: false)
  @raw_config = case config
  when String
    load_from_file(config)
  when Hash
    config.deep_symbolize_keys
  else
    raise Kamal::Dev::ConfigurationError, "Config must be a file path (String) or Hash"
  end

  validate! if validate
end

Instance Attribute Details

#raw_configObject (readonly)

Returns the value of attribute raw_config.



12
13
14
# File 'lib/kamal/dev/config.rb', line 12

def raw_config
  @raw_config
end

Instance Method Details

#buildHash

Build configuration for building images from source

Returns:

  • (Hash)

    Build configuration (devcontainer, dockerfile, context)



38
39
40
# File 'lib/kamal/dev/config.rb', line 38

def build
  raw_config[:build]&.deep_stringify_keys || {}
end

#build?Boolean

Check if build configuration is present

Returns:

  • (Boolean)

    true if build section exists



45
46
47
# File 'lib/kamal/dev/config.rb', line 45

def build?
  !build.empty?
end

#build_contextString

Get build context path

Returns:

  • (String)

    Build context (defaults to ".")



77
78
79
# File 'lib/kamal/dev/config.rb', line 77

def build_context
  build["context"] || "."
end

#build_source_pathString?

Get build source path (devcontainer.json or Dockerfile)

Returns:

  • (String, nil)

    Path to build source



65
66
67
68
69
70
71
72
# File 'lib/kamal/dev/config.rb', line 65

def build_source_path
  case build_source_type
  when :devcontainer
    build["devcontainer"]
  when :dockerfile
    build["dockerfile"]
  end
end

#build_source_typeSymbol?

Get build source type

Returns:

  • (Symbol, nil)

    :devcontainer, :dockerfile, or nil



52
53
54
55
56
57
58
59
60
# File 'lib/kamal/dev/config.rb', line 52

def build_source_type
  return nil unless build?

  if build["devcontainer"]
    :devcontainer
  elsif build["dockerfile"]
    :dockerfile
  end
end

#container_name(index) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/kamal/dev/config.rb', line 214

def container_name(index)
  pattern = naming_pattern

  # Handle zero-padded indexes like {index:03}
  pattern = pattern.gsub(/\{index:(\d+)\}/) do
    format("%0#{$1}d", index)
  end

  # Replace standard placeholders
  name = pattern.gsub("{service}", service.to_s)
    .gsub("{index}", index.to_s)

  validate_docker_name!(name)
  name
end

#defaultsObject



85
86
87
# File 'lib/kamal/dev/config.rb', line 85

def defaults
  raw_config[:defaults]&.deep_stringify_keys || {}
end

#devcontainerDevcontainer

Load and parse devcontainer configuration

Handles both:

  • Direct image reference: image: "ruby:3.2"
  • Devcontainer.json path: image: ".devcontainer/devcontainer.json"

Returns:



237
238
239
# File 'lib/kamal/dev/config.rb', line 237

def devcontainer
  @devcontainer ||= load_devcontainer
end

#devcontainer_json?Boolean

Check if using devcontainer.json for configuration

Supports both:

  • New format: build: { devcontainer: ".devcontainer/devcontainer.json" }
  • Old format: image: ".devcontainer/devcontainer.json" (backward compatibility)

Returns:

  • (Boolean)

    true if using devcontainer.json



248
249
250
251
252
253
254
# File 'lib/kamal/dev/config.rb', line 248

def devcontainer_json?
  # New format: build.devcontainer
  return true if build_source_type == :devcontainer

  # Old format: image points to .json file (backward compatibility)
  image.to_s.end_with?(".json") || image.to_s.include?("devcontainer")
end

#gitHash

Git configuration for remote code cloning

Returns:

  • (Hash)

    Git configuration (repository, branch, workspace_folder)



120
121
122
# File 'lib/kamal/dev/config.rb', line 120

def git
  raw_config[:git]&.deep_stringify_keys || {}
end

#git_branchString

Git branch to checkout (defaults to main)

Returns:

  • (String)

    Git branch name



134
135
136
# File 'lib/kamal/dev/config.rb', line 134

def git_branch
  git["branch"] || "main"
end

#git_clone_enabled?Boolean

Check if git clone is configured for remote deployments

Returns:

  • (Boolean)

    true if git repository is configured



165
166
167
# File 'lib/kamal/dev/config.rb', line 165

def git_clone_enabled?
  !git_repository.nil? && !git_repository.empty?
end

#git_repositoryString?

Git repository URL to clone on remote deployment

Returns:

  • (String, nil)

    Git repository URL



127
128
129
# File 'lib/kamal/dev/config.rb', line 127

def git_repository
  git["repository"]
end

#git_tokenString?

Git authentication token value loaded from environment

Returns:

  • (String, nil)

    Git token (GitHub PAT, GitLab token, etc.) from ENV



157
158
159
160
# File 'lib/kamal/dev/config.rb', line 157

def git_token
  return nil unless git_token_env
  ENV[git_token_env]
end

#git_token_envString?

Git authentication token (environment variable name) Used for HTTPS cloning of private repositories

Returns:

  • (String, nil)

    Environment variable name containing git token (e.g., GitHub PAT)



150
151
152
# File 'lib/kamal/dev/config.rb', line 150

def git_token_env
  git["token"]
end

#git_workspace_folderString

Workspace folder where code should be cloned Typically matches the workspaceFolder in devcontainer.json

Returns:

  • (String)

    Workspace folder path



142
143
144
# File 'lib/kamal/dev/config.rb', line 142

def git_workspace_folder
  git["workspace_folder"] || "/workspaces/#{service}"
end

#imageObject



31
32
33
# File 'lib/kamal/dev/config.rb', line 31

def image
  raw_config[:image]
end

#naming_patternObject



97
98
99
# File 'lib/kamal/dev/config.rb', line 97

def naming_pattern
  raw_config.dig(:naming, :pattern) || "{service}-{index}"
end

#providerObject



81
82
83
# File 'lib/kamal/dev/config.rb', line 81

def provider
  raw_config[:provider]&.deep_stringify_keys || {}
end

#registryHash

Registry configuration for image building and pushing

Returns:

  • (Hash)

    Registry configuration (server, username_env, password_env)



172
173
174
# File 'lib/kamal/dev/config.rb', line 172

def registry
  raw_config[:registry]&.deep_stringify_keys || {}
end

#registry_configured?Boolean

Check if registry credentials are configured

Returns:

  • (Boolean)

    true if both username and password ENV vars are set



210
211
212
# File 'lib/kamal/dev/config.rb', line 210

def registry_configured?
  !!(registry["username"] && registry["password"])
end

#registry_passwordString?

Registry password/token loaded from environment variable

Returns:

  • (String, nil)

    Registry password from ENV



198
199
200
201
202
203
204
205
# File 'lib/kamal/dev/config.rb', line 198

def registry_password
  return nil unless registry["password"]

  # Handle both string and array formats (YAML parsing inconsistency)
  env_var = registry["password"]
  env_var = env_var.first if env_var.is_a?(Array)
  ENV[env_var]
end

#registry_serverString

Registry server URL (defaults to ghcr.io)

Returns:

  • (String)

    Registry server URL



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

def registry_server
  registry["server"] || "ghcr.io"
end

#registry_usernameString?

Registry username loaded from environment variable

Returns:

  • (String, nil)

    Registry username from ENV



186
187
188
189
190
191
192
193
# File 'lib/kamal/dev/config.rb', line 186

def registry_username
  return nil unless registry["username"]

  # Handle both string and array formats (YAML parsing inconsistency)
  env_var = registry["username"]
  env_var = env_var.first if env_var.is_a?(Array)
  ENV[env_var]
end

#secretsObject



101
102
103
# File 'lib/kamal/dev/config.rb', line 101

def secrets
  raw_config[:secrets] || []
end

#secrets_fileObject



105
106
107
# File 'lib/kamal/dev/config.rb', line 105

def secrets_file
  raw_config[:secrets_file] || ".kamal/secrets"
end

#serviceObject



27
28
29
# File 'lib/kamal/dev/config.rb', line 27

def service
  raw_config[:service]
end

#sshObject



109
110
111
# File 'lib/kamal/dev/config.rb', line 109

def ssh
  raw_config[:ssh]&.deep_stringify_keys || {}
end

#ssh_key_pathObject



113
114
115
# File 'lib/kamal/dev/config.rb', line 113

def ssh_key_path
  ssh["key_path"] || "~/.ssh/id_rsa.pub"
end

#validate!Object



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/kamal/dev/config.rb', line 256

def validate!
  errors = []

  errors << "Configuration must include 'service' (service name is required)" if service.nil? || service.empty?
  errors << "Configuration must include 'image' (image reference is required)" if image.nil? || image.empty?

  if provider.empty?
    errors << "Configuration must include 'provider' (provider configuration is required)"
  elsif provider["type"].nil? || provider["type"].empty?
    errors << "Configuration must include 'provider.type' (provider type is required)"
  end

  # Validate service name against Docker naming rules
  unless service.nil? || service.empty?
    unless docker_name_valid?(service)
      errors << "Service name '#{service}' is invalid. Docker names must start with a letter or number and contain only [a-zA-Z0-9_.-]"
    end
  end

  raise Kamal::Dev::ConfigurationError, errors.join("\n") unless errors.empty?

  self
end

#vm_countObject



93
94
95
# File 'lib/kamal/dev/config.rb', line 93

def vm_count
  vms["count"] || 1
end

#vmsObject



89
90
91
# File 'lib/kamal/dev/config.rb', line 89

def vms
  raw_config[:vms]&.deep_stringify_keys || {}
end