mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-19 19:01:42 +00:00
192 lines
6.3 KiB
Go
192 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mulgadc/spinifex/internal/guestenv"
|
|
)
|
|
|
|
const (
|
|
defaultEnvFile = "/etc/spinifex-rds/agent.env"
|
|
defaultGatewayCA = "/etc/spinifex-rds/gateway-ca.pem"
|
|
defaultHandoffDir = "/run/spinifex-rds"
|
|
defaultEngineHost = "127.0.0.1"
|
|
// Where setup.sh stamps the engine the image bakes. The agent builds its
|
|
// engine implementation from this file rather than from anything delivered.
|
|
defaultEngineFile = "/etc/spinifex-rds/engine"
|
|
defaultRCService = "rc-service"
|
|
defaultEngineProbeTimeout = 5 * time.Second
|
|
// The long-poll window the agent asks the gateway to hold a request open
|
|
// for. The gateway caps it at 20s.
|
|
defaultPollWait = 20 * time.Second
|
|
)
|
|
|
|
// Static settings delivered per-instance by cloud-init. It carries no secrets:
|
|
// IMDS is readable by anything in the guest, so the master password only
|
|
// arrives via GetDBBootstrapConfig.
|
|
type config struct {
|
|
GatewayURL string
|
|
GatewayCA string
|
|
Region string
|
|
// Optional — the gateway resolves the instance from the caller's
|
|
// credentials. When set it is sent, so a mis-provisioned VM is rejected.
|
|
DBInstanceIdentifier string
|
|
// What this image actually ships. Empty leaves the control plane's recorded
|
|
// version alone rather than clearing it.
|
|
EngineVersion string
|
|
HandoffDir string
|
|
EngineHost string
|
|
EnginePort int
|
|
PollWait time.Duration
|
|
|
|
// The engine the control plane launched this VM as, delivered by cloud-init,
|
|
// and the engine the image itself bakes. Two independent statements of the
|
|
// same fact: the agent refuses to bootstrap when they disagree.
|
|
Engine string
|
|
EngineFile string
|
|
BakedEngine string
|
|
|
|
// Where the command handlers reach the engine: the client binaries, the
|
|
// datadir the parameter file is installed into, the socket they connect
|
|
// over, the OS user they drop to, and the service the stop goes through.
|
|
EngineBinDir string
|
|
EngineDataDir string
|
|
SocketDir string
|
|
EngineUser string
|
|
RCService string
|
|
EngineService string
|
|
// Where the engine records the pid its probe checks for liveness, for an
|
|
// engine whose own client cannot tell a server still recovering from one that
|
|
// is not running at all.
|
|
EnginePidFile string
|
|
EngineProbeTimeout time.Duration
|
|
// Where the engine records why it would not start, which the probe quotes
|
|
// when the server is up but not serving. Overridable so a test can point it
|
|
// at a fixture.
|
|
EngineErrorLog string
|
|
|
|
// Where the data volume is mounted, and the two kernel surfaces a storage
|
|
// grow resolves its device from. Overridable so a test can point them at
|
|
// fixtures rather than at the running host's.
|
|
DataMount string
|
|
MountsFile string
|
|
SysBlock string
|
|
}
|
|
|
|
// Reads the cloud-init env file, then lets real env vars override.
|
|
func loadConfig(envFile string) config {
|
|
get := guestenv.Load(envFile).Get
|
|
|
|
cfg := config{
|
|
GatewayURL: get("RDS_GATEWAY_URL"),
|
|
GatewayCA: get("RDS_GATEWAY_CA"),
|
|
Region: get("RDS_REGION"),
|
|
DBInstanceIdentifier: get("RDS_DB_INSTANCE_IDENTIFIER"),
|
|
Engine: get("RDS_ENGINE"),
|
|
EngineFile: get("RDS_ENGINE_FILE"),
|
|
EngineVersion: get("RDS_ENGINE_VERSION"),
|
|
HandoffDir: get("RDS_HANDOFF_DIR"),
|
|
EngineHost: get("RDS_ENGINE_HOST"),
|
|
EngineBinDir: get("RDS_ENGINE_BIN"),
|
|
EngineDataDir: get("RDS_ENGINE_DATA"),
|
|
SocketDir: get("RDS_SOCKET_DIR"),
|
|
EngineUser: get("RDS_ENGINE_USER"),
|
|
RCService: get("RDS_RC_SERVICE"),
|
|
EngineService: get("RDS_ENGINE_SERVICE"),
|
|
EnginePidFile: get("RDS_ENGINE_PIDFILE"),
|
|
EngineErrorLog: get("RDS_ENGINE_LOG"),
|
|
DataMount: get("RDS_DATA_MOUNT"),
|
|
PollWait: defaultPollWait,
|
|
EngineProbeTimeout: defaultEngineProbeTimeout,
|
|
}
|
|
if cfg.GatewayCA == "" {
|
|
cfg.GatewayCA = defaultGatewayCA
|
|
}
|
|
if cfg.HandoffDir == "" {
|
|
cfg.HandoffDir = defaultHandoffDir
|
|
}
|
|
if cfg.EngineHost == "" {
|
|
cfg.EngineHost = defaultEngineHost
|
|
}
|
|
if cfg.EngineFile == "" {
|
|
cfg.EngineFile = defaultEngineFile
|
|
}
|
|
if cfg.RCService == "" {
|
|
cfg.RCService = defaultRCService
|
|
}
|
|
// The image's own stamp rather than the delivered engine: an agent that took
|
|
// its layout from the payload could run one engine's implementation against
|
|
// another engine's datadir, which is the failure the stamp exists to prevent.
|
|
cfg.BakedEngine = readBakedEngine(cfg.EngineFile)
|
|
cfg.applyLayout(engineLayouts[cfg.BakedEngine])
|
|
|
|
// rds-datadir reads the same RDS_DATA_MOUNT, so the two agree on where the
|
|
// volume landed without either asserting it to the other.
|
|
cfg.MountsFile = defaultMountsFile
|
|
cfg.SysBlock = defaultSysBlock
|
|
// The authoritative port comes from the bootstrap config; this is only what
|
|
// the probe uses until that first fetch lands.
|
|
if v := get("RDS_ENGINE_PORT"); v != "" {
|
|
if p, err := strconv.Atoi(v); err == nil && p > 0 {
|
|
cfg.EnginePort = p
|
|
}
|
|
}
|
|
if v := get("RDS_POLL_WAIT"); v != "" {
|
|
if d, err := time.ParseDuration(v); err == nil && d > 0 {
|
|
cfg.PollWait = d
|
|
}
|
|
}
|
|
if v := get("RDS_ENGINE_PROBE_TIMEOUT"); v != "" {
|
|
if d, err := time.ParseDuration(v); err == nil && d > 0 {
|
|
cfg.EngineProbeTimeout = d
|
|
}
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
// Fills in whatever the delivered configuration left unset. An override always
|
|
// wins, so a test can point any of these at a fixture. An unrecognised engine
|
|
// leaves the layout empty, and New refuses rather than guessing at one.
|
|
func (c *config) applyLayout(layout engineLayout) {
|
|
if c.EngineBinDir == "" {
|
|
c.EngineBinDir = layout.binDir
|
|
}
|
|
if c.EngineDataDir == "" {
|
|
c.EngineDataDir = layout.dataDir
|
|
}
|
|
if c.SocketDir == "" {
|
|
c.SocketDir = layout.socketDir
|
|
}
|
|
if c.EngineUser == "" {
|
|
c.EngineUser = layout.osUser
|
|
}
|
|
if c.EngineService == "" {
|
|
c.EngineService = layout.service
|
|
}
|
|
if c.EnginePidFile == "" {
|
|
c.EnginePidFile = layout.pidFile
|
|
}
|
|
if c.EngineErrorLog == "" {
|
|
c.EngineErrorLog = layout.errorLog
|
|
}
|
|
if c.DataMount == "" {
|
|
c.DataMount = layout.dataMount
|
|
}
|
|
if c.EnginePort == 0 {
|
|
c.EnginePort = layout.port
|
|
}
|
|
}
|
|
|
|
// The engine the image bakes. An absent or unreadable stamp reads as empty,
|
|
// which New refuses: an agent that guessed would defeat the stamp.
|
|
func readBakedEngine(path string) string {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.ToLower(strings.TrimSpace(string(raw)))
|
|
}
|