cicadel

Package settings

An installable app declares its configuration as one typed Go struct. A deployment can override tagged fields with CICADEL_<APP>_<NAME> environment variables.

#How it works

  • The project passes the values in Go.
  • A deployment may override tagged fields under the app’s own prefix.
  • The application resolves and validates the settings before Start.
  • manage.go check reports an invalid key without printing its value.
  • Secret fields are never displayed.

Good to know

Unlike Django, there is no global settings object for apps. Each app reads its own resolved struct directly.

#Declare settings

  1. Tag each field a deployment may change with setting:"NAME".
  2. Add ,secret for credentials.
  3. Leave handles and callbacks untagged. They are set only in Go.

Supported types are string, bool, integers, unsigned integers, floats, time.Duration and []string (comma separated).

Go
package newsletter

import (
    "context"
    "errors"
    "net/http"
    "strings"
    "time"

    "github.com/fauzaanu/cicadel"
    "github.com/fauzaanu/cicadel/response"
    "github.com/fauzaanu/cicadel/settings"
    "github.com/fauzaanu/cicadel/urls"
)

// Options is configured by the project. A deployment may override tagged
// fields with CICADEL_NEWSLETTER_FROM, CICADEL_NEWSLETTER_BATCH,
// CICADEL_NEWSLETTER_INTERVAL and CICADEL_NEWSLETTER_API_KEY.
type Options struct {
    From     string        `setting:"FROM"`
    Batch    int           `setting:"BATCH"`
    Interval time.Duration `setting:"INTERVAL"`
    APIKey   string        `setting:"API_KEY,secret"`
}

// Validate runs after environment overrides. Name the field, never the value.
func (o Options) Validate() error {
    if !strings.Contains(o.From, "@") {
        return errors.New("From must be an email address")
    }
    if o.Batch < 1 || o.Batch > 1000 {
        return errors.New("Batch must be 1–1000")
    }
    return nil
}

func New(options Options) cicadel.App {
    if options.Batch == 0 {
        options.Batch = 100
    }
    config := settings.NewPackage(options)
    return cicadel.App{
        Name:     "newsletter",
        Settings: config,
        Start: func(context.Context) (cicadel.Cleanup, error) {
            _ = config.Value().Interval // resolved and valid here
            return nil, nil
        },
        Routes: []urls.Route{
            urls.Path("GET", "/newsletter/", "newsletter:index",
                func(w http.ResponseWriter, r *http.Request) {
                    if err := response.JSON(w, http.StatusOK, map[string]int{"batch": config.Value().Batch}); err != nil {
                        response.Error(w, http.StatusInternalServerError)
                    }
                }),
        },
    }
}

settings.NewPackage records the project’s values without reading the environment. Construct a fresh one inside the app constructor, as above, so each application gets its own.

If Options has a Validate() error method, it runs after overrides.

#Configure them in the project

Install the app in project/apps.go with explicit Go values:

Go
newsletter.New(newsletter.Options{From: "news@example.com", Interval: time.Hour})

cicadel.New resolves every app’s settings in dependency order. It reads the environment that settings.Load read (Settings.Env).

Override one value per deployment:

Terminal
CICADEL_NEWSLETTER_BATCH=500 go run manage.go runserver

#In tests

Settings from settings.Defaults() or apptest.Settings() read no environment. Tests see only the Go values. Inject overrides with settings.LoadWith(apptest.Settings(), lookup).

#Inspect and check

go run manage.go apps lists each key, its current value and where it came from. Secret values print as [redacted]:

Text
newsletter (local)
  CICADEL_NEWSLETTER_FROM="news@example.com" (default)
  CICADEL_NEWSLETTER_BATCH="500" (environment)
  CICADEL_NEWSLETTER_INTERVAL="1h0m0s" (default)
  CICADEL_NEWSLETTER_API_KEY="[redacted]" (default)

An invalid value does not stop construction. Instead:

  • go run manage.go check (and check --json) reports it as <app>:settings:invalid. It names the key but not the value.
  • Start refuses to run.
Text
error newsletter:settings:invalid: CICADEL_NEWSLETTER_BATCH must be an integer in range

Validation errors read app newsletter settings: Batch must be 1–1000. Application.PackageSettings returns the same redacted description for tools.

#Rules

  • Keys are always CICADEL_<APP>_<NAME>, with NAME in upper snake case.
  • cicadel.New rejects:
    • a key that collides with a project setting (settings.CoreKeys);
    • a key that collides with another app’s key;
    • a duplicate name;
    • an unsupported field type.
  • A system check named settings is reserved on apps that declare settings.
  • Settings resolve once. Sharing one settings.Package between two apps is an error.
  • A package that needs its values during construction resolves them itself with Package.Resolve(app, s.Env()). It still sets App.Settings to the same value, so apps and check see it.
  • settings.LoadPackage resolves without keeping the result.

#Accounts settings

The standard accounts app uses this contract. Generated projects set AccountOptions in project/accounts.go. A deployment may override:

  • CICADEL_ACCOUNTS_SESSION_LIFETIME
  • CICADEL_ACCOUNTS_RECOVERY_LIFETIME
  • CICADEL_ACCOUNTS_IDLE_TIMEOUT
  • CICADEL_ACCOUNTS_BCRYPT_COST (10–16)

With an invalid value, the project builds without account services. check reports the key and runserver refuses to start.

Type to search this version.