feat: reveal saved server deployment inputs

This commit is contained in:
npc0-hue
2026-07-27 09:24:44 +08:00
parent e5c94be1db
commit 6c5b74b915
19 changed files with 345 additions and 31 deletions
+1
View File
@@ -68,6 +68,7 @@ func (h *coreHandlers) register(mux *http.ServeMux) {
mux.HandleFunc("/api/v1/server-instances/{id}/process/status", h.serverInstanceProcessStatus)
mux.HandleFunc("/api/v1/server-instances/{id}/runtime/actions", h.serverRuntimeActions)
mux.HandleFunc("/api/v1/server-instances/{id}/runtime-binding", h.serverRuntimeBinding)
mux.HandleFunc("/api/v1/server-instances/{id}/deployment/reveal", h.serverDeploymentReveal)
mux.HandleFunc("/api/v1/server-instances/{id}/deployment", h.serverDeployment)
mux.HandleFunc("/api/v1/server-instances/{id}/deploy", h.serverInstanceDeploy)
mux.HandleFunc("/api/v1/server-instances/{id}/remote-adapters", h.remoteAdapters)
+30
View File
@@ -719,6 +719,36 @@ func TestServerLifecycleWorkflowAPI(t *testing.T) {
assertErrorResponse(t, invalidStop, http.StatusBadRequest, errorCodeValidation)
}
func TestServerDeploymentRevealAPIIsExplicitAndOwnerScoped(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
postJSON[dto.GamePluginResponse](t, router, "/api/v1/game-plugins", validGamePluginRequest())
endpoint := validRunEndpointRequest()
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan)
postJSON[dto.RunEndpointResponse](t, router, "/api/v1/run/endpoints", endpoint)
created := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "deployment-reveal", PluginID: "server.scum", RunEndpointID: "run-local", Name: "Reveal", IdempotencyKey: "deployment-reveal", ProfileKey: "local", Deployment: dto.ServerDeploymentRequest{Mode: domain.ServerDeploymentModeCustom, ServerRoot: "/srv/reveal", WorkingDirectory: "/srv/reveal", StartCommand: "./start-server"}}, adminSession)
redactedRecorder := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/deployment-reveal/deployment", "", adminSession)
assertStatus(t, redactedRecorder, http.StatusOK)
if body := redactedRecorder.Body.String(); strings.Contains(body, "/srv/reveal") || strings.Contains(body, "./start-server") {
t.Fatalf("normal deployment view leaked protected inputs: %s", body)
}
redacted := decodeBody[dto.ServerDeploymentResponse](t, redactedRecorder)
if redacted.LatestDispatch == nil || !redacted.LatestDispatch.DeploymentDefinitionIncluded || redacted.LatestDispatch.JobID != created.Job.ID || redacted.LatestDispatch.DeploymentRevision != 1 {
t.Fatalf("expected safe deployment dispatch evidence, got %+v", redacted.LatestDispatch)
}
revealed := getJSONWithAuth[dto.ServerDeploymentRevealResponse](t, router, "/api/v1/server-instances/deployment-reveal/deployment/reveal", adminSession)
if revealed.ServerRoot != "/srv/reveal" || revealed.WorkingDirectory != "/srv/reveal" || revealed.StartCommand != "./start-server" || revealed.InstallCommand != "" {
t.Fatalf("unexpected explicitly revealed deployment: %+v", revealed)
}
postJSONWithAuth[dto.UserResponse](t, router, "/api/v1/users", dto.UserCreateRequest{ID: "deployment-other", DisplayName: "Other", Email: "deployment-other@example.test", Roles: []string{"server-owner"}, Password: "other-password"}, adminSession)
other := postOKJSON[dto.AuthSessionResponse](t, router, "/api/v1/auth/login", dto.LoginRequest{Account: "deployment-other@example.test", Password: "other-password"})
denied := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/deployment-reveal/deployment/reveal", "", other.SessionID)
assertErrorResponse(t, denied, http.StatusForbidden, errorCodeForbidden)
}
func TestServerInstanceManagementAPI(t *testing.T) {
router := newTestRouter()
adminSession := createAdminSession(t, router)
+15
View File
@@ -37,6 +37,21 @@ func (h *coreHandlers) serverInstanceCreateWorkflow(w http.ResponseWriter, r *ht
writeJSON(w, http.StatusOK, dto.ServerLifecycleFromDomain(result))
}
// serverDeploymentReveal explicitly returns saved deployment paths and commands
// to an authorized server manager. Ordinary deployment views remain redacted.
func (h *coreHandlers) serverDeploymentReveal(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeMethodNotAllowed(w, http.MethodGet)
return
}
reveal, err := h.core.RevealServerDeploymentForSession(bearerToken(r), r.PathValue("id"))
if err != nil {
writeServiceError(w, err)
return
}
writeJSON(w, http.StatusOK, dto.ServerDeploymentRevealFromDomain(reveal))
}
// serverDeployment reads safe deployment metadata or updates protected deployment input.
func (h *coreHandlers) serverDeployment(w http.ResponseWriter, r *http.Request) {
switch r.Method {