Complete platform management workflows
This commit is contained in:
@@ -145,6 +145,7 @@ func ValidateGamePlugin(plugin domain.GamePlugin) error {
|
||||
violations = append(violations, validatePluginPages(plugin.Pages)...)
|
||||
violations = append(violations, duplicateViolations("tags", plugin.Tags)...)
|
||||
violations = append(violations, validateAIPurposes(plugin.AIPurposes)...)
|
||||
violations = append(violations, validateRemoteAccess("remoteAccess", plugin.RemoteAccess, plugin.RequiredRunCapabilities)...)
|
||||
violations = append(violations, validateSafePluginStrings("gamePlugin", pluginSafeStrings(plugin))...)
|
||||
return finish(violations)
|
||||
}
|
||||
@@ -196,6 +197,7 @@ func ValidateGamePluginManifestRegistration(registration domain.GamePluginManife
|
||||
violations = append(violations, validatePluginPages(manifest.Pages)...)
|
||||
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)...)
|
||||
violations = append(violations, validateSafePluginStrings("manifest", manifestSafeStrings(registration))...)
|
||||
return finish(violations)
|
||||
}
|
||||
@@ -348,6 +350,7 @@ func validatePluginMarketplacePlugin(prefix string, plugin domain.PluginMarketpl
|
||||
violations = append(violations, validatePluginPages(plugin.Pages)...)
|
||||
violations = append(violations, duplicateViolations(prefix+".tags", plugin.Tags)...)
|
||||
violations = append(violations, validateAIPurposes(plugin.AIPurposes)...)
|
||||
violations = append(violations, validateRemoteAccess(prefix+".remoteAccess", plugin.RemoteAccess, plugin.Capabilities)...)
|
||||
violations = append(violations, validateSafePluginStrings(prefix, marketplacePluginSafeStrings(plugin))...)
|
||||
return violations
|
||||
}
|
||||
@@ -401,6 +404,14 @@ func AuthorizePluginBridgeAction(plugin domain.GamePlugin, request domain.Plugin
|
||||
}
|
||||
|
||||
func ValidateServerInstance(instance domain.ServerInstance) error {
|
||||
return validateServerInstance(instance, false)
|
||||
}
|
||||
|
||||
func ValidateStoredServerInstance(instance domain.ServerInstance) error {
|
||||
return validateServerInstance(instance, true)
|
||||
}
|
||||
|
||||
func validateServerInstance(instance domain.ServerInstance, allowDeleted bool) error {
|
||||
var violations []string
|
||||
violations = appendRequired(violations, "id", instance.ID)
|
||||
violations = appendRequired(violations, "pluginId", instance.PluginID)
|
||||
@@ -425,7 +436,7 @@ func ValidateServerInstance(instance domain.ServerInstance) error {
|
||||
if !validServerInstanceState(instance.State) {
|
||||
violations = append(violations, "state is invalid")
|
||||
}
|
||||
if instance.State == domain.ServerInstanceStateDeleted {
|
||||
if instance.State == domain.ServerInstanceStateDeleted && !allowDeleted {
|
||||
violations = append(violations, "state must not be deleted on create")
|
||||
}
|
||||
if instance.ConfigVersion < 0 {
|
||||
@@ -434,6 +445,20 @@ func ValidateServerInstance(instance domain.ServerInstance) error {
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateServerInstanceUpdate(update domain.ServerInstanceUpdate) error {
|
||||
var violations []string
|
||||
if update.Name != nil {
|
||||
name := strings.TrimSpace(*update.Name)
|
||||
if name == "" {
|
||||
violations = append(violations, "name is required")
|
||||
}
|
||||
if name != *update.Name {
|
||||
violations = append(violations, "name must not have surrounding whitespace")
|
||||
}
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
func ValidateServerInstanceDependencies(instance domain.ServerInstance, plugin domain.GamePlugin, endpoint domain.RunEndpoint) error {
|
||||
var violations []string
|
||||
if plugin.ID == "" {
|
||||
@@ -698,6 +723,17 @@ func ValidateJob(job domain.Job) error {
|
||||
violations = append(violations, "inputRef is required for scoped write jobs")
|
||||
}
|
||||
}
|
||||
if isRemoteRunCapability(job.Capability) {
|
||||
if job.ServerInstanceID == "" {
|
||||
violations = append(violations, "serverInstanceId is required for remote access jobs")
|
||||
}
|
||||
if remoteCapabilityRequiresTargetKey(job.Capability) && job.TargetKey == "" {
|
||||
violations = append(violations, "targetKey is required for remote access jobs")
|
||||
}
|
||||
if remoteCapabilityRequiresInputRef(job.Capability) && job.InputRef == "" {
|
||||
violations = append(violations, "inputRef is required for remote access jobs")
|
||||
}
|
||||
}
|
||||
return finish(violations)
|
||||
}
|
||||
|
||||
@@ -877,6 +913,63 @@ func validateAIPurposes(purposes []string) []string {
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateRemoteAccess(field string, remote domain.GamePluginRemoteAccess, declaredCapabilities []string) []string {
|
||||
var violations []string
|
||||
if len(remote.Methods) == 0 && len(remote.RunCapabilities) == 0 && len(remote.DatabaseEngines) == 0 && !remote.RCON && !remote.LogTransfer {
|
||||
return violations
|
||||
}
|
||||
if len(remote.Methods) == 0 {
|
||||
violations = append(violations, field+".methods must not be empty when remote access is declared")
|
||||
}
|
||||
for i, method := range remote.Methods {
|
||||
if !validRemoteAccessMethod(method) {
|
||||
violations = append(violations, fmt.Sprintf("%s.methods[%d] is not allowed", field, i))
|
||||
}
|
||||
}
|
||||
violations = append(violations, duplicateViolations(field+".methods", remote.Methods)...)
|
||||
for i, capability := range remote.RunCapabilities {
|
||||
if !validPluginRunCapability(capability) || !isRemoteRunCapability(capability) {
|
||||
violations = append(violations, fmt.Sprintf("%s.runCapabilities[%d] is not allowed", field, i))
|
||||
continue
|
||||
}
|
||||
if !containsString(declaredCapabilities, capability) {
|
||||
violations = append(violations, fmt.Sprintf("%s.runCapabilities[%d] must also be declared in capabilities", field, i))
|
||||
}
|
||||
}
|
||||
violations = append(violations, duplicateViolations(field+".runCapabilities", remote.RunCapabilities)...)
|
||||
for i, engine := range remote.DatabaseEngines {
|
||||
if !validRemoteDatabaseEngine(engine) {
|
||||
violations = append(violations, fmt.Sprintf("%s.databaseEngines[%d] is not allowed", field, i))
|
||||
}
|
||||
}
|
||||
violations = append(violations, duplicateViolations(field+".databaseEngines", remote.DatabaseEngines)...)
|
||||
if containsString(remote.Methods, "run") && len(remote.RunCapabilities) == 0 {
|
||||
violations = append(violations, field+".runCapabilities must not be empty when run access is declared")
|
||||
}
|
||||
if containsString(remote.Methods, "ftp") && !containsAny(declaredCapabilities, []string{domain.JobCapabilityRemoteFTPRead, domain.JobCapabilityRemoteFTPWrite}) {
|
||||
violations = append(violations, field+" requires remote.ftp.read or remote.ftp.write when ftp is declared")
|
||||
}
|
||||
if containsString(remote.Methods, "rsync") && !containsAny(declaredCapabilities, []string{domain.JobCapabilityRemoteRsyncRead, domain.JobCapabilityRemoteRsyncWrite}) {
|
||||
violations = append(violations, field+" requires remote.rsync.read or remote.rsync.write when rsync is declared")
|
||||
}
|
||||
if remote.RCON && !containsString(remote.RunCapabilities, domain.JobCapabilityRemoteRunRCONCommand) {
|
||||
violations = append(violations, field+".rcon requires remote.run.rcon.command")
|
||||
}
|
||||
if remote.LogTransfer && !containsString(remote.RunCapabilities, domain.JobCapabilityRemoteRunLogsTransfer) {
|
||||
violations = append(violations, field+".logTransfer requires remote.run.logs.transfer")
|
||||
}
|
||||
for _, engine := range remote.DatabaseEngines {
|
||||
required := domain.JobCapabilityRemoteRunDBMySQLQuery
|
||||
if engine == "sqlite" {
|
||||
required = domain.JobCapabilityRemoteRunDBSQLiteQuery
|
||||
}
|
||||
if !containsString(remote.RunCapabilities, required) {
|
||||
violations = append(violations, fmt.Sprintf("%s.databaseEngines requires %s", field, required))
|
||||
}
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
func validateSafePluginStrings(prefix string, values []fieldString) []string {
|
||||
var violations []string
|
||||
for _, value := range values {
|
||||
@@ -908,6 +1001,9 @@ func pluginSafeStrings(plugin domain.GamePlugin) []fieldString {
|
||||
values = appendStringSliceFields(values, "tags", plugin.Tags)
|
||||
values = appendStringSliceFields(values, "aiPurposes", plugin.AIPurposes)
|
||||
values = appendStringSliceFields(values, "bridgeActions", plugin.BridgeActions)
|
||||
values = appendStringSliceFields(values, "remoteAccess.methods", plugin.RemoteAccess.Methods)
|
||||
values = appendStringSliceFields(values, "remoteAccess.runCapabilities", plugin.RemoteAccess.RunCapabilities)
|
||||
values = appendStringSliceFields(values, "remoteAccess.databaseEngines", plugin.RemoteAccess.DatabaseEngines)
|
||||
for i, page := range plugin.Pages {
|
||||
prefix := fmt.Sprintf("pages[%d]", i)
|
||||
values = append(values,
|
||||
@@ -944,6 +1040,9 @@ func manifestSafeStrings(registration domain.GamePluginManifestRegistration) []f
|
||||
values = appendStringSliceFields(values, "capabilities", manifest.Capabilities)
|
||||
values = appendStringSliceFields(values, "permissions", manifest.Permissions)
|
||||
values = appendStringSliceFields(values, "ai.purposes", manifest.AI.Purposes)
|
||||
values = appendStringSliceFields(values, "remoteAccess.methods", manifest.RemoteAccess.Methods)
|
||||
values = appendStringSliceFields(values, "remoteAccess.runCapabilities", manifest.RemoteAccess.RunCapabilities)
|
||||
values = appendStringSliceFields(values, "remoteAccess.databaseEngines", manifest.RemoteAccess.DatabaseEngines)
|
||||
for i, page := range manifest.Pages {
|
||||
prefix := fmt.Sprintf("pages[%d]", i)
|
||||
values = append(values,
|
||||
@@ -979,6 +1078,9 @@ func marketplacePluginSafeStrings(plugin domain.PluginMarketplacePlugin) []field
|
||||
values = appendStringSliceFields(values, "tags", plugin.Tags)
|
||||
values = appendStringSliceFields(values, "aiPurposes", plugin.AIPurposes)
|
||||
values = appendStringSliceFields(values, "bridgeActions", plugin.BridgeActions)
|
||||
values = appendStringSliceFields(values, "remoteAccess.methods", plugin.RemoteAccess.Methods)
|
||||
values = appendStringSliceFields(values, "remoteAccess.runCapabilities", plugin.RemoteAccess.RunCapabilities)
|
||||
values = appendStringSliceFields(values, "remoteAccess.databaseEngines", plugin.RemoteAccess.DatabaseEngines)
|
||||
for i, page := range plugin.Pages {
|
||||
prefix := fmt.Sprintf("pages[%d]", i)
|
||||
values = append(values,
|
||||
@@ -1132,7 +1234,14 @@ func validPluginRunCapability(capability string) bool {
|
||||
"config.write",
|
||||
"files.list", "files.read", "files.write", "files.patch",
|
||||
"file.list", "file.read", "file.write", "file.patch",
|
||||
"logs.read", "log.query",
|
||||
"logs.read", "log.query", domain.JobCapabilityLogsBackfill,
|
||||
domain.JobCapabilityRemoteFTPRead, domain.JobCapabilityRemoteFTPWrite,
|
||||
domain.JobCapabilityRemoteRsyncRead, domain.JobCapabilityRemoteRsyncWrite,
|
||||
domain.JobCapabilityRemoteRunFilesRead, domain.JobCapabilityRemoteRunFilesWrite,
|
||||
domain.JobCapabilityRemoteRunProcessStart, domain.JobCapabilityRemoteRunProcessStop,
|
||||
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteQuery,
|
||||
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
|
||||
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
|
||||
"artifacts.read", "artifacts.write", "artifact.read", "artifact.write",
|
||||
"ai.invoke":
|
||||
return true
|
||||
@@ -1141,6 +1250,51 @@ func validPluginRunCapability(capability string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func isRemoteRunCapability(capability string) bool {
|
||||
return strings.HasPrefix(capability, "remote.")
|
||||
}
|
||||
|
||||
func remoteCapabilityRequiresTargetKey(capability string) bool {
|
||||
switch capability {
|
||||
case domain.JobCapabilityRemoteRunProcessStart, domain.JobCapabilityRemoteRunProcessStop:
|
||||
return false
|
||||
default:
|
||||
return isRemoteRunCapability(capability)
|
||||
}
|
||||
}
|
||||
|
||||
func remoteCapabilityRequiresInputRef(capability string) bool {
|
||||
switch capability {
|
||||
case domain.JobCapabilityRemoteFTPWrite,
|
||||
domain.JobCapabilityRemoteRsyncWrite,
|
||||
domain.JobCapabilityRemoteRunFilesWrite,
|
||||
domain.JobCapabilityRemoteRunDBMySQLQuery,
|
||||
domain.JobCapabilityRemoteRunDBSQLiteQuery,
|
||||
domain.JobCapabilityRemoteRunRCONCommand:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validRemoteAccessMethod(method string) bool {
|
||||
switch method {
|
||||
case "ftp", "rsync", "run":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validRemoteDatabaseEngine(engine string) bool {
|
||||
switch engine {
|
||||
case "mysql", "sqlite":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validFileOperationKind(operation domain.FileOperationKind) bool {
|
||||
switch operation {
|
||||
case domain.FileOperationRead, domain.FileOperationWrite:
|
||||
@@ -1186,7 +1340,7 @@ func validScopedInputRef(ref string) bool {
|
||||
|
||||
func validPluginPermission(permission string) bool {
|
||||
switch permission {
|
||||
case "server.create", "server.read", "server.lifecycle", "server.files.read", "server.files.write", "server.logs.read", "server.artifacts.read", "server.artifacts.write", "ai.invoke":
|
||||
case "server.create", "server.read", "server.lifecycle", "server.files.read", "server.files.write", "server.logs.read", "server.artifacts.read", "server.artifacts.write", "server.remote.access", "server.run.distribution", "server.dependencies.manage", "server.client-manager.manage", "ai.invoke":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -1200,6 +1354,11 @@ func validPluginBridgeAction(action domain.PluginBridgeAction) bool {
|
||||
domain.PluginBridgeActionLogsQuery,
|
||||
domain.PluginBridgeActionArtifactsOpen,
|
||||
domain.PluginBridgeActionFilesRequest,
|
||||
domain.PluginBridgeActionRemoteAccessRequest,
|
||||
domain.PluginBridgeActionRunDistribution,
|
||||
domain.PluginBridgeActionDependenciesRequest,
|
||||
domain.PluginBridgeActionLogsBackfillRequest,
|
||||
domain.PluginBridgeActionClientManager,
|
||||
domain.PluginBridgeActionAIInvoke:
|
||||
return true
|
||||
default:
|
||||
@@ -1219,6 +1378,16 @@ func requiredBridgePermissions(action domain.PluginBridgeAction) []string {
|
||||
return []string{"server.artifacts.read"}
|
||||
case domain.PluginBridgeActionFilesRequest:
|
||||
return []string{"server.files.read"}
|
||||
case domain.PluginBridgeActionRemoteAccessRequest:
|
||||
return []string{"server.remote.access"}
|
||||
case domain.PluginBridgeActionRunDistribution:
|
||||
return []string{"server.run.distribution"}
|
||||
case domain.PluginBridgeActionDependenciesRequest:
|
||||
return []string{"server.dependencies.manage"}
|
||||
case domain.PluginBridgeActionLogsBackfillRequest:
|
||||
return []string{"server.logs.read"}
|
||||
case domain.PluginBridgeActionClientManager:
|
||||
return []string{"server.client-manager.manage"}
|
||||
case domain.PluginBridgeActionAIInvoke:
|
||||
return []string{"ai.invoke"}
|
||||
default:
|
||||
@@ -1259,6 +1428,15 @@ func containsAll(values []string, required []string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func containsAny(values []string, candidates []string) bool {
|
||||
for _, candidate := range candidates {
|
||||
if containsString(values, candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func containsString(values []string, target string) bool {
|
||||
for _, value := range values {
|
||||
if value == target {
|
||||
|
||||
Reference in New Issue
Block a user