mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-14 06:52:05 +00:00
feat(multiplayer): propagate allocated launch configuration
This commit is contained in:
@@ -23,7 +23,8 @@ import (
|
||||
type GameServer struct {
|
||||
// ObjectMeta.Annotations carries per-allocation data the agones package
|
||||
// requests on the GameServerAllocation (server/agones/allocation.go) --
|
||||
// currently cosmic-clash.io/match-id and cosmic-clash.io/allocation-id.
|
||||
// currently cosmic-clash.io/match-id, cosmic-clash.io/allocation-id, and
|
||||
// allocator-selected compatibility fields.
|
||||
// This is the only channel for match-specific config to reach an
|
||||
// already-Ready pod: env vars are fixed at pod creation, before Agones
|
||||
// assigns a match to it. NOTE: the exact JSON key for this field
|
||||
@@ -187,6 +188,10 @@ func (s *Supervisor) Start(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
command := withAllocatedConfig(s.config.Command, s.matchID(), s.config.ServerID, s.config.ImageDigest, rosterExpiry)
|
||||
command, err = withAllocatedCompatibility(command, s.lastGameServer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command = withPort(command, port)
|
||||
s.cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
} else {
|
||||
@@ -328,6 +333,55 @@ func withAllocatedConfig(command []string, matchID, serverID, imageDigest string
|
||||
return result
|
||||
}
|
||||
|
||||
// withAllocatedCompatibility overlays fields selected by the allocator onto
|
||||
// child flags. These values arrive through Agones allocation annotations after
|
||||
// the pod was created, so static Fleet defaults must never win over them.
|
||||
func withAllocatedCompatibility(command []string, gameServer GameServer) ([]string, error) {
|
||||
values := map[string]string{}
|
||||
annotations := gameServer.ObjectMeta.Annotations
|
||||
if annotations == nil {
|
||||
return command, nil
|
||||
}
|
||||
for annotation, flag := range map[string]string{
|
||||
"cosmic-clash.io/region": "region",
|
||||
"cosmic-clash.io/build": "client-build",
|
||||
"cosmic-clash.io/protocol": "protocol-version",
|
||||
"cosmic-clash.io/transport": "transport",
|
||||
} {
|
||||
value := annotations[annotation]
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if strings.ContainsAny(value, "\r\n\t") {
|
||||
return nil, fmt.Errorf("allocated annotation %q contains control characters", annotation)
|
||||
}
|
||||
values[flag] = value
|
||||
}
|
||||
return withAllocatedValues(command, values), nil
|
||||
}
|
||||
|
||||
func withAllocatedValues(command []string, values map[string]string) []string {
|
||||
result := append([]string(nil), command...)
|
||||
for key, value := range values {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
prefix := "--" + key + "="
|
||||
replaced := false
|
||||
for i, arg := range result {
|
||||
if strings.HasPrefix(arg, prefix) {
|
||||
result[i] = prefix + value
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !replaced {
|
||||
result = append(result, prefix+value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// reportAssignmentReady is best-effort: process-ready has already succeeded,
|
||||
// so the process is legitimately usable either way. A persistent failure is
|
||||
// written to stderr rather than returned, since treating it as fatal would
|
||||
|
||||
@@ -34,6 +34,36 @@ func TestWithAllocatedConfigOverridesAuthoritativeChildFlags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithAllocatedCompatibilityOverridesStaleFlagsAndRejectsUnsafeValues(t *testing.T) {
|
||||
command := []string{"game-server", "--region=EU", "--client-build=stale", "--protocol-version=1", "--transport=enet", "--custom=keep"}
|
||||
gameServer := GameServer{}
|
||||
gameServer.ObjectMeta.Annotations = map[string]string{
|
||||
"cosmic-clash.io/region": "NA", "cosmic-clash.io/build": "build-live",
|
||||
"cosmic-clash.io/protocol": "12", "cosmic-clash.io/transport": "steam_sdr",
|
||||
}
|
||||
got, err := withAllocatedCompatibility(command, gameServer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{"--region=NA", "--client-build=build-live", "--protocol-version=12", "--transport=steam_sdr", "--custom=keep"} {
|
||||
found := false
|
||||
for _, arg := range got {
|
||||
if arg == want {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("dynamic flag %q missing from %#v", want, got)
|
||||
}
|
||||
}
|
||||
unsafe := gameServer
|
||||
unsafe.ObjectMeta.Annotations = map[string]string{"cosmic-clash.io/region": "NA\nforged"}
|
||||
if _, err := withAllocatedCompatibility(command, unsafe); err == nil {
|
||||
t.Fatal("unsafe annotation did not fail closed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocatedStartInjectsDynamicEndpointAndCallsReadyAfterProbe(t *testing.T) {
|
||||
ready := false
|
||||
readyCalled := false
|
||||
|
||||
Reference in New Issue
Block a user