- Add deployment-name branding to titles, mastheads, and OG tags - Share one grant delivery-state query with lineage across grants surfaces - Show pool status/usage, org owners, and config readiness - Make billing views projection-aware with recency and sync vocabulary - Guard FedWiki creation without domains and render route-aware 404s
114 lines
5.2 KiB
Go
114 lines
5.2 KiB
Go
package config
|
|
|
|
import "strings"
|
|
|
|
// ConfigKey declares one configuration key an integration owns — see
|
|
// design.md's "Declared config, spec-driven validation" decision
|
|
// (openspec/changes/integration-extraction/design.md). It is the single
|
|
// declaration cmd/start.go, this package's ValidateStart, and (once task
|
|
// 4.4 lands) internal/embeds/mc-config.yaml all derive their behavior
|
|
// from, replacing three previously hand-maintained, independently-drifting
|
|
// lists (cmd's flag/viper-default calls, validate.go's hardcoded
|
|
// per-integration conditional, and the YAML defaults file).
|
|
//
|
|
// Name is the viper/flag key (kebab-case, e.g. "stripe-api-key"). Default,
|
|
// when non-nil, is bound via viper.SetDefault; its dynamic type (string or
|
|
// []string today) tells cmd which cobra flag constructor to use. Usage is
|
|
// the flag's help text. Secret marks the key as sensitive: cmd also binds
|
|
// a "<name>-file" flag and, before validation runs, resolves a configured
|
|
// file path into Name (loading the value from disk) — mirroring the
|
|
// handling core's own secrets (csrf-secret, oidc-sp-client-secret, ...)
|
|
// already receive. RequiredGroup, when non-empty, ties this key to every
|
|
// other declared key sharing the same tag: ValidateStart rejects a
|
|
// configuration where some but not all of a group's keys are set (e.g.
|
|
// Stripe's stripe-api-key and stripe-webhook-secret share the tag
|
|
// "Stripe" — both must be set to enable billing, or both left empty to
|
|
// disable it).
|
|
type ConfigKey struct {
|
|
Name string
|
|
Default any
|
|
Usage string
|
|
Secret bool
|
|
RequiredGroup string
|
|
// Enum, when non-empty, closes the key's value set. The runtime
|
|
// settings surface renders such keys as a select and rejects
|
|
// non-members at save time; the boot overlay re-checks stored
|
|
// overrides. ValidateStart also checks every declared Enum key's
|
|
// resolved environment/flag/config-file value for membership (design
|
|
// D8, schema-hardening), sharing enumMember with CoerceOverride so the
|
|
// two paths cannot diverge.
|
|
Enum []string
|
|
}
|
|
|
|
// ConfigProvider is implemented by integrations that declare their own
|
|
// configuration keys, so the composition root (cmd/start.go) can bind
|
|
// flags/env/defaults and this package can validate presence and
|
|
// required-together groups without a hardcoded per-integration block. It
|
|
// is declared here — in internal/config, not internal/integrations —
|
|
// because this package is the one that actually consumes ConfigKey values
|
|
// (ValidateStart's required-group check) and cmd/start.go already imports
|
|
// it for that call; declaring ConfigKey/ConfigProvider here instead means
|
|
// internal/config never has to import internal/integrations (and,
|
|
// transitively, every concrete adapter's own dependencies — internal/
|
|
// server, internal/workflows/*, the Temporal client, net/http) merely to
|
|
// reference a plain declaration struct. This mirrors why server.
|
|
// RouteProvider and workflows.WorkflowProvider are declared in their own
|
|
// consuming packages rather than in internal/integrations.
|
|
type ConfigProvider interface {
|
|
ConfigSpec() []ConfigKey
|
|
}
|
|
|
|
// SecretPair names a secret configuration key and its "<name>-file"
|
|
// companion flag, as cmd binds them. SecretPairsFrom derives the list cmd
|
|
// resolves (load the file, if any, into Name) before validation runs, so
|
|
// that list is data-driven from declarations instead of a hand-maintained
|
|
// literal per integration.
|
|
type SecretPair struct {
|
|
Name string
|
|
FileName string
|
|
}
|
|
|
|
// SecretPairsFrom returns the {Name, "<Name>-file"} pair for every
|
|
// declared key with Secret set, in declaration order.
|
|
func SecretPairsFrom(keys []ConfigKey) []SecretPair {
|
|
var pairs []SecretPair
|
|
for _, k := range keys {
|
|
if k.Secret {
|
|
pairs = append(pairs, SecretPair{Name: k.Name, FileName: k.Name + "-file"})
|
|
}
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
// RequiredKeysUnresolved returns the names, in declaration order, of every
|
|
// key whose RequiredGroup is non-empty (ValidateStart's "required
|
|
// together" keys — see validateRequiredGroups) and whose resolved value
|
|
// (environment/flag/config-file/default, read straight from viper — the
|
|
// same layer ValidateStart and the settings page itself read) is empty
|
|
// after trimming. A key with no RequiredGroup imposes no requirement and
|
|
// is never reported.
|
|
//
|
|
// This is the ONE resolution every honest-status surface shares (ux-
|
|
// honest-surfaces design decision 2): the Integrations list, the
|
|
// overview's system region, and the per-integration settings page all
|
|
// derive their configuration-readiness signal from this function, so
|
|
// "not configured" means the same thing everywhere it appears. Secret
|
|
// keys are covered too — they are still bound into viper by cmd/start.go
|
|
// (via the "<name>-file" resolution), only their *value* is withheld from
|
|
// display, never their presence.
|
|
func RequiredKeysUnresolved(keys []ConfigKey) []string {
|
|
var missing []string
|
|
for _, k := range keys {
|
|
if k.RequiredGroup == "" {
|
|
continue
|
|
}
|
|
// effectiveString (overlay.go) is the same per-key resolution
|
|
// ApplyOverlay and validateRequiredGroups already read; trimming
|
|
// matches validateRequiredGroups' own presence check exactly.
|
|
if strings.TrimSpace(effectiveString(k)) == "" {
|
|
missing = append(missing, k.Name)
|
|
}
|
|
}
|
|
return missing
|
|
}
|