package appconfig import ( "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/registry" ) var errHostWriteOptions = errors.New("appconfig: more than one hostwrite configuration") func hostWriteOption(options []hostwrite.Config) (hostwrite.Config, error) { if len(options) > 1 { return hostwrite.Config{}, errHostWriteOptions } if len(options) == 1 { return options[0], nil } return hostwrite.Config{}, nil } func renderedIdentity(rendered []byte, defaultPresent, environmentPresent, instancePresent bool) hostwrite.Identity { sum := sha256.Sum256(rendered) return hostwrite.Identity{Config: fmt.Sprintf("sha256:%s;default=%t;environment=%t;instance=%t", hex.EncodeToString(sum[:]), defaultPresent, environmentPresent, instancePresent)} } func guarded(ctx context.Context, work hostwrite.Work, id hostwrite.Identity, fn func() error) error { if ctx == nil || work == nil { return hostwrite.ErrUnavailable } return work.With(ctx, id, fn) } type legacyHostWork struct{} func (legacyHostWork) With(_ context.Context, _ hostwrite.Identity, fn func() error) error { return fn() } // Callbacks keeps legacy handlers for flag-off passes. Enabled passes require // the selected handlers, which must retain the selection until their final // effect. They run outside the config writer's flock. type Callbacks struct { Restart func(string) error Ack func(ConfigAck) SelectedRestart hostwrite.Callback SelectedAck func(context.Context, hostwrite.Selection, ConfigAck) error // Delivered, when set, is told the revision of a route's config file // each time this pass writes or removes it, before the restart callback // runs: the zero-downtime scheduler's "delivered" revision (spec ยง8.1). // It is never called for a shape whose process the platform does not // restart (the TCP-addr shape), for a sidecar Managed does not name, or // for a socket route Managed says no to. Nil keeps today's behaviour. Delivered func(host, rev string) // Managed, when set, names the routes the release scheduler moves itself // (rollout.Managed, handed in because this package sits below the // scheduler). It splits the pass the way the scheduler does (final // review F7): a socket route it does not manage is restarted by // Unmanaged, as config sync restarts it with the flag off, and is not // reported to Delivered; a native sidecar it manages is reported to // Delivered though config sync never restarts a sidecar, because the // scheduler's config attempt is that sidecar's restart. Nil: Restart and // Delivered serve every route, as before. Managed func(rt registry.Route) bool // Unmanaged is the restart a socket route outside Managed gets in // Restart's place. Ignored while Managed is nil. Unmanaged func(host string) error enabled bool } // ErrDeliveredOnWake is a restart callback's answer for an instance that is // asleep and reads the new file when it next wakes: nothing was restarted // and nothing failed. Its ack carries Hibernating (delivered on wake), the // marker the unit pass sets for a hibernated unit (sync.go), with Restarted // false and no RestartError. var ErrDeliveredOnWake = errors.New("asleep: config delivered on wake") func (c Callbacks) restart(ctx context.Context, selected hostwrite.Selection) error { if c.enabled { if c.SelectedRestart == nil { return hostwrite.ErrUnavailable } return c.SelectedRestart(ctx, selected) } if c.Restart == nil { return nil } return c.Restart(selected.Host) } func (c Callbacks) acknowledge(ctx context.Context, selected hostwrite.Selection, a ConfigAck) error { if c.enabled { if c.SelectedAck == nil { return hostwrite.ErrUnavailable } return c.SelectedAck(ctx, selected, a) } if c.Ack != nil { c.Ack(a) } return nil } func configCallbacks(work hostwrite.Work, restart func(string) error, ack func(ConfigAck), options []Callbacks) Callbacks { if len(options) != 0 { return options[0] } _, legacy := work.(legacyHostWork) return Callbacks{Restart: restart, Ack: ack, enabled: !legacy} }