feat: 完整游戏运维功能
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"browser.local/platform/domain"
|
||||
)
|
||||
|
||||
var clientManagerIdentifierPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$`)
|
||||
|
||||
func ValidateClientManagerInstallation(value domain.ClientManagerInstallation) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", value.ID)
|
||||
violations = appendRequired(violations, "serverInstanceId", value.ServerInstanceID)
|
||||
violations = appendRequired(violations, "pluginId", value.PluginID)
|
||||
violations = appendRequired(violations, "profileKey", value.ProfileKey)
|
||||
violations = appendRequired(violations, "runEndpointId", value.RunEndpointID)
|
||||
if !validDistributionLogicalKey(value.ProfileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if value.TargetOS != "" || value.TargetArch != "" {
|
||||
violations = appendDistributionTargetViolations(violations, value.TargetOS, value.TargetArch)
|
||||
}
|
||||
if !validClientManagerLifecycleStatus(value.Status) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
if !validClientManagerHealth(value.Health) {
|
||||
violations = append(violations, "health is invalid")
|
||||
}
|
||||
if value.KeyGeneration < 0 || value.DeploymentGeneration < 0 {
|
||||
violations = append(violations, "keyGeneration and deploymentGeneration must not be negative")
|
||||
}
|
||||
if value.Checksum != "" && !validSHA256Checksum(value.Checksum) {
|
||||
violations = append(violations, "checksum must be sha256:<hex>")
|
||||
}
|
||||
for field, content := range map[string]string{"phase": value.Phase, "healthReason": value.HealthReason} {
|
||||
if len(content) > 240 || unsafeLifecycleText(content) {
|
||||
violations = append(violations, field+" must be bounded and redacted")
|
||||
}
|
||||
}
|
||||
if value.CreatedAt.IsZero() || value.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "createdAt and updatedAt are required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerLifecycleTransition(from, to domain.ClientManagerLifecycleStatus) error {
|
||||
if from == to {
|
||||
return nil
|
||||
}
|
||||
allowed := map[domain.ClientManagerLifecycleStatus][]domain.ClientManagerLifecycleStatus{
|
||||
domain.ClientManagerLifecycleRequested: {domain.ClientManagerLifecycleBuilding, domain.ClientManagerLifecycleAvailable, domain.ClientManagerLifecycleDeploying, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleBuilding: {domain.ClientManagerLifecycleAvailable, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleAvailable: {domain.ClientManagerLifecycleDeploying, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleDeploying: {domain.ClientManagerLifecycleInstalled, domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleFailed, domain.ClientManagerLifecycleUninstalled},
|
||||
domain.ClientManagerLifecycleInstalled: {domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleUninstalled, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleRegistering: {domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleDegraded, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleOnline: {domain.ClientManagerLifecycleDegraded, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleDegraded: {domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleOffline: {domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleUninstalled, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleUpdating: {domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleDegraded, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleRollingBack: {domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleOnline, domain.ClientManagerLifecycleDegraded, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleStopping: {domain.ClientManagerLifecycleInstalled, domain.ClientManagerLifecycleOffline, domain.ClientManagerLifecycleUninstalled, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleUninstalled: {domain.ClientManagerLifecycleDeploying, domain.ClientManagerLifecycleFailed},
|
||||
domain.ClientManagerLifecycleFailed: {domain.ClientManagerLifecycleDeploying, domain.ClientManagerLifecycleRegistering, domain.ClientManagerLifecycleUpdating, domain.ClientManagerLifecycleRollingBack, domain.ClientManagerLifecycleStopping, domain.ClientManagerLifecycleUninstalled},
|
||||
}
|
||||
for _, candidate := range allowed[from] {
|
||||
if candidate == to {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return finish([]string{"client-manager lifecycle transition is invalid"})
|
||||
}
|
||||
|
||||
func ValidateClientManagerSession(value domain.ClientManagerSession) error {
|
||||
var violations []string
|
||||
for field, content := range map[string]string{"id": value.ID, "installationId": value.InstallationID, "serverInstanceId": value.ServerInstanceID, "profileKey": value.ProfileKey, "runEndpointId": value.RunEndpointID, "artifactId": value.ArtifactID, "tokenHash": value.TokenHash} {
|
||||
violations = appendRequired(violations, field, content)
|
||||
}
|
||||
if value.KeyGeneration <= 0 || value.DeploymentGeneration <= 0 {
|
||||
violations = append(violations, "keyGeneration and deploymentGeneration must be positive")
|
||||
}
|
||||
if len(value.TokenHash) != 64 || !regexp.MustCompile(`^[a-f0-9]{64}$`).MatchString(value.TokenHash) {
|
||||
violations = append(violations, "tokenHash must be a SHA-256 digest")
|
||||
}
|
||||
if !oneOf(string(value.Status), string(domain.ClientManagerSessionActive), string(domain.ClientManagerSessionRevoked), string(domain.ClientManagerSessionExpired)) {
|
||||
violations = append(violations, "status is invalid")
|
||||
}
|
||||
if value.ExpiresAt.IsZero() || value.CreatedAt.IsZero() || value.UpdatedAt.IsZero() {
|
||||
violations = append(violations, "session timestamps are required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerNonce(value domain.ClientManagerRegistrationNonce) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", value.ID)
|
||||
violations = appendRequired(violations, "installationId", value.InstallationID)
|
||||
if len(value.ID) != 64 || !regexp.MustCompile(`^[a-f0-9]{64}$`).MatchString(value.ID) {
|
||||
violations = append(violations, "id must be a nonce SHA-256 digest")
|
||||
}
|
||||
if value.ExpiresAt.IsZero() || value.CreatedAt.IsZero() || !value.ExpiresAt.After(value.CreatedAt) {
|
||||
violations = append(violations, "nonce expiry must follow creation")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerDeployRequest(value domain.ClientManagerDeployRequest) error {
|
||||
return validateClientManagerOperationRequest(value.ServerInstanceID, value.ProfileKey, value.DistributionID, value.IdempotencyKey, value.ExpectedDeploymentGeneration, true)
|
||||
}
|
||||
|
||||
func ValidateClientManagerControlRequest(value domain.ClientManagerControlRequest) error {
|
||||
if !oneOf(string(value.Operation), "start", "stop", "restart", "status", "rollback") {
|
||||
return finish([]string{"operation is invalid"})
|
||||
}
|
||||
return validateClientManagerOperationRequest(value.ServerInstanceID, value.ProfileKey, "", value.IdempotencyKey, value.ExpectedDeploymentGeneration, false)
|
||||
}
|
||||
|
||||
func ValidateClientManagerUpdateRequest(value domain.ClientManagerUpdateRequest) error {
|
||||
var violations []string
|
||||
if !value.Approved {
|
||||
violations = append(violations, "approved must be true")
|
||||
}
|
||||
if err := validateClientManagerOperationRequest(value.ServerInstanceID, value.ProfileKey, value.DistributionID, value.IdempotencyKey, value.ExpectedDeploymentGeneration, true); err != nil {
|
||||
violations = append(violations, err.Error())
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerUninstallRequest(value domain.ClientManagerUninstallRequest) error {
|
||||
var violations []string
|
||||
if !value.Confirmed {
|
||||
violations = append(violations, "confirmed must be true")
|
||||
}
|
||||
if err := validateClientManagerOperationRequest(value.ServerInstanceID, value.ProfileKey, "", value.IdempotencyKey, value.ExpectedDeploymentGeneration, false); err != nil {
|
||||
violations = append(violations, err.Error())
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerRetryRequest(value domain.ClientManagerRetryRequest) error {
|
||||
return validateClientManagerOperationRequest(value.ServerInstanceID, value.ProfileKey, "", value.IdempotencyKey, value.ExpectedDeploymentGeneration, false)
|
||||
}
|
||||
|
||||
func ValidateClientManagerLifecycleInputRequest(value domain.ClientManagerLifecycleInputRequest) error {
|
||||
var violations []string
|
||||
for field, content := range map[string]string{"runEndpointId": value.RunEndpointID, "sessionToken": value.SessionToken, "jobId": value.JobID, "leaseToken": value.LeaseToken} {
|
||||
violations = appendRequired(violations, field, content)
|
||||
}
|
||||
if value.Attempt <= 0 {
|
||||
violations = append(violations, "attempt must be positive")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerRegisterRequest(value domain.ClientManagerRegisterRequest) error {
|
||||
var violations []string
|
||||
for field, content := range map[string]string{"installationId": value.InstallationID, "serverInstanceId": value.ServerInstanceID, "profileKey": value.ProfileKey, "artifactId": value.ArtifactID, "version": value.Version, "sourceRevision": value.SourceRevision, "targetOs": value.TargetOS, "targetArch": value.TargetArch, "nonce": value.Nonce, "signature": value.Signature} {
|
||||
violations = appendRequired(violations, field, content)
|
||||
}
|
||||
violations = appendDistributionTargetViolations(violations, value.TargetOS, value.TargetArch)
|
||||
if value.KeyGeneration <= 0 || value.DeploymentGeneration <= 0 {
|
||||
violations = append(violations, "keyGeneration and deploymentGeneration must be positive")
|
||||
}
|
||||
if value.Timestamp.IsZero() {
|
||||
violations = append(violations, "timestamp is required")
|
||||
}
|
||||
if !regexp.MustCompile(`^[A-Za-z0-9_-]{16,128}$`).MatchString(value.Nonce) {
|
||||
violations = append(violations, "nonce is invalid")
|
||||
}
|
||||
if !regexp.MustCompile(`^sha256:[a-f0-9]{64}$`).MatchString(value.Signature) {
|
||||
violations = append(violations, "signature is invalid")
|
||||
}
|
||||
if len(value.Capabilities) == 0 {
|
||||
violations = append(violations, "capabilities must not be empty")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateClientManagerHeartbeat(value domain.ClientManagerHeartbeat) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "installationId", value.InstallationID)
|
||||
violations = appendRequired(violations, "sessionToken", value.SessionToken)
|
||||
if value.Sequence == 0 {
|
||||
violations = append(violations, "sequence must be positive")
|
||||
}
|
||||
if !validClientManagerHealth(value.Health) || value.Health == domain.ClientManagerHealthUnknown {
|
||||
violations = append(violations, "health is invalid")
|
||||
}
|
||||
if len(value.HealthReason) > 240 || unsafeLifecycleText(value.HealthReason) {
|
||||
violations = append(violations, "healthReason must be bounded and redacted")
|
||||
}
|
||||
if value.SentAt.IsZero() {
|
||||
violations = append(violations, "sentAt is required")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func validateClientManagerOperationRequest(serverID, profileKey, distributionID, idempotencyKey string, generation int, distributionRequired bool) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "serverInstanceId", serverID)
|
||||
violations = appendRequired(violations, "profileKey", profileKey)
|
||||
violations = appendRequired(violations, "idempotencyKey", idempotencyKey)
|
||||
if distributionRequired {
|
||||
violations = appendRequired(violations, "distributionId", distributionID)
|
||||
}
|
||||
if !validDistributionLogicalKey(profileKey) {
|
||||
violations = append(violations, "profileKey is invalid")
|
||||
}
|
||||
if !clientManagerIdentifierPattern.MatchString(idempotencyKey) {
|
||||
violations = append(violations, "idempotencyKey is invalid")
|
||||
}
|
||||
if generation < 0 {
|
||||
violations = append(violations, "expectedDeploymentGeneration must not be negative")
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func validClientManagerLifecycleStatus(value domain.ClientManagerLifecycleStatus) bool {
|
||||
return oneOf(string(value), "requested", "building", "available", "deploying", "installed", "registering", "online", "degraded", "offline", "updating", "rolling_back", "stopping", "uninstalled", "failed")
|
||||
}
|
||||
|
||||
func validClientManagerHealth(value domain.ClientManagerHealthStatus) bool {
|
||||
return oneOf(string(value), "unknown", "healthy", "degraded", "unhealthy", "offline")
|
||||
}
|
||||
|
||||
func unsafeLifecycleText(value string) bool {
|
||||
lowered := strings.ToLower(strings.TrimSpace(value))
|
||||
return strings.Contains(lowered, "secret://") || strings.Contains(lowered, "bearer ") || strings.Contains(lowered, "password=") || strings.Contains(lowered, "token=") || strings.Contains(lowered, "unix://") || strings.Contains(lowered, "tcp://") || looksLikeRawHostPath(value)
|
||||
}
|
||||
Reference in New Issue
Block a user