joetjen.net
EN DE
Konfigurationssprache

Imports und Schleifen

Konfiguration wächst: mit Imports auf Dateien verteilen, und Wiederkehrendes mit Schleifen erzeugen, statt es zu kopieren.

Format 1.0

Imports

import "shared/logging.casc"
import "config/{base,dev}/**.casc"
import "env/${APP_ENV:dev}.casc"
import "vault://secret/base"
  • Ein Pfad ist relativ zur importierenden Datei.
  • Klammern {a,b} und Globs *, ** werden expandiert; die Treffer laden in lexikografischer Reihenfolge.
  • Ein Pfad darf mit ${NAME} oder ${NAME:default} die Umgebung lesen — sonst nichts, ein Import hängt also nie von der Konfiguration ab, zu der er gehört.
  • Ein Pfad mit scheme:// geht an einen Loader, den die Anwendung registriert.
  • Ein Pfad ohne Treffer ist ein Fehler, ebenso ein Import-Zyklus, gemeldet mit der ganzen Kette.
  • Jede importierte Datei hat ihre eigene Versionszeile.

Ein Import mischt die Anweisungen der Datei an seiner eigenen Position ein, nach den üblichen Regeln: Späteres gewinnt. Imports gehören daher an den Anfang, damit die eigenen Anweisungen der Datei das Importierte überschreiben.

Schleifen

for <bindings> [from <template>] as <destination> { <body> }

Eine Schleife schreibt ihren Rumpf einmal pro Element, jedes Mal an einen Zielpfad, der das Element meist interpoliert. Bindungen:

  • @x in @{list} bindet jedes Element; mindestens eine solche Bindung ist Pflicht.
  • Mehrere Element-Bindungen laufen ihre Listen parallel ab (gezippt, nicht jede Kombination). Die Listen müssen gleich lang sein.
  • Ein nacktes @i bindet den Index, ab 0.
  • Iteriert wird über @{…}-Variablen; ein %{…}-Verweis kann keine Quelle sein, weil er erst nach dem Expandieren der Schleifen aufgelöst wird.
  • Bindungen existieren nur im Rumpf und verdecken äußere Variablen.
@domains = ["us-east.example.com", "eu-west.example.com"]
@ports = [8443, 8444]

for @idx, @domain in @{domains}, @port in @{ports} as endpoints."domain-@{idx}" {
  url = "https://@{domain}:@{port}"
}
%{"endpoints" => %{
    "domain-0" => %{"url" => "https://us-east.example.com:8443"},
    "domain-1" => %{"url" => "https://eu-west.example.com:8444"}}}

Vorlagen

from <pfad> beginnt jeden erzeugten Block als Kopie eines anderen Schlüssels und wendet den Rumpf darauf an. Die Vorlage selbst bleibt, wo sie ist.

defaults.replica {
  cpu = 1
  memory = 512MiB
}

@instances = ["a", "b"]
for @name in @{instances} from defaults.replica as replicas."@{name}" {
  cpu = 2
}
%{"defaults" => %{"replica" => %{"cpu" => 1, "memory" => {:bytes, 536870912}}},
  "replicas" => %{
    "a" => %{"cpu" => 2, "memory" => {:bytes, 536870912}},
    "b" => %{"cpu" => 2, "memory" => {:bytes, 536870912}}}}

Ein vollständiges Beispiel

Alles zusammen, so wie ein Dienst sich konfigurieren könnte. Die Datei lädt neben einer env/dev.casc, die server.port = 4000 setzt.

config.casc
#@version = 1.0

# Shared values first, so every imported file can see them.
@region = ${REGION:"eu-west"}
@*suffix = ".internal"
@shards = ["a", "b", "c"]
@weights = [3, 2, 1]

import "env/${APP_ENV:dev}.casc"

server {
  host = "0.0.0.0"
  port = !int(${PORT:8080})
  tags = ["web", "api"]
  location = (52.5200, 13.4050)
}

database {
  host = "db.@{region}@{suffix}"
  *password = ${DB_PASSWORD:?"DB_PASSWORD is required"}
  pool_size = 10
  timeout = 500ms
  idle_ttl = 1h30m
  cache = 512MiB
  allow = [10.0.0.0/8, ::1/128]
}

logger {
  level = info
  format = ${LOG_FORMAT:"text" | downcase}
  started = 2026-09-29T08:00:00Z
}

motd = """
    Welcome to the service.
    Maintenance window: Sundays.
    """

${?ENABLE_METRICS}
metrics.url = "http://%{server.host}:%{server.port}/metrics"

defaults.replica { cpu = 1, memory = 256MiB }
for @i, @shard in @{shards}, @w in @{weights} from defaults.replica as replicas."shard-@{shard}" {
  weight = @{w}
  index = @{i}
}

+server.tags = ["internal"]
#legacy.enabled = true

Mit gesetztem DB_PASSWORD, ENABLE_METRICS=1 und LOG_FORMAT=JSON behält der Server Port 8080 (sein Block steht nach dem Import), seine Tags werden ["web", "api", "internal"], der Datenbank-Host ist "db.eu-west.internal" mit geschwärztem Passwort, das Log-Format ist "json", die Metrics-URL "http://0.0.0.0:8080/metrics", drei Replikas shard-a bis shard-c beginnen jeweils bei der Vorlage, und legacy fehlt.