298 lines
9.9 KiB
Go
298 lines
9.9 KiB
Go
package runtime
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"browser.local/run/protocol"
|
|
)
|
|
|
|
const (
|
|
RuntimeModeLocalProcess = "local-process"
|
|
RuntimeModeHostedFTPRCON = "hosted-ftp-rcon"
|
|
RuntimeModeFTPOnly = "ftp-only"
|
|
RuntimeModeCustomClient = "custom-client"
|
|
)
|
|
|
|
type RuntimeProfiles struct {
|
|
Discovery []RuntimeDiscoveryProbe `json:"discovery,omitempty"`
|
|
LifecycleProfiles []RuntimeLifecycleProfile `json:"lifecycleProfiles,omitempty"`
|
|
DependencyProbes []RuntimeDependencyProbe `json:"dependencyProbes,omitempty"`
|
|
InstallPlans []RuntimeInstallPlan `json:"installPlans,omitempty"`
|
|
LogSources []RuntimeLogSource `json:"logSources,omitempty"`
|
|
TransportProfiles []RuntimeTransportProfile `json:"transportProfiles,omitempty"`
|
|
ClientManagers []RuntimeClientManagerSpec `json:"clientManagers,omitempty"`
|
|
}
|
|
|
|
type RuntimeDiscoveryProbe struct {
|
|
Key string `json:"key"`
|
|
Kind string `json:"kind"`
|
|
TargetKey string `json:"targetKey"`
|
|
Required bool `json:"required,omitempty"`
|
|
Platforms []string `json:"platforms,omitempty"`
|
|
}
|
|
|
|
type RuntimeLifecycleProfile struct {
|
|
Key string `json:"key"`
|
|
Mode string `json:"mode"`
|
|
Capabilities []string `json:"capabilities"`
|
|
ActionRefs map[string]string `json:"actionRefs,omitempty"`
|
|
TransportKeys []string `json:"transportKeys,omitempty"`
|
|
ClientManagerRef string `json:"clientManagerRef,omitempty"`
|
|
Platforms []string `json:"platforms,omitempty"`
|
|
}
|
|
|
|
type RuntimeDependencyProbe struct {
|
|
Key string `json:"key"`
|
|
Kind string `json:"kind"`
|
|
TargetKey string `json:"targetKey"`
|
|
Required bool `json:"required,omitempty"`
|
|
Platforms []string `json:"platforms,omitempty"`
|
|
}
|
|
|
|
type RuntimeInstallPlan struct {
|
|
Key string `json:"key"`
|
|
Title string `json:"title"`
|
|
Platforms []string `json:"platforms,omitempty"`
|
|
Steps []RuntimeInstallStep `json:"steps"`
|
|
}
|
|
|
|
type RuntimeInstallStep struct {
|
|
Type string `json:"type"`
|
|
TargetKey string `json:"targetKey"`
|
|
PackageManager string `json:"packageManager,omitempty"`
|
|
PackageName string `json:"packageName,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
DownloadRef string `json:"downloadRef,omitempty"`
|
|
Checksum string `json:"checksum,omitempty"`
|
|
}
|
|
|
|
type RuntimeLogSource struct {
|
|
Key string `json:"key"`
|
|
Kind string `json:"kind"`
|
|
TargetKey string `json:"targetKey,omitempty"`
|
|
StreamKey string `json:"streamKey"`
|
|
CursorKind string `json:"cursorKind,omitempty"`
|
|
RetentionDays int `json:"retentionDays,omitempty"`
|
|
}
|
|
|
|
type RuntimeTransportProfile struct {
|
|
Key string `json:"key"`
|
|
Kind string `json:"kind"`
|
|
TargetKey string `json:"targetKey,omitempty"`
|
|
Capabilities []string `json:"capabilities"`
|
|
}
|
|
|
|
type RuntimeClientManagerSpec struct {
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
type RuntimeBindingSet struct {
|
|
ProfileKey string `json:"profileKey"`
|
|
Mode string `json:"mode"`
|
|
Bindings map[string]string `json:"bindings,omitempty"`
|
|
MissingKeys []string `json:"missingKeys,omitempty"`
|
|
}
|
|
|
|
type RuntimeResolution struct {
|
|
ProfileKey string `json:"profileKey"`
|
|
Mode string `json:"mode"`
|
|
Capabilities []string `json:"capabilities"`
|
|
ActionRefs map[string]string `json:"actionRefs,omitempty"`
|
|
TransportKeys []string `json:"transportKeys,omitempty"`
|
|
Transports []RuntimeTransportProfile `json:"transports,omitempty"`
|
|
LogSources []RuntimeLogSource `json:"logSources,omitempty"`
|
|
Discovery []RuntimeDiscoveryProbe `json:"discovery,omitempty"`
|
|
ClientManagerRef string `json:"clientManagerRef,omitempty"`
|
|
MissingKeys []string `json:"missingKeys,omitempty"`
|
|
Available bool `json:"available"`
|
|
}
|
|
|
|
func ResolveRuntimeProfile(profiles RuntimeProfiles, profileKey string, targetOS string, binding RuntimeBindingSet) (RuntimeResolution, error) {
|
|
profile, ok := findLifecycleProfile(profiles.LifecycleProfiles, profileKey)
|
|
if !ok {
|
|
return RuntimeResolution{}, fmt.Errorf("runtime profile is not declared")
|
|
}
|
|
if !supportedRuntimeMode(profile.Mode) {
|
|
return RuntimeResolution{}, fmt.Errorf("runtime mode is unsupported")
|
|
}
|
|
if targetOS != "" && !supportsPlatform(profile.Platforms, targetOS) {
|
|
return RuntimeResolution{}, fmt.Errorf("runtime profile does not support target platform")
|
|
}
|
|
if binding.ProfileKey != "" && binding.ProfileKey != profile.Key {
|
|
return RuntimeResolution{}, fmt.Errorf("runtime binding profile does not match")
|
|
}
|
|
if binding.Mode != "" && binding.Mode != profile.Mode {
|
|
return RuntimeResolution{}, fmt.Errorf("runtime binding mode does not match")
|
|
}
|
|
if err := validateRuntimeProfile(profile); err != nil {
|
|
return RuntimeResolution{}, err
|
|
}
|
|
|
|
transports, err := resolveTransports(profile.TransportKeys, profiles.TransportProfiles)
|
|
if err != nil {
|
|
return RuntimeResolution{}, err
|
|
}
|
|
missing := missingRuntimeBindingKeys(profile, transports, profiles.Discovery, profiles.LogSources, binding)
|
|
return RuntimeResolution{
|
|
ProfileKey: profile.Key,
|
|
Mode: profile.Mode,
|
|
Capabilities: append([]string(nil), profile.Capabilities...),
|
|
ActionRefs: copyStringMap(profile.ActionRefs),
|
|
TransportKeys: append([]string(nil), profile.TransportKeys...),
|
|
Transports: transports,
|
|
LogSources: safeLogSources(profiles.LogSources, targetOS),
|
|
Discovery: safeDiscovery(profiles.Discovery, targetOS),
|
|
ClientManagerRef: profile.ClientManagerRef,
|
|
MissingKeys: missing,
|
|
Available: len(missing) == 0,
|
|
}, nil
|
|
}
|
|
|
|
func findLifecycleProfile(profiles []RuntimeLifecycleProfile, key string) (RuntimeLifecycleProfile, bool) {
|
|
for _, profile := range profiles {
|
|
if profile.Key == key {
|
|
return profile, true
|
|
}
|
|
}
|
|
return RuntimeLifecycleProfile{}, false
|
|
}
|
|
|
|
func validateRuntimeProfile(profile RuntimeLifecycleProfile) error {
|
|
if !protocol.ValidLogicalFileKey(profile.Key) {
|
|
return fmt.Errorf("runtime profile key is unsafe")
|
|
}
|
|
for _, capability := range profile.Capabilities {
|
|
if strings.TrimSpace(capability) == "" || containsUnsafeRuntimeText(capability) {
|
|
return fmt.Errorf("runtime capability is unsafe")
|
|
}
|
|
}
|
|
for action, ref := range profile.ActionRefs {
|
|
if !protocol.ValidLogicalFileKey(action) || !protocol.ValidLogicalFileKey(ref) {
|
|
return fmt.Errorf("runtime action ref is unsafe")
|
|
}
|
|
}
|
|
if profile.ClientManagerRef != "" && !protocol.ValidLogicalFileKey(profile.ClientManagerRef) {
|
|
return fmt.Errorf("client manager ref is unsafe")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveTransports(keys []string, profiles []RuntimeTransportProfile) ([]RuntimeTransportProfile, error) {
|
|
out := make([]RuntimeTransportProfile, 0, len(keys))
|
|
for _, key := range keys {
|
|
if !protocol.ValidLogicalFileKey(key) {
|
|
return nil, fmt.Errorf("transport key is unsafe")
|
|
}
|
|
found := false
|
|
for _, profile := range profiles {
|
|
if profile.Key != key {
|
|
continue
|
|
}
|
|
if !protocol.ValidLogicalFileKey(profile.Key) || (profile.TargetKey != "" && !protocol.ValidLogicalFileKey(profile.TargetKey)) {
|
|
return nil, fmt.Errorf("transport profile is unsafe")
|
|
}
|
|
out = append(out, profile)
|
|
found = true
|
|
break
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("transport profile %q is not declared", key)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func missingRuntimeBindingKeys(profile RuntimeLifecycleProfile, transports []RuntimeTransportProfile, discovery []RuntimeDiscoveryProbe, logs []RuntimeLogSource, binding RuntimeBindingSet) []string {
|
|
required := map[string]struct{}{}
|
|
for _, transport := range transports {
|
|
if transport.TargetKey != "" {
|
|
required[transport.TargetKey] = struct{}{}
|
|
}
|
|
}
|
|
for _, probe := range discovery {
|
|
if probe.Required && probe.TargetKey != "" {
|
|
required[probe.TargetKey] = struct{}{}
|
|
}
|
|
}
|
|
logTargets := map[string]struct{}{}
|
|
for _, source := range logs {
|
|
if source.TargetKey != "" {
|
|
logTargets[source.TargetKey] = struct{}{}
|
|
}
|
|
}
|
|
if profile.ClientManagerRef != "" {
|
|
required[profile.ClientManagerRef] = struct{}{}
|
|
}
|
|
for _, key := range binding.MissingKeys {
|
|
if _, logTarget := logTargets[key]; logTarget {
|
|
continue
|
|
}
|
|
if protocol.ValidLogicalFileKey(key) {
|
|
required[key] = struct{}{}
|
|
}
|
|
}
|
|
missing := make([]string, 0, len(required))
|
|
for key := range required {
|
|
if _, ok := binding.Bindings[key]; !ok {
|
|
missing = append(missing, key)
|
|
}
|
|
}
|
|
sort.Strings(missing)
|
|
return missing
|
|
}
|
|
|
|
func safeDiscovery(probes []RuntimeDiscoveryProbe, targetOS string) []RuntimeDiscoveryProbe {
|
|
out := []RuntimeDiscoveryProbe{}
|
|
for _, probe := range probes {
|
|
if supportsPlatform(probe.Platforms, targetOS) && protocol.ValidLogicalFileKey(probe.Key) && protocol.ValidLogicalFileKey(probe.TargetKey) {
|
|
out = append(out, probe)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func safeLogSources(sources []RuntimeLogSource, targetOS string) []RuntimeLogSource {
|
|
_ = targetOS
|
|
out := []RuntimeLogSource{}
|
|
for _, source := range sources {
|
|
if protocol.ValidLogicalFileKey(source.Key) && protocol.ValidLogicalFileKey(source.StreamKey) && (source.TargetKey == "" || protocol.ValidLogicalFileKey(source.TargetKey)) {
|
|
out = append(out, source)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func supportedRuntimeMode(mode string) bool {
|
|
switch mode {
|
|
case RuntimeModeLocalProcess, RuntimeModeHostedFTPRCON, RuntimeModeFTPOnly, RuntimeModeCustomClient:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func supportsPlatform(platforms []string, targetOS string) bool {
|
|
if targetOS == "" || len(platforms) == 0 {
|
|
return true
|
|
}
|
|
for _, platform := range platforms {
|
|
if platform == targetOS {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func copyStringMap(values map[string]string) map[string]string {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string]string, len(values))
|
|
for key, value := range values {
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|