Complete SCUM controlled deployment lifecycle
This commit is contained in:
+159
-24
@@ -521,11 +521,52 @@ type RuntimeConfigTemplate struct {
|
||||
OutputRef string
|
||||
}
|
||||
|
||||
// RuntimeServerConfigMapping maps one safe plugin create field to a logical
|
||||
// game configuration key. Paths and file syntax remain Run-owned.
|
||||
type RuntimeServerConfigMapping struct {
|
||||
FieldKey string
|
||||
ConfigKey string
|
||||
ValueType string
|
||||
Required bool
|
||||
}
|
||||
|
||||
type RuntimeServerDiscoveryMarker struct {
|
||||
Key string
|
||||
Kind string
|
||||
TargetKey string
|
||||
Expected string
|
||||
Required bool
|
||||
}
|
||||
|
||||
type RuntimeServerVerificationCheck struct {
|
||||
Key string
|
||||
Kind string
|
||||
TargetKey string
|
||||
Required bool
|
||||
}
|
||||
|
||||
// RuntimeServerDeploymentProfile is a frozen, game-specific deployment
|
||||
// template. It contains logical references only; Run resolves them locally.
|
||||
type RuntimeServerDeploymentProfile struct {
|
||||
Key string
|
||||
Version string
|
||||
SupportedTargets []RuntimeTarget
|
||||
SteamAppID string
|
||||
ExecutableKey string
|
||||
InstallRootKey string
|
||||
ConfigKey string
|
||||
ConfigFormat string
|
||||
ConfigMappings []RuntimeServerConfigMapping
|
||||
DiscoveryMarkers []RuntimeServerDiscoveryMarker
|
||||
VerificationChecks []RuntimeServerVerificationCheck
|
||||
}
|
||||
|
||||
type GamePluginRuntimeProfiles struct {
|
||||
Discovery []RuntimeDiscoveryProbe
|
||||
LifecycleProfiles []RuntimeLifecycleProfile
|
||||
DependencyProbes []RuntimeDependencyProbe
|
||||
InstallPlans []RuntimeInstallPlan
|
||||
ServerDeployments []RuntimeServerDeploymentProfile
|
||||
LogSources []RuntimeLogSource
|
||||
LogEvents []RuntimeLogEvent
|
||||
TransportProfiles []RuntimeTransportProfile
|
||||
@@ -694,7 +735,24 @@ type ServerInstance struct {
|
||||
UpdatedAt time.Time
|
||||
// Deployment stores operator-supplied deployment inputs. Its protected path
|
||||
// and command values are never included in normal platform read projections.
|
||||
Deployment ServerDeploymentDefinition
|
||||
Deployment ServerDeploymentDefinition
|
||||
DeploymentProjection ServerDeploymentProjection
|
||||
}
|
||||
|
||||
type ServerDeploymentProjection struct {
|
||||
State string
|
||||
Operation string
|
||||
TemplateKey string
|
||||
TemplateVersion string
|
||||
PreflightState string
|
||||
DiscoveryState string
|
||||
MappingState string
|
||||
VerificationState string
|
||||
DiscoveredFacts map[string]string
|
||||
MappingResults map[string]string
|
||||
VerificationResults map[string]string
|
||||
FailureCode string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type ServerInstanceUpdate struct {
|
||||
@@ -803,6 +861,7 @@ type ServerDeploymentView struct {
|
||||
Shell ServerCommandShell
|
||||
Revision int
|
||||
UpdatedAt time.Time
|
||||
Projection ServerDeploymentProjection
|
||||
}
|
||||
|
||||
type ConfigDiffLine struct {
|
||||
@@ -914,6 +973,7 @@ const (
|
||||
// JobCapabilityDeploymentPlan gates Run implementations that understand
|
||||
// protected deployment definitions, absolute paths, and custom commands.
|
||||
JobCapabilityDeploymentPlan = "deployment.plan.v1"
|
||||
JobCapabilitySCUMDeploymentPlan = "deployment.scum.v1"
|
||||
JobCapabilityDeploymentShellPosix = "deployment.shell.posix-sh"
|
||||
JobCapabilityDeploymentShellPowerShell = "deployment.shell.powershell"
|
||||
JobCapabilityDeploymentShellCmd = "deployment.shell.cmd"
|
||||
@@ -944,33 +1004,64 @@ type JobRetryPolicy struct {
|
||||
}
|
||||
|
||||
type JobExecutionInput struct {
|
||||
WorkspaceScope string
|
||||
Content string
|
||||
ExpectedVersion int
|
||||
ExpectedChecksum string
|
||||
MaxReadBytes int
|
||||
RemoteAdapterKey string
|
||||
RemoteAdapterKind string
|
||||
TimeoutSeconds int
|
||||
WorkspaceScope string
|
||||
Content string
|
||||
ExpectedVersion int
|
||||
ExpectedChecksum string
|
||||
MaxReadBytes int
|
||||
RemoteAdapterKey string
|
||||
RemoteAdapterKind string
|
||||
TimeoutSeconds int
|
||||
PluginID string
|
||||
LifecycleOperation string
|
||||
TargetVersion string
|
||||
Inputs map[string]string
|
||||
DLLExtensions []RuntimeDLLExtensionPlan
|
||||
SourceRCON *RuntimeSourceRCONPlan
|
||||
Deployment *ServerDeploymentDefinition
|
||||
ServerDeploymentPlan *ServerDeploymentPlan
|
||||
}
|
||||
|
||||
type ServerDeploymentPlan struct {
|
||||
SchemaVersion string
|
||||
Operation string
|
||||
PluginID string
|
||||
LifecycleOperation string
|
||||
TargetVersion string
|
||||
Inputs map[string]string
|
||||
DLLExtensions []RuntimeDLLExtensionPlan
|
||||
SourceRCON *RuntimeSourceRCONPlan
|
||||
Deployment *ServerDeploymentDefinition
|
||||
TemplateKey string
|
||||
TemplateVersion string
|
||||
SteamAppID string
|
||||
ExecutableKey string
|
||||
InstallRootKey string
|
||||
ConfigKey string
|
||||
ConfigFormat string
|
||||
ConfigMappings []RuntimeServerConfigMapping
|
||||
DiscoveryMarkers []RuntimeServerDiscoveryMarker
|
||||
VerificationChecks []RuntimeServerVerificationCheck
|
||||
}
|
||||
|
||||
type ServerDeploymentEvidence struct {
|
||||
TemplateKey string
|
||||
TemplateVersion string
|
||||
PreflightState string
|
||||
DiscoveryState string
|
||||
MappingState string
|
||||
VerificationState string
|
||||
DiscoveredFacts map[string]string
|
||||
MappingResults map[string]string
|
||||
VerificationResults map[string]string
|
||||
FailureCode string
|
||||
}
|
||||
|
||||
type JobExecutionResult struct {
|
||||
Kind string
|
||||
ProcessState string
|
||||
ExitClassification string
|
||||
ExitCode int
|
||||
Version int
|
||||
Checksum string
|
||||
SizeBytes int64
|
||||
AuditSummary string
|
||||
Content string
|
||||
Kind string
|
||||
ProcessState string
|
||||
ExitClassification string
|
||||
ExitCode int
|
||||
Version int
|
||||
Checksum string
|
||||
SizeBytes int64
|
||||
AuditSummary string
|
||||
Content string
|
||||
ServerDeploymentEvidence *ServerDeploymentEvidence
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
@@ -1570,6 +1661,10 @@ func CopyGamePluginRuntimeProfiles(profiles GamePluginRuntimeProfiles) GamePlugi
|
||||
profiles.InstallPlans[i].Platforms = CopyStringSlice(profiles.InstallPlans[i].Platforms)
|
||||
profiles.InstallPlans[i].Steps = append([]RuntimeInstallStep(nil), profiles.InstallPlans[i].Steps...)
|
||||
}
|
||||
profiles.ServerDeployments = append([]RuntimeServerDeploymentProfile(nil), profiles.ServerDeployments...)
|
||||
for i := range profiles.ServerDeployments {
|
||||
profiles.ServerDeployments[i] = CopyRuntimeServerDeploymentProfile(profiles.ServerDeployments[i])
|
||||
}
|
||||
profiles.LogSources = append([]RuntimeLogSource(nil), profiles.LogSources...)
|
||||
profiles.LogEvents = append([]RuntimeLogEvent(nil), profiles.LogEvents...)
|
||||
profiles.TransportProfiles = append([]RuntimeTransportProfile(nil), profiles.TransportProfiles...)
|
||||
@@ -1637,9 +1732,47 @@ func CopyPluginBridgeExecuteResponse(response PluginBridgeExecuteResponse) Plugi
|
||||
func CopyServerInstance(instance ServerInstance) ServerInstance {
|
||||
instance.AdminUserIDs = CopyStringSlice(instance.AdminUserIDs)
|
||||
instance.Deployment = CopyServerDeploymentDefinition(instance.Deployment)
|
||||
instance.DeploymentProjection = CopyServerDeploymentProjection(instance.DeploymentProjection)
|
||||
return instance
|
||||
}
|
||||
|
||||
func CopyRuntimeServerDeploymentProfile(profile RuntimeServerDeploymentProfile) RuntimeServerDeploymentProfile {
|
||||
profile.SupportedTargets = append([]RuntimeTarget(nil), profile.SupportedTargets...)
|
||||
profile.ConfigMappings = append([]RuntimeServerConfigMapping(nil), profile.ConfigMappings...)
|
||||
profile.DiscoveryMarkers = append([]RuntimeServerDiscoveryMarker(nil), profile.DiscoveryMarkers...)
|
||||
profile.VerificationChecks = append([]RuntimeServerVerificationCheck(nil), profile.VerificationChecks...)
|
||||
return profile
|
||||
}
|
||||
|
||||
func CopyServerDeploymentProjection(projection ServerDeploymentProjection) ServerDeploymentProjection {
|
||||
projection.DiscoveredFacts = CopyStringMap(projection.DiscoveredFacts)
|
||||
projection.MappingResults = CopyStringMap(projection.MappingResults)
|
||||
projection.VerificationResults = CopyStringMap(projection.VerificationResults)
|
||||
return projection
|
||||
}
|
||||
|
||||
func CopyServerDeploymentPlan(plan *ServerDeploymentPlan) *ServerDeploymentPlan {
|
||||
if plan == nil {
|
||||
return nil
|
||||
}
|
||||
copy := *plan
|
||||
copy.ConfigMappings = append([]RuntimeServerConfigMapping(nil), plan.ConfigMappings...)
|
||||
copy.DiscoveryMarkers = append([]RuntimeServerDiscoveryMarker(nil), plan.DiscoveryMarkers...)
|
||||
copy.VerificationChecks = append([]RuntimeServerVerificationCheck(nil), plan.VerificationChecks...)
|
||||
return ©
|
||||
}
|
||||
|
||||
func CopyServerDeploymentEvidence(evidence *ServerDeploymentEvidence) *ServerDeploymentEvidence {
|
||||
if evidence == nil {
|
||||
return nil
|
||||
}
|
||||
copy := *evidence
|
||||
copy.DiscoveredFacts = CopyStringMap(evidence.DiscoveredFacts)
|
||||
copy.MappingResults = CopyStringMap(evidence.MappingResults)
|
||||
copy.VerificationResults = CopyStringMap(evidence.VerificationResults)
|
||||
return ©
|
||||
}
|
||||
|
||||
func CopyServerDeploymentDefinition(definition ServerDeploymentDefinition) ServerDeploymentDefinition {
|
||||
definition.RuntimeBindings = CopyStringMap(definition.RuntimeBindings)
|
||||
definition.CreateInputs = CopyStringMap(definition.CreateInputs)
|
||||
@@ -1713,6 +1846,8 @@ func CopyJob(job Job) Job {
|
||||
job.ExecutionInput.Inputs = CopyStringMap(job.ExecutionInput.Inputs)
|
||||
job.ExecutionInput.DLLExtensions = append([]RuntimeDLLExtensionPlan(nil), job.ExecutionInput.DLLExtensions...)
|
||||
job.ExecutionInput.SourceRCON = CopyRuntimeSourceRCONPlan(job.ExecutionInput.SourceRCON)
|
||||
job.ExecutionInput.ServerDeploymentPlan = CopyServerDeploymentPlan(job.ExecutionInput.ServerDeploymentPlan)
|
||||
job.ExecutionResult.ServerDeploymentEvidence = CopyServerDeploymentEvidence(job.ExecutionResult.ServerDeploymentEvidence)
|
||||
if job.ExecutionInput.Deployment != nil {
|
||||
copy := CopyServerDeploymentDefinition(*job.ExecutionInput.Deployment)
|
||||
job.ExecutionInput.Deployment = ©
|
||||
|
||||
@@ -111,6 +111,13 @@ Run sessions persist only a token hash, generation, status, expiry, capability f
|
||||
|
||||
Installed `GamePlugin` records persist the validated manifest `runtimeProfiles` contract, including discovery, lifecycle, dependency/install, log, transport, and client-manager declarations. One server binding selects one declared lifecycle profile. Platform derives allowed and required logical keys; clients cannot assert `missingKeys` or `status`.
|
||||
|
||||
SCUM plugins may additionally declare `runtimeProfiles.serverDeployments`. These
|
||||
profiles are versioned, freeze into a leased Run assignment, and contain only
|
||||
logical executable/config markers, field mappings, discovery markers, and
|
||||
verification checks. A server's `DeploymentProjection` stores bounded evidence
|
||||
from Run for operator views; protected paths, commands, credentials, process
|
||||
ids, and sockets never enter that projection.
|
||||
|
||||
Bindings are used for action gating and future run-side profile resolution. File and MySQL metadata snapshots include them so a platform restart does not make a configured server appear complete or lose its selected profile. API responses expose only logical key names, configured/secret-backed flags, missing keys, and safe reasons. They never expose stored binding values, raw host paths, direct sockets, FTP/RCON passwords, SQL DSNs, component auth keys, or internal secret locations.
|
||||
|
||||
## Runtime Component Keys And Distributions
|
||||
|
||||
+88
-26
@@ -92,21 +92,51 @@ type RunJobResultRequest struct {
|
||||
}
|
||||
|
||||
type RunJobExecutionInputBody struct {
|
||||
WorkspaceScope string `json:"workspaceScope,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ExpectedVersion int `json:"expectedVersion,omitempty"`
|
||||
ExpectedChecksum string `json:"expectedChecksum,omitempty"`
|
||||
MaxReadBytes int `json:"maxReadBytes,omitempty"`
|
||||
RemoteAdapterKey string `json:"remoteAdapterKey,omitempty"`
|
||||
RemoteAdapterKind string `json:"remoteAdapterKind,omitempty"`
|
||||
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
||||
PluginID string `json:"pluginId,omitempty"`
|
||||
LifecycleOperation string `json:"lifecycleOperation,omitempty"`
|
||||
TargetVersion string `json:"targetVersion,omitempty"`
|
||||
Inputs map[string]string `json:"inputs,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionPlanBody `json:"dllExtensions,omitempty"`
|
||||
SourceRCON *RuntimeSourceRCONPlanBody `json:"sourceRcon,omitempty"`
|
||||
Deployment *ServerDeploymentExecutionBody `json:"deployment,omitempty"`
|
||||
WorkspaceScope string `json:"workspaceScope,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ExpectedVersion int `json:"expectedVersion,omitempty"`
|
||||
ExpectedChecksum string `json:"expectedChecksum,omitempty"`
|
||||
MaxReadBytes int `json:"maxReadBytes,omitempty"`
|
||||
RemoteAdapterKey string `json:"remoteAdapterKey,omitempty"`
|
||||
RemoteAdapterKind string `json:"remoteAdapterKind,omitempty"`
|
||||
TimeoutSeconds int `json:"timeoutSeconds,omitempty"`
|
||||
PluginID string `json:"pluginId,omitempty"`
|
||||
LifecycleOperation string `json:"lifecycleOperation,omitempty"`
|
||||
TargetVersion string `json:"targetVersion,omitempty"`
|
||||
Inputs map[string]string `json:"inputs,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionPlanBody `json:"dllExtensions,omitempty"`
|
||||
SourceRCON *RuntimeSourceRCONPlanBody `json:"sourceRcon,omitempty"`
|
||||
Deployment *ServerDeploymentExecutionBody `json:"deployment,omitempty"`
|
||||
ServerDeploymentPlan *ServerDeploymentPlanBody `json:"serverDeploymentPlan,omitempty"`
|
||||
}
|
||||
|
||||
type ServerDeploymentPlanBody struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
Operation string `json:"operation"`
|
||||
PluginID string `json:"pluginId"`
|
||||
TemplateKey string `json:"templateKey"`
|
||||
TemplateVersion string `json:"templateVersion"`
|
||||
SteamAppID string `json:"steamAppId"`
|
||||
ExecutableKey string `json:"executableKey"`
|
||||
InstallRootKey string `json:"installRootKey"`
|
||||
ConfigKey string `json:"configKey"`
|
||||
ConfigFormat string `json:"configFormat"`
|
||||
ConfigMappings []RuntimeServerConfigMappingBody `json:"configMappings"`
|
||||
DiscoveryMarkers []RuntimeServerDiscoveryMarkerBody `json:"discoveryMarkers"`
|
||||
VerificationChecks []RuntimeServerVerificationCheckBody `json:"verificationChecks"`
|
||||
}
|
||||
|
||||
type ServerDeploymentEvidenceBody struct {
|
||||
TemplateKey string `json:"templateKey,omitempty"`
|
||||
TemplateVersion string `json:"templateVersion,omitempty"`
|
||||
PreflightState string `json:"preflightState,omitempty"`
|
||||
DiscoveryState string `json:"discoveryState,omitempty"`
|
||||
MappingState string `json:"mappingState,omitempty"`
|
||||
VerificationState string `json:"verificationState,omitempty"`
|
||||
DiscoveredFacts map[string]string `json:"discoveredFacts,omitempty"`
|
||||
MappingResults map[string]string `json:"mappingResults,omitempty"`
|
||||
VerificationResults map[string]string `json:"verificationResults,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
}
|
||||
|
||||
// ServerDeploymentExecutionBody is included only in a leased Run assignment.
|
||||
@@ -136,15 +166,16 @@ type RuntimeSourceRCONPlanBody struct {
|
||||
}
|
||||
|
||||
type RunJobExecutionResultBody struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
ProcessState string `json:"processState,omitempty"`
|
||||
ExitClassification string `json:"exitClassification,omitempty"`
|
||||
ExitCode int `json:"exitCode,omitempty"`
|
||||
Version int `json:"version,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
||||
AuditSummary string `json:"auditSummary,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
ProcessState string `json:"processState,omitempty"`
|
||||
ExitClassification string `json:"exitClassification,omitempty"`
|
||||
ExitCode int `json:"exitCode,omitempty"`
|
||||
Version int `json:"version,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
||||
AuditSummary string `json:"auditSummary,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ServerDeploymentEvidence *ServerDeploymentEvidenceBody `json:"serverDeploymentEvidence,omitempty"`
|
||||
}
|
||||
|
||||
type RunJobResultResponse struct {
|
||||
@@ -377,7 +408,7 @@ func (request RunJobResultRequest) ToDomain() domain.RunJobResult {
|
||||
Message: request.Message,
|
||||
ErrorCode: request.ErrorCode,
|
||||
Retryable: request.Retryable,
|
||||
ExecutionResult: domain.JobExecutionResult{Kind: request.ExecutionResult.Kind, ProcessState: request.ExecutionResult.ProcessState, ExitClassification: request.ExecutionResult.ExitClassification, ExitCode: request.ExecutionResult.ExitCode, Version: request.ExecutionResult.Version, Checksum: request.ExecutionResult.Checksum, SizeBytes: request.ExecutionResult.SizeBytes, AuditSummary: request.ExecutionResult.AuditSummary, Content: request.ExecutionResult.Content},
|
||||
ExecutionResult: domain.JobExecutionResult{Kind: request.ExecutionResult.Kind, ProcessState: request.ExecutionResult.ProcessState, ExitClassification: request.ExecutionResult.ExitClassification, ExitCode: request.ExecutionResult.ExitCode, Version: request.ExecutionResult.Version, Checksum: request.ExecutionResult.Checksum, SizeBytes: request.ExecutionResult.SizeBytes, AuditSummary: request.ExecutionResult.AuditSummary, Content: request.ExecutionResult.Content, ServerDeploymentEvidence: serverDeploymentEvidenceToDomain(request.ExecutionResult.ServerDeploymentEvidence)},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +614,7 @@ func RunJobAssignmentFromDomain(assignment domain.RunJobAssignment) RunJobAssign
|
||||
State: assignment.State,
|
||||
Progress: progressReportFromDomain(assignment.Progress),
|
||||
ResultRef: assignment.ResultRef,
|
||||
ExecutionInput: RunJobExecutionInputBody{WorkspaceScope: assignment.ExecutionInput.WorkspaceScope, Content: assignment.ExecutionInput.Content, ExpectedVersion: assignment.ExecutionInput.ExpectedVersion, ExpectedChecksum: assignment.ExecutionInput.ExpectedChecksum, MaxReadBytes: assignment.ExecutionInput.MaxReadBytes, RemoteAdapterKey: assignment.ExecutionInput.RemoteAdapterKey, RemoteAdapterKind: assignment.ExecutionInput.RemoteAdapterKind, TimeoutSeconds: assignment.ExecutionInput.TimeoutSeconds, PluginID: assignment.ExecutionInput.PluginID, LifecycleOperation: assignment.ExecutionInput.LifecycleOperation, TargetVersion: assignment.ExecutionInput.TargetVersion, Inputs: domain.CopyStringMap(assignment.ExecutionInput.Inputs), DLLExtensions: dllExtensionPlansFromDomain(assignment.ExecutionInput.DLLExtensions), SourceRCON: runtimeSourceRCONPlanFromDomain(assignment.ExecutionInput.SourceRCON), Deployment: deploymentExecutionFromDomain(assignment.ExecutionInput.Deployment)},
|
||||
ExecutionInput: RunJobExecutionInputBody{WorkspaceScope: assignment.ExecutionInput.WorkspaceScope, Content: assignment.ExecutionInput.Content, ExpectedVersion: assignment.ExecutionInput.ExpectedVersion, ExpectedChecksum: assignment.ExecutionInput.ExpectedChecksum, MaxReadBytes: assignment.ExecutionInput.MaxReadBytes, RemoteAdapterKey: assignment.ExecutionInput.RemoteAdapterKey, RemoteAdapterKind: assignment.ExecutionInput.RemoteAdapterKind, TimeoutSeconds: assignment.ExecutionInput.TimeoutSeconds, PluginID: assignment.ExecutionInput.PluginID, LifecycleOperation: assignment.ExecutionInput.LifecycleOperation, TargetVersion: assignment.ExecutionInput.TargetVersion, Inputs: domain.CopyStringMap(assignment.ExecutionInput.Inputs), DLLExtensions: dllExtensionPlansFromDomain(assignment.ExecutionInput.DLLExtensions), SourceRCON: runtimeSourceRCONPlanFromDomain(assignment.ExecutionInput.SourceRCON), Deployment: deploymentExecutionFromDomain(assignment.ExecutionInput.Deployment), ServerDeploymentPlan: serverDeploymentPlanFromDomain(assignment.ExecutionInput.ServerDeploymentPlan)},
|
||||
LeaseToken: assignment.LeaseToken,
|
||||
Attempt: assignment.Attempt,
|
||||
MaxAttempts: assignment.MaxAttempts,
|
||||
@@ -596,6 +627,37 @@ func RunJobAssignmentFromDomain(assignment domain.RunJobAssignment) RunJobAssign
|
||||
}
|
||||
}
|
||||
|
||||
func serverDeploymentPlanFromDomain(plan *domain.ServerDeploymentPlan) *ServerDeploymentPlanBody {
|
||||
if plan == nil {
|
||||
return nil
|
||||
}
|
||||
body := &ServerDeploymentPlanBody{SchemaVersion: plan.SchemaVersion, Operation: plan.Operation, PluginID: plan.PluginID, TemplateKey: plan.TemplateKey, TemplateVersion: plan.TemplateVersion, SteamAppID: plan.SteamAppID, ExecutableKey: plan.ExecutableKey, InstallRootKey: plan.InstallRootKey, ConfigKey: plan.ConfigKey, ConfigFormat: plan.ConfigFormat}
|
||||
for _, mapping := range plan.ConfigMappings {
|
||||
body.ConfigMappings = append(body.ConfigMappings, RuntimeServerConfigMappingBody{FieldKey: mapping.FieldKey, ConfigKey: mapping.ConfigKey, ValueType: mapping.ValueType, Required: mapping.Required})
|
||||
}
|
||||
for _, marker := range plan.DiscoveryMarkers {
|
||||
body.DiscoveryMarkers = append(body.DiscoveryMarkers, RuntimeServerDiscoveryMarkerBody{Key: marker.Key, Kind: marker.Kind, TargetKey: marker.TargetKey, Expected: marker.Expected, Required: marker.Required})
|
||||
}
|
||||
for _, check := range plan.VerificationChecks {
|
||||
body.VerificationChecks = append(body.VerificationChecks, RuntimeServerVerificationCheckBody{Key: check.Key, Kind: check.Kind, TargetKey: check.TargetKey, Required: check.Required})
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
func serverDeploymentEvidenceToDomain(body *ServerDeploymentEvidenceBody) *domain.ServerDeploymentEvidence {
|
||||
if body == nil {
|
||||
return nil
|
||||
}
|
||||
return &domain.ServerDeploymentEvidence{TemplateKey: body.TemplateKey, TemplateVersion: body.TemplateVersion, PreflightState: body.PreflightState, DiscoveryState: body.DiscoveryState, MappingState: body.MappingState, VerificationState: body.VerificationState, DiscoveredFacts: domain.CopyStringMap(body.DiscoveredFacts), MappingResults: domain.CopyStringMap(body.MappingResults), VerificationResults: domain.CopyStringMap(body.VerificationResults), FailureCode: body.FailureCode}
|
||||
}
|
||||
|
||||
func serverDeploymentEvidenceFromDomain(evidence *domain.ServerDeploymentEvidence) *ServerDeploymentEvidenceBody {
|
||||
if evidence == nil {
|
||||
return nil
|
||||
}
|
||||
return &ServerDeploymentEvidenceBody{TemplateKey: evidence.TemplateKey, TemplateVersion: evidence.TemplateVersion, PreflightState: evidence.PreflightState, DiscoveryState: evidence.DiscoveryState, MappingState: evidence.MappingState, VerificationState: evidence.VerificationState, DiscoveredFacts: domain.CopyStringMap(evidence.DiscoveredFacts), MappingResults: domain.CopyStringMap(evidence.MappingResults), VerificationResults: domain.CopyStringMap(evidence.VerificationResults), FailureCode: evidence.FailureCode}
|
||||
}
|
||||
|
||||
func deploymentExecutionFromDomain(definition *domain.ServerDeploymentDefinition) *ServerDeploymentExecutionBody {
|
||||
if definition == nil {
|
||||
return nil
|
||||
|
||||
+40
-37
@@ -492,20 +492,21 @@ type ServerMemberListResponse struct {
|
||||
}
|
||||
|
||||
type ServerInstanceResponse struct {
|
||||
ID string `json:"id"`
|
||||
PluginID string `json:"pluginId"`
|
||||
PluginVersion string `json:"pluginVersion"`
|
||||
RunEndpointID string `json:"runEndpointId"`
|
||||
Name string `json:"name"`
|
||||
OwnerUserID string `json:"ownerUserId,omitempty"`
|
||||
AdminUserIDs []string `json:"adminUserIds"`
|
||||
State domain.ServerInstanceState `json:"state"`
|
||||
ConfigVersion int `json:"configVersion"`
|
||||
ConfigKey string `json:"configKey,omitempty"`
|
||||
ConfigChecksum string `json:"configChecksum,omitempty"`
|
||||
ConfigUpdatedAt *time.Time `json:"configUpdatedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID string `json:"id"`
|
||||
PluginID string `json:"pluginId"`
|
||||
PluginVersion string `json:"pluginVersion"`
|
||||
RunEndpointID string `json:"runEndpointId"`
|
||||
Name string `json:"name"`
|
||||
OwnerUserID string `json:"ownerUserId,omitempty"`
|
||||
AdminUserIDs []string `json:"adminUserIds"`
|
||||
State domain.ServerInstanceState `json:"state"`
|
||||
ConfigVersion int `json:"configVersion"`
|
||||
ConfigKey string `json:"configKey,omitempty"`
|
||||
ConfigChecksum string `json:"configChecksum,omitempty"`
|
||||
ConfigUpdatedAt *time.Time `json:"configUpdatedAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeploymentProjection ServerDeploymentProjectionBody `json:"deploymentProjection,omitempty"`
|
||||
}
|
||||
|
||||
type ServerInstanceListResponse struct {
|
||||
@@ -708,14 +709,15 @@ type JobResponse struct {
|
||||
}
|
||||
|
||||
type JobExecutionResultResponse struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
ProcessState string `json:"processState,omitempty"`
|
||||
ExitClassification string `json:"exitClassification,omitempty"`
|
||||
ExitCode int `json:"exitCode,omitempty"`
|
||||
Version int `json:"version,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
||||
AuditSummary string `json:"auditSummary,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
ProcessState string `json:"processState,omitempty"`
|
||||
ExitClassification string `json:"exitClassification,omitempty"`
|
||||
ExitCode int `json:"exitCode,omitempty"`
|
||||
Version int `json:"version,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
||||
AuditSummary string `json:"auditSummary,omitempty"`
|
||||
ServerDeploymentEvidence *ServerDeploymentEvidenceBody `json:"serverDeploymentEvidence,omitempty"`
|
||||
}
|
||||
|
||||
type JobListResponse struct {
|
||||
@@ -1440,20 +1442,21 @@ func ServerInstanceFromDomain(instance domain.ServerInstance) ServerInstanceResp
|
||||
adminUserIDs = []string{}
|
||||
}
|
||||
return ServerInstanceResponse{
|
||||
ID: instance.ID,
|
||||
PluginID: instance.PluginID,
|
||||
PluginVersion: instance.PluginVersion,
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
Name: instance.Name,
|
||||
OwnerUserID: instance.OwnerUserID,
|
||||
AdminUserIDs: adminUserIDs,
|
||||
State: instance.State,
|
||||
ConfigVersion: instance.ConfigVersion,
|
||||
ConfigKey: instance.ConfigKey,
|
||||
ConfigChecksum: instance.ConfigChecksum,
|
||||
ConfigUpdatedAt: optionalTime(instance.ConfigUpdatedAt),
|
||||
CreatedAt: instance.CreatedAt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
ID: instance.ID,
|
||||
PluginID: instance.PluginID,
|
||||
PluginVersion: instance.PluginVersion,
|
||||
RunEndpointID: instance.RunEndpointID,
|
||||
Name: instance.Name,
|
||||
OwnerUserID: instance.OwnerUserID,
|
||||
AdminUserIDs: adminUserIDs,
|
||||
State: instance.State,
|
||||
ConfigVersion: instance.ConfigVersion,
|
||||
ConfigKey: instance.ConfigKey,
|
||||
ConfigChecksum: instance.ConfigChecksum,
|
||||
ConfigUpdatedAt: optionalTime(instance.ConfigUpdatedAt),
|
||||
CreatedAt: instance.CreatedAt,
|
||||
UpdatedAt: instance.UpdatedAt,
|
||||
DeploymentProjection: deploymentProjectionFromDomain(instance.DeploymentProjection),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1618,7 +1621,7 @@ func JobFromDomain(job domain.Job) JobResponse {
|
||||
State: job.State,
|
||||
Progress: progressFromDomain(job.Progress),
|
||||
ResultRef: job.ResultRef,
|
||||
ExecutionResult: JobExecutionResultResponse{Kind: job.ExecutionResult.Kind, ProcessState: job.ExecutionResult.ProcessState, ExitClassification: job.ExecutionResult.ExitClassification, ExitCode: job.ExecutionResult.ExitCode, Version: job.ExecutionResult.Version, Checksum: job.ExecutionResult.Checksum, SizeBytes: job.ExecutionResult.SizeBytes, AuditSummary: job.ExecutionResult.AuditSummary},
|
||||
ExecutionResult: JobExecutionResultResponse{Kind: job.ExecutionResult.Kind, ProcessState: job.ExecutionResult.ProcessState, ExitClassification: job.ExecutionResult.ExitClassification, ExitCode: job.ExecutionResult.ExitCode, Version: job.ExecutionResult.Version, Checksum: job.ExecutionResult.Checksum, SizeBytes: job.ExecutionResult.SizeBytes, AuditSummary: job.ExecutionResult.AuditSummary, ServerDeploymentEvidence: serverDeploymentEvidenceFromDomain(job.ExecutionResult.ServerDeploymentEvidence)},
|
||||
RetryPolicy: JobRetryPolicyResponse{
|
||||
MaxAttempts: job.RetryPolicy.MaxAttempts,
|
||||
InitialBackoffSeconds: job.RetryPolicy.InitialBackoffSeconds,
|
||||
|
||||
@@ -58,6 +58,42 @@ type RuntimeInstallPlanBody struct {
|
||||
Steps []RuntimeInstallStepBody `json:"steps"`
|
||||
}
|
||||
|
||||
type RuntimeServerConfigMappingBody struct {
|
||||
FieldKey string `json:"fieldKey"`
|
||||
ConfigKey string `json:"configKey"`
|
||||
ValueType string `json:"valueType"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeServerDiscoveryMarkerBody struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
TargetKey string `json:"targetKey"`
|
||||
Expected string `json:"expected,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeServerVerificationCheckBody struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
TargetKey string `json:"targetKey"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeServerDeploymentProfileBody struct {
|
||||
Key string `json:"key"`
|
||||
Version string `json:"version"`
|
||||
SupportedTargets []RuntimeTargetBody `json:"supportedTargets"`
|
||||
SteamAppID string `json:"steamAppId"`
|
||||
ExecutableKey string `json:"executableKey"`
|
||||
InstallRootKey string `json:"installRootKey"`
|
||||
ConfigKey string `json:"configKey"`
|
||||
ConfigFormat string `json:"configFormat"`
|
||||
ConfigMappings []RuntimeServerConfigMappingBody `json:"configMappings"`
|
||||
DiscoveryMarkers []RuntimeServerDiscoveryMarkerBody `json:"discoveryMarkers"`
|
||||
VerificationChecks []RuntimeServerVerificationCheckBody `json:"verificationChecks"`
|
||||
}
|
||||
|
||||
type RuntimeLogSourceBody struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
@@ -213,15 +249,16 @@ type RuntimeDLLExtensionPlanBody struct {
|
||||
}
|
||||
|
||||
type GamePluginRuntimeProfilesBody struct {
|
||||
Discovery []RuntimeDiscoveryProbeBody `json:"discovery,omitempty"`
|
||||
LifecycleProfiles []RuntimeLifecycleProfileBody `json:"lifecycleProfiles,omitempty"`
|
||||
DependencyProbes []RuntimeDependencyProbeBody `json:"dependencyProbes,omitempty"`
|
||||
InstallPlans []RuntimeInstallPlanBody `json:"installPlans,omitempty"`
|
||||
LogSources []RuntimeLogSourceBody `json:"logSources,omitempty"`
|
||||
LogEvents []RuntimeLogEventBody `json:"logEvents,omitempty"`
|
||||
TransportProfiles []RuntimeTransportProfileBody `json:"transportProfiles,omitempty"`
|
||||
ClientManagers []RuntimeClientManagerProfileBody `json:"clientManagers,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionProfileBody `json:"dllExtensions,omitempty"`
|
||||
Discovery []RuntimeDiscoveryProbeBody `json:"discovery,omitempty"`
|
||||
LifecycleProfiles []RuntimeLifecycleProfileBody `json:"lifecycleProfiles,omitempty"`
|
||||
DependencyProbes []RuntimeDependencyProbeBody `json:"dependencyProbes,omitempty"`
|
||||
InstallPlans []RuntimeInstallPlanBody `json:"installPlans,omitempty"`
|
||||
ServerDeployments []RuntimeServerDeploymentProfileBody `json:"serverDeployments,omitempty"`
|
||||
LogSources []RuntimeLogSourceBody `json:"logSources,omitempty"`
|
||||
LogEvents []RuntimeLogEventBody `json:"logEvents,omitempty"`
|
||||
TransportProfiles []RuntimeTransportProfileBody `json:"transportProfiles,omitempty"`
|
||||
ClientManagers []RuntimeClientManagerProfileBody `json:"clientManagers,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionProfileBody `json:"dllExtensions,omitempty"`
|
||||
}
|
||||
|
||||
// GamePluginRuntimeProfilesResponseBody is intentionally distinct from the
|
||||
@@ -232,6 +269,7 @@ type GamePluginRuntimeProfilesResponseBody struct {
|
||||
LifecycleProfiles []RuntimeLifecycleProfileBody `json:"lifecycleProfiles,omitempty"`
|
||||
DependencyProbes []RuntimeDependencyProbeBody `json:"dependencyProbes,omitempty"`
|
||||
InstallPlans []RuntimeInstallPlanBody `json:"installPlans,omitempty"`
|
||||
ServerDeployments []RuntimeServerDeploymentProfileBody `json:"serverDeployments,omitempty"`
|
||||
LogSources []RuntimeLogSourceBody `json:"logSources,omitempty"`
|
||||
LogEvents []RuntimeLogEventBody `json:"logEvents,omitempty"`
|
||||
TransportProfiles []RuntimeTransportProfileBody `json:"transportProfiles,omitempty"`
|
||||
@@ -257,6 +295,22 @@ func (body GamePluginRuntimeProfilesBody) ToDomain() domain.GamePluginRuntimePro
|
||||
}
|
||||
profiles.InstallPlans = append(profiles.InstallPlans, plan)
|
||||
}
|
||||
for _, item := range body.ServerDeployments {
|
||||
profile := domain.RuntimeServerDeploymentProfile{Key: item.Key, Version: item.Version, SteamAppID: item.SteamAppID, ExecutableKey: item.ExecutableKey, InstallRootKey: item.InstallRootKey, ConfigKey: item.ConfigKey, ConfigFormat: item.ConfigFormat}
|
||||
for _, target := range item.SupportedTargets {
|
||||
profile.SupportedTargets = append(profile.SupportedTargets, domain.RuntimeTarget{OS: target.OS, Arch: target.Arch})
|
||||
}
|
||||
for _, mapping := range item.ConfigMappings {
|
||||
profile.ConfigMappings = append(profile.ConfigMappings, domain.RuntimeServerConfigMapping{FieldKey: mapping.FieldKey, ConfigKey: mapping.ConfigKey, ValueType: mapping.ValueType, Required: mapping.Required})
|
||||
}
|
||||
for _, marker := range item.DiscoveryMarkers {
|
||||
profile.DiscoveryMarkers = append(profile.DiscoveryMarkers, domain.RuntimeServerDiscoveryMarker{Key: marker.Key, Kind: marker.Kind, TargetKey: marker.TargetKey, Expected: marker.Expected, Required: marker.Required})
|
||||
}
|
||||
for _, check := range item.VerificationChecks {
|
||||
profile.VerificationChecks = append(profile.VerificationChecks, domain.RuntimeServerVerificationCheck{Key: check.Key, Kind: check.Kind, TargetKey: check.TargetKey, Required: check.Required})
|
||||
}
|
||||
profiles.ServerDeployments = append(profiles.ServerDeployments, profile)
|
||||
}
|
||||
for _, item := range body.LogSources {
|
||||
profiles.LogSources = append(profiles.LogSources, domain.RuntimeLogSource{Key: item.Key, Kind: item.Kind, TargetKey: item.TargetKey, StreamKey: item.StreamKey, CursorKind: item.CursorKind, RetentionDays: item.RetentionDays})
|
||||
}
|
||||
@@ -314,6 +368,22 @@ func runtimeProfilesFromDomain(profiles domain.GamePluginRuntimeProfiles) GamePl
|
||||
}
|
||||
body.InstallPlans = append(body.InstallPlans, plan)
|
||||
}
|
||||
for _, item := range profiles.ServerDeployments {
|
||||
bodyProfile := RuntimeServerDeploymentProfileBody{Key: item.Key, Version: item.Version, SteamAppID: item.SteamAppID, ExecutableKey: item.ExecutableKey, InstallRootKey: item.InstallRootKey, ConfigKey: item.ConfigKey, ConfigFormat: item.ConfigFormat}
|
||||
for _, target := range item.SupportedTargets {
|
||||
bodyProfile.SupportedTargets = append(bodyProfile.SupportedTargets, RuntimeTargetBody{OS: target.OS, Arch: target.Arch})
|
||||
}
|
||||
for _, mapping := range item.ConfigMappings {
|
||||
bodyProfile.ConfigMappings = append(bodyProfile.ConfigMappings, RuntimeServerConfigMappingBody{FieldKey: mapping.FieldKey, ConfigKey: mapping.ConfigKey, ValueType: mapping.ValueType, Required: mapping.Required})
|
||||
}
|
||||
for _, marker := range item.DiscoveryMarkers {
|
||||
bodyProfile.DiscoveryMarkers = append(bodyProfile.DiscoveryMarkers, RuntimeServerDiscoveryMarkerBody{Key: marker.Key, Kind: marker.Kind, TargetKey: marker.TargetKey, Expected: marker.Expected, Required: marker.Required})
|
||||
}
|
||||
for _, check := range item.VerificationChecks {
|
||||
bodyProfile.VerificationChecks = append(bodyProfile.VerificationChecks, RuntimeServerVerificationCheckBody{Key: check.Key, Kind: check.Kind, TargetKey: check.TargetKey, Required: check.Required})
|
||||
}
|
||||
body.ServerDeployments = append(body.ServerDeployments, bodyProfile)
|
||||
}
|
||||
for _, item := range profiles.LogSources {
|
||||
body.LogSources = append(body.LogSources, RuntimeLogSourceBody{Key: item.Key, Kind: item.Kind, TargetKey: item.TargetKey, StreamKey: item.StreamKey, CursorKind: item.CursorKind, RetentionDays: item.RetentionDays})
|
||||
}
|
||||
|
||||
@@ -22,19 +22,36 @@ type ServerDeploymentRequest struct {
|
||||
}
|
||||
|
||||
type ServerDeploymentResponse struct {
|
||||
ServerInstanceID string `json:"serverInstanceId"`
|
||||
Mode domain.ServerDeploymentMode `json:"mode,omitempty"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
CreateInputs map[string]string `json:"createInputs,omitempty"`
|
||||
ServerRootConfigured bool `json:"serverRootConfigured"`
|
||||
WorkingDirectoryConfigured bool `json:"workingDirectoryConfigured"`
|
||||
InstallCommandConfigured bool `json:"installCommandConfigured"`
|
||||
StartCommandConfigured bool `json:"startCommandConfigured"`
|
||||
StopCommandConfigured bool `json:"stopCommandConfigured"`
|
||||
StatusCommandConfigured bool `json:"statusCommandConfigured"`
|
||||
Shell domain.ServerCommandShell `json:"shell,omitempty"`
|
||||
Revision int `json:"revision"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
ServerInstanceID string `json:"serverInstanceId"`
|
||||
Mode domain.ServerDeploymentMode `json:"mode,omitempty"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
CreateInputs map[string]string `json:"createInputs,omitempty"`
|
||||
ServerRootConfigured bool `json:"serverRootConfigured"`
|
||||
WorkingDirectoryConfigured bool `json:"workingDirectoryConfigured"`
|
||||
InstallCommandConfigured bool `json:"installCommandConfigured"`
|
||||
StartCommandConfigured bool `json:"startCommandConfigured"`
|
||||
StopCommandConfigured bool `json:"stopCommandConfigured"`
|
||||
StatusCommandConfigured bool `json:"statusCommandConfigured"`
|
||||
Shell domain.ServerCommandShell `json:"shell,omitempty"`
|
||||
Revision int `json:"revision"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
Projection ServerDeploymentProjectionBody `json:"projection,omitempty"`
|
||||
}
|
||||
|
||||
type ServerDeploymentProjectionBody struct {
|
||||
State string `json:"state,omitempty"`
|
||||
Operation string `json:"operation,omitempty"`
|
||||
TemplateKey string `json:"templateKey,omitempty"`
|
||||
TemplateVersion string `json:"templateVersion,omitempty"`
|
||||
PreflightState string `json:"preflightState,omitempty"`
|
||||
DiscoveryState string `json:"discoveryState,omitempty"`
|
||||
MappingState string `json:"mappingState,omitempty"`
|
||||
VerificationState string `json:"verificationState,omitempty"`
|
||||
DiscoveredFacts map[string]string `json:"discoveredFacts,omitempty"`
|
||||
MappingResults map[string]string `json:"mappingResults,omitempty"`
|
||||
VerificationResults map[string]string `json:"verificationResults,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type ServerLifecycleCreateRequest struct {
|
||||
@@ -85,7 +102,11 @@ func (request ServerDeploymentRequest) deploymentDefinition() domain.ServerDeplo
|
||||
}
|
||||
|
||||
func ServerDeploymentFromDomain(view domain.ServerDeploymentView) ServerDeploymentResponse {
|
||||
return ServerDeploymentResponse{ServerInstanceID: view.ServerInstanceID, Mode: view.Mode, ProfileKey: view.ProfileKey, CreateInputs: domain.CopyStringMap(view.CreateInputs), ServerRootConfigured: view.ServerRootConfigured, WorkingDirectoryConfigured: view.WorkingDirectoryConfigured, InstallCommandConfigured: view.InstallCommandConfigured, StartCommandConfigured: view.StartCommandConfigured, StopCommandConfigured: view.StopCommandConfigured, StatusCommandConfigured: view.StatusCommandConfigured, Shell: view.Shell, Revision: view.Revision, UpdatedAt: optionalTime(view.UpdatedAt)}
|
||||
return ServerDeploymentResponse{ServerInstanceID: view.ServerInstanceID, Mode: view.Mode, ProfileKey: view.ProfileKey, CreateInputs: domain.CopyStringMap(view.CreateInputs), ServerRootConfigured: view.ServerRootConfigured, WorkingDirectoryConfigured: view.WorkingDirectoryConfigured, InstallCommandConfigured: view.InstallCommandConfigured, StartCommandConfigured: view.StartCommandConfigured, StopCommandConfigured: view.StopCommandConfigured, StatusCommandConfigured: view.StatusCommandConfigured, Shell: view.Shell, Revision: view.Revision, UpdatedAt: optionalTime(view.UpdatedAt), Projection: deploymentProjectionFromDomain(view.Projection)}
|
||||
}
|
||||
|
||||
func deploymentProjectionFromDomain(projection domain.ServerDeploymentProjection) ServerDeploymentProjectionBody {
|
||||
return ServerDeploymentProjectionBody{State: projection.State, Operation: projection.Operation, TemplateKey: projection.TemplateKey, TemplateVersion: projection.TemplateVersion, PreflightState: projection.PreflightState, DiscoveryState: projection.DiscoveryState, MappingState: projection.MappingState, VerificationState: projection.VerificationState, DiscoveredFacts: domain.CopyStringMap(projection.DiscoveredFacts), MappingResults: domain.CopyStringMap(projection.MappingResults), VerificationResults: domain.CopyStringMap(projection.VerificationResults), FailureCode: projection.FailureCode, UpdatedAt: optionalTime(projection.UpdatedAt)}
|
||||
}
|
||||
|
||||
func (request ServerLifecycleCommandRequest) ToDomain(serverInstanceID string) domain.ServerLifecycleCommand {
|
||||
|
||||
@@ -5,6 +5,14 @@ execute a protected server deployment plan. Platform only sends the plan in a
|
||||
leased `RunJobAssignmentResponse.executionInput.deployment`; it never appears
|
||||
in public server, job, audit, log, or plugin-bridge responses.
|
||||
|
||||
SCUM controlled deployments additionally require `deployment.scum.v1`. The
|
||||
leased assignment includes `executionInput.serverDeploymentPlan` with
|
||||
`schemaVersion: "1"`, an operation of `install` or `adopt`, the frozen template
|
||||
key/version, Steam app id, logical executable/config references, explicit field
|
||||
mappings, discovery markers, and required verification checks. The plan is
|
||||
secret-free and contains logical keys only; the protected deployment body still
|
||||
holds the operator's paths/commands and is resolved only by Run.
|
||||
|
||||
## Capability and policy
|
||||
|
||||
Run advertises `deployment.plan.v1` along with its normal lifecycle
|
||||
@@ -25,6 +33,21 @@ Before a write, install, or process action, Run validates the selected plan:
|
||||
- no raw command, path, secret, socket address, or credential is emitted in a
|
||||
result, diagnostic, log batch, or artifact name.
|
||||
|
||||
For an SCUM `install`, Run performs SteamCMD app installation followed by
|
||||
configuration materialization and health checks. For `adopt`, Run performs a
|
||||
scan first and must not reinstall or overwrite existing configuration. A
|
||||
controlled SCUM install or adoption requires an explicit protected server root;
|
||||
the root is never exposed in browser projections. Adoption does not require
|
||||
new-install create inputs because its mapping phase is read-only unless a
|
||||
separate approved write is dispatched. A
|
||||
terminal SCUM result must include bounded `serverDeploymentEvidence` with
|
||||
preflight, discovery, mapping, and verification states. Required mapping
|
||||
results are `applied` or `unchanged`; required verification results are
|
||||
`passed` (adoption may report mapping as `skipped`). Failed results include a stable `failureCode` and never include the
|
||||
resolved path or command text. Successful result kinds are
|
||||
`scum.install.completed` and `scum.adopt.completed`; failed result kinds are
|
||||
`scum.install.failed` and `scum.adopt.failed`.
|
||||
|
||||
An `existing-server` plan may omit installation. A `custom-command` plan
|
||||
requires a start command. Guided templates remain plugin recommendations;
|
||||
Run owns their local resolution and execution.
|
||||
@@ -32,8 +55,8 @@ Run owns their local resolution and execution.
|
||||
## Safe progress reports
|
||||
|
||||
Run reports bounded progress with `percent`, `phase`, and a safe message. The
|
||||
allowed phase vocabulary is `queued`, `claimed`, `preflight`, `install`,
|
||||
`configure`, `start`, and `health`. On failure it reports a stable safe error
|
||||
allowed phase vocabulary is `queued`, `claimed`, `preflight`, `scan`, `install`,
|
||||
`configure`, `mapping`, `start`, and `health`. On failure it reports a stable safe error
|
||||
code and summary such as `working-directory-unavailable`, never the supplied
|
||||
path or command text.
|
||||
|
||||
|
||||
@@ -160,6 +160,9 @@ func (svc *CoreService) UpdateRunJobProgress(progress domain.RunJobProgress) (do
|
||||
if err := svc.updateScheduledJob(job); err != nil {
|
||||
return domain.RunJobProgressResult{}, err
|
||||
}
|
||||
if err := svc.projectServerDeploymentProgress(job, stamp); err != nil {
|
||||
return domain.RunJobProgressResult{}, err
|
||||
}
|
||||
if err := svc.projectDistributionBuildProgress(job, stamp); err != nil {
|
||||
return domain.RunJobProgressResult{}, err
|
||||
}
|
||||
@@ -266,6 +269,9 @@ func (svc *CoreService) CompleteRunJob(result domain.RunJobResult) (domain.RunJo
|
||||
}
|
||||
|
||||
func validateExecutionResultForJob(job domain.Job, result domain.RunJobResult) error {
|
||||
if job.ExecutionInput.ServerDeploymentPlan != nil {
|
||||
return validateSCUMDeploymentEvidence(job.ExecutionInput.ServerDeploymentPlan, result)
|
||||
}
|
||||
if result.ExecutionResult.Kind == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
"browser.local/platform/validator"
|
||||
)
|
||||
|
||||
const scumPluginID = "game.scum"
|
||||
|
||||
func applyPluginCreateDefaults(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition) domain.ServerDeploymentDefinition {
|
||||
definition = domain.CopyServerDeploymentDefinition(definition)
|
||||
if definition.Mode != domain.ServerDeploymentModeGuided {
|
||||
return definition
|
||||
}
|
||||
if definition.CreateInputs == nil {
|
||||
definition.CreateInputs = map[string]string{}
|
||||
}
|
||||
for _, field := range plugin.CreateFields {
|
||||
if _, present := definition.CreateInputs[field.Key]; !present && field.DefaultValue != "" {
|
||||
definition.CreateInputs[field.Key] = field.DefaultValue
|
||||
}
|
||||
}
|
||||
return definition
|
||||
}
|
||||
|
||||
func scumDeploymentPlan(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition, operation string) (*domain.ServerDeploymentPlan, error) {
|
||||
if plugin.ID != scumPluginID || (definition.Mode != domain.ServerDeploymentModeGuided && definition.Mode != domain.ServerDeploymentModeExisting) {
|
||||
return nil, nil
|
||||
}
|
||||
if operation != "install" && operation != "adopt" {
|
||||
return nil, validationError("SCUM deployment operation is invalid")
|
||||
}
|
||||
if len(plugin.RuntimeProfiles.ServerDeployments) == 0 {
|
||||
return nil, validationError("SCUM deployment template is not registered")
|
||||
}
|
||||
profile := plugin.RuntimeProfiles.ServerDeployments[0]
|
||||
if strings.TrimSpace(profile.Key) == "" || strings.TrimSpace(profile.Version) == "" || profile.SteamAppID == "" {
|
||||
return nil, validationError("SCUM deployment template is incomplete")
|
||||
}
|
||||
fieldKeys := make(map[string]struct{}, len(plugin.CreateFields))
|
||||
for _, field := range plugin.CreateFields {
|
||||
fieldKeys[field.Key] = struct{}{}
|
||||
}
|
||||
for _, mapping := range profile.ConfigMappings {
|
||||
if _, ok := fieldKeys[mapping.FieldKey]; !ok {
|
||||
return nil, validationError("SCUM deployment template maps an undeclared create field")
|
||||
}
|
||||
if strings.TrimSpace(mapping.ConfigKey) == "" || strings.TrimSpace(mapping.ValueType) == "" {
|
||||
return nil, validationError("SCUM deployment template contains an incomplete config mapping")
|
||||
}
|
||||
}
|
||||
for _, check := range profile.VerificationChecks {
|
||||
if check.Required && (strings.TrimSpace(check.Key) == "" || strings.TrimSpace(check.Kind) == "") {
|
||||
return nil, validationError("SCUM deployment template contains an incomplete verification check")
|
||||
}
|
||||
}
|
||||
return &domain.ServerDeploymentPlan{
|
||||
SchemaVersion: "1",
|
||||
Operation: operation,
|
||||
PluginID: plugin.ID,
|
||||
TemplateKey: profile.Key,
|
||||
TemplateVersion: profile.Version,
|
||||
SteamAppID: profile.SteamAppID,
|
||||
ExecutableKey: profile.ExecutableKey,
|
||||
InstallRootKey: profile.InstallRootKey,
|
||||
ConfigKey: profile.ConfigKey,
|
||||
ConfigFormat: profile.ConfigFormat,
|
||||
ConfigMappings: append([]domain.RuntimeServerConfigMapping(nil), profile.ConfigMappings...),
|
||||
DiscoveryMarkers: append([]domain.RuntimeServerDiscoveryMarker(nil), profile.DiscoveryMarkers...),
|
||||
VerificationChecks: append([]domain.RuntimeServerVerificationCheck(nil), profile.VerificationChecks...),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func scumDeploymentProjection(plan *domain.ServerDeploymentPlan, operation string, stamp time.Time) domain.ServerDeploymentProjection {
|
||||
projection := domain.ServerDeploymentProjection{State: "draft", Operation: operation, UpdatedAt: stamp}
|
||||
if plan != nil {
|
||||
projection.TemplateKey = plan.TemplateKey
|
||||
projection.TemplateVersion = plan.TemplateVersion
|
||||
}
|
||||
return projection
|
||||
}
|
||||
|
||||
func scumDeploymentCapabilityRequired(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition) bool {
|
||||
return plugin.ID == scumPluginID && (definition.Mode == domain.ServerDeploymentModeGuided || definition.Mode == domain.ServerDeploymentModeExisting)
|
||||
}
|
||||
|
||||
func validateSCUMDeploymentTarget(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition, endpoint domain.RunEndpoint) error {
|
||||
if !scumDeploymentCapabilityRequired(plugin, definition) {
|
||||
return nil
|
||||
}
|
||||
plan, err := scumDeploymentPlan(plugin, definition, "install")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, profile := range plugin.RuntimeProfiles.ServerDeployments {
|
||||
if profile.Key != plan.TemplateKey {
|
||||
continue
|
||||
}
|
||||
for _, target := range profile.SupportedTargets {
|
||||
if target.OS == endpoint.Platform && target.Arch == endpoint.Architecture {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return validationError("SCUM deployment template is incompatible with the selected Run target")
|
||||
}
|
||||
|
||||
func validateScumDeploymentInputs(plugin domain.GamePlugin, definition domain.ServerDeploymentDefinition) error {
|
||||
plan, err := scumDeploymentPlan(plugin, definition, "install")
|
||||
if err != nil || plan == nil {
|
||||
return err
|
||||
}
|
||||
if definition.Mode == domain.ServerDeploymentModeExisting {
|
||||
if strings.TrimSpace(definition.ServerRoot) == "" {
|
||||
return validationError("SCUM adoption requires an existing server directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(definition.ServerRoot) == "" {
|
||||
return validationError("SCUM installation requires an install directory")
|
||||
}
|
||||
for _, mapping := range plan.ConfigMappings {
|
||||
if mapping.Required && strings.TrimSpace(definition.CreateInputs[mapping.FieldKey]) == "" {
|
||||
field, found := findPluginCreateField(plugin.CreateFields, mapping.FieldKey)
|
||||
if !found || field.DefaultValue == "" {
|
||||
return validator.ValidationError{Violations: []string{"createInputs." + mapping.FieldKey + " is required by the SCUM deployment template"}}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPluginCreateField(fields []domain.PluginCreateField, key string) (domain.PluginCreateField, bool) {
|
||||
for _, field := range fields {
|
||||
if field.Key == key {
|
||||
return field, true
|
||||
}
|
||||
}
|
||||
return domain.PluginCreateField{}, false
|
||||
}
|
||||
|
||||
func validateSCUMDeploymentEvidence(plan *domain.ServerDeploymentPlan, result domain.RunJobResult) error {
|
||||
if plan == nil {
|
||||
return nil
|
||||
}
|
||||
evidence := result.ExecutionResult.ServerDeploymentEvidence
|
||||
if evidence == nil {
|
||||
return validationError("SCUM deployment result must include deployment evidence")
|
||||
}
|
||||
if evidence.TemplateKey != plan.TemplateKey || evidence.TemplateVersion != plan.TemplateVersion {
|
||||
return validationError("SCUM deployment result template fence is invalid")
|
||||
}
|
||||
for name, values := range map[string]map[string]string{"discoveredFacts": evidence.DiscoveredFacts, "mappingResults": evidence.MappingResults, "verificationResults": evidence.VerificationResults} {
|
||||
if len(values) > 64 {
|
||||
return validationError("SCUM deployment evidence contains too many " + name)
|
||||
}
|
||||
for key, value := range values {
|
||||
if !regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]{0,119}$`).MatchString(key) || len(value) > 160 || strings.TrimSpace(value) != value || unsafeSCUMEvidenceValue(value) {
|
||||
return validationError(fmt.Sprintf("SCUM deployment evidence contains an unsafe %s value", name))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(evidence.FailureCode) > 80 || (evidence.FailureCode != "" && !regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,79}$`).MatchString(evidence.FailureCode)) {
|
||||
return validationError("SCUM deployment failure code is invalid")
|
||||
}
|
||||
if result.State == domain.JobStateFailed {
|
||||
if result.ExecutionResult.Kind != "scum.install.failed" && result.ExecutionResult.Kind != "scum.adopt.failed" {
|
||||
return validationError("SCUM deployment failure result type is invalid")
|
||||
}
|
||||
if strings.TrimSpace(evidence.FailureCode) == "" {
|
||||
return validationError("SCUM deployment failure must include a stable failure code")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if result.State != domain.JobStateSucceeded {
|
||||
return nil
|
||||
}
|
||||
expectedKind := "scum.install.completed"
|
||||
if plan.Operation == "adopt" {
|
||||
expectedKind = "scum.adopt.completed"
|
||||
}
|
||||
if result.ExecutionResult.Kind != expectedKind {
|
||||
return validationError("SCUM deployment result type is invalid")
|
||||
}
|
||||
if evidence.PreflightState != "passed" || evidence.DiscoveryState != "passed" || evidence.VerificationState != "passed" {
|
||||
return validationError("SCUM deployment result is missing successful preflight, discovery, or verification evidence")
|
||||
}
|
||||
validMappingState := evidence.MappingState == "passed" || evidence.MappingState == "applied" || evidence.MappingState == "unchanged"
|
||||
if plan.Operation == "adopt" && evidence.MappingState == "skipped" {
|
||||
validMappingState = true
|
||||
}
|
||||
if !validMappingState {
|
||||
return validationError("SCUM deployment result is missing a successful config mapping state")
|
||||
}
|
||||
for _, mapping := range plan.ConfigMappings {
|
||||
mappingResult := evidence.MappingResults[mapping.FieldKey]
|
||||
if plan.Operation == "adopt" && mappingResult == "" {
|
||||
mappingResult = "skipped"
|
||||
}
|
||||
if mapping.Required && mappingResult != "applied" && mappingResult != "unchanged" && !(plan.Operation == "adopt" && mappingResult == "skipped") {
|
||||
return validationError("SCUM deployment result is missing a required config mapping")
|
||||
}
|
||||
}
|
||||
for _, check := range plan.VerificationChecks {
|
||||
if check.Required && evidence.VerificationResults[check.Key] != "passed" {
|
||||
return validationError("SCUM deployment result is missing a required verification check")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unsafeSCUMEvidenceValue(value string) bool {
|
||||
lower := strings.ToLower(value)
|
||||
for _, token := range []string{"password", "secret", "credential", "token", "private key", "powershell", "cmd.exe", "bash -c", "ssh://", "tcp://", "udp://"} {
|
||||
if strings.Contains(lower, token) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return strings.HasPrefix(value, "/") || strings.HasPrefix(value, `\\`) || regexp.MustCompile(`^[A-Za-z]:[\\/]`).MatchString(value)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
func scumDeploymentTestPlugin() domain.GamePlugin {
|
||||
return domain.GamePlugin{
|
||||
ID: "game.scum",
|
||||
CreateFields: []domain.PluginCreateField{
|
||||
{Key: "serverName", Type: "text", DefaultValue: "SCUM Test"},
|
||||
{Key: "gamePort", Type: "port", DefaultValue: "7777"},
|
||||
{Key: "queryPort", Type: "port", DefaultValue: "27015"},
|
||||
{Key: "maxPlayers", Type: "number", DefaultValue: "64"},
|
||||
},
|
||||
RuntimeProfiles: domain.GamePluginRuntimeProfiles{ServerDeployments: []domain.RuntimeServerDeploymentProfile{{
|
||||
Key: "scum-steamcmd-windows", Version: "1.0.0", SteamAppID: "3792580", ExecutableKey: "scum/server-executable", InstallRootKey: "server/install-root", ConfigKey: "scum/server-settings", ConfigFormat: "ini",
|
||||
ConfigMappings: []domain.RuntimeServerConfigMapping{{FieldKey: "serverName", ConfigKey: "server-settings.server-name", ValueType: "text", Required: true}, {FieldKey: "gamePort", ConfigKey: "server-settings.game-port", ValueType: "port", Required: true}, {FieldKey: "queryPort", ConfigKey: "server-settings.query-port", ValueType: "port", Required: true}, {FieldKey: "maxPlayers", ConfigKey: "server-settings.max-players", ValueType: "integer", Required: true}},
|
||||
VerificationChecks: []domain.RuntimeServerVerificationCheck{{Key: "executable", Kind: "executable.present", TargetKey: "scum/server-executable", Required: true}, {Key: "version", Kind: "version.matches", TargetKey: "scum/server-executable", Required: true}, {Key: "game-port", Kind: "port.bound", TargetKey: "game-port", Required: true}, {Key: "config", Kind: "config.readable", TargetKey: "scum/server-settings", Required: true}, {Key: "process", Kind: "process.healthy", TargetKey: "scum/server-executable", Required: true}},
|
||||
}}},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMDeploymentPlanSeparatesInstallAndAdopt(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
definition := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, CreateInputs: map[string]string{"serverName": "Alpha", "gamePort": "7777", "queryPort": "27015", "maxPlayers": "64"}}
|
||||
plan, err := scumDeploymentPlan(plugin, definition, "install")
|
||||
if err != nil || plan == nil || plan.Operation != "install" || plan.SteamAppID != "3792580" {
|
||||
t.Fatalf("unexpected install plan: %+v, %v", plan, err)
|
||||
}
|
||||
definition.Mode = domain.ServerDeploymentModeExisting
|
||||
plan, err = scumDeploymentPlan(plugin, definition, "adopt")
|
||||
if err != nil || plan == nil || plan.Operation != "adopt" {
|
||||
t.Fatalf("unexpected adopt plan: %+v, %v", plan, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMDeploymentEvidenceRequiresAllRequiredChecks(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
plan, err := scumDeploymentPlan(plugin, domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided}, "install")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := domain.RunJobResult{State: domain.JobStateSucceeded, ExecutionResult: domain.JobExecutionResult{Kind: "scum.install.completed", ServerDeploymentEvidence: &domain.ServerDeploymentEvidence{TemplateKey: plan.TemplateKey, TemplateVersion: plan.TemplateVersion, PreflightState: "passed", DiscoveryState: "passed", MappingState: "applied", VerificationState: "passed", MappingResults: map[string]string{"serverName": "applied", "gamePort": "applied", "queryPort": "applied", "maxPlayers": "applied"}, VerificationResults: map[string]string{"executable": "passed", "version": "passed", "game-port": "passed", "config": "passed", "process": "passed"}}}}
|
||||
if err := validateSCUMDeploymentEvidence(plan, result); err != nil {
|
||||
t.Fatalf("valid evidence rejected: %v", err)
|
||||
}
|
||||
delete(result.ExecutionResult.ServerDeploymentEvidence.VerificationResults, "process")
|
||||
if err := validateSCUMDeploymentEvidence(plan, result); err == nil {
|
||||
t.Fatal("missing required process check was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMAdoptionMaySkipConfigurationMappingAfterDiscovery(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
plan, err := scumDeploymentPlan(plugin, domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeExisting, ServerRoot: `C:\\scum`}, "adopt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result := domain.RunJobResult{State: domain.JobStateSucceeded, ExecutionResult: domain.JobExecutionResult{Kind: "scum.adopt.completed", ServerDeploymentEvidence: &domain.ServerDeploymentEvidence{
|
||||
TemplateKey: plan.TemplateKey, TemplateVersion: plan.TemplateVersion, PreflightState: "passed", DiscoveryState: "passed", MappingState: "skipped", VerificationState: "passed",
|
||||
VerificationResults: map[string]string{"executable": "passed", "version": "passed", "game-port": "passed", "config": "passed", "process": "passed"},
|
||||
}}}
|
||||
if err := validateSCUMDeploymentEvidence(plan, result); err != nil {
|
||||
t.Fatalf("valid adoption evidence rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMInstallRequiresDirectoryButAdoptionDoesNotRequireCreateInputs(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
install := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided, CreateInputs: map[string]string{"serverName": "Alpha"}}
|
||||
if err := validateScumDeploymentInputs(plugin, install); err == nil {
|
||||
t.Fatal("SCUM install without a directory was accepted")
|
||||
}
|
||||
install.ServerRoot = `C:\\scum`
|
||||
if err := validateScumDeploymentInputs(plugin, install); err != nil {
|
||||
t.Fatalf("SCUM install with defaults was rejected: %v", err)
|
||||
}
|
||||
adopt := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeExisting, ServerRoot: `C:\\scum`}
|
||||
if err := validateScumDeploymentInputs(plugin, adopt); err != nil {
|
||||
t.Fatalf("SCUM adoption without create inputs was rejected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMDeploymentFailureRequiresStableCode(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
plan, _ := scumDeploymentPlan(plugin, domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeExisting}, "adopt")
|
||||
result := domain.RunJobResult{State: domain.JobStateFailed, ExecutionResult: domain.JobExecutionResult{Kind: "scum.adopt.failed", ServerDeploymentEvidence: &domain.ServerDeploymentEvidence{TemplateKey: plan.TemplateKey, TemplateVersion: plan.TemplateVersion}}}
|
||||
if err := validateSCUMDeploymentEvidence(plan, result); err == nil {
|
||||
t.Fatal("failed deployment without failure code was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSCUMDeploymentTargetMustMatchTemplate(t *testing.T) {
|
||||
plugin := scumDeploymentTestPlugin()
|
||||
plugin.RuntimeProfiles.ServerDeployments[0].SupportedTargets = []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}}
|
||||
definition := domain.ServerDeploymentDefinition{Mode: domain.ServerDeploymentModeGuided}
|
||||
if err := validateSCUMDeploymentTarget(plugin, definition, domain.RunEndpoint{Platform: "linux", Architecture: "amd64"}); err == nil {
|
||||
t.Fatal("linux Run target was accepted for the Windows-only SCUM template")
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,13 @@ func (svc *CoreService) UpdateServerDeploymentForSession(sessionID, serverInstan
|
||||
if err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
}
|
||||
definition = applyPluginCreateDefaults(plugin, definition)
|
||||
if err := validator.ValidatePluginCreateInputs(plugin.CreateFields, definition.CreateInputs); err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
}
|
||||
if err := validateScumDeploymentInputs(plugin, definition); err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
}
|
||||
if update.RunEndpointID != "" {
|
||||
if _, err := svc.store.RunEndpoints().Get(update.RunEndpointID); err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
@@ -48,6 +52,17 @@ func (svc *CoreService) UpdateServerDeploymentForSession(sessionID, serverInstan
|
||||
instance.RunEndpointID = update.RunEndpointID
|
||||
}
|
||||
instance.Deployment = definition
|
||||
operation := "install"
|
||||
if definition.Mode == domain.ServerDeploymentModeExisting {
|
||||
operation = "adopt"
|
||||
}
|
||||
if plan, planErr := scumDeploymentPlan(plugin, definition, operation); planErr != nil {
|
||||
return domain.ServerDeploymentView{}, planErr
|
||||
} else if plan != nil {
|
||||
instance.DeploymentProjection = scumDeploymentProjection(plan, operation, definition.UpdatedAt)
|
||||
} else {
|
||||
instance.DeploymentProjection = domain.ServerDeploymentProjection{}
|
||||
}
|
||||
instance.UpdatedAt = definition.UpdatedAt
|
||||
if err := validator.ValidateServerInstance(instance); err != nil {
|
||||
return domain.ServerDeploymentView{}, err
|
||||
@@ -85,9 +100,16 @@ func (svc *CoreService) DeployServerInstanceForSession(sessionID string, command
|
||||
if err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
instance.Deployment = applyPluginCreateDefaults(plugin, instance.Deployment)
|
||||
if !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")
|
||||
}
|
||||
@@ -97,6 +119,9 @@ func (svc *CoreService) DeployServerInstanceForSession(sessionID string, command
|
||||
if err := validator.ValidateServerInstanceDependencies(instance, plugin, endpoint); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if err := validateScumDeploymentInputs(plugin, instance.Deployment); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityInstall); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
@@ -123,6 +148,11 @@ func (svc *CoreService) DeployServerInstanceForSession(sessionID string, command
|
||||
}
|
||||
instance.State = domain.ServerInstanceStateInstalling
|
||||
instance.UpdatedAt = svc.now()
|
||||
if instance.DeploymentProjection.TemplateKey != "" {
|
||||
instance.DeploymentProjection.State = "queued"
|
||||
instance.DeploymentProjection.PreflightState = "queued"
|
||||
instance.DeploymentProjection.UpdatedAt = instance.UpdatedAt
|
||||
}
|
||||
if err := svc.store.ServerInstances().Update(instance); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
@@ -185,5 +215,6 @@ func deploymentView(instance domain.ServerInstance) domain.ServerDeploymentView
|
||||
return domain.CopyServerDeploymentView(domain.ServerDeploymentView{
|
||||
ServerInstanceID: instance.ID, Mode: definition.Mode, ProfileKey: definition.ProfileKey, CreateInputs: domain.CopyStringMap(definition.CreateInputs),
|
||||
ServerRootConfigured: definition.ServerRoot != "", WorkingDirectoryConfigured: definition.WorkingDirectory != "", InstallCommandConfigured: definition.InstallCommand != "", StartCommandConfigured: definition.StartCommand != "", StopCommandConfigured: definition.StopCommand != "", StatusCommandConfigured: definition.StatusCommand != "", Shell: definition.Shell, Revision: definition.Revision, UpdatedAt: definition.UpdatedAt,
|
||||
Projection: instance.DeploymentProjection,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
if plugin.Status != domain.GamePluginStatusInstalled {
|
||||
return domain.ServerLifecycleResult{}, validationError("plugin must be installed")
|
||||
}
|
||||
create.Deployment = applyPluginCreateDefaults(plugin, create.Deployment)
|
||||
if err := validateLifecycleActionRef(plugin, domain.ServerLifecycleActionCreate); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
@@ -32,6 +33,9 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
if err := validator.ValidatePluginCreateInputs(plugin.CreateFields, create.Deployment.CreateInputs); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if err := validateScumDeploymentInputs(plugin, create.Deployment); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
stamp := svc.now()
|
||||
@@ -54,6 +58,15 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
instance.Deployment.RuntimeBindings = domain.CopyStringMap(create.Bindings)
|
||||
instance.Deployment.Revision = maxInt(1, instance.Deployment.Revision)
|
||||
instance.Deployment.UpdatedAt = stamp
|
||||
operation := "install"
|
||||
if instance.Deployment.Mode == domain.ServerDeploymentModeExisting {
|
||||
operation = "adopt"
|
||||
}
|
||||
if plan, planErr := scumDeploymentPlan(plugin, instance.Deployment, operation); planErr != nil {
|
||||
return domain.ServerLifecycleResult{}, planErr
|
||||
} else if plan != nil {
|
||||
instance.DeploymentProjection = scumDeploymentProjection(plan, operation, stamp)
|
||||
}
|
||||
}
|
||||
instance.ConfigContent = buildLogicalServerConfig(instance)
|
||||
instance.ConfigChecksum = validator.BytesChecksum([]byte(instance.ConfigContent))
|
||||
@@ -80,9 +93,20 @@ func (svc *CoreService) CreateServerInstanceWorkflow(create domain.ServerLifecyc
|
||||
if err := svc.validateRunnableEndpoint(endpoint, domain.LifecycleCapabilityForAction(domain.ServerLifecycleActionCreate)); err != nil {
|
||||
return domain.ServerLifecycleResult{}, err
|
||||
}
|
||||
if instance.DeploymentProjection.TemplateKey != "" {
|
||||
instance.DeploymentProjection.State = "queued"
|
||||
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")
|
||||
}
|
||||
@@ -284,6 +308,17 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act
|
||||
return domain.Job{}, err
|
||||
}
|
||||
}
|
||||
var serverDeploymentPlan *domain.ServerDeploymentPlan
|
||||
if action == domain.ServerLifecycleActionCreate {
|
||||
operation := "install"
|
||||
if instance.Deployment.Mode == domain.ServerDeploymentModeExisting {
|
||||
operation = "adopt"
|
||||
}
|
||||
serverDeploymentPlan, err = scumDeploymentPlan(plugin, instance.Deployment, operation)
|
||||
if err != nil {
|
||||
return domain.Job{}, err
|
||||
}
|
||||
}
|
||||
job, err := svc.CreateJob(domain.Job{
|
||||
ID: lifecycleJobID(instance.ID, action, idempotencyKey),
|
||||
ServerInstanceID: instance.ID,
|
||||
@@ -293,11 +328,12 @@ func (svc *CoreService) dispatchLifecycleJob(instance domain.ServerInstance, act
|
||||
IdempotencyKey: idempotencyKey,
|
||||
Progress: lifecycleJobProgress(instance.Deployment),
|
||||
ExecutionInput: domain.JobExecutionInput{
|
||||
WorkspaceScope: profileKey,
|
||||
PluginID: plugin.ID,
|
||||
LifecycleOperation: lifecycleExecutionOperation(action),
|
||||
DLLExtensions: dllExtensions,
|
||||
Deployment: deploymentPlanForDispatch(instance.Deployment),
|
||||
WorkspaceScope: profileKey,
|
||||
PluginID: plugin.ID,
|
||||
LifecycleOperation: lifecycleExecutionOperation(action),
|
||||
DLLExtensions: dllExtensions,
|
||||
Deployment: deploymentPlanForDispatch(instance.Deployment),
|
||||
ServerDeploymentPlan: serverDeploymentPlan,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -58,6 +58,31 @@ func (svc *CoreService) projectLifecycleJobResult(job domain.Job, stamp time.Tim
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if plan := job.ExecutionInput.ServerDeploymentPlan; plan != nil {
|
||||
projection := domain.CopyServerDeploymentProjection(instance.DeploymentProjection)
|
||||
projection.Operation = plan.Operation
|
||||
projection.TemplateKey = plan.TemplateKey
|
||||
projection.TemplateVersion = plan.TemplateVersion
|
||||
projection.UpdatedAt = stamp
|
||||
if evidence := job.ExecutionResult.ServerDeploymentEvidence; evidence != nil {
|
||||
projection.State = "verified"
|
||||
projection.PreflightState = evidence.PreflightState
|
||||
projection.DiscoveryState = evidence.DiscoveryState
|
||||
projection.MappingState = evidence.MappingState
|
||||
projection.VerificationState = evidence.VerificationState
|
||||
projection.DiscoveredFacts = domain.CopyStringMap(evidence.DiscoveredFacts)
|
||||
projection.MappingResults = domain.CopyStringMap(evidence.MappingResults)
|
||||
projection.VerificationResults = domain.CopyStringMap(evidence.VerificationResults)
|
||||
projection.FailureCode = evidence.FailureCode
|
||||
}
|
||||
if job.State == domain.JobStateFailed || job.State == domain.JobStateCancelled {
|
||||
projection.State = "failed"
|
||||
if projection.FailureCode == "" && job.State == domain.JobStateCancelled {
|
||||
projection.FailureCode = "cancelled"
|
||||
}
|
||||
}
|
||||
instance.DeploymentProjection = projection
|
||||
}
|
||||
instance.State = nextState
|
||||
instance.UpdatedAt = stamp
|
||||
if err := validator.ValidateServerInstance(instance); err != nil {
|
||||
@@ -73,6 +98,36 @@ func (svc *CoreService) projectLifecycleJobResult(job domain.Job, stamp time.Tim
|
||||
return svc.recordAuditEvent("run:"+job.RunEndpointID, "lifecycle.result", "server-instance", instance.ID, auditResult, job.Progress.Message)
|
||||
}
|
||||
|
||||
func (svc *CoreService) projectServerDeploymentProgress(job domain.Job, stamp time.Time) error {
|
||||
plan := job.ExecutionInput.ServerDeploymentPlan
|
||||
if plan == nil || job.ServerInstanceID == "" {
|
||||
return nil
|
||||
}
|
||||
instance, err := svc.store.ServerInstances().Get(job.ServerInstanceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projection := domain.CopyServerDeploymentProjection(instance.DeploymentProjection)
|
||||
projection.State = "running"
|
||||
projection.Operation = plan.Operation
|
||||
projection.TemplateKey = plan.TemplateKey
|
||||
projection.TemplateVersion = plan.TemplateVersion
|
||||
switch job.Progress.Phase {
|
||||
case "preflight":
|
||||
projection.PreflightState = "running"
|
||||
case "scan", "discover", "discovery":
|
||||
projection.DiscoveryState = "running"
|
||||
case "install", "configure", "mapping":
|
||||
projection.MappingState = "running"
|
||||
case "start", "health":
|
||||
projection.VerificationState = "running"
|
||||
}
|
||||
projection.UpdatedAt = stamp
|
||||
instance.DeploymentProjection = projection
|
||||
instance.UpdatedAt = stamp
|
||||
return svc.store.ServerInstances().Update(instance)
|
||||
}
|
||||
|
||||
func lifecycleProjectedState(capability string, jobState domain.JobState) (domain.ServerInstanceState, bool) {
|
||||
if capability != domain.LifecycleCapabilityInstall && capability != domain.LifecycleCapabilityStart && capability != domain.LifecycleCapabilityStop {
|
||||
return "", false
|
||||
|
||||
@@ -1757,7 +1757,7 @@ func validPluginRunCapability(capability string) bool {
|
||||
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteQuery,
|
||||
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
|
||||
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
|
||||
domain.JobCapabilityDeploymentPlan, domain.JobCapabilityDeploymentShellPosix, domain.JobCapabilityDeploymentShellPowerShell, domain.JobCapabilityDeploymentShellCmd,
|
||||
domain.JobCapabilityDeploymentPlan, domain.JobCapabilitySCUMDeploymentPlan, domain.JobCapabilityDeploymentShellPosix, domain.JobCapabilityDeploymentShellPowerShell, domain.JobCapabilityDeploymentShellCmd,
|
||||
domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate,
|
||||
domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall,
|
||||
"artifacts.read", "artifacts.write", "artifact.read", "artifact.write",
|
||||
|
||||
@@ -29,6 +29,7 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
discoveryKeys := map[string]struct{}{}
|
||||
dependencyKeys := map[string]struct{}{}
|
||||
installPlanKeys := map[string]struct{}{}
|
||||
serverDeploymentKeys := map[string]struct{}{}
|
||||
logSourceKeys := map[string]struct{}{}
|
||||
logSourceRetentions := map[string]int{}
|
||||
logEventKeys := map[string]struct{}{}
|
||||
@@ -150,6 +151,76 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
}
|
||||
violations = append(violations, validateRuntimePlatforms(prefix+".platforms", plan.Platforms)...)
|
||||
}
|
||||
for i, profile := range profiles.ServerDeployments {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.serverDeployments[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", profile.Key)...)
|
||||
violations = append(violations, recordRuntimeProfileKey(serverDeploymentKeys, prefix+".key", profile.Key)...)
|
||||
if !validSemanticVersion(profile.Version) {
|
||||
violations = append(violations, prefix+".version must be semantic")
|
||||
}
|
||||
if !regexp.MustCompile(`^[0-9]{1,12}$`).MatchString(profile.SteamAppID) {
|
||||
violations = append(violations, prefix+".steamAppId must be numeric")
|
||||
}
|
||||
for field, value := range map[string]string{"executableKey": profile.ExecutableKey, "installRootKey": profile.InstallRootKey, "configKey": profile.ConfigKey} {
|
||||
violations = append(violations, validateProfileKey(prefix+"."+field, value)...)
|
||||
}
|
||||
if profile.ConfigFormat != "ini" && profile.ConfigFormat != "json" && profile.ConfigFormat != "yaml" && profile.ConfigFormat != "properties" {
|
||||
violations = append(violations, prefix+".configFormat is invalid")
|
||||
}
|
||||
if len(profile.SupportedTargets) == 0 {
|
||||
violations = append(violations, prefix+".supportedTargets must not be empty")
|
||||
}
|
||||
for j, target := range profile.SupportedTargets {
|
||||
if !validPluginSupportedOS(target.OS) || !oneOf(target.Arch, "amd64", "arm64") {
|
||||
violations = append(violations, fmt.Sprintf("%s.supportedTargets[%d] is invalid", prefix, j))
|
||||
}
|
||||
}
|
||||
mappingKeys := map[string]struct{}{}
|
||||
for j, mapping := range profile.ConfigMappings {
|
||||
mappingPrefix := fmt.Sprintf("%s.configMappings[%d]", prefix, j)
|
||||
if !regexp.MustCompile(`^[A-Za-z][A-Za-z0-9._/-]{0,79}$`).MatchString(mapping.FieldKey) {
|
||||
violations = append(violations, mappingPrefix+".fieldKey is invalid")
|
||||
}
|
||||
violations = append(violations, validateProfileKey(mappingPrefix+".configKey", mapping.ConfigKey)...)
|
||||
if _, exists := mappingKeys[mapping.FieldKey]; exists {
|
||||
violations = append(violations, mappingPrefix+".fieldKey duplicates another mapping")
|
||||
}
|
||||
mappingKeys[mapping.FieldKey] = struct{}{}
|
||||
if !oneOf(mapping.ValueType, "text", "integer", "number", "boolean", "port") {
|
||||
violations = append(violations, mappingPrefix+".valueType is invalid")
|
||||
}
|
||||
}
|
||||
markerKeys := map[string]struct{}{}
|
||||
for j, marker := range profile.DiscoveryMarkers {
|
||||
markerPrefix := fmt.Sprintf("%s.discoveryMarkers[%d]", prefix, j)
|
||||
violations = append(violations, validateProfileKey(markerPrefix+".key", marker.Key)...)
|
||||
violations = append(violations, validateProfileKey(markerPrefix+".targetKey", marker.TargetKey)...)
|
||||
if _, exists := markerKeys[marker.Key]; exists {
|
||||
violations = append(violations, markerPrefix+".key duplicates another marker")
|
||||
}
|
||||
markerKeys[marker.Key] = struct{}{}
|
||||
if !oneOf(marker.Kind, "file.exists", "command.version", "port.open", "steam.app") {
|
||||
violations = append(violations, markerPrefix+".kind is invalid")
|
||||
}
|
||||
violations = append(violations, validateSafeRuntimeValue(markerPrefix+".expected", marker.Expected)...)
|
||||
}
|
||||
checkKeys := map[string]struct{}{}
|
||||
for j, check := range profile.VerificationChecks {
|
||||
checkPrefix := fmt.Sprintf("%s.verificationChecks[%d]", prefix, j)
|
||||
violations = append(violations, validateProfileKey(checkPrefix+".key", check.Key)...)
|
||||
violations = append(violations, validateProfileKey(checkPrefix+".targetKey", check.TargetKey)...)
|
||||
if _, exists := checkKeys[check.Key]; exists {
|
||||
violations = append(violations, checkPrefix+".key duplicates another check")
|
||||
}
|
||||
checkKeys[check.Key] = struct{}{}
|
||||
if !oneOf(check.Kind, "executable.present", "version.matches", "port.bound", "config.readable", "process.healthy") {
|
||||
violations = append(violations, checkPrefix+".kind is invalid")
|
||||
}
|
||||
}
|
||||
if len(profile.VerificationChecks) == 0 || !containsRequiredVerification(profile.VerificationChecks) {
|
||||
violations = append(violations, prefix+".verificationChecks must include executable, config, port, and process checks")
|
||||
}
|
||||
}
|
||||
for i, source := range profiles.LogSources {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.logSources[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", source.Key)...)
|
||||
@@ -396,6 +467,23 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func containsRequiredVerification(checks []domain.RuntimeServerVerificationCheck) bool {
|
||||
required := map[string]bool{"executable.present": false, "version.matches": false, "port.bound": false, "config.readable": false, "process.healthy": false}
|
||||
for _, check := range checks {
|
||||
if check.Required {
|
||||
if _, ok := required[check.Kind]; ok {
|
||||
required[check.Kind] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, present := range required {
|
||||
if !present {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func validateRuntimeDLLExtensionProfile(prefix string, extension domain.RuntimeDLLExtensionProfile) []string {
|
||||
var violations []string
|
||||
if extension.Kind != "ue4ss-dll" || extension.Activation != "server-start" {
|
||||
|
||||
Reference in New Issue
Block a user