init
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
package protocol
|
||||
|
||||
import "strings"
|
||||
|
||||
type RunAutonomousLifecyclePlan struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
ServerInstanceID string `json:"serverInstanceId"`
|
||||
PluginID string `json:"pluginId"`
|
||||
PluginVersion string `json:"pluginVersion"`
|
||||
RunEndpointID string `json:"runEndpointId"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
TargetOS string `json:"targetOs"`
|
||||
TargetArch string `json:"targetArch"`
|
||||
TargetRelease string `json:"targetRelease"`
|
||||
DeploymentRevision int `json:"deploymentRevision,omitempty"`
|
||||
Bootstrap *RunAutonomousLifecycleAction `json:"bootstrap,omitempty"`
|
||||
Actions []RunAutonomousLifecycleAction `json:"actions,omitempty"`
|
||||
DependencyProbes []DependencyProbe `json:"dependencyProbes,omitempty"`
|
||||
InstallPlans []DependencyInstallPlan `json:"installPlans,omitempty"`
|
||||
LogSources []RuntimeLogSourcePlan `json:"logSources,omitempty"`
|
||||
DLLExtensions []RuntimeDLLExtensionPlan `json:"dllExtensions,omitempty"`
|
||||
DataTargets []RunAutonomousDataTarget `json:"dataTargets,omitempty"`
|
||||
RuntimeBindings map[string]string `json:"runtimeBindings,omitempty"`
|
||||
Deployment *RunAutonomousDeployment `json:"deployment,omitempty"`
|
||||
}
|
||||
|
||||
type RunAutonomousLifecycleAction struct {
|
||||
Action string `json:"action"`
|
||||
Operation string `json:"operation"`
|
||||
Capability string `json:"capability"`
|
||||
TargetKey string `json:"targetKey"`
|
||||
}
|
||||
|
||||
type RunAutonomousDeployment struct {
|
||||
SchemaVersion string `json:"schemaVersion"`
|
||||
Mode string `json:"mode"`
|
||||
ProfileKey string `json:"profileKey,omitempty"`
|
||||
RuntimeBindings map[string]string `json:"runtimeBindings,omitempty"`
|
||||
CreateInputs map[string]string `json:"createInputs,omitempty"`
|
||||
ServerRoot string `json:"serverRoot,omitempty"`
|
||||
WorkingDirectory string `json:"workingDirectory,omitempty"`
|
||||
InstallCommand string `json:"installCommand,omitempty"`
|
||||
StartCommand string `json:"startCommand,omitempty"`
|
||||
StopCommand string `json:"stopCommand,omitempty"`
|
||||
StatusCommand string `json:"statusCommand,omitempty"`
|
||||
Shell string `json:"shell,omitempty"`
|
||||
Revision int `json:"revision,omitempty"`
|
||||
}
|
||||
|
||||
type RunAutonomousDataTarget struct {
|
||||
Key string `json:"key"`
|
||||
Kind string `json:"kind"`
|
||||
TransportKey string `json:"transportKey"`
|
||||
SourceRootKey string `json:"sourceRootKey"`
|
||||
SourcePath string `json:"sourcePath"`
|
||||
WorkspaceKey string `json:"workspaceKey"`
|
||||
RefreshPolicy string `json:"refreshPolicy"`
|
||||
MaxBytes int64 `json:"maxBytes,omitempty"`
|
||||
Platforms []string `json:"platforms,omitempty"`
|
||||
}
|
||||
|
||||
const maxAutonomousDataTargetBytes = int64(1024 * 1024 * 1024)
|
||||
|
||||
func ValidateRunAutonomousLifecyclePlan(plan RunAutonomousLifecyclePlan) error {
|
||||
if plan.SchemaVersion != "1" {
|
||||
return ValidationError("autonomous lifecycle plan schema is unsupported")
|
||||
}
|
||||
if !ValidLogicalFileKey(plan.ServerInstanceID) || !ValidLogicalFileKey(plan.RunEndpointID) || !validAutonomousPluginID(plan.PluginID) {
|
||||
return ValidationError("autonomous lifecycle plan identity is invalid")
|
||||
}
|
||||
if plan.ProfileKey != "" && !ValidLogicalFileKey(plan.ProfileKey) {
|
||||
return ValidationError("autonomous lifecycle profile key is invalid")
|
||||
}
|
||||
if !validAutonomousTarget(plan.TargetOS, plan.TargetArch) {
|
||||
return ValidationError("autonomous lifecycle target platform is invalid")
|
||||
}
|
||||
if plan.TargetRelease != "" && !validAutonomousToken(plan.TargetRelease, 240) {
|
||||
return ValidationError("autonomous lifecycle target release is invalid")
|
||||
}
|
||||
if plan.PluginVersion != "" && !validAutonomousToken(plan.PluginVersion, 120) {
|
||||
return ValidationError("autonomous lifecycle plugin version is invalid")
|
||||
}
|
||||
if plan.Bootstrap != nil {
|
||||
if err := validateAutonomousLifecycleAction(*plan.Bootstrap); err != nil {
|
||||
return err
|
||||
}
|
||||
if plan.Bootstrap.Capability != RunCapabilityProcessInstall && plan.Bootstrap.Capability != RunCapabilityProcessStart {
|
||||
return ValidationError("autonomous lifecycle bootstrap action is invalid")
|
||||
}
|
||||
}
|
||||
if len(plan.Actions) > 16 || len(plan.DependencyProbes) > 64 || len(plan.InstallPlans) > 64 || len(plan.LogSources) > 16 || len(plan.DLLExtensions) > 16 || len(plan.DataTargets) > 16 {
|
||||
return ValidationError("autonomous lifecycle plan is too large")
|
||||
}
|
||||
for _, action := range plan.Actions {
|
||||
if err := validateAutonomousLifecycleAction(action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, probe := range plan.DependencyProbes {
|
||||
if !ValidLogicalFileKey(probe.Key) || !ValidLogicalFileKey(probe.TargetKey) || !validAutonomousToken(probe.Kind, 80) || probe.MinimumVersion != "" && !validAutonomousToken(probe.MinimumVersion, 80) {
|
||||
return ValidationError("autonomous dependency probe is invalid")
|
||||
}
|
||||
}
|
||||
for _, installPlan := range plan.InstallPlans {
|
||||
if !ValidLogicalFileKey(installPlan.Key) || len(installPlan.Steps) > 64 {
|
||||
return ValidationError("autonomous install plan is invalid")
|
||||
}
|
||||
for _, step := range installPlan.Steps {
|
||||
if !ValidLogicalFileKey(step.TargetKey) || !validAutonomousToken(step.Type, 80) || step.PackageManager != "" && !validAutonomousToken(step.PackageManager, 80) || step.PackageName != "" && !validAutonomousToken(step.PackageName, 160) || step.Version != "" && !validAutonomousToken(step.Version, 120) {
|
||||
return ValidationError("autonomous install step is invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, source := range plan.LogSources {
|
||||
if err := validateRuntimeProcessLogSourcePlan(source); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
seenDataTargets := map[string]struct{}{}
|
||||
seenWorkspaceTargets := map[string]struct{}{}
|
||||
for _, target := range plan.DataTargets {
|
||||
if err := validateAutonomousDataTarget(target); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exists := seenDataTargets[target.Key]; exists {
|
||||
return ValidationError("autonomous data target key is duplicated")
|
||||
}
|
||||
if _, exists := seenWorkspaceTargets[target.WorkspaceKey]; exists {
|
||||
return ValidationError("autonomous data target workspace is duplicated")
|
||||
}
|
||||
seenDataTargets[target.Key] = struct{}{}
|
||||
seenWorkspaceTargets[target.WorkspaceKey] = struct{}{}
|
||||
}
|
||||
if plan.Deployment != nil {
|
||||
if err := validateAutonomousDeployment(*plan.Deployment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAutonomousDataTarget(target RunAutonomousDataTarget) error {
|
||||
if !ValidLogicalFileKey(target.Key) || !ValidLogicalFileKey(target.TransportKey) || !ValidLogicalFileKey(target.SourceRootKey) || !ValidLogicalFileKey(target.SourcePath) || !ValidLogicalFileKey(target.WorkspaceKey) {
|
||||
return ValidationError("autonomous data target is invalid")
|
||||
}
|
||||
if target.Kind != "sqlite.snapshot" || target.RefreshPolicy != "on-demand-snapshot" || !strings.HasPrefix(target.WorkspaceKey, "databases/") {
|
||||
return ValidationError("autonomous data target is invalid")
|
||||
}
|
||||
if target.MaxBytes <= 0 || target.MaxBytes > maxAutonomousDataTargetBytes {
|
||||
return ValidationError("autonomous data target byte limit is invalid")
|
||||
}
|
||||
for _, platform := range target.Platforms {
|
||||
if platform != "windows" && platform != "linux" && platform != "darwin" {
|
||||
return ValidationError("autonomous data target platform is invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAutonomousLifecycleAction(action RunAutonomousLifecycleAction) error {
|
||||
if !ValidLogicalFileKey(action.TargetKey) || !validAutonomousLifecycleCapability(action.Capability) || !validAutonomousLifecycleName(action.Action) || !validAutonomousLifecycleName(action.Operation) {
|
||||
return ValidationError("autonomous lifecycle action is invalid")
|
||||
}
|
||||
if expected := autonomousCapabilityForAction(action.Action); expected != "" && expected != action.Capability {
|
||||
return ValidationError("autonomous lifecycle action capability mismatch")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autonomousCapabilityForAction(action string) string {
|
||||
switch action {
|
||||
case "create":
|
||||
return RunCapabilityProcessInstall
|
||||
case "start":
|
||||
return RunCapabilityProcessStart
|
||||
case "stop":
|
||||
return RunCapabilityProcessStop
|
||||
case "status":
|
||||
return RunCapabilityProcessStatus
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func validAutonomousLifecycleCapability(capability string) bool {
|
||||
switch capability {
|
||||
case RunCapabilityProcessInstall, RunCapabilityProcessStart, RunCapabilityProcessStop, RunCapabilityProcessStatus:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validAutonomousLifecycleName(value string) bool {
|
||||
switch value {
|
||||
case "create", "install", "start", "stop", "status":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validateAutonomousDeployment(deployment RunAutonomousDeployment) error {
|
||||
if deployment.SchemaVersion != "1" || deployment.Mode == "" || deployment.Revision < 0 || deployment.ProfileKey != "" && !ValidLogicalFileKey(deployment.ProfileKey) {
|
||||
return ValidationError("autonomous deployment is invalid")
|
||||
}
|
||||
for key := range deployment.RuntimeBindings {
|
||||
if !ValidLogicalFileKey(key) {
|
||||
return ValidationError("autonomous deployment binding is invalid")
|
||||
}
|
||||
}
|
||||
for key := range deployment.CreateInputs {
|
||||
if !ValidLogicalFileKey(key) {
|
||||
return ValidationError("autonomous deployment input is invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validAutonomousPluginID(value string) bool {
|
||||
return ValidLogicalFileKey(value)
|
||||
}
|
||||
|
||||
func validAutonomousTarget(osName string, arch string) bool {
|
||||
switch osName {
|
||||
case "windows", "linux", "darwin":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
switch arch {
|
||||
case "amd64", "arm64":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func validAutonomousToken(value string, maxRunes int) bool {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" || trimmed != value || len([]rune(value)) > maxRunes {
|
||||
return false
|
||||
}
|
||||
lower := strings.ToLower(value)
|
||||
if strings.Contains(value, "..") || strings.Contains(value, `\`) || strings.Contains(value, "://") || strings.Contains(lower, "/users/") || strings.Contains(lower, "password=") || strings.Contains(lower, "secret=") || strings.Contains(lower, "bearer ") || strings.Contains(lower, "sk-") {
|
||||
return false
|
||||
}
|
||||
for _, char := range value {
|
||||
if char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char >= '0' && char <= '9' || char == '_' || char == '-' || char == '.' || char == '/' || char == ':' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user