feat: support custom server deployment drafts
This commit is contained in:
@@ -18,13 +18,21 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
|
||||
plugin, endpoint, err := svc.lifecycleDependencies(create.PluginID, create.RunEndpointID)
|
||||
plugin, err := svc.store.GamePlugins().Get(create.PluginID)
|
||||
if err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
return domain.ServerLifecycleResult{}, fmt.Errorf("get plugin dependency: %w", err)
|
||||
}
|
||||
if plugin.Status != domain.GamePluginStatusInstalled {
|
||||
return domain.ServerLifecycleResult{}, validationError("plugin must be installed")
|
||||
}
|
||||
if err := validateLifecycleActionRef(plugin, domain.ServerLifecycleActionCreate); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if create.Deployment.Mode != "" {
|
||||
if err := validator.ValidatePluginCreateInputs(plugin.CreateFields, create.Deployment.CreateInputs); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
stamp := svc.now()
|
||||
instance := domain.ServerInstance{
|
||||
@@ -39,31 +47,63 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
ConfigKey: "server.properties",
|
||||
CreatedAt: stamp,
|
||||
UpdatedAt: stamp,
|
||||
Deployment: create.Deployment,
|
||||
}
|
||||
if instance.Deployment.Mode != "" {
|
||||
instance.Deployment.ProfileKey = create.ProfileKey
|
||||
instance.Deployment.RuntimeBindings = domain.CopyStringMap(create.Bindings)
|
||||
instance.Deployment.Revision = maxInt(1, instance.Deployment.Revision)
|
||||
instance.Deployment.UpdatedAt = stamp
|
||||
}
|
||||
instance.ConfigContent = buildLogicalServerConfig(instance)
|
||||
instance.ConfigChecksum = validator.BytesChecksum([]byte(instance.ConfigContent))
|
||||
instance.ConfigUpdatedAt = stamp
|
||||
if strings.TrimSpace(create.RunEndpointID) == "" {
|
||||
instance.State = domain.ServerInstanceStateDraft
|
||||
}
|
||||
if err := validator.ValidateServerInstance(instance); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if strings.TrimSpace(create.RunEndpointID) == "" {
|
||||
if err := svc.store.ServerInstances().Create(instance); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
return domain.CopyServerLifecycleResult(domain.ServerLifecycleResult{Accepted: true, Action: domain.ServerLifecycleActionCreate, Instance: instance}), nil
|
||||
}
|
||||
endpoint, err := svc.store.RunEndpoints().Get(create.RunEndpointID)
|
||||
if err != nil {
|
||||
return domain.ServerLifecycleResult{}, fmt.Errorf("get run endpoint dependency: %w", err)
|
||||
}
|
||||
if err := validator.ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityForAction(domain.ServerLifecycleActionCreate)); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if instance.Deployment.Mode != "" && !containsString(endpoint.Capabilities, domain.JobCapabilityDeploymentPlan) {
|
||||
return domain.ServerLifecycleResult{}, validationError("run endpoint missing required capability: deployment.plan.v1")
|
||||
}
|
||||
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
|
||||
}
|
||||
binding, err := svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: create.ProfileKey, Bindings: create.Bindings}, true)
|
||||
if err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
var binding domain.RuntimeBinding
|
||||
hasBinding := strings.TrimSpace(create.ProfileKey) != ""
|
||||
if hasBinding {
|
||||
binding, err = svc.buildRuntimeBinding(instance, plugin, domain.RuntimeBindingUpdate{ProfileKey: create.ProfileKey, Bindings: create.Bindings}, true)
|
||||
if err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
}
|
||||
if err := svc.store.ServerInstances().Create(instance); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if err := svc.store.RuntimeBindings().Create(binding); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
if hasBinding {
|
||||
if err := svc.store.RuntimeBindings().Create(binding); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
job, err := svc.dispatchLifecycleJob(instance, domain.ServerLifecycleActionCreate, create.IdempotencyKey)
|
||||
@@ -208,15 +248,21 @@ func (svc *CoreService) lifecycleDependencies(pluginID string, runEndpointID str
|
||||
func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, action domain.ServerLifecycleAction, idempotencyKey string) (domain.Job, error) {
|
||||
capability := domain.LifecycleCapabilityForAction(action)
|
||||
binding, err := svc.runtimeBindingForServer(instance.ID)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, repo.ErrNotFound) {
|
||||
return domain.Job{}, err
|
||||
}
|
||||
plugin, err := svc.store.GamePlugins().Get(instance.PluginID)
|
||||
if err != nil {
|
||||
return domain.Job{}, err
|
||||
}
|
||||
actionRef := binding.ProfileKey
|
||||
profile, hasProfile := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, binding.ProfileKey)
|
||||
profileKey := ""
|
||||
if err == nil {
|
||||
profileKey = binding.ProfileKey
|
||||
} else {
|
||||
profileKey = instance.Deployment.ProfileKey
|
||||
}
|
||||
actionRef := profileKey
|
||||
profile, hasProfile := runtimeLifecycleProfileForKey(plugin.RuntimeProfiles, profileKey)
|
||||
if hasProfile {
|
||||
if ref := runtimeProfileActionRef(profile.ActionRefs, action); ref != "" {
|
||||
actionRef = ref
|
||||
@@ -245,11 +291,13 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act
|
||||
Capability: capability,
|
||||
TargetKey: actionRef,
|
||||
IdempotencyKey: idempotencyKey,
|
||||
Progress: lifecycleJobProgress(instance.Deployment),
|
||||
ExecutionInput: domain.JobExecutionInput{
|
||||
WorkspaceScope: binding.ProfileKey,
|
||||
WorkspaceScope: profileKey,
|
||||
PluginID: plugin.ID,
|
||||
LifecycleOperation: lifecycleExecutionOperation(action),
|
||||
DLLExtensions: dllExtensions,
|
||||
Deployment: deploymentPlanForDispatch(instance.Deployment),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -261,6 +309,21 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func lifecycleJobProgress(deployment domain.ServerDeploymentDefinition) domain.JobProgress {
|
||||
if deployment.Mode != "" {
|
||||
return domain.JobProgress{Percent: 0, Phase: "queued", Message: "deployment queued; awaiting Run claim"}
|
||||
}
|
||||
return domain.JobProgress{}
|
||||
}
|
||||
|
||||
func deploymentPlanForDispatch(definition domain.ServerDeploymentDefinition) *domain.ServerDeploymentDefinition {
|
||||
if definition.Mode == "" {
|
||||
return nil
|
||||
}
|
||||
copy := domain.CopyServerDeploymentDefinition(definition)
|
||||
return ©
|
||||
}
|
||||
|
||||
func lifecycleExecutionOperation(action domain.ServerLifecycleAction) string {
|
||||
switch action {
|
||||
case domain.ServerLifecycleActionCreate:
|
||||
|
||||
Reference in New Issue
Block a user