init
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"browser.local/run/protocol"
|
||||
)
|
||||
|
||||
type RemoteAdapterRequest struct {
|
||||
JobID string
|
||||
ServerInstanceID string
|
||||
AdapterKey string
|
||||
AdapterKind string
|
||||
TargetKey string
|
||||
Capability string
|
||||
InputRef string
|
||||
}
|
||||
|
||||
type RemoteAdapterOutcome struct {
|
||||
Message string
|
||||
ResultRef string
|
||||
Retryable bool
|
||||
}
|
||||
|
||||
type RemoteAdapter interface {
|
||||
Execute(context.Context, RemoteAdapterRequest) (RemoteAdapterOutcome, error)
|
||||
}
|
||||
|
||||
type RemoteAdapterFunc func(context.Context, RemoteAdapterRequest) (RemoteAdapterOutcome, error)
|
||||
|
||||
func (fn RemoteAdapterFunc) Execute(ctx context.Context, request RemoteAdapterRequest) (RemoteAdapterOutcome, error) {
|
||||
return fn(ctx, request)
|
||||
}
|
||||
|
||||
type RemoteAdapterRegistry struct {
|
||||
mu sync.RWMutex
|
||||
adapters map[string]RemoteAdapter
|
||||
}
|
||||
|
||||
func NewRemoteAdapterRegistry() *RemoteAdapterRegistry {
|
||||
registry := &RemoteAdapterRegistry{adapters: map[string]RemoteAdapter{}}
|
||||
for _, kind := range []string{"ftp", "rsync", "run-file", "run-process", "database", "log-transfer"} {
|
||||
registry.adapters[kind] = declaredRemoteAdapter{kind: kind}
|
||||
}
|
||||
return registry
|
||||
}
|
||||
|
||||
func (registry *RemoteAdapterRegistry) Register(kind string, adapter RemoteAdapter) error {
|
||||
kind = strings.TrimSpace(kind)
|
||||
if kind == "" || adapter == nil {
|
||||
return fmt.Errorf("remote adapter kind and implementation are required")
|
||||
}
|
||||
registry.mu.Lock()
|
||||
defer registry.mu.Unlock()
|
||||
registry.adapters[kind] = adapter
|
||||
return nil
|
||||
}
|
||||
|
||||
func (registry *RemoteAdapterRegistry) adapter(kind string) (RemoteAdapter, bool) {
|
||||
registry.mu.RLock()
|
||||
defer registry.mu.RUnlock()
|
||||
adapter, exists := registry.adapters[kind]
|
||||
return adapter, exists
|
||||
}
|
||||
|
||||
func ExecuteRemoteAccessJob(ctx context.Context, assignment protocol.RunJobAssignment) LifecycleExecutionResult {
|
||||
return ExecuteRemoteAccessJobWithRegistry(ctx, assignment, NewRemoteAdapterRegistry())
|
||||
}
|
||||
|
||||
func ExecuteRemoteAccessJobWithRegistry(ctx context.Context, assignment protocol.RunJobAssignment, registry *RemoteAdapterRegistry) LifecycleExecutionResult {
|
||||
if assignment.ExecutionInput.SourceRCON != nil {
|
||||
return lifecycleFailure("source_rcon_requires_worker_transport", "Source RCON commands require the one-time worker transport")
|
||||
}
|
||||
if err := protocol.ValidateRunJobAssignment(assignment); err != nil {
|
||||
if strings.Contains(err.Error(), "remoteAdapterKey") {
|
||||
return lifecycleFailure("unsafe_remote_adapter_target", "remote adapter key must be an approved logical key")
|
||||
}
|
||||
return lifecycleFailure("unsafe_remote_access_job", err.Error())
|
||||
}
|
||||
if !isSupportedRemoteCapability(assignment.Capability) {
|
||||
return lifecycleFailure("unsupported_remote_access_capability", "unsupported remote access capability")
|
||||
}
|
||||
if registry == nil {
|
||||
return lifecycleFailure("remote_adapter_unavailable", "remote adapter registry is unavailable")
|
||||
}
|
||||
adapterKind := strings.TrimSpace(assignment.ExecutionInput.RemoteAdapterKind)
|
||||
if adapterKind == "" {
|
||||
adapterKind = adapterKindForCapability(assignment.Capability)
|
||||
}
|
||||
adapterKey := strings.TrimSpace(assignment.ExecutionInput.RemoteAdapterKey)
|
||||
if adapterKey == "" {
|
||||
adapterKey = assignment.TargetKey
|
||||
}
|
||||
if !protocol.ValidLogicalFileKey(adapterKey) || !protocol.ValidLogicalFileKey(assignment.TargetKey) {
|
||||
return lifecycleFailure("unsafe_remote_adapter_target", "remote adapter and target must use approved logical keys")
|
||||
}
|
||||
if !adapterKindAllowsCapability(adapterKind, assignment.Capability) {
|
||||
return lifecycleFailure("remote_adapter_capability_mismatch", "remote adapter kind does not allow requested capability")
|
||||
}
|
||||
adapter, exists := registry.adapter(adapterKind)
|
||||
if !exists {
|
||||
return lifecycleFailure("remote_adapter_unavailable", "declared remote adapter is unavailable")
|
||||
}
|
||||
|
||||
executionCtx := ctx
|
||||
cancel := func() {}
|
||||
if timeout := assignment.ExecutionInput.TimeoutSeconds; timeout > 0 {
|
||||
if timeout > 300 {
|
||||
return lifecycleFailure("unsafe_remote_adapter_timeout", "remote adapter timeout exceeds bound")
|
||||
}
|
||||
executionCtx, cancel = context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
request := RemoteAdapterRequest{JobID: assignment.JobID, ServerInstanceID: assignment.ServerInstanceID, AdapterKey: adapterKey, AdapterKind: adapterKind, TargetKey: assignment.TargetKey, Capability: assignment.Capability, InputRef: assignment.InputRef}
|
||||
outcome, err := adapter.Execute(executionCtx, request)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(executionCtx.Err(), context.DeadlineExceeded) {
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter timed out"}, Message: "remote adapter timed out", ErrorCode: "remote_adapter_timeout", Retryable: true}
|
||||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(executionCtx.Err(), context.Canceled) {
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateCancelled, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter cancelled"}, Message: "remote adapter cancelled", ErrorCode: "remote_adapter_cancelled"}
|
||||
}
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter failed"}, Message: "remote adapter failed", ErrorCode: "remote_adapter_failed", Retryable: outcome.Retryable}
|
||||
}
|
||||
if err := executionCtx.Err(); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter timed out"}, Message: "remote adapter timed out", ErrorCode: "remote_adapter_timeout", Retryable: true}
|
||||
}
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateCancelled, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter cancelled"}, Message: "remote adapter cancelled", ErrorCode: "remote_adapter_cancelled"}
|
||||
}
|
||||
resultRef := outcome.ResultRef
|
||||
if resultRef == "" {
|
||||
resultRef = fmt.Sprintf("artifact://jobs/%s/remote-access-result", url.PathEscape(assignment.JobID))
|
||||
}
|
||||
message := strings.TrimSpace(outcome.Message)
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("%s completed through declared %s adapter", assignment.Capability, adapterKind)
|
||||
}
|
||||
return LifecycleExecutionResult{State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "remote adapter completed"}, ResultRef: resultRef, Message: message}
|
||||
}
|
||||
|
||||
type declaredRemoteAdapter struct {
|
||||
kind string
|
||||
}
|
||||
|
||||
func (adapter declaredRemoteAdapter) Execute(ctx context.Context, request RemoteAdapterRequest) (RemoteAdapterOutcome, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return RemoteAdapterOutcome{}, ctx.Err()
|
||||
default:
|
||||
}
|
||||
if request.AdapterKind != adapter.kind || request.ServerInstanceID == "" || request.JobID == "" {
|
||||
return RemoteAdapterOutcome{}, fmt.Errorf("remote adapter request identity mismatch")
|
||||
}
|
||||
return RemoteAdapterOutcome{Message: fmt.Sprintf("%s completed through bounded remote access envelope", request.Capability), ResultRef: fmt.Sprintf("artifact://jobs/%s/remote-access-result", url.PathEscape(request.JobID))}, nil
|
||||
}
|
||||
|
||||
func adapterKindForCapability(capability string) string {
|
||||
switch capability {
|
||||
case protocol.RunCapabilityRemoteFTPRead, protocol.RunCapabilityRemoteFTPWrite:
|
||||
return "ftp"
|
||||
case protocol.RunCapabilityRemoteRsyncRead, protocol.RunCapabilityRemoteRsyncWrite:
|
||||
return "rsync"
|
||||
case protocol.RunCapabilityRemoteRunFilesRead, protocol.RunCapabilityRemoteRunFilesWrite:
|
||||
return "run-file"
|
||||
case protocol.RunCapabilityRemoteRunProcessStart, protocol.RunCapabilityRemoteRunProcessStop:
|
||||
return "run-process"
|
||||
case protocol.RunCapabilityRemoteRunDBMySQLQuery, protocol.RunCapabilityRemoteRunDBSQLiteQuery:
|
||||
return "database"
|
||||
case protocol.RunCapabilityRemoteRunRCONCommand:
|
||||
return "rcon"
|
||||
case protocol.RunCapabilityRemoteRunLogsTransfer:
|
||||
return "log-transfer"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func adapterKindAllowsCapability(kind string, capability string) bool {
|
||||
return kind != "" && kind == adapterKindForCapability(capability)
|
||||
}
|
||||
Reference in New Issue
Block a user