Fix SCUM lifecycle install path handling
This commit is contained in:
@@ -290,7 +290,10 @@ func (svc *CoreService) CompleteRunJob(result domain.RunJobResult) (domain.RunJo
|
||||
func validateExecutionResultForJob(job domain.Job, result domain.RunJobResult) error {
|
||||
if definition := job.ExecutionInput.Deployment; definition != nil {
|
||||
receipt := result.ExecutionResult.DeploymentReceipt
|
||||
if receipt == nil || receipt.SchemaVersion != "1" || receipt.Revision != definition.Revision || receipt.Action != job.ExecutionInput.LifecycleOperation || receipt.Mode != definition.Mode || receipt.Shell != definition.Shell {
|
||||
if result.State == domain.JobStateSucceeded && definition.Mode == domain.ServerDeploymentModeCustom && receipt == nil {
|
||||
return validationError("deployment execution receipt does not match leased definition")
|
||||
}
|
||||
if receipt != nil && (receipt.SchemaVersion != "1" || receipt.Revision != definition.Revision || receipt.Action != job.ExecutionInput.LifecycleOperation || receipt.Mode != definition.Mode || receipt.Shell != definition.Shell) {
|
||||
return validationError("deployment execution receipt does not match leased definition")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,134 @@ func TestCoreServiceSCUMGuidedDeployDispatchesPluginOwnedInstallAction(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceDeploymentLifecycleFailureDoesNotRequireExecutionReceipt(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "deployment-failure-owner", DisplayName: "Deployment Failure Owner", Email: "deployment-failure@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||
endpoint, err := svc.store.RunEndpoints().Get("run-local")
|
||||
if err != nil {
|
||||
t.Fatalf("get endpoint: %v", err)
|
||||
}
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan)
|
||||
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||
t.Fatalf("enable deployment capability: %v", err)
|
||||
}
|
||||
created, err := svc.CreateServerInstanceWorkflowForSession(ownerSession, domain.ServerLifecycleCreate{
|
||||
ID: "deployment-failure", PluginID: "server.scum", RunEndpointID: "run-local", Name: "Deployment Failure", IdempotencyKey: "deployment-failure-create", ProfileKey: "local",
|
||||
Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: "C:\\scumserver", WorkingDirectory: "C:\\scumserver", CreateInputs: map[string]string{"gamePort": "27000", "maxPlayers": "128"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create deployment lifecycle server: %v", err)
|
||||
}
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: sessionToken, Capabilities: []string{domain.LifecycleCapabilityInstall}, Capacity: domain.RunCapacity{MaxJobs: 1}})
|
||||
if err != nil || !claim.HasJob || claim.Job.JobID != created.Job.ID || claim.Job.ExecutionInput.Deployment == nil {
|
||||
t.Fatalf("claim deployment lifecycle job: claim=%+v err=%v", claim, err)
|
||||
}
|
||||
if _, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "installing"}); err != nil {
|
||||
t.Fatalf("ack deployment lifecycle job: %v", err)
|
||||
}
|
||||
result, err := svc.CompleteRunJob(domain.RunJobResult{
|
||||
RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt,
|
||||
State: domain.JobStateFailed, Progress: domain.RunJobProgressReport{Percent: 100, Message: "exit status 1"}, Message: "exit status 1", ErrorCode: "lifecycle_process_failed",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed deployment lifecycle result without receipt should be terminal: %v", err)
|
||||
}
|
||||
if result.Job.State != domain.JobStateFailed {
|
||||
t.Fatalf("expected terminal failed job, got %+v", result.Job)
|
||||
}
|
||||
stored, err := svc.GetJob(claim.Job.JobID)
|
||||
if err != nil {
|
||||
t.Fatalf("get terminal failed job: %v", err)
|
||||
}
|
||||
if stored.State != domain.JobStateFailed || stored.TerminalAt.IsZero() {
|
||||
t.Fatalf("expected stored terminal failed job, got %+v", stored)
|
||||
}
|
||||
instance, err := svc.GetServerInstance(created.Instance.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get failed deployment instance: %v", err)
|
||||
}
|
||||
if instance.State != domain.ServerInstanceStateFailed {
|
||||
t.Fatalf("expected failed deployment lifecycle to project instance failed, got %+v", instance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceGuidedPluginLifecycleSuccessDoesNotRequireExecutionReceipt(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "guided-success-owner", DisplayName: "Guided Success Owner", Email: "guided-success@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||
endpoint, err := svc.store.RunEndpoints().Get("run-local")
|
||||
if err != nil {
|
||||
t.Fatalf("get endpoint: %v", err)
|
||||
}
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan)
|
||||
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||
t.Fatalf("enable deployment capability: %v", err)
|
||||
}
|
||||
created, err := svc.CreateServerInstanceWorkflowForSession(ownerSession, domain.ServerLifecycleCreate{
|
||||
ID: "guided-success", PluginID: "server.scum", RunEndpointID: "run-local", Name: "Guided Success", IdempotencyKey: "guided-success-create", ProfileKey: "local",
|
||||
Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, ServerRoot: "C:\\scumserver", WorkingDirectory: "C:\\scumserver", CreateInputs: map[string]string{"gamePort": "27000", "maxPlayers": "128"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create guided lifecycle server: %v", err)
|
||||
}
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: sessionToken, Capabilities: []string{domain.LifecycleCapabilityInstall}, Capacity: domain.RunCapacity{MaxJobs: 1}})
|
||||
if err != nil || !claim.HasJob || claim.Job.JobID != created.Job.ID || claim.Job.ExecutionInput.Deployment == nil {
|
||||
t.Fatalf("claim guided lifecycle job: claim=%+v err=%v", claim, err)
|
||||
}
|
||||
if _, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "installing"}); err != nil {
|
||||
t.Fatalf("ack guided lifecycle job: %v", err)
|
||||
}
|
||||
if _, err := svc.CompleteRunJob(domain.RunJobResult{
|
||||
RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt,
|
||||
State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100, Message: "done"}, Message: "done", ResultRef: "artifact://jobs/guided-success/lifecycle-result",
|
||||
}); err != nil {
|
||||
t.Fatalf("guided plugin lifecycle success without receipt should be terminal: %v", err)
|
||||
}
|
||||
instance, err := svc.GetServerInstance(created.Instance.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get ready guided instance: %v", err)
|
||||
}
|
||||
if instance.State != domain.ServerInstanceStateReady {
|
||||
t.Fatalf("expected guided plugin lifecycle success to mark ready, got %+v", instance)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoreServiceCustomCommandLifecycleSuccessRequiresExecutionReceipt(t *testing.T) {
|
||||
svc, sessionToken := newLifecycleRunService(t)
|
||||
createLifecyclePlugin(t, svc)
|
||||
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "custom-receipt-owner", DisplayName: "Custom Receipt Owner", Email: "custom-receipt@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||
endpoint, err := svc.store.RunEndpoints().Get("run-local")
|
||||
if err != nil {
|
||||
t.Fatalf("get endpoint: %v", err)
|
||||
}
|
||||
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan)
|
||||
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||
t.Fatalf("enable deployment capability: %v", err)
|
||||
}
|
||||
created, err := svc.CreateServerInstanceWorkflowForSession(ownerSession, domain.ServerLifecycleCreate{
|
||||
ID: "custom-receipt", PluginID: "server.scum", RunEndpointID: "run-local", Name: "Custom Receipt", IdempotencyKey: "custom-receipt-create", ProfileKey: "local",
|
||||
Deployment: domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeCustom, ServerRoot: "C:\\custom", WorkingDirectory: "C:\\custom", StartCommand: "server.exe"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create custom lifecycle server: %v", err)
|
||||
}
|
||||
claim, err := svc.ClaimRunJob(domain.RunJobClaim{RunEndpointID: "run-local", SessionToken: sessionToken, Capabilities: []string{domain.LifecycleCapabilityInstall}, Capacity: domain.RunCapacity{MaxJobs: 1}})
|
||||
if err != nil || !claim.HasJob || claim.Job.JobID != created.Job.ID || claim.Job.ExecutionInput.Deployment == nil {
|
||||
t.Fatalf("claim custom lifecycle job: claim=%+v err=%v", claim, err)
|
||||
}
|
||||
if _, err := svc.AckRunJob(domain.RunJobAck{RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt, Message: "installing"}); err != nil {
|
||||
t.Fatalf("ack custom lifecycle job: %v", err)
|
||||
}
|
||||
_, err = svc.CompleteRunJob(domain.RunJobResult{
|
||||
RunEndpointID: "run-local", SessionToken: sessionToken, JobID: claim.Job.JobID, LeaseToken: claim.Job.LeaseToken, Attempt: claim.Job.Attempt,
|
||||
State: domain.JobStateSucceeded, Progress: domain.RunJobProgressReport{Percent: 100, Message: "done"}, Message: "done",
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "deployment execution receipt") {
|
||||
t.Fatalf("expected custom-command success without receipt rejection, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDeploymentPreservesOmittedShellAndClearsOnlyExplicitFields(t *testing.T) {
|
||||
current := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeCustom, ServerRoot: "/srv/game", StartCommand: "./start", StopCommand: "./stop", Shell: domain.ServerCommandShellCmd}
|
||||
preserved := mergeDeploymentDefinition(current, domain.ServerDeploymentUpdate{Mode: domain.ServerDeploymentModeCustom})
|
||||
|
||||
Reference in New Issue
Block a user