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
+68 -1
View File
@@ -18,6 +18,7 @@ const (
maxPluginBridgePayloadSize = 4096
maxProgressMessageLength = 256
maxServerConfigContentSize = 64 * 1024
maxJobExecutionContentSize = 64 * 1024
maxLogicalFileKeyLength = 160
)
@@ -146,6 +147,10 @@ func ValidateGamePlugin(plugin domain.GamePlugin) error {
violations = append(violations, duplicateViolations("tags", plugin.Tags)...)
violations = append(violations, validateAIPurposes(plugin.AIPurposes)...)
violations = append(violations, validateRemoteAccess("remoteAccess", plugin.RemoteAccess, plugin.RequiredRunCapabilities)...)
if err := ValidateGamePluginRuntimeProfiles(plugin.RuntimeProfiles); err != nil {
violations = append(violations, err.Error())
}
violations = append(violations, validateRuntimeProfileCapabilityDeclarations(plugin.RuntimeProfiles, plugin.RequiredRunCapabilities)...)
violations = append(violations, validateSafePluginStrings("gamePlugin", pluginSafeStrings(plugin))...)
return finish(violations)
}
@@ -198,6 +203,10 @@ func ValidateGamePluginManifestRegistration(registration domain.GamePluginManife
violations = append(violations, duplicateViolations("manifest.tags", manifest.Tags)...)
violations = append(violations, validateAIPurposes(manifest.AI.Purposes)...)
violations = append(violations, validateRemoteAccess("manifest.remoteAccess", manifest.RemoteAccess, manifest.Capabilities)...)
if err := ValidateGamePluginRuntimeProfiles(manifest.RuntimeProfiles); err != nil {
violations = append(violations, err.Error())
}
violations = append(violations, validateRuntimeProfileCapabilityDeclarations(manifest.RuntimeProfiles, manifest.Capabilities)...)
violations = append(violations, validateSafePluginStrings("manifest", manifestSafeStrings(registration))...)
return finish(violations)
}
@@ -549,6 +558,9 @@ func ValidateServerConfig(config domain.ServerConfig) error {
if len([]byte(config.Content)) > maxServerConfigContentSize {
violations = append(violations, "content is too large")
}
if config.Checksum != "" && !validSHA256Checksum(config.Checksum) {
violations = append(violations, "checksum must be sha256:<hex>")
}
if config.UpdatedAt.IsZero() {
violations = append(violations, "updatedAt is required")
}
@@ -620,6 +632,12 @@ func ValidateFileOperationDispatchRequest(request domain.FileOperationDispatchRe
if request.InputRef != "" && !validScopedInputRef(request.InputRef) {
violations = append(violations, "inputRef is not allowed")
}
if len([]byte(request.Content)) > maxJobExecutionContentSize {
violations = append(violations, "content is too large")
}
if containsUnsafeRuntimeSecret(request.Content) {
violations = append(violations, "content must not expose raw secrets, host paths, or direct sockets")
}
if request.ExpectedConfigVersion < 0 {
violations = append(violations, "expectedConfigVersion must not be negative")
}
@@ -672,6 +690,14 @@ func ValidateRunEndpoint(endpoint domain.RunEndpoint) error {
violations = appendRequired(violations, "id", endpoint.ID)
violations = appendRequired(violations, "displayName", endpoint.DisplayName)
violations = appendRequired(violations, "version", endpoint.Version)
if endpoint.Architecture != "" {
if !validDistributionTargetOS(endpoint.Platform) {
violations = append(violations, "platform is invalid")
}
if !validDistributionTargetArch(endpoint.Architecture) {
violations = append(violations, "architecture is invalid")
}
}
if !validRunEndpointStatus(endpoint.Status) {
violations = append(violations, "status is invalid")
}
@@ -704,12 +730,51 @@ func ValidateJob(job domain.Job) error {
if len(job.Progress.Message) > maxProgressMessageLength {
violations = append(violations, "progress.message is too long")
}
if job.Attempt < 0 {
violations = append(violations, "attempt must not be negative")
}
if job.RetryPolicy.MaxAttempts <= 0 {
violations = append(violations, "retryPolicy.maxAttempts must be positive")
}
if job.RetryPolicy.InitialBackoffSeconds <= 0 || job.RetryPolicy.MaxBackoffSeconds < job.RetryPolicy.InitialBackoffSeconds {
violations = append(violations, "retryPolicy backoff must be positive and bounded")
}
if job.Attempt > job.RetryPolicy.MaxAttempts {
violations = append(violations, "attempt must not exceed retryPolicy.maxAttempts")
}
if job.LeaseTokenHash != "" && len(job.LeaseTokenHash) != 64 {
violations = append(violations, "leaseTokenHash must be a SHA-256 hash")
}
if len(job.CancelReason) > maxProgressMessageLength {
violations = append(violations, "cancelReason is too long")
}
if len(job.ReconcileOutcome) > maxProgressMessageLength {
violations = append(violations, "reconcileOutcome is too long")
}
if job.TargetKey != "" && !validLogicalFileKey(job.TargetKey) {
violations = append(violations, "targetKey is not allowed")
}
if job.InputRef != "" && !validScopedInputRef(job.InputRef) {
violations = append(violations, "inputRef is not allowed")
}
if len([]byte(job.ExecutionInput.Content)) > maxJobExecutionContentSize {
violations = append(violations, "executionInput.content is too large")
}
if job.ExecutionInput.MaxReadBytes < 0 || job.ExecutionInput.MaxReadBytes > maxJobExecutionContentSize {
violations = append(violations, "executionInput.maxReadBytes is out of bounds")
}
if job.ExecutionInput.ExpectedChecksum != "" && !validSHA256Checksum(job.ExecutionInput.ExpectedChecksum) {
violations = append(violations, "executionInput.expectedChecksum must be sha256:<hex>")
}
if job.ExecutionResult.Checksum != "" && !validSHA256Checksum(job.ExecutionResult.Checksum) {
violations = append(violations, "executionResult.checksum must be sha256:<hex>")
}
if len([]byte(job.ExecutionResult.Content)) > maxJobExecutionContentSize {
violations = append(violations, "executionResult.content is too large")
}
if len(job.ExecutionResult.AuditSummary) > maxAuditSummaryLength {
violations = append(violations, "executionResult.auditSummary is too long")
}
if job.Capability == domain.JobCapabilityConfigWrite || job.Capability == domain.JobCapabilityFilesRead || job.Capability == domain.JobCapabilityFilesWrite {
if job.ServerInstanceID == "" {
violations = append(violations, "serverInstanceId is required for scoped file jobs")
@@ -1242,6 +1307,8 @@ func validPluginRunCapability(capability string) bool {
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteQuery,
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
domain.JobCapabilityClientManagerDeploy, domain.JobCapabilityClientManagerControl, domain.JobCapabilityClientManagerUpdate,
domain.JobCapabilityClientManagerRollback, domain.JobCapabilityClientManagerUninstall,
"artifacts.read", "artifacts.write", "artifact.read", "artifact.write",
"ai.invoke":
return true
@@ -1497,7 +1564,7 @@ func validRunEndpointStatus(status domain.RunEndpointStatus) bool {
func validJobState(state domain.JobState) bool {
switch state {
case domain.JobStateQueued, domain.JobStateAccepted, domain.JobStateRunning, domain.JobStateSucceeded, domain.JobStateFailed, domain.JobStateCancelled:
case domain.JobStateQueued, domain.JobStateAccepted, domain.JobStateRunning, domain.JobStateRetrying, domain.JobStateSucceeded, domain.JobStateFailed, domain.JobStateCancelled:
return true
default:
return false