Fix SCUM generated run deployment flow

This commit is contained in:
npc0-hue
2026-07-31 14:10:30 +08:00
parent 6d71d8d232
commit f23d18ea57
11 changed files with 291 additions and 35 deletions
+38 -15
View File
@@ -88,7 +88,7 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
return domain.ServerLifecycleResult{}, err
}
var binding domain.RuntimeBinding
if strings.TrimSpace(create.ProfileKey) != "" {
if strings.TrimSpace(create.ProfileKey) != "" && deploymentNeedsCompleteRuntimeBinding(plugin, instance.Deployment) {
var bindingErr error
binding, bindingErr = svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: create.ProfileKey, Bindings: create.Bindings}, true)
if bindingErr != nil {
@@ -121,7 +121,7 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
if err != nil {
return domain.ServerLifecycleResult{}, fmt.Errorf("get run endpoint dependency: %w", err)
}
if err := validator.ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil {
if err := validateServerInstanceLifecycleDependencies(instance, plugin, endpoint, domain.ServerLifecycleActionCreate); err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityForAction(domain.ServerLifecycleActionCreate)); err != nil {
@@ -132,23 +132,14 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
instance.DeploymentProjection.PreflightState = "queued"
instance.DeploymentProjection.UpdatedAt = stamp
}
if instance.Deployment.Mode != "" && !containsString(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan) {
return domain.ServerLifecycleResult{}, validationError("run endpoint missing required capability: deployment.plan.v1")
}
if scumDeploymentCapabilityRequired(plugin, instance.Deployment) && !containsString(endpoint.Capabilities, domain.JobCapabilitySCUMDeploymentPlan) {
return domain.ServerLifecycleResult{}, validationError("run endpoint missing required capability: deployment.scum.v1")
}
if err := validateSCUMDeploymentTarget(plugin, instance.Deployment, endpoint); err != nil {
return domain.ServerLifecycleResult{}, err
}
if requiredShellCapability := deploymentShellCapability(instance.Deployment.Shell); requiredShellCapability != "" && !containsString(endpoint.Capabilities, requiredShellCapability) {
return domain.ServerLifecycleResult{}, validationError("run endpoint policy does not allow selected command shell")
}
if err := svc.validateLifecycleIdempotency(instance.RunEndpointID, create.IdempotencyKey, instance.ID, domain.LifecycleCapabilityForAction(domain.ServerLifecycleActionCreate)); err != nil {
return domain.ServerLifecycleResult{}, err
}
var binding domain.RuntimeBinding
hasBinding := strings.TrimSpace(create.ProfileKey) != ""
hasBinding := strings.TrimSpace(create.ProfileKey) != "" && deploymentNeedsCompleteRuntimeBinding(plugin, instance.Deployment)
if hasBinding {
binding, err = svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: create.ProfileKey, Bindings: create.Bindings}, true)
if err != nil {
@@ -260,7 +251,7 @@ func (svc *CoreService) dispatchExistingServerLifecycle(command domain.ServerLif
if err := validateLifecycleActionRef(plugin, action); err != nil {
return domain.ServerLifecycleResult{}, err
}
if err := validator.ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil {
if err := validateServerInstanceLifecycleDependencies(instance, plugin, endpoint, action); err != nil {
return domain.ServerLifecycleResult{}, err
}
if action == domain.ServerLifecycleActionStart {
@@ -276,8 +267,10 @@ func (svc *CoreService) dispatchExistingServerLifecycle(command domain.ServerLif
}
}
}
if err := svc.requireCompleteRuntimeBindings(instance.OwnerUserID, instance.ID, "server.lifecycle."+string(action)+".denied"); err != nil {
return domain.ServerLifecycleResult{}, err
if deploymentNeedsCompleteRuntimeBinding(plugin, instance.Deployment) {
if err := svc.requireCompleteRuntimeBindings(instance.OwnerUserID, instance.ID, "server.lifecycle."+string(action)+".denied"); err != nil {
return domain.ServerLifecycleResult{}, err
}
}
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityForAction(action)); err != nil {
return domain.ServerLifecycleResult{}, err
@@ -517,3 +510,33 @@ func lifecycleJobID(serverInstanceID string, action domain.ServerLifecycleAction
sum := sha256.Sum256([]byte(idempotencyKey))
return fmt.Sprintf("server-lifecycle:%s:%s:%s", serverInstanceID, action, hex.EncodeToString(sum[:8]))
}
func validateServerInstanceLifecycleDependencies(instance domain.ServerInstance, plugin domain.GamePlugin, endpoint domain.RunEndpoint, action domain.ServerLifecycleAction) error {
if requiredShellCapability := deploymentShellCapability(instance.Deployment.Shell); requiredShellCapability != "" && !containsString(endpoint.Capabilities, requiredShellCapability) {
return validationError("run endpoint policy does not allow selected command shell")
}
return validator.ValidateServerInstanceDependenciesForCapabilities(instance, plugin, endpoint, lifecycleRequiredRunCapabilities(instance, plugin, action))
}
func lifecycleRequiredRunCapabilities(instance domain.ServerInstance, plugin domain.GamePlugin, action domain.ServerLifecycleAction) []string {
required := append([]string(nil), domain.LifecycleCapabilityForAction(action))
if instance.Deployment.Mode != "" {
required = append(required, domain.JobCapabilityDeploymentPlan)
if scumDeploymentCapabilityRequired(plugin, instance.Deployment) {
required = append(required, domain.JobCapabilitySCUMDeploymentPlan)
}
}
return compactUniqueStrings(required)
}
func compactUniqueStrings(values []string) []string {
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value == "" || containsString(out, value) {
continue
}
out = append(out, value)
}
return out
}