Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
77bb5b1
fix: back up stopped servers and bind quiesce to pod identity
Baylem Oct 21, 2026
3e16560
merge: include CSI restore routing correction
Baylem Oct 21, 2026
a223a9e
merge: include shared web build prerequisite
Baylem Oct 21, 2026
fe3b965
docs: describe pod-bound agent operations and clarify test fakes
Baylem Oct 21, 2026
eb7db1c
fix(operator): preserve condition changes before status comparison
Baylem Oct 22, 2026
b4268dc
fix(operator): drain failed restore startup before releasing ownership
Baylem Oct 23, 2026
84119f2
fix(ci): drive real changes during event stream checks
Baylem Oct 23, 2026
02df370
test: preserve controller updates in lifecycle fixtures
Baylem Oct 23, 2026
ff4d4fc
test: synchronize closed RCON response fixtures
Baylem Oct 23, 2026
11e7d65
fix: allow guarded wipe recovery to drain workloads
Baylem Oct 24, 2026
2bb24d1
Merge branch 'master' into fix/audit-backup-stopped
ValgulNecron Oct 25, 2026
d9f3f8e
test: isolate stopped backup recovery fixtures
Baylem Oct 25, 2026
1842bda
test: simplify lifecycle regression setup
Baylem Oct 25, 2026
2f3edd1
chore: resolve upstream conflict in stopped backup branch
Baylem Oct 27, 2026
f41bb0a
Merge branch 'master' into fix/audit-backup-stopped
ValgulNecron Oct 28, 2026
50398fc
Merge upstream and reject unbound legacy backup cleanup
Baylem Oct 31, 2026
6445f69
chore: synchronize stopped backup PR with upstream
Baylem Nov 4, 2026
f209797
fix(ci): install pinned Trivy from GitHub releases
Baylem Nov 5, 2026
832d888
merge: share pinned Trivy installer with stopped backups
Baylem Nov 5, 2026
28add95
test: cover disabled-agent cleanup with a pod identity
Baylem Nov 6, 2026
6045280
fix(deps): update compress for GO-2026-6865
Baylem Nov 6, 2026
fca0966
merge: update compression dependency for stopped backups
Baylem Nov 6, 2026
9391601
fix(deps): retain workspace graph checksum
Baylem Nov 6, 2026
725499a
merge: retain compression workspace checksum for code generation
Baylem Nov 6, 2026
96450e9
fix(operator): bound initial backup source admission
Baylem Nov 6, 2026
c3d31cd
test(backup): assert source admission before metadata support
Baylem Nov 6, 2026
d74269d
test(operator): update backup source pod status through subresource
Baylem Nov 6, 2026
09831c3
fix(deps): update x/net in shipped Go binaries
Baylem Nov 6, 2026
0cc80c1
fix(deps): normalize source module checksums after x/net update
Baylem Nov 6, 2026
cfb7258
fix(deps): retain workspace graph metadata after x/net update
Baylem Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/actions/install-trivy/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Install pinned Trivy
description: Install the checksum-verified Linux amd64 scanner from its GitHub release.
runs:
using: composite
steps:
- name: Download and verify Trivy
shell: bash
run: |
set -euo pipefail
if [[ "$(uname -s)" != Linux || "$(uname -m)" != x86_64 ]]; then
echo "This installer requires a Linux amd64 runner" >&2
exit 1
fi
version=0.74.0
sha256=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a
archive="trivy_${version}_Linux-64bit.tar.gz"
download_dir=$(mktemp -d "${RUNNER_TEMP:?}/trivy-download.XXXXXX")
trap 'rm -rf -- "$download_dir"' EXIT
# Bound DNS, connection and HTTP retries. Verify before extraction or execution.
curl --fail --show-error --location --retry 3 --retry-all-errors \
--retry-delay 2 --retry-max-time 180 --connect-timeout 15 --max-time 90 \
--output "$download_dir/$archive" \
"https://github.com/aquasecurity/trivy/releases/download/v${version}/${archive}"
printf '%s %s\n' "$sha256" "$download_dir/$archive" | sha256sum --check --status
tar -xzf "$download_dir/$archive" -C "$download_dir" trivy
sudo install -m 0755 "$download_dir/trivy" /usr/local/bin/trivy
trivy --version
4 changes: 1 addition & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,7 @@ jobs:
path: ~/.cache/trivy
key: trivy-db-${{ steps.week.outputs.week }}
- name: install trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
with:
version: v0.74.0
uses: ./.github/actions/install-trivy
- name: prove the gate rejects a vulnerable image
run: ./hack/check-image-scan_test.sh
- name: scan every e2e image
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/publish-edge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ on:
- "**/Dockerfile"
- "**/Dockerfile.*"
- ".github/workflows/publish-edge.yaml"
- ".github/actions/install-trivy/**"
- "hack/ci_scope.py"
- ".dockerignore"
- "signing/cosign.pub"
Expand Down Expand Up @@ -149,9 +150,7 @@ jobs:
# Spec 013 G2: never sign an image that has a fixed HIGH/CRITICAL
# vulnerability. Same flags as the `vuln (images)` CI gate.
- name: install trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
with:
version: v0.74.0
uses: ./.github/actions/install-trivy
- name: scan pushed image
env:
IMAGE: ${{ steps.image.outputs.name }}
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,7 @@ jobs:
# Spec 013 G2: never sign an image that has a fixed HIGH/CRITICAL
# vulnerability. Same flags as the `vuln (images)` CI gate.
- name: install trivy
uses: aquasecurity/setup-trivy@81e514348e19b6112ce2a7e3ecbafe19c1e1f567 # v0.3.1
with:
version: v0.74.0
uses: ./.github/actions/install-trivy
- name: scan pushed image
env:
IMAGE: ${{ steps.image.outputs.name }}
Expand Down
7 changes: 7 additions & 0 deletions agent/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ func main() {
protected.Use(authCheck.Middleware, target.Guard(serverUID))
mountProtected(protected)
})
// Backups bind save-off/save-on to the process they prepared. The service
// may already route to a replacement pod, which must reject the old UID.
r.Route("/v1/pods/{uid}", func(protected chi.Router) {
protected.Use(authCheck.Middleware, target.Guard(envOr("GAMEPLANE_POD_UID", "")), middleware.Timeout(30*time.Second))
protected.Get("/identity", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
quiesce.Mount(protected, rconClient, gameName, quiesceSpec)
})

srv := &http.Server{
Addr: addr,
Expand Down
7 changes: 4 additions & 3 deletions agent/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.20.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand All @@ -52,10 +53,10 @@ require (
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.60.0 // indirect
golang.org/x/net v0.62.0 // indirect
golang.org/x/oauth2 v0.37.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
golang.org/x/term v0.47.0 // indirect
golang.org/x/text v0.43.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions agent/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/compress v1.20.2 h1:v5tiZvm1KGLyg5d5oXdXe3lsSPrbEUiKoXGia//I7Qo=
github.com/klauspost/compress v1.20.2/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -98,16 +98,16 @@ go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
Expand Down
28 changes: 28 additions & 0 deletions agent/internal/quiesce/quiesce_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-chi/chi/v5"

"github.com/GameplanePanel/gameplane/agent/internal/caps"
"github.com/GameplanePanel/gameplane/agent/internal/target"
)

type fakeRcon struct {
Expand Down Expand Up @@ -86,6 +87,33 @@ func newTestRouter(rc Rcon, spec *caps.Quiesce) *chi.Mux {
return r
}

func TestPodBoundRoutesRejectReplacementBeforeRCON(t *testing.T) {
for _, actual := range []string{"original", "replacement", ""} {
t.Run("pod="+actual, func(t *testing.T) {
rc := &fakeRcon{}
r := chi.NewRouter()
r.Route("/v1/pods/{uid}", func(bound chi.Router) {
bound.Use(target.Guard(actual))
Mount(bound, rc, "test", minecraftQuiesceSpec())
})
for _, path := range []string{"quiesce", "unquiesce"} {
response := httptest.NewRecorder()
r.ServeHTTP(response, httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/v1/pods/original/"+path, nil))
if actual == "original" {
if response.Code != http.StatusOK {
t.Fatalf("matching process rejected: %d", response.Code)
}
} else if response.Code != http.StatusNotFound {
t.Fatalf("wrong process admitted: %d", response.Code)
}
}
if (len(rc.calls()) > 0) != (actual == "original") {
t.Fatalf("commands reached wrong process: %v", rc.calls())
}
})
}
}

func decodeResponse(t *testing.T, body []byte) response {
t.Helper()
var got response
Expand Down
11 changes: 6 additions & 5 deletions agent/internal/target/target.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package target binds versioned agent routes to an immutable GameServer UID.
// Package target binds versioned agent routes to an immutable resource UID.
package target

import (
Expand All @@ -7,12 +7,13 @@ import (
"github.com/go-chi/chi/v5"
)

// Guard rejects a route for any server other than the one that started this
// agent. Legacy routes stay available for local callers during rolling upgrades.
func Guard(serverUID string) func(http.Handler) http.Handler {
// Guard rejects a route for any resource other than the one that started this
// agent (GameServer or Pod, according to the route). Legacy routes stay available
// for local callers during rolling upgrades.
func Guard(resourceUID string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if serverUID == "" || chi.URLParam(req, "uid") != serverUID {
if resourceUID == "" || chi.URLParam(req, "uid") != resourceUID {
http.NotFound(w, req)
return
}
Expand Down
3 changes: 3 additions & 0 deletions agent/specs.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ Resource usage env vars (set by the operator):
| `/players/banned` | GET | Currently banned players; response: `[ { name, reason?, source? } ]`; 502 if RCON unavailable |
| `/quiesce` | POST | Pause auto-saves before snapshot; response: `{ quiesced, reason? }` (boolean, reason optional); unsupported games return `quiesced: false` with a reason |
| `/unquiesce` | POST | Resume auto-saves after snapshot; response: `{ quiesced, reason? }` |
| `/v1/pods/{uid}/identity` | GET | Operator preflight: 204 only when `{uid}` matches `GAMEPLANE_POD_UID`; never mutates game state |
| `/v1/pods/{uid}/quiesce`, `/v1/pods/{uid}/unquiesce` | POST | Pod-bound backup RPCs with the same response contract as the legacy routes; reject missing/mismatched Pod UID with 404 before RCON |
| `/lifecycle/stop` | POST | Run stop sequence before scale-to-zero; RCON connection drop is the expected outcome |
| `/actions/run` | POST | Execute module-declared operator action; request: `{ id, params }`; response: `{ ok, raw? }` |
| `/status` | GET | Live game metrics (module-declared); response: bare JSON array `[ { id, displayName?, value, unit? } ]` |
Expand Down Expand Up @@ -209,6 +211,7 @@ All endpoints on the `--addr` control mux, except `/healthz`, return `401 Unauth
- **Extracted mod files stay world-readable**: `mods.moduleFileMode` is `0o644`, not a tighter `0o600`, because the mods volume is shared with the game container, which runs as whatever uid its image needs — a different uid than the agent's own. At `0o600` the game process couldn't read its own mods. This is the rationale behind the `.golangci.yml` gosec G302 exclusion scoped to `agent/internal/mods/mods.go`.
- **Resource usage is in-pod**: The `usage` package reads from `/proc` or cgroups; no external metrics pipeline required. Cgroup mode is a fallback for older clusters; proc mode (default in production) requires the operator to set `ShareProcessNamespace: true`.
- **Module capabilities drive behavior**: Every game-specific handler (players, quiesce, lifecycle, status, actions) reads its config from `--capabilities` (JSON unmarshaled into `caps.Spec`). New games require no agent code change.
- **Backup process identity**: The operator injects `GAMEPLANE_POD_UID` from `metadata.uid` through the DownwardAPI. Pod-bound backup routes require the existing mTLS authentication and check this UID at the final network hop, including when the Service resolves to a replacement pod. Missing UID fails closed. Deploy the updated agent image and allow pod rollout when upgrading the operator; older agents return 404 and the operator does not fall back to unbound routes. Legacy endpoints remain available to existing callers during rollout.
- **RCON connection errors are graceful**: A lost RCON connection does not crash the agent. `console`, `players`, `quiesce`, and `lifecycle` handlers catch connection errors and return appropriate HTTP status (e.g., `502 Bad Gateway`).
- **Source RCON sends complete frames**: Each AUTH or command frame is submitted in one socket write, with its existing byte-size limit, request ID, type, body, and two NUL terminators preserved. This avoids separate header-only writes rejected by vanilla Minecraft's RCON reader. A short write is an error and drops the connection without replaying the command; response grace and healthy connection reuse remain unchanged.
- **Log streams are tail-only**: The `logs` package does not support random-access reads. It streams from the current end (live mode) or from file start (backlog mode); clients must handle partial output and reconnection.
Expand Down
4 changes: 2 additions & 2 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.20.1 // indirect
github.com/klauspost/compress v1.20.2 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
Expand All @@ -110,7 +110,7 @@ require (
go.uber.org/zap v1.28.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/net v0.60.0 // indirect
golang.org/x/net v0.62.0 // indirect
golang.org/x/term v0.47.0 // indirect
golang.org/x/time v0.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
Expand Down
8 changes: 4 additions & 4 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/compress v1.20.2 h1:v5tiZvm1KGLyg5d5oXdXe3lsSPrbEUiKoXGia//I7Qo=
github.com/klauspost/compress v1.20.2/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
Expand Down Expand Up @@ -191,8 +191,8 @@ golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
Expand Down
11 changes: 11 additions & 0 deletions docs/operator-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ including backups with `quiesce: false` and CSI VolumeSnapshots. A held operatio
also defers idle sleep/wake, restart, and pod-template rollout. Requests remain
pending and reconciliation resumes after the operation's cleanup completes.

For a quiesced backup of a running workload, an initially absent, non-Running or
terminating source Pod gets a two-minute startup grace period from the Backup's
creation time. If it is still unavailable afterward, the Backup fails and runs
normal cleanup to release its lifecycle owner and workload fence. A source that
is already Running can be admitted after that window; prepared source identity
and outstanding worker or save-on obligations retain their existing safety checks.

The GameServer annotation `operations.gameplane.local/owner` identifies the
operation kind, name, UID, admitted generation and target UID. A wipe is identified
by the GameServer UID and its request token. The existing backup, restore and wipe
Expand All @@ -26,6 +33,10 @@ finalizers cannot establish that a worker has stopped. A failed or canceled rest
leaves its target suspended for inspection.

Upgrades recognize existing owner annotations and import backup recovery lineage.
If that legacy record lacks the original Pod UID, cleanup cannot safely address
save-on to a running Pod. Its owner remains held until an uncached read proves
the game Pod absent or terminal and not terminating. An available agent or a
replacement Pod does not establish this proof; no unbound save-on is sent.
Already-running legacy quiesced copies continue being observed until their workers
finish and cleanup succeeds. Legacy raw workers also block new admission. Conflicting
legacy guards fail closed until their existing operations finish cleanup. Keep a
Expand Down
3 changes: 3 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.10.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
Expand Down Expand Up @@ -892,6 +893,7 @@ golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
Expand Down Expand Up @@ -982,6 +984,7 @@ golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
Expand Down
Loading
Loading