Remove legacy runtime deployment paths

This commit is contained in:
npc0-hue
2026-09-04 12:36:21 +08:00
parent 14cbc63e61
commit 3cfb98ed47
39 changed files with 407 additions and 589 deletions
@@ -52,7 +52,7 @@ func TestValidateGamePluginRuntimeProfilesRejectsUnsafeOrUnpublishedDLLExtension
func TestValidateJobRejectsDLLPlanOutsideProcessStart(t *testing.T) {
profiles := validRuntimeDLLExtensionProfiles()
extension := profiles.DLLExtensions[0]
job := domain.Job{ID: "dll-job", ServerInstanceID: "server-1", RunEndpointID: "run-1", Capability: domain.LifecycleCapabilityStop, IdempotencyKey: "dll-stop", State: domain.JobStateQueued, RetryPolicy: domain.JobRetryPolicy{MaxAttempts: 1, InitialBackoffSeconds: 1, MaxBackoffSeconds: 1}, ExecutionInput: domain.JobExecutionInput{LifecycleOperation: "stop", DLLExtensions: []domain.RuntimeDLLExtensionPlan{{Key: extension.Key, Version: extension.Version, ReleaseURL: extension.ReleaseURL, Checksum: extension.Checksum, SizeBytes: extension.SizeBytes, TargetKey: extension.TargetKey, ModKey: extension.ModKey, DLLRef: extension.DLLRef, SCUMExecutableChecksum: extension.SCUMExecutableChecksum, UE4SSABI: extension.UE4SSABI, RCONPort: extension.RCONPort}}}}
job := domain.Job{ID: "dll-job", ServerInstanceID: "server-1", RunEndpointID: "run-1", Capability: domain.LifecycleCapabilityStop, IdempotencyKey: "dll-stop", State: domain.JobStateQueued, RetryPolicy: domain.JobRetryPolicy{MaxAttempts: 1, InitialBackoffSeconds: 1, MaxBackoffSeconds: 1}, ExecutionInput: domain.JobExecutionInput{LifecycleOperation: "stop", DLLExtensions: []domain.RuntimeDLLExtensionPlan{{Key: extension.Key, Version: extension.Version, ReleaseURL: extension.ReleaseURL, Checksum: extension.Checksum, SizeBytes: extension.SizeBytes, TargetKey: extension.TargetKey, ModKey: extension.ModKey, DLLRef: extension.DLLRef, TargetExecutableChecksum: extension.TargetExecutableChecksum, UE4SSABI: extension.UE4SSABI, RCONPort: extension.RCONPort}}}}
if err := ValidateJob(job); err == nil || !strings.Contains(err.Error(), "process.start") {
t.Fatalf("expected process.start plan restriction, got %v", err)
}
@@ -65,7 +65,7 @@ func validRuntimeDLLExtensionProfiles() domain.GamePluginRuntimeProfiles {
Key: "scum-simple-rcon", DisplayName: "SCUM Simple RCON", Kind: "ue4ss-dll", Activation: "server-start", Version: "0.1.0", ReleaseState: "ready",
ReleaseURL: "https://cdn.npc0.com/scum_simple_rcon_ue4s.dll", Checksum: "sha256:" + strings.Repeat("a", 64), SizeBytes: 1024,
TargetKey: "ue4ss/scum-simple-rcon", ModKey: "scum_simple_rcon", DLLRef: "ue4ss/Mods/scum_simple_rcon/dlls/main.dll",
SCUMExecutableChecksum: "sha256:" + strings.Repeat("b", 64), UE4SSABI: "ue4ss-3.0", SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}}, UpdateOnStart: true, RCONPort: 27015,
TargetExecutableChecksum: "sha256:" + strings.Repeat("b", 64), UE4SSABI: "ue4ss-3.0", SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}}, UpdateOnStart: true, RCONPort: 27015,
}},
}
}
+20 -124
View File
@@ -29,7 +29,6 @@ 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{}{}
for i, probe := range profiles.Discovery {
@@ -95,7 +94,7 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
}
for j, step := range plan.Steps {
stepPrefix := fmt.Sprintf("%s.steps[%d]", prefix, j)
if !oneOf(step.Type, "package", "verified-download", "steamcmd-app", "manual") {
if !oneOf(step.Type, "package", "verified-download", "manual") {
violations = append(violations, stepPrefix+".type is invalid")
}
violations = append(violations, validateProfileKey(stepPrefix+".targetKey", step.TargetKey)...)
@@ -133,100 +132,14 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
if step.DownloadRef == "" || step.Checksum == "" {
violations = append(violations, stepPrefix+" requires downloadRef and checksum")
}
case "steamcmd-app":
if step.PackageManager != "" && step.PackageManager != "steamcmd" || !regexp.MustCompile(`^[0-9]{1,12}$`).MatchString(step.PackageName) {
violations = append(violations, stepPrefix+" requires a numeric Steam app and steamcmd adapter")
}
case "manual":
if step.DownloadRef != "" || step.Checksum != "" || step.PackageName != "" {
if step.DownloadRef != "" || step.Checksum != "" || step.PackageManager != "" || step.PackageName != "" || step.Version != "" {
violations = append(violations, stepPrefix+" manual step cannot contain machine execution fields")
}
}
}
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")
}
prerequisiteKeys := map[string]struct{}{}
for j, prerequisite := range profile.Prerequisites {
prerequisitePrefix := fmt.Sprintf("%s.prerequisites[%d]", prefix, j)
violations = append(violations, validateProfileKey(prerequisitePrefix+".key", prerequisite.Key)...)
if _, exists := prerequisiteKeys[prerequisite.Key]; exists {
violations = append(violations, prerequisitePrefix+".key duplicates another prerequisite")
}
prerequisiteKeys[prerequisite.Key] = struct{}{}
if !oneOf(prerequisite.Kind, "steamcmd", "windows-vcredist", "windows-directx") {
violations = append(violations, prerequisitePrefix+".kind is invalid")
}
}
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)...)
@@ -329,23 +242,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
return finish(violations)
}
func containsRequiredVerification(checks []domain.RuntimeServerVerificationCheck) bool {
required := map[string]bool{"executable.present": 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" {
@@ -381,8 +277,8 @@ func validateRuntimeDLLExtensionProfile(prefix string, extension domain.RuntimeD
if extension.ReleaseURL == "" {
violations = append(violations, prefix+".releaseUrl is required for a ready release")
}
if !validSHA256Checksum(extension.Checksum) || !validSHA256Checksum(extension.SCUMExecutableChecksum) {
violations = append(violations, prefix+".checksum and scumExecutableChecksum must be SHA-256")
if !validSHA256Checksum(extension.Checksum) || !validSHA256Checksum(extension.TargetExecutableChecksum) {
violations = append(violations, prefix+".checksum and targetExecutableChecksum must be SHA-256")
}
if extension.SizeBytes < 1 || extension.SizeBytes > 128*1024*1024 {
violations = append(violations, prefix+".sizeBytes is out of bounds")
@@ -409,22 +305,22 @@ func validateRuntimeDLLReleaseURL(field string, value string) []string {
func validateRuntimeDLLExtensionPlan(prefix string, plan domain.RuntimeDLLExtensionPlan) []string {
return validateRuntimeDLLExtensionProfile(prefix, domain.RuntimeDLLExtensionProfile{
Key: plan.Key,
Kind: "ue4ss-dll",
Activation: "server-start",
Version: plan.Version,
ReleaseState: "ready",
ReleaseURL: plan.ReleaseURL,
Checksum: plan.Checksum,
SizeBytes: plan.SizeBytes,
TargetKey: plan.TargetKey,
ModKey: plan.ModKey,
DLLRef: plan.DLLRef,
SCUMExecutableChecksum: plan.SCUMExecutableChecksum,
UE4SSABI: plan.UE4SSABI,
SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}},
UpdateOnStart: true,
RCONPort: plan.RCONPort,
Key: plan.Key,
Kind: "ue4ss-dll",
Activation: "server-start",
Version: plan.Version,
ReleaseState: "ready",
ReleaseURL: plan.ReleaseURL,
Checksum: plan.Checksum,
SizeBytes: plan.SizeBytes,
TargetKey: plan.TargetKey,
ModKey: plan.ModKey,
DLLRef: plan.DLLRef,
TargetExecutableChecksum: plan.TargetExecutableChecksum,
UE4SSABI: plan.UE4SSABI,
SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}},
UpdateOnStart: true,
RCONPort: plan.RCONPort,
})
}
@@ -0,0 +1,26 @@
package validator
import (
"strings"
"testing"
"browser.local/platform/domain"
)
func TestValidateGamePluginRuntimeProfilesRejectsLegacySteamCMDAppStep(t *testing.T) {
profiles := domain.GamePluginRuntimeProfiles{InstallPlans: []domain.RuntimeInstallPlan{{Key: "install-game", Title: "Install game", Steps: []domain.RuntimeInstallStep{{Type: "steamcmd-app", TargetKey: "server/install-root", PackageManager: "steamcmd", PackageName: "123456"}}}}}
err := ValidateGamePluginRuntimeProfiles(profiles)
if err == nil || !strings.Contains(err.Error(), "type is invalid") {
t.Fatalf("expected legacy steamcmd-app step rejection, got %v", err)
}
}
func TestValidateGamePluginRuntimeProfilesRejectsManualStepExecutionFields(t *testing.T) {
profiles := domain.GamePluginRuntimeProfiles{InstallPlans: []domain.RuntimeInstallPlan{{Key: "manual-plan", Title: "Manual plan", Steps: []domain.RuntimeInstallStep{{Type: "manual", TargetKey: "operator/manual", PackageManager: "steamcmd"}}}}}
err := ValidateGamePluginRuntimeProfiles(profiles)
if err == nil || !strings.Contains(err.Error(), "manual step cannot contain machine execution fields") {
t.Fatalf("expected manual execution field rejection, got %v", err)
}
}