Align runtime profiles with plugin-owned records
This commit is contained in:
@@ -12,10 +12,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
runtimeLogEventSchemaRefPattern = regexp.MustCompile(`^[A-Za-z0-9_./-]+\.json$`)
|
||||
runtimeLogEventTypePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9._-]{0,119}$`)
|
||||
runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`)
|
||||
runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`)
|
||||
runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`)
|
||||
runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`)
|
||||
)
|
||||
|
||||
func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles) error {
|
||||
@@ -23,6 +21,9 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
var violations []string
|
||||
lifecycleKeys := map[string]struct{}{}
|
||||
transportKeys := map[string]struct{}{}
|
||||
transportProfiles := map[string]domain.RuntimeTransportProfile{}
|
||||
dataTargetKeys := map[string]struct{}{}
|
||||
dataTargetWorkspaces := map[string]struct{}{}
|
||||
managerKeys := map[string]struct{}{}
|
||||
dllExtensionKeys := map[string]struct{}{}
|
||||
dllExtensionStates := map[string]string{}
|
||||
@@ -31,9 +32,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
installPlanKeys := map[string]struct{}{}
|
||||
serverDeploymentKeys := map[string]struct{}{}
|
||||
logSourceKeys := map[string]struct{}{}
|
||||
logSourceRetentions := map[string]int{}
|
||||
logEventKeys := map[string]struct{}{}
|
||||
logEventTypes := map[string]struct{}{}
|
||||
|
||||
for i, probe := range profiles.Discovery {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.discovery[%d]", i)
|
||||
@@ -237,9 +235,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
prefix := fmt.Sprintf("runtimeProfiles.logSources[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", source.Key)...)
|
||||
violations = append(violations, recordRuntimeProfileKey(logSourceKeys, prefix+".key", source.Key)...)
|
||||
if source.Key != "" {
|
||||
logSourceRetentions[source.Key] = source.RetentionDays
|
||||
}
|
||||
if !oneOf(source.Kind, "process.stdout", "process.stderr", "file.tail", "ftp.poll", "sql.query", "client-manager") {
|
||||
violations = append(violations, prefix+".kind is invalid")
|
||||
}
|
||||
@@ -254,44 +249,6 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
violations = append(violations, prefix+".retentionDays is invalid")
|
||||
}
|
||||
}
|
||||
if len(profiles.LogEvents) > 128 {
|
||||
violations = append(violations, "runtimeProfiles.logEvents must not exceed 128")
|
||||
}
|
||||
for i, event := range profiles.LogEvents {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.logEvents[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", event.Key)...)
|
||||
violations = append(violations, recordRuntimeProfileKey(logEventKeys, prefix+".key", event.Key)...)
|
||||
if strings.TrimSpace(event.Title) == "" || len([]rune(event.Title)) > 80 {
|
||||
violations = append(violations, prefix+".title is invalid")
|
||||
}
|
||||
violations = append(violations, validateSafeRuntimeValue(prefix+".title", event.Title)...)
|
||||
violations = append(violations, validateProfileKey(prefix+".sourceKey", event.SourceKey)...)
|
||||
if _, exists := logSourceKeys[event.SourceKey]; !exists {
|
||||
violations = append(violations, prefix+".sourceKey must reference a declared runtime log source")
|
||||
}
|
||||
if !runtimeLogEventTypePattern.MatchString(event.EventType) {
|
||||
violations = append(violations, prefix+".eventType is invalid")
|
||||
}
|
||||
violations = append(violations, recordRuntimeProfileKey(logEventTypes, prefix+".eventType", event.EventType)...)
|
||||
if hasUnsafeRuntimeLogEventSemantics(event.EventType) {
|
||||
violations = append(violations, prefix+".eventType contains unsafe operation semantics")
|
||||
}
|
||||
if !validPluginPermission(event.Permission) {
|
||||
violations = append(violations, prefix+".permission is not allowed")
|
||||
}
|
||||
if len(event.SchemaRef) > 240 || !runtimeLogEventSchemaRefPattern.MatchString(event.SchemaRef) || !safeRelativeJSONRef(event.SchemaRef) {
|
||||
violations = append(violations, prefix+".schemaRef must be a bounded safe relative JSON reference")
|
||||
}
|
||||
if event.RetentionDays < 1 || event.RetentionDays > 365 {
|
||||
violations = append(violations, prefix+".retentionDays is invalid")
|
||||
}
|
||||
if sourceRetention, exists := logSourceRetentions[event.SourceKey]; exists && sourceRetention > 0 && event.RetentionDays > sourceRetention {
|
||||
violations = append(violations, prefix+".retentionDays must not exceed the source retention")
|
||||
}
|
||||
if !oneOf(string(event.Severity), string(domain.RuntimeLogEventSeverityInfo), string(domain.RuntimeLogEventSeverityNotice), string(domain.RuntimeLogEventSeverityWarning), string(domain.RuntimeLogEventSeverityCritical)) {
|
||||
violations = append(violations, prefix+".severity is invalid")
|
||||
}
|
||||
}
|
||||
for i, transport := range profiles.TransportProfiles {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.transportProfiles[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", transport.Key)...)
|
||||
@@ -311,6 +268,36 @@ func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles
|
||||
}
|
||||
}
|
||||
violations = append(violations, duplicateViolations(prefix+".capabilities", transport.Capabilities)...)
|
||||
if transport.Key != "" {
|
||||
transportProfiles[transport.Key] = transport
|
||||
}
|
||||
}
|
||||
if len(profiles.DataTargets) > 16 {
|
||||
violations = append(violations, "runtimeProfiles.dataTargets must not exceed 16")
|
||||
}
|
||||
for i, target := range profiles.DataTargets {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.dataTargets[%d]", i)
|
||||
violations = append(violations, validateProfileKey(prefix+".key", target.Key)...)
|
||||
violations = append(violations, recordRuntimeProfileKey(dataTargetKeys, prefix+".key", target.Key)...)
|
||||
violations = append(violations, validateProfileKey(prefix+".transportKey", target.TransportKey)...)
|
||||
violations = append(violations, validateProfileKey(prefix+".sourceRootKey", target.SourceRootKey)...)
|
||||
violations = append(violations, validateSafeRelativeRuntimePath(prefix+".sourcePath", target.SourcePath)...)
|
||||
violations = append(violations, validateProfileKey(prefix+".workspaceKey", target.WorkspaceKey)...)
|
||||
if target.Kind != "sqlite.snapshot" || target.RefreshPolicy != "on-demand-snapshot" || !strings.HasPrefix(target.WorkspaceKey, "databases/") {
|
||||
violations = append(violations, prefix+" must declare an on-demand sqlite snapshot workspace")
|
||||
}
|
||||
if target.MaxBytes < 1 || target.MaxBytes > 1024*1024*1024 {
|
||||
violations = append(violations, prefix+".maxBytes is invalid")
|
||||
}
|
||||
violations = append(violations, validateRuntimePlatforms(prefix+".platforms", target.Platforms)...)
|
||||
if _, exists := dataTargetWorkspaces[target.WorkspaceKey]; exists {
|
||||
violations = append(violations, prefix+".workspaceKey is duplicated")
|
||||
}
|
||||
dataTargetWorkspaces[target.WorkspaceKey] = struct{}{}
|
||||
transport, exists := transportProfiles[target.TransportKey]
|
||||
if !exists || transport.Kind != "sqlite" || transport.TargetKey != target.TransportKey || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery) {
|
||||
violations = append(violations, prefix+".transportKey must reference a declared SQLite query transport")
|
||||
}
|
||||
}
|
||||
for i, manager := range profiles.ClientManagers {
|
||||
prefix := fmt.Sprintf("runtimeProfiles.clientManagers[%d]", i)
|
||||
@@ -609,42 +596,6 @@ func validateSafeRuntimeValue(field, value string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasUnsafeRuntimeLogEventSemantics(eventType string) bool {
|
||||
lowered := strings.ToLower(strings.TrimSpace(eventType))
|
||||
tokens := strings.FieldsFunc(lowered, func(char rune) bool {
|
||||
return char == '.' || char == '_' || char == '-' || char == '/'
|
||||
})
|
||||
unsafeTokens := map[string]struct{}{
|
||||
"apikey": {}, "credential": {}, "credentials": {}, "eval": {}, "exec": {},
|
||||
"execute": {}, "password": {}, "powershell": {}, "script": {}, "secret": {},
|
||||
"shell": {}, "socket": {}, "terminal": {}, "token": {},
|
||||
}
|
||||
for _, token := range tokens {
|
||||
if _, unsafe := unsafeTokens[token]; unsafe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for index := 0; index+1 < len(tokens); index++ {
|
||||
pair := tokens[index] + "." + tokens[index+1]
|
||||
switch pair {
|
||||
case "absolute.path", "access.key", "api.key", "component.key", "database.query", "direct.socket", "file.path", "host.path", "private.key", "raw.path", "run.direct", "run.socket", "unix.socket":
|
||||
return true
|
||||
}
|
||||
}
|
||||
tokenSet := make(map[string]struct{}, len(tokens))
|
||||
for _, token := range tokens {
|
||||
tokenSet[token] = struct{}{}
|
||||
}
|
||||
if _, hasSQL := tokenSet["sql"]; hasSQL {
|
||||
for _, token := range []string{"query", "statement", "raw"} {
|
||||
if _, unsafe := tokenSet[token]; unsafe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func recordRuntimeProfileKey(seen map[string]struct{}, field, key string) []string {
|
||||
if key == "" {
|
||||
return nil
|
||||
@@ -681,20 +632,6 @@ func validateRuntimeProfileCapabilityDeclarations(profiles domain.GamePluginRunt
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateRuntimeLogEventPermissionDeclarations(field string, profiles domain.GamePluginRuntimeProfiles, declared []string) []string {
|
||||
declaredSet := make(map[string]struct{}, len(declared))
|
||||
for _, permission := range declared {
|
||||
declaredSet[permission] = struct{}{}
|
||||
}
|
||||
var violations []string
|
||||
for i, event := range profiles.LogEvents {
|
||||
if _, exists := declaredSet[event.Permission]; !exists {
|
||||
violations = append(violations, fmt.Sprintf("%s[%d].permission must be declared by the plugin", field, i))
|
||||
}
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateLifecycleActionsOptional(actions domain.PluginLifecycleActions) []string {
|
||||
var violations []string
|
||||
for field, value := range map[string]string{"install": actions.Install, "start": actions.Start, "stop": actions.Stop, "restart": actions.Restart, "status": actions.Status} {
|
||||
|
||||
Reference in New Issue
Block a user