feat: 完整游戏运维功能

This commit is contained in:
npc0-hue
2026-07-18 09:04:01 +08:00
parent f3b14b7945
commit 48b8ad8d6c
187 changed files with 16607 additions and 1140 deletions
+39 -3
View File
@@ -2,6 +2,7 @@ package validator
import (
"fmt"
"net/url"
"strings"
"browser.local/platform/domain"
@@ -14,6 +15,7 @@ func ValidateRuntimeBinding(binding domain.RuntimeBinding) error {
violations = appendRequired(violations, "id", binding.ID)
violations = appendRequired(violations, "serverInstanceId", binding.ServerInstanceID)
violations = appendRequired(violations, "pluginId", binding.PluginID)
violations = appendRequired(violations, "pluginVersion", binding.PluginVersion)
violations = appendRequired(violations, "profileKey", binding.ProfileKey)
violations = appendRequired(violations, "mode", binding.Mode)
if !validRuntimeBindingStatus(binding.Status) {
@@ -23,7 +25,10 @@ func ValidateRuntimeBinding(binding domain.RuntimeBinding) error {
if !validDistributionLogicalKey(key) {
violations = append(violations, "bindings key is invalid")
}
if containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(strings.ToLower(value), "://") && !strings.HasPrefix(value, "secret://") {
trimmed := strings.TrimSpace(value)
lowerKey := strings.ToLower(key)
sensitiveKey := strings.Contains(lowerKey, "password") || strings.Contains(lowerKey, "credential") || strings.Contains(lowerKey, "secret") || strings.Contains(lowerKey, "token") || strings.Contains(lowerKey, "dsn")
if trimmed != value || strings.HasPrefix(value, "/") || strings.HasPrefix(value, `\`) || containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(strings.ToLower(value), "://") && !strings.HasPrefix(value, "secret://") || sensitiveKey && value != "" && !strings.HasPrefix(value, "secret://") {
violations = append(violations, "bindings."+key+" must use safe logical or secret refs")
}
}
@@ -176,6 +181,18 @@ func ValidateDependencyStatus(status domain.DependencyStatus) error {
if status.InstallPlanKey != "" && !validDistributionLogicalKey(status.InstallPlanKey) {
violations = append(violations, "installPlanKey is invalid")
}
if status.PlanDigest != "" && !validSHA256Checksum(status.PlanDigest) {
violations = append(violations, "planDigest must be sha256:<hex>")
}
if len(status.JobID) > 180 || containsUnsafeRuntimeSecret(status.JobID) || looksLikeRawHostPath(status.JobID) {
violations = append(violations, "jobId is unsafe or too long")
}
if status.CompletedSteps < 0 || status.CompletedSteps > 64 {
violations = append(violations, "completedSteps is out of bounds")
}
if len(status.Evidence) > maxDistributionMessageLength || containsUnsafeRuntimeSecret(status.Evidence) || looksLikeRawHostPath(status.Evidence) {
violations = append(violations, "evidence is unsafe or too long")
}
if len(status.Message) > maxDistributionMessageLength || containsUnsafeRuntimeSecret(status.Message) || looksLikeRawHostPath(status.Message) {
violations = append(violations, "message is unsafe or too long")
}
@@ -229,6 +246,9 @@ func ValidateRunUpdateJob(job domain.RunUpdateJob) error {
violations = appendRequired(violations, "runEndpointId", job.RunEndpointID)
violations = appendRequired(violations, "artifactId", job.ArtifactID)
violations = appendRequired(violations, "checksum", job.Checksum)
violations = appendRequired(violations, "targetOs", job.TargetOS)
violations = appendRequired(violations, "targetArch", job.TargetArch)
violations = appendRequired(violations, "targetRelease", job.TargetRelease)
violations = appendRequired(violations, "idempotencyKey", job.IdempotencyKey)
if job.Checksum != "" && !validSHA256Checksum(job.Checksum) {
violations = append(violations, "checksum must be sha256:<hex>")
@@ -236,6 +256,13 @@ func ValidateRunUpdateJob(job domain.RunUpdateJob) error {
if !validDistributionJobStatus(job.Status) {
violations = append(violations, "status is invalid")
}
violations = appendDistributionTargetViolations(violations, job.TargetOS, job.TargetArch)
if !validRunUpdatePhase(job.Phase) {
violations = append(violations, "phase is invalid")
}
if len(job.Message) > maxDistributionMessageLength || containsUnsafeRuntimeSecret(job.Message) || looksLikeRawHostPath(job.Message) {
violations = append(violations, "message is unsafe or too long")
}
if containsUnsafeRuntimeSecret(job.IdempotencyKey) || looksLikeRawHostPath(job.IdempotencyKey) {
violations = append(violations, "idempotencyKey is unsafe")
}
@@ -248,6 +275,15 @@ func ValidateRunUpdateJob(job domain.RunUpdateJob) error {
return finish(violations)
}
func validRunUpdatePhase(phase domain.RunUpdatePhase) bool {
switch phase {
case domain.RunUpdatePhaseQueued, domain.RunUpdatePhaseDownloading, domain.RunUpdatePhaseStaged, domain.RunUpdatePhaseRestartRequested, domain.RunUpdatePhaseActivating, domain.RunUpdatePhaseSucceeded, domain.RunUpdatePhaseRolledBack, domain.RunUpdatePhaseFailed:
return true
default:
return false
}
}
func ValidateRunDistributionGenerateRequest(request domain.RunDistributionGenerateRequest) error {
var violations []string
violations = appendRequired(violations, "serverInstanceId", request.ServerInstanceID)
@@ -341,8 +377,8 @@ func validateRepositoryURL(field string, value string) []string {
if strings.TrimSpace(value) == "" {
return nil
}
lowered := strings.ToLower(strings.TrimSpace(value))
if !strings.HasPrefix(lowered, "https://") || !strings.HasSuffix(lowered, ".git") {
parsed, err := url.ParseRequestURI(strings.TrimSpace(value))
if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" || !strings.HasSuffix(parsed.Path, ".git") {
return []string{field + " must be an HTTPS git repository URL"}
}
for _, reason := range unsafePluginStringReasons(value) {