fix server creation run binding
This commit is contained in:
@@ -783,14 +783,22 @@ func TestServerLifecycleWorkflowAPI(t *testing.T) {
|
||||
created := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{
|
||||
ID: "server-create",
|
||||
PluginID: "server.scum",
|
||||
RunEndpointID: "run-local",
|
||||
Name: "SCUM Create",
|
||||
IdempotencyKey: "idem-create",
|
||||
ProfileKey: "local",
|
||||
}, adminSession)
|
||||
if created.Action != domain.ServerLifecycleActionCreate || created.Instance.State != domain.ServerInstanceStateInstalling || created.Job.Capability != domain.LifecycleCapabilityInstall {
|
||||
if created.Action != domain.ServerLifecycleActionCreate || created.Instance.State != domain.ServerInstanceStateDraft || created.Job.ID != "" || created.Instance.RunEndpointID != "" {
|
||||
t.Fatalf("expected create workflow response, got %+v", created)
|
||||
}
|
||||
legacyCreate := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{
|
||||
ID: "server-create-legacy",
|
||||
PluginID: "server.scum",
|
||||
RunEndpointID: "run-local",
|
||||
Name: "SCUM Legacy Create",
|
||||
IdempotencyKey: "idem-create-legacy",
|
||||
ProfileKey: "local",
|
||||
Bindings: map[string]string{"server-root": "runtime.server-root"},
|
||||
}, adminSession)
|
||||
assertErrorResponse(t, legacyCreate, http.StatusBadRequest, errorCodeValidation)
|
||||
|
||||
ready := postJSONWithAuth[dto.ServerInstanceResponse](t, router, "/api/v1/server-instances", dto.ServerInstanceCreateRequest{
|
||||
ID: "server-ready",
|
||||
@@ -844,7 +852,7 @@ func TestServerDeploymentRevealAPIIsExplicitAndOwnerScoped(t *testing.T) {
|
||||
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)
|
||||
postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "deployment-reveal", PluginID: "server.scum", Name: "Reveal", IdempotencyKey: "deployment-reveal", 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)
|
||||
@@ -852,8 +860,8 @@ func TestServerDeploymentRevealAPIIsExplicitAndOwnerScoped(t *testing.T) {
|
||||
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)
|
||||
if redacted.LatestDispatch != nil {
|
||||
t.Fatalf("create-time deployment settings must not dispatch to Run, got %+v", redacted.LatestDispatch)
|
||||
}
|
||||
|
||||
revealed := getJSONWithAuth[dto.ServerDeploymentRevealResponse](t, router, "/api/v1/server-instances/deployment-reveal/deployment/reveal", adminSession)
|
||||
@@ -1631,10 +1639,8 @@ func TestRuntimeBindingAPIIsAuthorizedValidatedAndRedacted(t *testing.T) {
|
||||
undeclared := requestJSONWithAuth(t, router, http.MethodPut, "/api/v1/server-instances/"+server.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{"host.socket": "runtime.socket"}}, adminSession)
|
||||
assertErrorResponse(t, undeclared, http.StatusBadRequest, errorCodeValidation)
|
||||
|
||||
created := postOKJSONWithAuth[dto.ServerLifecycleResponse](t, router, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "runtime-create-complete", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Create Complete", IdempotencyKey: "runtime-create-complete", ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root", "rcon.password": "secret://runtime-create-complete/rcon"}}, adminSession)
|
||||
if created.Job.TargetKey != "actions/install.json" {
|
||||
t.Fatalf("create workflow did not dispatch selected profile: %+v", created.Job)
|
||||
}
|
||||
legacyCreate := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "runtime-create-complete", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Create Complete", IdempotencyKey: "runtime-create-complete", ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root", "rcon.password": "secret://runtime-create-complete/rcon"}}, adminSession)
|
||||
assertErrorResponse(t, legacyCreate, http.StatusBadRequest, errorCodeValidation)
|
||||
incompleteCreate := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/workflows/create", dto.ServerLifecycleCreateRequest{ID: "runtime-create-incomplete", PluginID: registration.Manifest.ID, RunEndpointID: "run-local", Name: "Runtime Create Incomplete", IdempotencyKey: "runtime-create-incomplete", ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root"}}, adminSession)
|
||||
assertErrorResponse(t, incompleteCreate, http.StatusBadRequest, errorCodeValidation)
|
||||
missingServer := requestWithAuth(t, router, http.MethodGet, "/api/v1/server-instances/runtime-create-incomplete", "", adminSession)
|
||||
|
||||
@@ -137,7 +137,7 @@ Artifact bridge execution returns safe metadata and platform content routes only
|
||||
|
||||
## Implemented Server Lifecycle Actions
|
||||
|
||||
- `POST /api/v1/server-instances/workflows/create`: accepts `ServerLifecycleCreateRequest`. Creation starts from `pluginId` and `name`, and may include the create-wizard deployment definition such as deployment mode, plugin create inputs, server root, or custom start command. The browser must not require a deployment target, run endpoint, or runtime profile at creation; those bindings remain post-creation/runtime-registration concerns. Legacy `runEndpointId` and `deploymentTargetId` inputs remain accepted for compatible programmatic flows, but are never prerequisites for creation and do not select a distribution builder.
|
||||
- `POST /api/v1/server-instances/workflows/create`: accepts `ServerLifecycleCreateRequest`. Creation starts from `pluginId` and `name`, and may include the create-wizard deployment definition such as deployment mode, plugin create inputs, server root, or custom start command. The browser must not require or submit a deployment target, run endpoint, runtime profile, or runtime bindings during creation; those bindings remain post-creation/runtime-registration concerns. Requests that include `deploymentTargetId`, `runEndpointId`, `profileKey`, `bindings`, `deployment.runEndpointId`, `deployment.profileKey`, or `deployment.runtimeBindings` are rejected.
|
||||
- `POST /api/v1/server-instances/{id}/start`: accept `ServerLifecycleCommandRequest`, validate state/config version/run capability, and queue a `process.start` job using `ServerLifecycleResponse`.
|
||||
- `POST /api/v1/server-instances/{id}/stop`: accept `ServerLifecycleCommandRequest`, validate state/config version/run capability, and queue a `process.stop` job using `ServerLifecycleResponse`.
|
||||
|
||||
|
||||
@@ -29,6 +29,10 @@ func (h *coreHandlers) serverInstanceCreateWorkflow(w http.ResponseWriter, r *ht
|
||||
writeDecodeError(w, err)
|
||||
return
|
||||
}
|
||||
if err := request.ValidateCreateOnly(); err != nil {
|
||||
writeServiceError(w, err)
|
||||
return
|
||||
}
|
||||
result, err := h.core.CreateServerInstanceWorkflowForSession(bearerToken(r), request.ToDomain())
|
||||
if err != nil {
|
||||
writeServiceError(w, err)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
"browser.local/platform/validator"
|
||||
)
|
||||
|
||||
type ServerDeploymentRequest struct {
|
||||
@@ -78,7 +80,7 @@ type ServerLifecycleCreateRequest struct {
|
||||
ID string `json:"id"`
|
||||
PluginID string `json:"pluginId"`
|
||||
DeploymentTargetID string `json:"deploymentTargetId,omitempty"`
|
||||
RunEndpointID string `json:"runEndpointId"`
|
||||
RunEndpointID string `json:"runEndpointId,omitempty"`
|
||||
Name string `json:"name"`
|
||||
OwnerUserID string `json:"ownerUserId,omitempty"`
|
||||
IdempotencyKey string `json:"idempotencyKey"`
|
||||
@@ -87,6 +89,35 @@ type ServerLifecycleCreateRequest struct {
|
||||
Deployment ServerDeploymentRequest `json:"deployment,omitempty"`
|
||||
}
|
||||
|
||||
func (request ServerLifecycleCreateRequest) ValidateCreateOnly() error {
|
||||
var violations []string
|
||||
if strings.TrimSpace(request.DeploymentTargetID) != "" {
|
||||
violations = append(violations, "deploymentTargetId must not be provided during server creation")
|
||||
}
|
||||
if strings.TrimSpace(request.RunEndpointID) != "" {
|
||||
violations = append(violations, "runEndpointId must not be provided during server creation")
|
||||
}
|
||||
if strings.TrimSpace(request.ProfileKey) != "" {
|
||||
violations = append(violations, "profileKey must not be provided during server creation")
|
||||
}
|
||||
if len(request.Bindings) > 0 {
|
||||
violations = append(violations, "bindings must not be provided during server creation")
|
||||
}
|
||||
if strings.TrimSpace(request.Deployment.RunEndpointID) != "" {
|
||||
violations = append(violations, "deployment.runEndpointId must not be provided during server creation")
|
||||
}
|
||||
if strings.TrimSpace(request.Deployment.ProfileKey) != "" {
|
||||
violations = append(violations, "deployment.profileKey must not be provided during server creation")
|
||||
}
|
||||
if len(request.Deployment.RuntimeBindings) > 0 {
|
||||
violations = append(violations, "deployment.runtimeBindings must not be provided during server creation")
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return validator.ValidationError{Violations: violations}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerLifecycleCommandRequest struct {
|
||||
ExpectedConfigVersion int `json:"expectedConfigVersion"`
|
||||
IdempotencyKey string `json:"idempotencyKey"`
|
||||
@@ -103,13 +134,13 @@ func (request ServerLifecycleCreateRequest) ToDomain() domain.ServerLifecycleCre
|
||||
return domain.ServerLifecycleCreate{
|
||||
ID: request.ID,
|
||||
PluginID: request.PluginID,
|
||||
DeploymentTargetID: request.DeploymentTargetID,
|
||||
RunEndpointID: request.RunEndpointID,
|
||||
DeploymentTargetID: "",
|
||||
RunEndpointID: "",
|
||||
Name: request.Name,
|
||||
OwnerUserID: request.OwnerUserID,
|
||||
IdempotencyKey: request.IdempotencyKey,
|
||||
ProfileKey: request.ProfileKey,
|
||||
Bindings: domain.CopyStringMap(request.Bindings),
|
||||
ProfileKey: "",
|
||||
Bindings: nil,
|
||||
Deployment: request.Deployment.deploymentDefinition(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user