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).
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:
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:
CICADEL_NEWSLETTER_BATCH=500 go run manage.go runserverSettings 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]:
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:
error newsletter:settings:invalid: CICADEL_NEWSLETTER_BATCH must be an integer in rangeValidation 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>, withNAMEin upper snake case.cicadel.Newrejects 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
settingsis reserved on apps that declare settings. - Settings resolve once. Sharing one
settings.Packagebetween 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 setsApp.Settingsto the same value, soappsandchecksee it.settings.LoadPackageresolves 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.