mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 20:33:44 +00:00
544f76c502
Closes the remaining gap the previous two commits left open: WorkloadVerify itself worked, but nothing minted a real token at allocation time or handed it to a running pod, so it had no real caller yet. agones.Client gains WorkloadSecret/WorkloadTokenTTL. When set, Allocate mints a signed workload token for the allocation (allocation_id is known at request-construction time, before Agones has picked a server -- see the previous commit for why that's the only identifier the token can bind) and requests it as a third cosmic-clash.io/workload-token annotation, alongside the existing match-id/allocation-id ones. Left unset (the default), Allocate requests no such annotation, so a deployment not yet using this path is unaffected. cmd/allocator wires it from a new --workload-secret / COSMIC_CLASH_WORKLOAD_SECRET flag (must match cmd/control-plane's own), with a startup warning if left unset. supervisor.Supervisor.workloadToken() resolves the bearer credential for control-plane registration: an explicitly configured --workload-token-path always wins (kept for a future Kubernetes-projected-JWT WorkloadVerify path, not yet wired server-side), otherwise it falls back to the cosmic-clash.io/workload-token annotation on the allocated GameServer -- the same annotation-fallback pattern matchID already used for cosmic-clash.io/match-id. WorkloadTokenPath is accordingly no longer required at construction time when ControlPlaneURL is set. Verified: new agones test proves the annotation is requested (and parses/ verifies against the same secret, naming the right allocation) when WorkloadSecret is configured, and that it's absent when it isn't; new supervisor tests prove the annotation-sourced token is what's actually sent as the Authorization bearer, and that Start fails closed with neither a configured path nor an annotation present. Full `go build ./... && go vet ./... && gofmt -l . && go test ./... -race` and `go test -tags integration ./... -race` both clean.
90 lines
3.8 KiB
Go
90 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/cosmic-clash/cosmic-clash/server/supervisor"
|
|
)
|
|
|
|
const usageText = `Usage: game-server-supervisor [options] -- <game-server-command> [args...]
|
|
|
|
The child command is started only after an allocated Agones endpoint has been
|
|
validated and, when configured, an explicit process-ready probe succeeds.
|
|
SIGTERM/SIGINT requests authenticated drain before the bounded grace deadline.
|
|
`
|
|
|
|
func main() {
|
|
args := os.Args[1:]
|
|
separator := -1
|
|
for i, arg := range args {
|
|
if arg == "--" {
|
|
separator = i
|
|
break
|
|
}
|
|
}
|
|
if separator < 0 || separator == len(args)-1 {
|
|
fmt.Fprint(os.Stderr, usageText)
|
|
os.Exit(2)
|
|
}
|
|
|
|
options := flag.NewFlagSet("game-server-supervisor", flag.ContinueOnError)
|
|
options.SetOutput(os.Stderr)
|
|
sdkBaseURL := options.String("sdk-base-url", "", "Agones SDK REST base URL; empty enables direct mode")
|
|
readyURL := options.String("ready-url", "", "explicit process-ready probe URL")
|
|
drainURL := options.String("drain-url", "", "loopback drain URL")
|
|
drainTokenEnv := options.String("drain-token-env", "COSMIC_CLASH_DRAIN_TOKEN", "environment variable containing the drain bearer token")
|
|
transport := options.String("transport", "enet", "enet or steam_sdr")
|
|
grace := options.Duration("drain-grace", supervisor.DefaultDrainGrace, "maximum graceful drain duration")
|
|
controlPlaneURL := options.String("control-plane-url", "", "matchmaking control-plane base URL; empty skips process-ready registration entirely")
|
|
workloadTokenPath := options.String("workload-token-path", "", "path to a projected workload service-account token, read fresh on every registration call; if unset, falls back to the cosmic-clash.io/workload-token annotation Agones applied to this GameServer at allocation time")
|
|
serverIDEnv := options.String("server-id-env", "COSMIC_CLASH_SERVER_ID", "environment variable containing this GameServer's control-plane server ID (populate via the Kubernetes Downward API, fieldRef: metadata.name)")
|
|
matchIDEnv := options.String("match-id-env", "COSMIC_CLASH_MATCH_ID", "environment variable containing the allocated match ID; if unset/empty, falls back to the cosmic-clash.io/match-id annotation on the allocated GameServer")
|
|
protocolVersion := options.Int("protocol-version", 0, "protocol version reported at registration")
|
|
imageDigestEnv := options.String("image-digest-env", "COSMIC_CLASH_IMAGE_DIGEST", "environment variable containing this build's sha256 image digest")
|
|
assignmentReadyAttempts := options.Int("assignment-ready-attempts", 5, "retry attempts for assignment-ready registration after process-ready succeeds (a slow-to-propagate signed roster is not fatal)")
|
|
assignmentReadyBackoff := options.Duration("assignment-ready-backoff", 2*time.Second, "delay between assignment-ready retry attempts")
|
|
if err := options.Parse(args[:separator]); err != nil {
|
|
os.Exit(2)
|
|
}
|
|
|
|
token := ""
|
|
if *drainTokenEnv != "" {
|
|
token = os.Getenv(*drainTokenEnv)
|
|
}
|
|
s, err := supervisor.New(supervisor.Config{
|
|
Command: args[separator+1:],
|
|
SDKBaseURL: *sdkBaseURL,
|
|
ReadyURL: *readyURL,
|
|
DrainURL: *drainURL,
|
|
DrainToken: token,
|
|
Transport: *transport,
|
|
ReadyTimeout: 30 * time.Second,
|
|
|
|
ControlPlaneURL: *controlPlaneURL,
|
|
WorkloadTokenPath: *workloadTokenPath,
|
|
ServerID: os.Getenv(*serverIDEnv),
|
|
MatchID: os.Getenv(*matchIDEnv),
|
|
ProtocolVersion: *protocolVersion,
|
|
ImageDigest: os.Getenv(*imageDigestEnv),
|
|
|
|
AssignmentReadyAttempts: *assignmentReadyAttempts,
|
|
AssignmentReadyBackoff: *assignmentReadyBackoff,
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "game-server-supervisor: %v\n", err)
|
|
os.Exit(2)
|
|
}
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
|
defer stop()
|
|
if err := s.Run(ctx, *grace); err != nil {
|
|
fmt.Fprintf(os.Stderr, "game-server-supervisor: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|