cicadel

Package settings

An installable app declares its configuration as one typed Go struct. The project passes the values in Go; a deployment may override tagged fields with environment variables under the app’s own prefix, CICADEL_<APP>_<NAME>. The application resolves and validates the settings before Start, manage.go check reports an invalid key without printing its value, and secret fields are never displayed. There are no global settings: the app reads its resolved struct directly.

#Declare settings

Tag each field a deployment may change with setting:"NAME". Add ,secret for credentials. 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 from the environment that settings.Load read (Settings.Env), in dependency order. Override one per deployment:

Terminal
CICADEL_NEWSLETTER_BATCH=500 go run manage.go runserver

Settings from settings.Defaults() or apptest.Settings() read no environment, so tests see only the Go values. Inject overrides in a test 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. go run manage.go check (and check --json) reports it as <app>:settings:invalid, naming the key but not the value, and 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) or with another app’s key, a duplicate name and 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 while it is being constructed resolves them itself with Package.Resolve(app, s.Env()) and 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, and a deployment may override CICADEL_ACCOUNTS_SESSION_LIFETIME, CICADEL_ACCOUNTS_RECOVERY_LIFETIME, CICADEL_ACCOUNTS_IDLE_TIMEOUT and CICADEL_ACCOUNTS_BCRYPT_COST (10–16). With an invalid value the project builds without account services, so check reports the key and runserver refuses to start.

Type to search this version.