ce7cb702218693d08ea6d83464d2457c9250027c
This commit is contained in:
commit
34c5f8963c
1 changed files with 145336 additions and 0 deletions
145
services/stalwart/cmd/controller/options.go
Normal file
145
services/stalwart/cmd/controller/options.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
dsnFile string
|
||||
plan string
|
||||
renderedPlan string
|
||||
secretRoot string
|
||||
cli string
|
||||
stalwartURL string
|
||||
certificateSAN string
|
||||
updateApps bool
|
||||
oidcIssuerFile string
|
||||
oidcAudienceFile string
|
||||
oidcClientIDFile string
|
||||
oidcClientSecretFile string
|
||||
appPasswords string
|
||||
vaultAddress string
|
||||
vaultRole string
|
||||
vaultTokenFile string
|
||||
vaultCACertFile string
|
||||
bootstrapUser string
|
||||
bootstrapPasswordFile string
|
||||
statusAddress string
|
||||
interval time.Duration
|
||||
initialBackoff time.Duration
|
||||
maxBackoff time.Duration
|
||||
reconcileTimeout time.Duration
|
||||
persistentFailureAfter int
|
||||
standbyReady bool
|
||||
once bool
|
||||
}
|
||||
|
||||
func parseOptions(arguments []string) (options, error) {
|
||||
var value options
|
||||
flags := flag.NewFlagSet("stalwart-controller", flag.ContinueOnError)
|
||||
flags.StringVar(&value.dsnFile, "dsn-file", "", "PostgreSQL DSN file used for controller ownership")
|
||||
flags.StringVar(&value.plan, "plan", "", "Stalwart desired-state plan")
|
||||
flags.StringVar(&value.renderedPlan, "rendered-plan", "/tmp/stalwart-plan.ndjson", "Rendered plan destination")
|
||||
flags.StringVar(&value.secretRoot, "secret-root", "/run/control", "Root for plan secret-file references")
|
||||
flags.StringVar(&value.cli, "cli", "/usr/local/bin/stalwart-cli", "Stalwart CLI executable")
|
||||
flags.StringVar(&value.stalwartURL, "stalwart-url", "", "Stalwart management URL")
|
||||
flags.StringVar(&value.certificateSAN, "certificate-san", "", "SAN used to select the default certificate")
|
||||
flags.BoolVar(&value.updateApps, "update-apps", false, "Refresh embedded web applications")
|
||||
flags.StringVar(&value.oidcIssuerFile, "oidc-issuer-file", "", "OIDC issuer URL file")
|
||||
flags.StringVar(&value.oidcAudienceFile, "oidc-audience-file", "", "OIDC audience file")
|
||||
flags.StringVar(&value.oidcClientIDFile, "oidc-client-id-file", "", "OIDC client ID file")
|
||||
flags.StringVar(&value.oidcClientSecretFile, "oidc-client-secret-file", "", "OIDC client secret file")
|
||||
flags.StringVar(&value.appPasswords, "app-passwords", "", "Managed app-password identity file")
|
||||
flags.StringVar(&value.vaultAddress, "vault-address", "", "Vault API address")
|
||||
flags.StringVar(&value.vaultRole, "vault-role", "", "Vault Kubernetes authentication role")
|
||||
flags.StringVar(&value.vaultTokenFile, "vault-token-file", "", "Vault service-account token file")
|
||||
flags.StringVar(&value.vaultCACertFile, "vault-ca-cert-file", "", "Vault CA certificate file")
|
||||
flags.StringVar(&value.bootstrapUser, "bootstrap-user", "", "Temporary recovery username")
|
||||
flags.StringVar(&value.bootstrapPasswordFile, "bootstrap-password-file", "", "Temporary recovery password file")
|
||||
flags.StringVar(&value.statusAddress, "status-address", "127.0.0.1:9090", "Status server listen address")
|
||||
flags.DurationVar(&value.interval, "interval", 5*time.Minute, "Successful reconciliation interval")
|
||||
flags.DurationVar(&value.initialBackoff, "initial-backoff", 5*time.Second, "Initial failure retry delay")
|
||||
flags.DurationVar(&value.maxBackoff, "max-backoff", 5*time.Minute, "Maximum failure retry delay")
|
||||
flags.DurationVar(&value.reconcileTimeout, "reconcile-timeout", 2*time.Minute, "Per-reconciliation timeout")
|
||||
flags.IntVar(&value.persistentFailureAfter, "persistent-failure-after", 5, "Failures before relinquishing ownership")
|
||||
flags.BoolVar(&value.standbyReady, "standby-ready", false, "Report a healthy lock contender as ready")
|
||||
flags.BoolVar(&value.once, "once", false, "Reconcile once without ownership or status serving")
|
||||
if err := flags.Parse(arguments); err != nil {
|
||||
return options{}, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func allOrNone(name string, fields ...string) (bool, error) {
|
||||
configured := 0
|
||||
for _, field := range fields {
|
||||
if field != "" {
|
||||
configured++
|
||||
}
|
||||
}
|
||||
if configured != 0 && configured != len(fields) {
|
||||
return false, fmt.Errorf("%s fields must be set together", name)
|
||||
}
|
||||
return configured != 0, nil
|
||||
}
|
||||
|
||||
func validateOptions(value options) error {
|
||||
if value.plan == "" {
|
||||
return errors.New("plan is required")
|
||||
}
|
||||
if value.stalwartURL == "" {
|
||||
return errors.New("stalwart-url is required")
|
||||
}
|
||||
if !value.once && value.dsnFile == "" {
|
||||
return errors.New("dsn-file is required in controller mode")
|
||||
}
|
||||
oidcConfigured, err := allOrNone(
|
||||
"OIDC",
|
||||
value.oidcIssuerFile,
|
||||
value.oidcAudienceFile,
|
||||
value.oidcClientIDFile,
|
||||
value.oidcClientSecretFile,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vaultConfigured, err := allOrNone(
|
||||
"vault",
|
||||
value.vaultAddress,
|
||||
value.vaultRole,
|
||||
value.vaultTokenFile,
|
||||
value.vaultCACertFile,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value.appPasswords != "" && !vaultConfigured {
|
||||
return errors.New("app-password identities require vault")
|
||||
}
|
||||
if value.appPasswords != "" && !oidcConfigured {
|
||||
return errors.New("app-password identities require OIDC issuer and audience")
|
||||
}
|
||||
bootstrapConfigured, err := allOrNone("bootstrap", value.bootstrapUser, value.bootstrapPasswordFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oidcConfigured && bootstrapConfigured {
|
||||
return errors.New("OIDC and bootstrap credentials are mutually exclusive")
|
||||
}
|
||||
if !value.once && !oidcConfigured && !bootstrapConfigured {
|
||||
return errors.New("controller mode requires OIDC or bootstrap credentials")
|
||||
}
|
||||
if value.interval <= 0 || value.initialBackoff <= 0 || value.maxBackoff <= 0 {
|
||||
return errors.New("interval and backoff durations must be positive")
|
||||
}
|
||||
if value.initialBackoff > value.maxBackoff {
|
||||
return errors.New("initial backoff exceeds maximum backoff")
|
||||
}
|
||||
if value.reconcileTimeout <= 0 || value.persistentFailureAfter <= 0 {
|
||||
return errors.New("reconcile timeout and failure threshold must be positive")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in a new issue