Support declared graceful stop and steam.update probe

- LifecycleActionTemplate 新增 gracefulStop:stop 动作可声明插件自有的优雅关闭命令、
  参数、环境、超时与 fallback;超时且 fallback=report 时任务失败,避免默认杀进程。
- 依赖探针新增 steam.update:通过 steamcmd +app_info_print 读取公开分支 buildid,
  与本地 appmanifest_<appid>.acf 比较,输出 installed/latest/update=yes|no|unknown。
- 依赖执行输入新增 ServerRoot,供探针定位插件声明的服务器安装目录。
This commit is contained in:
npc0-hue
2026-09-15 13:31:36 +08:00
parent a695aafd0f
commit 4e88199d18
7 changed files with 464 additions and 26 deletions
+72 -1
View File
@@ -92,7 +92,7 @@ func TestDependencyInstallExecutesTypedPlanAndResumesCompletedSteps(t *testing.T
first := worker.executeDependencyJob(context.Background(), assignment)
if first.State != lifecycleResultStateSucceeded || first.ExecutionResult.Kind != "dependency.install" || first.ExecutionResult.Checksum != dependencyTestDigest {
state, evidence, probeErr := worker.executor.runDependencyProbe(context.Background(), client.dependencyInput.Probe, client.dependencyInput.Bindings)
state, evidence, probeErr := worker.executor.runDependencyProbe(context.Background(), client.dependencyInput.Probe, client.dependencyInput.Bindings, client.dependencyInput.ServerRoot)
t.Fatalf("expected real typed dependency install, got %+v calls=%+v present=%v probe=%s evidence=%s err=%v", first, runner.calls, runner.present, state, evidence, probeErr)
}
second := worker.executeDependencyJob(context.Background(), assignment)
@@ -165,6 +165,77 @@ func TestDependencyInstallRejectsLegacySteamCMDAppAdapter(t *testing.T) {
}
}
type steamProbeSupervisor struct {
output string
exit int
calls [][]string
}
func (supervisor *steamProbeSupervisor) Run(_ context.Context, command ProcessCommand) (ProcessResult, error) {
supervisor.calls = append(supervisor.calls, append([]string(nil), command.Args...))
return ProcessResult{ExitCode: supervisor.exit, Stdout: supervisor.output}, nil
}
func TestSteamUpdateProbeReportsInstalledAndPublishedBuilds(t *testing.T) {
dir := t.TempDir()
steamcmdDir := filepath.Join(dir, "steamcmd")
if err := os.MkdirAll(steamcmdDir, 0o700); err != nil {
t.Fatalf("create steamcmd fixture dir: %v", err)
}
steamcmd := filepath.Join(steamcmdDir, "steamcmd.exe")
if err := os.WriteFile(steamcmd, []byte("steamcmd fixture"), 0o700); err != nil {
t.Fatalf("write steamcmd fixture: %v", err)
}
installRoot := filepath.Join(dir, "scumserver")
if err := os.MkdirAll(filepath.Join(installRoot, "steamapps"), 0o700); err != nil {
t.Fatalf("create install root fixture: %v", err)
}
manifest := "\"AppState\"\n{\n\t\"appid\"\t\t\"3792580\"\n\t\"buildid\"\t\t\"16912763\"\n}\n"
if err := os.WriteFile(filepath.Join(installRoot, "steamapps", "appmanifest_3792580.acf"), []byte(manifest), 0o600); err != nil {
t.Fatalf("write appmanifest fixture: %v", err)
}
published := "AppID : 3792580, change number : 1\n\"depots\"\n{\n\t\"buildid\"\t\t\"99999999\"\n}\n\"branches\"\n{\n\t\"public\"\n\t{\n\t\t\"buildid\"\t\t\"16919999\"\n\t}\n}\n"
runner := &steamProbeSupervisor{output: published}
executor := NewLifecycleExecutor(WithLifecycleWorkspaceRoot(t.TempDir()), WithDependencyCommandRunner(runner))
probe := protocol.DependencyProbe{Key: "scum-server-build", Kind: "steam.update", TargetKey: "steamcmd", SteamAppID: "3792580"}
state, evidence, err := executor.runDependencyProbe(context.Background(), probe, map[string]string{"steamcmd": steamcmd}, installRoot)
if err != nil {
t.Fatalf("steam update probe failed: %v", err)
}
if state != "present" || evidence != "installed=16912763 latest=16919999 update=yes" {
t.Fatalf("unexpected steam update evidence: state=%s evidence=%s", state, evidence)
}
if len(runner.calls) != 1 || !strings.Contains(strings.Join(runner.calls[0], " "), "+app_info_print 3792580") {
t.Fatalf("unexpected steamcmd invocation: %+v", runner.calls)
}
runner.output = strings.Replace(published, "16919999", "16912763", 1)
state, evidence, err = executor.runDependencyProbe(context.Background(), probe, map[string]string{"steamcmd": steamcmd}, installRoot)
if err != nil {
t.Fatalf("steam update probe failed: %v", err)
}
if state != "present" || evidence != "installed=16912763 latest=16912763 update=no" {
t.Fatalf("unexpected up-to-date evidence: state=%s evidence=%s", state, evidence)
}
if err := os.RemoveAll(filepath.Join(installRoot, "steamapps")); err != nil {
t.Fatalf("remove install root manifest: %v", err)
}
state, evidence, err = executor.runDependencyProbe(context.Background(), probe, map[string]string{"steamcmd": steamcmd}, "")
if err != nil {
t.Fatalf("steam update probe without install root failed: %v", err)
}
if state != "unknown" || evidence != "installed=none latest=16912763 update=yes" {
t.Fatalf("unexpected unknown installed build evidence: state=%s evidence=%s", state, evidence)
}
probe.SteamAppID = "3792580;rm"
if _, _, err := executor.runDependencyProbe(context.Background(), probe, map[string]string{"steamcmd": steamcmd}, installRoot); err == nil {
t.Fatal("expected invalid steam app id rejection")
}
}
func dependencyAssignment(capability string) protocol.RunJobAssignment {
assignment := workerJobAssignment(capability)
assignment.LeaseToken = "lease-dependency"