Keep run logs opaque and streamline transfers

This commit is contained in:
npc0-hue
2026-09-03 16:40:05 +08:00
parent 48dd540253
commit 330b1c0130
27 changed files with 429 additions and 673 deletions
+10 -10
View File
@@ -27,7 +27,7 @@ func LoadAutonomousLifecyclePlan(cfg config.Config) (*protocol.RunAutonomousLife
}
scope, err := seededWorkspaceScope(cfg)
if err != nil {
log.Printf("RUN phase=autonomous_lifecycle status=scope_failed server=%s componentKey=%s error=%s", safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKey), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=scope_failed server=%s componentKey=%s error=%s", safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKey), err.Error())
return nil, "", false, err
}
path, err := NewWorkspaceResolver(cfg.WorkspaceRoot).ExistingTarget(scope, autonomousLifecyclePlanKey)
@@ -36,12 +36,12 @@ func LoadAutonomousLifecyclePlan(cfg config.Config) (*protocol.RunAutonomousLife
log.Printf("RUN phase=autonomous_lifecycle status=skipped reason=plan_missing scope=%s", safeOptional(scope))
return nil, scope, false, nil
}
log.Printf("RUN phase=autonomous_lifecycle status=load_failed scope=%s error=%s", safeOptional(scope), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=load_failed scope=%s error=%s", safeOptional(scope), err.Error())
return nil, scope, false, err
}
file, err := os.Open(path)
if err != nil {
log.Printf("RUN phase=autonomous_lifecycle status=open_failed scope=%s error=%s", safeOptional(scope), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=open_failed scope=%s error=%s", safeOptional(scope), err.Error())
return nil, scope, false, err
}
defer file.Close()
@@ -49,11 +49,11 @@ func LoadAutonomousLifecyclePlan(cfg config.Config) (*protocol.RunAutonomousLife
decoder := json.NewDecoder(io.LimitReader(file, 64*1024))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&plan); err != nil {
log.Printf("RUN phase=autonomous_lifecycle status=decode_failed scope=%s error=%s", safeOptional(scope), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=decode_failed scope=%s error=%s", safeOptional(scope), err.Error())
return nil, scope, false, err
}
if err := protocol.ValidateRunAutonomousLifecyclePlan(plan); err != nil {
log.Printf("RUN phase=autonomous_lifecycle status=invalid scope=%s error=%s", safeOptional(scope), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=invalid scope=%s error=%s", safeOptional(scope), err.Error())
return nil, scope, false, err
}
log.Printf("RUN phase=autonomous_lifecycle status=loaded server=%s plugin=%s endpoint=%s profile=%s bootstrap=%t actions=%d dependencies=%d installs=%d", plan.ServerInstanceID, plan.PluginID, plan.RunEndpointID, safeOptional(plan.ProfileKey), plan.Bootstrap != nil, len(plan.Actions), len(plan.DependencyProbes), len(plan.InstallPlans))
@@ -70,7 +70,7 @@ func (worker *Worker) RunAutonomousLifecycleOnce(ctx context.Context) error {
return err
}
if err := validateAutonomousLifecycleScope(worker.cfg, state, *plan); err != nil {
log.Printf("RUN phase=autonomous_lifecycle status=scope_mismatch error=%s", RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle status=scope_mismatch error=%s", err.Error())
return err
}
if err := worker.runAutonomousDependencies(ctx, *plan); err != nil {
@@ -124,7 +124,7 @@ func (worker *Worker) reportAutonomousLifecycle(ctx context.Context, assignment
log.Printf("RUN phase=autonomous_lifecycle.report status=starting server=%s capability=%s state=%s processState=%s", assignment.ServerInstanceID, assignment.Capability, execution.State, safeOptional(execution.ExecutionResult.ProcessState))
response, err := worker.client.ReportLifecycle(ctx, request)
if err != nil {
log.Printf("RUN phase=autonomous_lifecycle.report status=failed server=%s error=%s", assignment.ServerInstanceID, RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.report status=failed server=%s error=%s", assignment.ServerInstanceID, err.Error())
return err
}
if !response.Accepted || response.RunEndpointID != state.RunEndpointID || response.ServerInstanceID != assignment.ServerInstanceID {
@@ -196,10 +196,10 @@ func (worker *Worker) runAutonomousDependencies(ctx context.Context, plan protoc
state, evidence, err := worker.executor.runDependencyProbe(ctx, probe, plan.RuntimeBindings)
if err != nil {
if probe.Required {
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=failed probe=%s error=%s", safeOptional(probe.Key), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=failed probe=%s error=%s", safeOptional(probe.Key), err.Error())
return err
}
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=optional_probe_failed probe=%s error=%s", safeOptional(probe.Key), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=optional_probe_failed probe=%s error=%s", safeOptional(probe.Key), err.Error())
continue
}
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=probed probe=%s state=%s evidence=%s required=%t", safeOptional(probe.Key), safeOptional(state), safeOptional(evidence), probe.Required)
@@ -215,7 +215,7 @@ func (worker *Worker) runAutonomousDependencies(ctx context.Context, plan protoc
input := protocol.DependencyExecutionInputResponse{JobID: assignment.JobID, ServerInstanceID: plan.ServerInstanceID, RunEndpointID: plan.RunEndpointID, PluginID: plan.PluginID, PluginVersion: plan.PluginVersion, ProfileKey: autonomousProfileKey(worker.cfg, plan), TargetOS: plan.TargetOS, TargetArch: plan.TargetArch, PlanDigest: autonomousInstallPlanDigest(installPlan), Probe: probe, Plan: installPlan, Bindings: plan.RuntimeBindings}
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_start probe=%s plan=%s steps=%d", safeOptional(probe.Key), safeOptional(installPlan.Key), len(installPlan.Steps))
if err := worker.runAutonomousInstallPlan(ctx, assignment, input); err != nil {
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_failed probe=%s plan=%s error=%s", safeOptional(probe.Key), safeOptional(installPlan.Key), RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_failed probe=%s plan=%s error=%s", safeOptional(probe.Key), safeOptional(installPlan.Key), err.Error())
return err
}
log.Printf("RUN phase=autonomous_lifecycle.dependencies status=install_complete probe=%s plan=%s", safeOptional(probe.Key), safeOptional(installPlan.Key))
+46 -234
View File
@@ -1,9 +1,6 @@
package runtime
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/base64"
@@ -19,7 +16,6 @@ import (
"strings"
"time"
"browser.local/run/config"
"browser.local/run/protocol"
)
@@ -108,15 +104,6 @@ func (worker *Worker) executeDistributionBuild(ctx context.Context, assignment p
return distributionBuildFailure("env_check_failed", "Go build environment is unavailable")
}
configPath := ""
if input.ComponentKind == "client-manager" {
var err error
configPath, err = writeDistributionConfig(sourceRoot, input, worker.clientPlatformURL())
if err != nil {
return distributionBuildFailure("config_injection_failed", "could not inject scoped component configuration")
}
}
if err := report(45, "deps_download: downloading Go modules"); err != nil {
return distributionBuildFailure("progress_report_failed", "could not report dependency download")
}
@@ -129,14 +116,8 @@ func (worker *Worker) executeDistributionBuild(ctx context.Context, assignment p
return distributionBuildFailure("progress_report_failed", "could not report compilation")
}
binaryPath := filepath.Join(workspace, input.OutputFilename)
entry := "."
if input.ComponentKind == "run" {
entry = "./cmd/run"
}
ldflags := "-s -w"
if input.ComponentKind == "run" {
ldflags = buildRunLDFlags(input, distributionBuildPlatformURL(worker, input))
}
entry := "./cmd/run"
ldflags := buildRunLDFlags(input, distributionBuildPlatformURL(worker, input))
if err := fixedCommand(ctx, sourceRoot, buildEnv, "go", "build", "-trimpath", "-ldflags", ldflags, "-o", binaryPath, entry); err != nil {
return distributionBuildFailure("build_compile_failed", "Go compilation failed")
}
@@ -144,33 +125,7 @@ func (worker *Worker) executeDistributionBuild(ctx context.Context, assignment p
if err := report(82, "package_finalize: creating distribution archive"); err != nil {
return distributionBuildFailure("progress_report_failed", "could not report packaging")
}
if input.ComponentKind == "run" {
payload, err := os.ReadFile(binaryPath)
if err != nil {
return distributionBuildFailure("package_finalize_failed", "run executable could not be read")
}
if err := worker.uploadDistributionArtifact(ctx, assignment, input.ArtifactID, payload); err != nil {
return distributionBuildFailure("artifact_upload_failed", "distribution artifact upload failed")
}
if err := report(96, "package_finalize: artifact upload completed"); err != nil {
return distributionBuildFailure("progress_report_failed", "could not report artifact upload")
}
return LifecycleExecutionResult{
State: lifecycleResultStateSucceeded,
Progress: protocol.RunJobProgressReport{Percent: 100, Message: "package_finalize: build artifact available"},
ResultRef: "artifact://" + input.ArtifactID,
Message: "distribution build completed",
}
}
archivePath := filepath.Join(workspace, archiveFilename(input))
if err := createDistributionArchive(archivePath, input.PackageFormat, binaryPath, configPath); err != nil {
return distributionBuildFailure("package_finalize_failed", "distribution archive creation failed")
}
payload, err := os.ReadFile(archivePath)
if err != nil {
return distributionBuildFailure("package_finalize_failed", "distribution archive could not be read")
}
if err := worker.uploadDistributionArtifact(ctx, assignment, input.ArtifactID, payload); err != nil {
if err := worker.uploadDistributionArtifact(ctx, assignment, input.ArtifactID, binaryPath); err != nil {
return distributionBuildFailure("artifact_upload_failed", "distribution artifact upload failed")
}
if err := report(96, "package_finalize: artifact upload completed"); err != nil {
@@ -184,45 +139,26 @@ func (worker *Worker) executeDistributionBuild(ctx context.Context, assignment p
}
}
func (worker *Worker) prepareDistributionSource(ctx context.Context, workspace string, input protocol.DistributionBuildInputResponse) (string, error) {
if input.ComponentKind == "run" {
root, err := filepath.Abs(worker.cfg.BuildSourceRoot)
if err != nil {
return "", err
}
root, err = filepath.EvalSymlinks(root)
if err != nil {
return "", err
}
if _, err := os.Stat(filepath.Join(root, "go.mod")); err != nil {
return "", err
}
isolatedSource := filepath.Join(workspace, "source")
if err := copyDistributionSource(root, isolatedSource, workspace); err != nil {
return "", err
}
if err := writeRunWorkspaceSeedConfig(isolatedSource, input.WorkspaceSeed); err != nil {
return "", err
}
return isolatedSource, nil
}
checkout := filepath.Join(workspace, "source")
if err := os.MkdirAll(checkout, 0o700); err != nil {
func (worker *Worker) prepareDistributionSource(_ context.Context, workspace string, input protocol.DistributionBuildInputResponse) (string, error) {
root, err := filepath.Abs(worker.cfg.BuildSourceRoot)
if err != nil {
return "", err
}
if err := fixedCommand(ctx, checkout, nil, "git", "init", "--quiet"); err != nil {
root, err = filepath.EvalSymlinks(root)
if err != nil {
return "", err
}
if err := fixedCommand(ctx, checkout, nil, "git", "remote", "add", "origin", input.RepositoryURL); err != nil {
if _, err := os.Stat(filepath.Join(root, "go.mod")); err != nil {
return "", err
}
if err := fixedCommand(ctx, checkout, nil, "git", "fetch", "--quiet", "--depth", "1", "origin", input.SourceRevision); err != nil {
isolatedSource := filepath.Join(workspace, "source")
if err := copyDistributionSource(root, isolatedSource, workspace); err != nil {
return "", err
}
if err := fixedCommand(ctx, checkout, nil, "git", "checkout", "--quiet", "--detach", "FETCH_HEAD"); err != nil {
if err := writeRunWorkspaceSeedConfig(isolatedSource, input.WorkspaceSeed); err != nil {
return "", err
}
return checkout, nil
return isolatedSource, nil
}
func writeRunWorkspaceSeedConfig(sourceRoot string, encodedSeed string) error {
@@ -302,18 +238,6 @@ func copyDistributionSource(sourceRoot string, destinationRoot string, workspace
})
}
func writeDistributionConfig(sourceRoot string, input protocol.DistributionBuildInputResponse, platformURL string) (string, error) {
if input.ComponentKind == "client-manager" {
content := fmt.Sprintf("server_url: %q\nserver_instance_id: %q\nscum_client_credential: %q\nscum_client_name: %q\nscum_client_version: %q\nscum_client_machine_label: %q\nftp_provider: 3\n",
platformURL, input.ServerInstanceID, input.AuthKey, input.ProfileKey, "platform-build", "managed-client")
if err := os.WriteFile(filepath.Join(sourceRoot, "config.yaml"), []byte(content), 0o600); err != nil {
return "", err
}
return filepath.Join(sourceRoot, "config.yaml"), nil
}
return "", fmt.Errorf("run distributions do not use sidecar package config")
}
func distributionBuildPlatformURL(worker *Worker, input protocol.DistributionBuildInputResponse) string {
if value := strings.TrimSpace(input.PlatformURL); value != "" {
return value
@@ -342,15 +266,15 @@ func buildRunLDFlags(input protocol.DistributionBuildInputResponse, platformURL
return strings.Join(flags, " ")
}
func runBuildComponentKey(input protocol.DistributionBuildInputResponse) string {
if input.ComponentKind == config.PackageComponentRun {
return ""
}
return strings.TrimSpace(input.ProfileKey)
func runBuildComponentKey(protocol.DistributionBuildInputResponse) string {
return ""
}
func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment protocol.RunJobAssignment, artifactID string, payload []byte) error {
checksum := bytesChecksum(payload)
func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment protocol.RunJobAssignment, artifactID string, artifactPath string) error {
checksum, sizeBytes, err := checksumFile(artifactPath)
if err != nil {
return err
}
state, err := worker.registeredState()
if err != nil {
return err
@@ -362,7 +286,7 @@ func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment
Direction: "upload",
OwnerKind: "job",
OwnerID: assignment.JobID,
SizeBytes: int64(len(payload)),
SizeBytes: sizeBytes,
ChunkSizeBytes: distributionArtifactChunkSize,
Checksum: checksum,
IdempotencyKey: "distribution-build:" + assignment.JobID,
@@ -374,7 +298,13 @@ func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment
for _, index := range opened.ReceivedChunkIndexes {
received[index] = true
}
for index, offset := 0, 0; offset < len(payload); index, offset = index+1, offset+distributionArtifactChunkSize {
file, err := os.Open(artifactPath)
if err != nil {
return err
}
defer file.Close()
buffer := make([]byte, distributionArtifactChunkSize)
for index, offset := 0, int64(0); offset < sizeBytes; index, offset = index+1, offset+int64(distributionArtifactChunkSize) {
if received[index] {
continue
}
@@ -382,18 +312,25 @@ func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment
if err != nil {
return err
}
end := offset + distributionArtifactChunkSize
if end > len(payload) {
end = len(payload)
length := distributionArtifactChunkSize
if remaining := sizeBytes - offset; remaining < int64(length) {
length = int(remaining)
}
chunk := payload[offset:end]
read, err := file.ReadAt(buffer[:length], offset)
if err != nil && !(err == io.EOF && read == length) {
return err
}
if read != length {
return fmt.Errorf("distribution artifact chunk is shorter than expected")
}
chunk := buffer[:length]
if _, err := worker.client.UploadArtifactChunk(ctx, protocol.ArtifactChunkUploadRequest{
RunEndpointID: state.RunEndpointID,
SessionToken: state.SessionToken,
TransferID: opened.TransferID,
ArtifactID: artifactID,
ChunkIndex: index,
Offset: int64(offset),
Offset: offset,
SizeBytes: len(chunk),
Checksum: bytesChecksum(chunk),
Payload: chunk,
@@ -411,7 +348,7 @@ func (worker *Worker) uploadDistributionArtifact(ctx context.Context, assignment
TransferID: opened.TransferID,
ArtifactID: artifactID,
Checksum: checksum,
SizeBytes: int64(len(payload)),
SizeBytes: sizeBytes,
})
if err != nil {
return err
@@ -426,24 +363,15 @@ func validateDistributionBuildInput(assignment protocol.RunJobAssignment, input
if input.JobID != assignment.JobID || input.ServerInstanceID != assignment.ServerInstanceID {
return fmt.Errorf("build input scope does not match job")
}
if input.ComponentKind != "run" && input.ComponentKind != "client-manager" {
if input.ComponentKind != "run" {
return fmt.Errorf("build component kind is unsupported")
}
if !protocol.ValidLogicalFileKey(input.RunEndpointID) {
return fmt.Errorf("generated Run endpoint identity is unsafe")
}
if input.ComponentKind == "client-manager" && input.RunEndpointID != assignment.RunEndpointID {
return fmt.Errorf("client-manager build target does not match job")
}
if strings.TrimSpace(input.PluginID) == "" {
return fmt.Errorf("build plugin id is required")
}
if input.ComponentKind == "client-manager" && !approvedHTTPSGitRepository(input.RepositoryURL) {
return fmt.Errorf("client-manager repository is not approved")
}
if input.ComponentKind == "client-manager" && strings.TrimSpace(input.SourceRevision) == "" {
return fmt.Errorf("client-manager source revision is required")
}
if input.TargetOS != "windows" && input.TargetOS != "linux" && input.TargetOS != "darwin" {
return fmt.Errorf("target OS is unsupported")
}
@@ -464,20 +392,12 @@ func validateDistributionBuildInput(assignment protocol.RunJobAssignment, input
return fmt.Errorf("run workspace seed is invalid")
}
}
if input.ComponentKind == "client-manager" && input.PackageFormat != "zip" && input.PackageFormat != "tar.gz" {
return fmt.Errorf("package format is unsupported")
}
if strings.TrimSpace(input.ArtifactID) == "" || strings.TrimSpace(input.OutputFilename) == "" || strings.TrimSpace(input.AuthKey) == "" {
return fmt.Errorf("build input is incomplete")
}
return nil
}
func approvedHTTPSGitRepository(value string) bool {
parsed, err := url.ParseRequestURI(strings.TrimSpace(value))
return err == nil && parsed.Scheme == "https" && parsed.Host != "" && parsed.User == nil && parsed.RawQuery == "" && parsed.Fragment == "" && strings.HasSuffix(parsed.Path, ".git")
}
func validDistributionPlatformURL(value string) bool {
parsed, err := url.ParseRequestURI(strings.TrimSpace(value))
return err == nil && (parsed.Scheme == "https" || parsed.Scheme == "http") && parsed.Host != "" && parsed.User == nil && parsed.RawQuery == "" && parsed.Fragment == ""
@@ -485,7 +405,7 @@ func validDistributionPlatformURL(value string) bool {
func fixedCommand(ctx context.Context, dir string, extraEnv []string, name string, args ...string) error {
startedAt := time.Now()
commandLine := redactedDistributionCommandLine(name, args)
commandLine := distributionCommandLine(name, args)
log.Printf("RUN phase=distribution_build.command status=starting workdir=%s command=%s envKeys=%s", safeOptional(dir), commandLine, envKeysSummary(extraEnvMap(extraEnv), nil))
command := exec.CommandContext(ctx, name, args...)
command.Dir = dir
@@ -493,26 +413,16 @@ func fixedCommand(ctx context.Context, dir string, extraEnv []string, name strin
command.Stdout = io.Discard
command.Stderr = io.Discard
if err := command.Run(); err != nil {
log.Printf("RUN phase=distribution_build.command status=failed command=%s durationMs=%d error=%s", commandLine, time.Since(startedAt).Milliseconds(), RedactText(err.Error()))
log.Printf("RUN phase=distribution_build.command status=failed command=%s durationMs=%d error=%s", commandLine, time.Since(startedAt).Milliseconds(), err.Error())
return err
}
log.Printf("RUN phase=distribution_build.command status=complete command=%s durationMs=%d", commandLine, time.Since(startedAt).Milliseconds())
return nil
}
func redactedDistributionCommandLine(name string, args []string) string {
func distributionCommandLine(name string, args []string) string {
parts := append([]string{name}, args...)
redacted := append([]string(nil), parts...)
for index, part := range redacted {
if part == "-ldflags" && index+1 < len(redacted) {
redacted[index+1] = "[redacted-ldflags]"
continue
}
if strings.Contains(part, "BuildRegistrationToken=") {
redacted[index] = "[redacted-ldflags]"
}
}
return redactedCommandLine(redacted)
return quotedCommandLine(parts)
}
func extraEnvMap(entries []string) map[string]string {
@@ -531,104 +441,6 @@ func extraEnvMap(entries []string) map[string]string {
return env
}
func createDistributionArchive(path string, format string, binaryPath string, configPath string) error {
if format == "zip" {
file, err := os.Create(path)
if err != nil {
return err
}
writer := zip.NewWriter(file)
if err := addZipFile(writer, binaryPath); err != nil {
writer.Close()
file.Close()
return err
}
if err := addZipFile(writer, configPath); err != nil {
writer.Close()
file.Close()
return err
}
if err := writer.Close(); err != nil {
file.Close()
return err
}
return file.Close()
}
file, err := os.Create(path)
if err != nil {
return err
}
gzipWriter := gzip.NewWriter(file)
tarWriter := tar.NewWriter(gzipWriter)
if err := addTarFile(tarWriter, binaryPath); err != nil {
tarWriter.Close()
gzipWriter.Close()
file.Close()
return err
}
if err := addTarFile(tarWriter, configPath); err != nil {
tarWriter.Close()
gzipWriter.Close()
file.Close()
return err
}
if err := tarWriter.Close(); err != nil {
gzipWriter.Close()
file.Close()
return err
}
if err := gzipWriter.Close(); err != nil {
file.Close()
return err
}
return file.Close()
}
func addZipFile(writer *zip.Writer, path string) error {
body, err := os.ReadFile(path)
if err != nil {
return err
}
entry, err := writer.Create(filepath.Base(path))
if err != nil {
return err
}
_, err = entry.Write(body)
return err
}
func addTarFile(writer *tar.Writer, path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
header := &tar.Header{Name: filepath.Base(path), Mode: 0o600, Size: info.Size()}
if strings.HasSuffix(filepath.Base(path), ".exe") || filepath.Base(path) == "run" {
header.Mode = 0o700
}
if err := writer.WriteHeader(header); err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
return err
}
func archiveFilename(input protocol.DistributionBuildInputResponse) string {
base := "run-" + input.ServerInstanceID
if input.ComponentKind == "client-manager" {
base = input.ProfileKey + "-" + input.ServerInstanceID
}
if input.PackageFormat == "zip" {
return base + ".zip"
}
return base + ".tar.gz"
}
func distributionBuildWorkspace(workspaceRoot string, pluginID string, jobID string) string {
return filepath.Join(workspaceRoot, "distribution-builds", safeWorkspaceName(pluginID), safeWorkspaceName(jobID))
}
+17 -145
View File
@@ -1,14 +1,10 @@
package runtime
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"io"
"os"
"os/exec"
"path/filepath"
@@ -181,13 +177,22 @@ func TestDistributionBuildIsolationUsesPluginJobWorkspaceAndDistinctPackageState
worker := &Worker{cfg: workerTestConfig(t), client: client}
worker.state.RunEndpointID = "run-test"
worker.state.SessionToken = "session-token"
artifactDir := t.TempDir()
firstArtifactPath := filepath.Join(artifactDir, "alpha.bin")
secondArtifactPath := filepath.Join(artifactDir, "beta.bin")
if err := os.WriteFile(firstArtifactPath, []byte("alpha archive"), 0o600); err != nil {
t.Fatalf("write first artifact: %v", err)
}
if err := os.WriteFile(secondArtifactPath, []byte("beta archive"), 0o600); err != nil {
t.Fatalf("write second artifact: %v", err)
}
client.buildInput = firstInput
if err := worker.uploadDistributionArtifact(context.Background(), firstAssignment, firstInput.ArtifactID, []byte("alpha archive")); err != nil {
if err := worker.uploadDistributionArtifact(context.Background(), firstAssignment, firstInput.ArtifactID, firstArtifactPath); err != nil {
t.Fatalf("upload first artifact: %v", err)
}
client.artifactPayload = nil
client.buildInput = secondInput
if err := worker.uploadDistributionArtifact(context.Background(), secondAssignment, secondInput.ArtifactID, []byte("beta archive")); err != nil {
if err := worker.uploadDistributionArtifact(context.Background(), secondAssignment, secondInput.ArtifactID, secondArtifactPath); err != nil {
t.Fatalf("upload second artifact: %v", err)
}
if len(client.artifactOpenRequests) != 2 {
@@ -201,38 +206,6 @@ func TestDistributionBuildIsolationUsesPluginJobWorkspaceAndDistinctPackageState
}
}
func TestCreateDistributionArchiveIncludesExecutableAndConfigForSupportedFormats(t *testing.T) {
for _, format := range []string{"tar.gz", "zip"} {
t.Run(format, func(t *testing.T) {
root := t.TempDir()
executableName := "run"
if format == "zip" {
executableName = "run.exe"
}
binaryPath := filepath.Join(root, executableName)
configPath := filepath.Join(root, "config.json")
if err := os.WriteFile(binaryPath, []byte("binary"), 0o700); err != nil {
t.Fatalf("write binary: %v", err)
}
if err := os.WriteFile(configPath, []byte(`{"kind":"run"}`), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
archivePath := filepath.Join(root, "package."+strings.ReplaceAll(format, ".", ""))
if err := createDistributionArchive(archivePath, format, binaryPath, configPath); err != nil {
t.Fatalf("create archive: %v", err)
}
payload, err := os.ReadFile(archivePath)
if err != nil {
t.Fatalf("read archive: %v", err)
}
entries := archiveEntries(t, format, payload)
if !entries[executableName] || !entries["config.json"] {
t.Fatalf("expected executable and config in %s archive, got %+v", format, entries)
}
})
}
}
func TestPrepareDistributionSourceCopiesTrustedRunSourceIntoWorkspace(t *testing.T) {
sourceRoot := t.TempDir()
if err := os.WriteFile(filepath.Join(sourceRoot, "go.mod"), []byte("module example.test/trusted\n\ngo 1.24\n"), 0o600); err != nil {
@@ -269,23 +242,14 @@ func TestPrepareDistributionSourceCopiesTrustedRunSourceIntoWorkspace(t *testing
}
}
func TestValidateDistributionBuildInputRejectsUnapprovedClientSource(t *testing.T) {
func TestValidateDistributionBuildInputRejectsLegacyClientManagerBuilds(t *testing.T) {
assignment := protocol.RunJobAssignment{JobID: "job-build", ServerInstanceID: "server-build", RunEndpointID: "run-build"}
base := protocol.DistributionBuildInputResponse{
input := protocol.DistributionBuildInputResponse{
JobID: assignment.JobID, ComponentKind: "client-manager", ServerInstanceID: assignment.ServerInstanceID, RunEndpointID: assignment.RunEndpointID,
PluginID: "game.scum", TargetOS: "linux", TargetArch: "amd64", PackageFormat: "tar.gz", ArtifactID: "artifact-build", OutputFilename: "manager", AuthKey: "key", SourceRevision: "main",
}
for _, repository := range []string{"http://example.test/manager.git", "https://token@example.test/manager.git", "https://example.test/manager.git?ref=main"} {
input := base
input.RepositoryURL = repository
if err := validateDistributionBuildInput(assignment, input); err == nil {
t.Fatalf("expected repository %q to be rejected", repository)
}
}
base.RepositoryURL = "https://example.test/manager.git"
base.SourceRevision = ""
if err := validateDistributionBuildInput(assignment, base); err == nil {
t.Fatal("expected an unpinned client-manager source to be rejected")
if err := validateDistributionBuildInput(assignment, input); err == nil || !strings.Contains(err.Error(), "component kind") {
t.Fatalf("expected legacy component kind to be rejected, got %v", err)
}
}
@@ -301,99 +265,7 @@ func TestValidateDistributionBuildInputAllowsDedicatedRunIdentity(t *testing.T)
}
input.WorkspaceSeed = ""
input.ComponentKind = "client-manager"
input.PackageFormat = "zip"
input.RepositoryURL = "https://example.test/manager.git"
input.SourceRevision = "main"
if err := validateDistributionBuildInput(assignment, input); err == nil {
t.Fatal("client-manager build must remain bound to its assigned builder")
if err := validateDistributionBuildInput(assignment, input); err == nil || !strings.Contains(err.Error(), "component kind") {
t.Fatalf("expected client-manager build to be rejected, got %v", err)
}
}
func archiveEntries(t *testing.T, format string, payload []byte) map[string]bool {
t.Helper()
entries := map[string]bool{}
if format == "zip" {
reader, err := zip.NewReader(bytes.NewReader(payload), int64(len(payload)))
if err != nil {
t.Fatalf("open zip: %v", err)
}
for _, file := range reader.File {
entries[file.Name] = true
}
return entries
}
gzipReader, err := gzip.NewReader(bytes.NewReader(payload))
if err != nil {
t.Fatalf("open gzip: %v", err)
}
defer gzipReader.Close()
reader := tar.NewReader(gzipReader)
for {
header, err := reader.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("read tar: %v", err)
}
entries[header.Name] = true
}
return entries
}
func extractArchive(t *testing.T, format string, payload []byte, destination string) {
t.Helper()
if format == "zip" {
reader, err := zip.NewReader(bytes.NewReader(payload), int64(len(payload)))
if err != nil {
t.Fatalf("open zip: %v", err)
}
for _, file := range reader.File {
input, err := file.Open()
if err != nil {
t.Fatalf("open zip entry: %v", err)
}
body, err := io.ReadAll(input)
closeErr := input.Close()
if err != nil || closeErr != nil {
t.Fatalf("read zip entry: err=%v close=%v", err, closeErr)
}
mode := os.FileMode(0o600)
if file.Name == "run" || strings.HasSuffix(file.Name, ".exe") {
mode = 0o700
}
if err := os.WriteFile(filepath.Join(destination, file.Name), body, mode); err != nil {
t.Fatalf("write zip entry: %v", err)
}
}
return
}
gzipReader, err := gzip.NewReader(bytes.NewReader(payload))
if err != nil {
t.Fatalf("open gzip: %v", err)
}
defer gzipReader.Close()
reader := tar.NewReader(gzipReader)
for {
header, err := reader.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("read tar: %v", err)
}
mode := os.FileMode(header.Mode)
if err := os.WriteFile(filepath.Join(destination, header.Name), mustReadAll(t, reader), mode); err != nil {
t.Fatalf("write tar entry: %v", err)
}
}
}
func mustReadAll(t *testing.T, reader io.Reader) []byte {
t.Helper()
body, err := io.ReadAll(reader)
if err != nil {
t.Fatalf("read archive entry: %v", err)
}
return body
}
+1 -1
View File
@@ -60,7 +60,7 @@ func (worker *Worker) uploadFileArtifact(ctx context.Context, assignment protoco
if read != length {
return fmt.Errorf("file artifact chunk is shorter than expected")
}
chunk := append([]byte(nil), buffer[:length]...)
chunk := buffer[:length]
state, err := worker.registeredState()
if err != nil {
return err
+31 -43
View File
@@ -338,7 +338,7 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
if len(assignment.ExecutionInput.DLLExtensions) > 0 {
log.Printf("RUN phase=lifecycle.dll status=validating job=%s extensions=%d", assignment.JobID, len(assignment.ExecutionInput.DLLExtensions))
if err := protocol.ValidateRunJobAssignment(assignment); err != nil {
log.Printf("RUN phase=lifecycle.dll status=invalid job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.dll status=invalid job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("unsafe_dll_extension_plan", "DLL extension plan is invalid")
}
}
@@ -353,7 +353,7 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
log.Printf("RUN phase=lifecycle.template status=loading job=%s target=%s", assignment.JobID, safeOptional(assignment.TargetKey))
template, scope, err := executor.loadLifecycleTemplate(assignment)
if err != nil {
log.Printf("RUN phase=lifecycle.template status=failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.template status=failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("unsafe_lifecycle_command", err.Error())
}
log.Printf("RUN phase=lifecycle.template status=loaded job=%s action=%s mode=%s scope=%s commandArgs=%d envKeys=%s", assignment.JobID, safeOptional(template.Action), safeOptional(template.Mode), scope, len(template.Command)+len(template.Arguments), envKeysSummary(template.Env, template.Environment))
@@ -375,7 +375,7 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
if assignment.Capability == protocol.RunCapabilityProcessStart && len(assignment.ExecutionInput.DLLExtensions) > 0 {
log.Printf("RUN phase=lifecycle.dll status=synchronizing job=%s extensions=%d", assignment.JobID, len(assignment.ExecutionInput.DLLExtensions))
if err := executor.synchronizeUE4SSDLLExtensions(ctx, assignment, template, scope); err != nil {
log.Printf("RUN phase=lifecycle.dll status=failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.dll status=failed job=%s error=%s", assignment.JobID, err.Error())
return dllExtensionLifecycleFailure(err)
}
log.Printf("RUN phase=lifecycle.dll status=complete job=%s", assignment.JobID)
@@ -387,16 +387,16 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
log.Printf("RUN phase=lifecycle.command status=building job=%s", assignment.JobID)
command, err := template.ToProcessCommand(scope, NewWorkspaceResolver(executor.workspaceRoot), assignment)
if err != nil {
log.Printf("RUN phase=lifecycle.command status=build_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.command status=build_failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("unsafe_lifecycle_command", err.Error())
}
log.Printf("RUN phase=lifecycle.command status=starting job=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", assignment.JobID, safeOptional(command.WorkDir), redactedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
log.Printf("RUN phase=lifecycle.command status=starting job=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", assignment.JobID, safeOptional(command.WorkDir), quotedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
command.OutputLine = func(stream string, line string) {
_ = executor.logSink.Append(ctx, assignment, stream, line)
}
result, err := executor.supervisor.Run(ctx, command)
if err != nil && ctx.Err() != nil {
log.Printf("RUN phase=lifecycle.command status=cancelled job=%s error=%s", assignment.JobID, RedactText(ctx.Err().Error()))
log.Printf("RUN phase=lifecycle.command status=cancelled job=%s error=%s", assignment.JobID, ctx.Err().Error())
return LifecycleExecutionResult{
State: lifecycleResultStateCancelled,
Progress: protocol.RunJobProgressReport{Percent: 100, Message: "lifecycle action cancelled"},
@@ -408,8 +408,8 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
executor.writeProcessLogs(ctx, assignment, result)
}
if err != nil {
log.Printf("RUN phase=lifecycle.command status=failed job=%s exitCode=%d error=%s", assignment.JobID, result.ExitCode, RedactText(err.Error()))
return lifecycleFailure("lifecycle_process_failed", RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.command status=failed job=%s exitCode=%d error=%s", assignment.JobID, result.ExitCode, err.Error())
return lifecycleFailure("lifecycle_process_failed", err.Error())
}
if result.ExitCode != 0 {
log.Printf("RUN phase=lifecycle.command status=failed job=%s exitCode=%d", assignment.JobID, result.ExitCode)
@@ -418,7 +418,7 @@ func (executor LifecycleExecutor) ExecuteContext(ctx context.Context, assignment
log.Printf("RUN phase=lifecycle.command status=exited job=%s exitCode=%d stdoutBytes=%d stderrBytes=%d", assignment.JobID, result.ExitCode, len(result.Stdout), len(result.Stderr))
artifactRef, err := executor.artifactHook.QueueLifecycleResult(ctx, assignment, result)
if err != nil {
log.Printf("RUN phase=lifecycle.artifact status=failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.artifact status=failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("lifecycle_artifact_hook_failed", err.Error())
}
log.Printf("RUN phase=lifecycle status=succeeded job=%s resultRef=%s", assignment.JobID, safeOptional(artifactRef))
@@ -468,11 +468,11 @@ func (executor LifecycleExecutor) executeDeployment(ctx context.Context, assignm
return lifecycleFailure("deployment_shell_unsupported", "deployment shell is not supported by this Run")
}
command := ProcessCommand{Args: args, WorkDir: workdir, JobID: assignment.JobID, Capability: assignment.Capability, Action: action}
log.Printf("RUN phase=deployment.command status=starting job=%s action=%s revision=%d root=%s workdir=%s command=%s", assignment.JobID, action, definition.Revision, safeOptional(definition.ServerRoot), safeOptional(workdir), redactedCommandLine(command.Args))
log.Printf("RUN phase=deployment.command status=starting job=%s action=%s revision=%d root=%s workdir=%s command=%s", assignment.JobID, action, definition.Revision, safeOptional(definition.ServerRoot), safeOptional(workdir), quotedCommandLine(command.Args))
result, err := executor.supervisor.Run(ctx, command)
if err != nil {
log.Printf("RUN phase=deployment.command status=failed job=%s exitCode=%d error=%s", assignment.JobID, result.ExitCode, RedactText(err.Error()))
return lifecycleFailure("lifecycle_process_failed", RedactText(err.Error()))
log.Printf("RUN phase=deployment.command status=failed job=%s exitCode=%d error=%s", assignment.JobID, result.ExitCode, err.Error())
return lifecycleFailure("lifecycle_process_failed", err.Error())
}
executor.writeProcessLogs(ctx, assignment, result)
if result.ExitCode != 0 {
@@ -590,18 +590,18 @@ func (executor LifecycleExecutor) executeManaged(ctx context.Context, assignment
log.Printf("RUN phase=lifecycle.managed status=building_start_command job=%s scope=%s", assignment.JobID, scope)
command, err := template.ToManagedProcessCommand(resolver, scope, assignment)
if err != nil {
log.Printf("RUN phase=lifecycle.managed status=build_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.managed status=build_failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("unsafe_lifecycle_command", err.Error())
}
log.Printf("RUN phase=lifecycle.managed status=starting job=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", assignment.JobID, safeOptional(command.WorkDir), redactedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
log.Printf("RUN phase=lifecycle.managed status=starting job=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", assignment.JobID, safeOptional(command.WorkDir), quotedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
item, err := executor.managed.Start(ctx, command, identity, executor.managedProcessOutput(ctx, assignment))
if err != nil {
if ctx.Err() != nil {
log.Printf("RUN phase=lifecycle.managed status=cancelled job=%s error=%s", assignment.JobID, RedactText(ctx.Err().Error()))
log.Printf("RUN phase=lifecycle.managed status=cancelled job=%s error=%s", assignment.JobID, ctx.Err().Error())
return lifecycleExecutionFailure("lifecycle_cancelled", "lifecycle action cancelled", false)
}
log.Printf("RUN phase=lifecycle.managed status=failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
return lifecycleFailure("lifecycle_process_failed", RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.managed status=failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("lifecycle_process_failed", err.Error())
}
log.Printf("RUN phase=lifecycle.managed status=started job=%s pid=%d state=%s stdoutRef=%s stderrRef=%s", assignment.JobID, item.PID, item.State, safeOptional(item.StdoutLogRef), safeOptional(item.StderrLogRef))
return processExecutionResult(item, "process started")
@@ -610,8 +610,8 @@ func (executor LifecycleExecutor) executeManaged(ctx context.Context, assignment
log.Printf("RUN phase=lifecycle.managed status=stopping job=%s scope=%s", assignment.JobID, scope)
item, err := executor.managed.Stop(ctx, identity)
if err != nil {
log.Printf("RUN phase=lifecycle.managed status=stop_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
return lifecycleFailure("lifecycle_stop_failed", RedactText(err.Error()))
log.Printf("RUN phase=lifecycle.managed status=stop_failed job=%s error=%s", assignment.JobID, err.Error())
return lifecycleFailure("lifecycle_stop_failed", err.Error())
}
log.Printf("RUN phase=lifecycle.managed status=stopped job=%s pid=%d state=%s classification=%s", assignment.JobID, item.PID, item.State, safeOptional(item.ExitClassification))
return processExecutionResult(item, "process stopped")
@@ -695,7 +695,7 @@ func processExecutionResult(item ProcessIdentity, message string) LifecycleExecu
}
func lifecycleExecutionFailure(code string, message string, retryable bool) LifecycleExecutionResult {
return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: RedactText(message)}, Message: RedactText(message), ErrorCode: code, Retryable: retryable, ExecutionResult: protocol.RunJobExecutionResult{Kind: "file", Summary: code}}
return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: message}, Message: message, ErrorCode: code, Retryable: retryable, ExecutionResult: protocol.RunJobExecutionResult{Kind: "file", Summary: code}}
}
func (template LifecycleActionTemplate) ToProcessCommand(workdir string, resolver WorkspaceResolver, assignment protocol.RunJobAssignment) (ProcessCommand, error) {
@@ -948,7 +948,7 @@ func (supervisor OSProcessSupervisor) Run(ctx context.Context, command ProcessCo
return ProcessResult{ExitCode: -1}, fmt.Errorf("command is required")
}
startedAt := time.Now()
log.Printf("RUN phase=process.command status=starting job=%s capability=%s action=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), safeOptional(command.WorkDir), redactedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
log.Printf("RUN phase=process.command status=starting job=%s capability=%s action=%s workdir=%s command=%s timeoutMs=%d envKeys=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), safeOptional(command.WorkDir), quotedCommandLine(command.Args), command.Timeout.Milliseconds(), envKeysSummary(command.Env, nil))
if command.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, command.Timeout)
@@ -967,14 +967,14 @@ func (supervisor OSProcessSupervisor) Run(ctx context.Context, command ProcessCo
cmd.Stdout = stdoutWriter
cmd.Stderr = stderrWriter
if err := cmd.Start(); err != nil {
log.Printf("RUN phase=process.command status=start_failed job=%s capability=%s action=%s command=%s durationMs=%d error=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), redactedCommandLine(command.Args), time.Since(startedAt).Milliseconds(), RedactText(err.Error()))
log.Printf("RUN phase=process.command status=start_failed job=%s capability=%s action=%s command=%s durationMs=%d error=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), quotedCommandLine(command.Args), time.Since(startedAt).Milliseconds(), err.Error())
return ProcessResult{ExitCode: -1}, err
}
pid := 0
if cmd.Process != nil {
pid = cmd.Process.Pid
}
log.Printf("RUN phase=process.command status=started job=%s capability=%s action=%s pid=%d command=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, redactedCommandLine(command.Args))
log.Printf("RUN phase=process.command status=started job=%s capability=%s action=%s pid=%d command=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, quotedCommandLine(command.Args))
err := cmd.Wait()
stdoutWriter.Flush()
stderrWriter.Flush()
@@ -983,10 +983,10 @@ func (supervisor OSProcessSupervisor) Run(ctx context.Context, command ProcessCo
result.ExitCode = cmd.ProcessState.ExitCode()
}
if err != nil {
log.Printf("RUN phase=process.command status=failed job=%s capability=%s action=%s pid=%d command=%s exitCode=%d durationMs=%d stdoutBytes=%d stderrBytes=%d error=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, redactedCommandLine(command.Args), result.ExitCode, time.Since(startedAt).Milliseconds(), len(result.Stdout), len(result.Stderr), RedactText(err.Error()))
log.Printf("RUN phase=process.command status=failed job=%s capability=%s action=%s pid=%d command=%s exitCode=%d durationMs=%d stdoutBytes=%d stderrBytes=%d error=%s", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, quotedCommandLine(command.Args), result.ExitCode, time.Since(startedAt).Milliseconds(), len(result.Stdout), len(result.Stderr), err.Error())
return result, err
}
log.Printf("RUN phase=process.command status=exited job=%s capability=%s action=%s pid=%d command=%s exitCode=%d durationMs=%d stdoutBytes=%d stderrBytes=%d", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, redactedCommandLine(command.Args), result.ExitCode, time.Since(startedAt).Milliseconds(), len(result.Stdout), len(result.Stderr))
log.Printf("RUN phase=process.command status=exited job=%s capability=%s action=%s pid=%d command=%s exitCode=%d durationMs=%d stdoutBytes=%d stderrBytes=%d", safeOptional(command.JobID), safeOptional(command.Capability), safeOptional(command.Action), pid, quotedCommandLine(command.Args), result.ExitCode, time.Since(startedAt).Milliseconds(), len(result.Stdout), len(result.Stderr))
return result, nil
}
@@ -1127,8 +1127,8 @@ func isSupportedRemoteCapability(capability string) bool {
func lifecycleFailure(code string, message string) LifecycleExecutionResult {
return LifecycleExecutionResult{
State: lifecycleResultStateFailed,
Progress: protocol.RunJobProgressReport{Percent: 100, Message: RedactText(message)},
Message: RedactText(message),
Progress: protocol.RunJobProgressReport{Percent: 100, Message: message},
Message: message,
ErrorCode: code,
}
}
@@ -1183,23 +1183,23 @@ func safeOptional(value string) string {
if value == "" {
return "-"
}
return RedactText(value)
return value
}
func errorSummary(err error) string {
if err == nil {
return "-"
}
return RedactText(err.Error())
return err.Error()
}
func redactedCommandLine(args []string) string {
func quotedCommandLine(args []string) string {
if len(args) == 0 {
return "-"
}
parts := make([]string, len(args))
for i, arg := range args {
parts[i] = strconv.Quote(RedactText(arg))
parts[i] = strconv.Quote(arg)
}
return strings.Join(parts, " ")
}
@@ -1223,18 +1223,6 @@ func envKeysSummary(first map[string]string, second map[string]string) string {
return strings.Join(keys, ",")
}
func RedactText(value string) string {
redacted := value
replacements := []string{"/Users/", "[host]/", "Bearer ", "Bearer [redacted] ", "sk-", "sk-[redacted]", "password=", "password=[redacted]", "api_key=", "api_key=[redacted]", "secret=", "secret=[redacted]", "unix://", "socket://"}
for i := 0; i+1 < len(replacements); i += 2 {
redacted = strings.ReplaceAll(redacted, replacements[i], replacements[i+1])
}
if len(redacted) > maxLifecycleOutputBytes {
return redacted[:maxLifecycleOutputBytes]
}
return redacted
}
// splitRawLogLines only removes the newline framing used by LogEntry. It
// deliberately preserves every other byte, including blank lines and spaces.
func splitRawLogLines(value string) []string {
+9 -23
View File
@@ -417,7 +417,6 @@ func TestResolveRuntimeProfilesSupportsDeclaredModesAndSafeMissingKeys(t *testin
{Key: "run-local", Mode: RuntimeModeLocalProcess, Capabilities: []string{protocol.RunCapabilityProcessStart}, ActionRefs: map[string]string{"start": "actions/start.json"}, TransportKeys: []string{"server-files"}, Platforms: []string{"linux"}},
{Key: "hosted-ftp", Mode: RuntimeModeHostedFTPRCON, Capabilities: []string{protocol.RunCapabilityRemoteFTPRead, protocol.RunCapabilityRemoteRunRCONCommand}, TransportKeys: []string{"ftp", "rcon"}},
{Key: "ftp-only", Mode: RuntimeModeFTPOnly, Capabilities: []string{protocol.RunCapabilityRemoteFTPRead}, TransportKeys: []string{"ftp"}},
{Key: "custom-client", Mode: RuntimeModeCustomClient, Capabilities: []string{protocol.RunCapabilityRemoteRunRCONCommand}, TransportKeys: []string{"rcon"}, ClientManagerRef: "scum-client-manager"},
},
LogSources: []RuntimeLogSource{{Key: "latest-log", Kind: "file.tail", TargetKey: "logs/latest", StreamKey: "latest-log"}},
TransportProfiles: []RuntimeTransportProfile{
@@ -426,23 +425,6 @@ func TestResolveRuntimeProfilesSupportsDeclaredModesAndSafeMissingKeys(t *testin
{Key: "rcon", Kind: "rcon", TargetKey: "rcon", Capabilities: []string{protocol.RunCapabilityRemoteRunRCONCommand}},
},
}
resolution, err := ResolveRuntimeProfile(profiles, "custom-client", "windows", RuntimeBindingSet{
ProfileKey: "custom-client",
Mode: RuntimeModeCustomClient,
Bindings: map[string]string{
"rcon": "binding://rcon/current",
"logs/latest": "binding://logs/latest",
"steamcmd": "binding://probe/steamcmd",
"scum-client-manager": "binding://client/current",
},
})
if err != nil {
t.Fatalf("resolve custom client profile: %v", err)
}
if !resolution.Available || resolution.Mode != RuntimeModeCustomClient || resolution.ClientManagerRef != "scum-client-manager" {
t.Fatalf("unexpected custom client resolution: %+v", resolution)
}
missing, err := ResolveRuntimeProfile(profiles, "hosted-ftp", "linux", RuntimeBindingSet{ProfileKey: "hosted-ftp", Mode: RuntimeModeHostedFTPRCON, Bindings: map[string]string{"ftp-root": "binding://ftp/current"}})
if err != nil {
t.Fatalf("resolve hosted profile: %v", err)
@@ -476,7 +458,7 @@ func TestTailDeclaredFileLogSourceUsesCheckpointAndVerbatimOutput(t *testing.T)
t.Fatalf("expected verbatim tailed lines, got %+v", sink.lines)
}
checkpoint := store.GetLogCheckpoint("latest-log")
if checkpoint.Offset == 0 || checkpoint.Sequence != 2 || strings.Contains(RedactedLogCheckpointSummary(checkpoint), "/Users/") {
if checkpoint.Offset == 0 || checkpoint.Sequence != 2 || strings.Contains(LogCheckpointSummary(checkpoint), "/Users/") {
t.Fatalf("expected durable safe checkpoint, got %+v", checkpoint)
}
@@ -508,14 +490,18 @@ func TestTailDeclaredFileLogSourceDoesNotLimitOrRewriteOutput(t *testing.T) {
result := TailDeclaredFileLogSource(context.Background(), root, assignment, source, sink, store)
if result.State != lifecycleResultStateSucceeded || len(sink.lines) != 3 {
if result.State != lifecycleResultStateSucceeded || len(sink.lines) < 3 {
t.Fatalf("expected all unbounded log entries, result=%+v lines=%d", result, len(sink.lines))
}
if sink.lines[0] != "current-log:"+longLine || sink.lines[1] != "current-log:" || sink.lines[2] != "current-log:final" {
t.Fatalf("expected byte-for-byte log payloads, got lengths=%d,%d,%d", len(sink.lines[0]), len(sink.lines[1]), len(sink.lines[2]))
payloads := make([]string, len(sink.lines))
for index, line := range sink.lines {
payloads[index] = strings.TrimPrefix(line, "current-log:")
}
if strings.Join(payloads[:len(payloads)-2], "") != longLine || payloads[len(payloads)-2] != "" || payloads[len(payloads)-1] != "final" {
t.Fatalf("expected byte-for-byte log payloads after chunk reassembly, got %d entries", len(payloads))
}
checkpoint := store.GetLogCheckpoint(source.Key)
if checkpoint.Offset != int64(len(longLine+"\n\nfinal")) || checkpoint.Sequence != 3 {
if checkpoint.Offset != int64(len(longLine+"\n\nfinal")) || checkpoint.Sequence != uint64(len(payloads)) {
t.Fatalf("expected complete checkpoint after unbounded tail, got %+v", checkpoint)
}
}
+5 -5
View File
@@ -82,14 +82,14 @@ func TailDeclaredFileLogSource(ctx context.Context, workspaceRoot string, assign
return lifecycleFailure("log_source_seek_failed", err.Error())
}
}
reader := bufio.NewReader(file)
reader := bufio.NewReaderSize(file, 64*1024)
for {
// A newline only frames an entry. Every other byte, including CR, blank
// lines, and arbitrarily long output, remains untouched.
line, readErr := reader.ReadString('\n')
line, readErr := reader.ReadSlice('\n')
if len(line) > 0 {
checkpoint.Sequence++
if err := sink.Append(ctx, assignment, source.StreamKey, strings.TrimSuffix(line, "\n")); err != nil {
if err := sink.Append(ctx, assignment, source.StreamKey, strings.TrimSuffix(string(line), "\n")); err != nil {
return lifecycleFailure("log_source_sink_failed", err.Error())
}
checkpoint.SourceKey = source.Key
@@ -97,7 +97,7 @@ func TailDeclaredFileLogSource(ctx context.Context, workspaceRoot string, assign
checkpoint.CursorRef = fmt.Sprintf("artifact://jobs/%s/live-log-checkpoint", url.PathEscape(assignment.JobID))
store.PutLogCheckpoint(checkpoint)
}
if readErr == nil {
if readErr == nil || readErr == bufio.ErrBufferFull {
continue
}
if readErr == io.EOF {
@@ -116,7 +116,7 @@ func TailDeclaredFileLogSource(ctx context.Context, workspaceRoot string, assign
}
}
func RedactedLogCheckpointSummary(checkpoint LogSourceCheckpoint) string {
func LogCheckpointSummary(checkpoint LogSourceCheckpoint) string {
return strings.Join([]string{
"source=" + checkpoint.SourceKey,
fmt.Sprintf("offset=%d", checkpoint.Offset),
+2 -2
View File
@@ -60,7 +60,7 @@ func WithMetricCollector(collector MetricCollector) LifecycleExecutorOption {
func (worker *Worker) reportMetricsDegraded(ctx context.Context, trigger string) {
if err := worker.ReportMetricsOnce(ctx); err != nil {
log.Printf("RUN phase=metrics status=degraded trigger=%s error=%s", safeOptional(trigger), RedactText(err.Error()))
log.Printf("RUN phase=metrics status=degraded trigger=%s error=%s", safeOptional(trigger), err.Error())
}
}
@@ -79,7 +79,7 @@ func (worker *Worker) ReportMetricsOnce(ctx context.Context) error {
sample.MemoryPercent = utilization.MemoryPercent
sample.DiskPercent = utilization.DiskPercent
if collectErr != nil {
log.Printf("RUN phase=metrics status=utilization_unavailable error=%s", RedactText(collectErr.Error()))
log.Printf("RUN phase=metrics status=utilization_unavailable error=%s", collectErr.Error())
}
}
reportCtx, cancel := context.WithTimeout(ctx, metricReportTimeout)
+21 -17
View File
@@ -22,6 +22,7 @@ const (
managedProcessOutputPollInterval = 50 * time.Millisecond
managedProcessOutputDrainDelay = 750 * time.Millisecond
managedProcessOutputRetryDelay = 500 * time.Millisecond
managedProcessOutputReaderSize = 64 * 1024
)
type ProcessIdentity struct {
@@ -143,7 +144,7 @@ func (supervisor *OSManagedProcessSupervisor) Start(ctx context.Context, command
supervisor.mu.Lock()
defer supervisor.mu.Unlock()
key := identity.Scope
log.Printf("RUN phase=process.managed status=start_requested job=%s server=%s scope=%s command=%s workdir=%s", safeOptional(identity.JobID), identity.ServerInstanceID, safeOptional(identity.Scope), redactedCommandLine(command.Args), safeOptional(command.WorkDir))
log.Printf("RUN phase=process.managed status=start_requested job=%s server=%s scope=%s command=%s workdir=%s", safeOptional(identity.JobID), identity.ServerInstanceID, safeOptional(identity.Scope), quotedCommandLine(command.Args), safeOptional(command.WorkDir))
if existing, ok := supervisor.items[key]; ok && existing.State == "running" && supervisor.isAlive(existing) {
if existing.LogSessionID == "" {
logSessionID, err := newManagedProcessLogSessionID()
@@ -167,7 +168,7 @@ func (supervisor *OSManagedProcessSupervisor) Start(ctx context.Context, command
return existing, nil
}
if err := ctx.Err(); err != nil {
log.Printf("RUN phase=process.managed status=context_done job=%s error=%s", safeOptional(identity.JobID), RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=context_done job=%s error=%s", safeOptional(identity.JobID), err.Error())
return ProcessIdentity{}, err
}
if len(command.Args) == 0 {
@@ -187,14 +188,14 @@ func (supervisor *OSManagedProcessSupervisor) Start(ctx context.Context, command
}
files, identity, err := supervisor.prepareOutputFilesLocked(identity, startedAt)
if err != nil {
log.Printf("RUN phase=process.managed status=prepare_output_failed job=%s error=%s", safeOptional(identity.JobID), RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=prepare_output_failed job=%s error=%s", safeOptional(identity.JobID), err.Error())
return ProcessIdentity{}, err
}
log.Printf("RUN phase=process.managed status=output_ready job=%s stdoutRef=%s stderrRef=%s", safeOptional(identity.JobID), safeOptional(identity.StdoutLogRef), safeOptional(identity.StderrLogRef))
process, err := startManagedProcess(command, files, identity.StopEventName)
if err != nil {
files.close()
log.Printf("RUN phase=process.managed status=start_failed job=%s error=%s", safeOptional(identity.JobID), RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=start_failed job=%s error=%s", safeOptional(identity.JobID), err.Error())
return ProcessIdentity{}, err
}
identity.SupervisorPID = process.PID()
@@ -214,7 +215,7 @@ func (supervisor *OSManagedProcessSupervisor) Start(ctx context.Context, command
} else {
delete(supervisor.items, key)
}
log.Printf("RUN phase=process.managed status=persist_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), identity.PID, RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=persist_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), identity.PID, err.Error())
return ProcessIdentity{}, err
}
supervisor.startTailersLocked(identity, output)
@@ -241,7 +242,7 @@ func (supervisor *OSManagedProcessSupervisor) Stop(ctx context.Context, identity
}
log.Printf("RUN phase=process.managed status=stop_requested job=%s pid=%d scope=%s", safeOptional(identity.JobID), current.PID, safeOptional(identity.Scope))
if err := requestManagedProcessStop(current); err != nil {
log.Printf("RUN phase=process.managed status=stop_signal_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=stop_signal_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, err.Error())
}
supervisor.mu.Unlock()
deadline := time.NewTimer(2 * time.Second)
@@ -264,11 +265,11 @@ func (supervisor *OSManagedProcessSupervisor) Stop(ctx context.Context, identity
}
select {
case <-ctx.Done():
log.Printf("RUN phase=process.managed status=stop_context_done job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, RedactText(ctx.Err().Error()))
log.Printf("RUN phase=process.managed status=stop_context_done job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, ctx.Err().Error())
return ProcessIdentity{}, ctx.Err()
case <-deadline.C:
if err := forceManagedProcessStop(current); err != nil {
log.Printf("RUN phase=process.managed status=forced_stop_signal_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=forced_stop_signal_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), current.PID, err.Error())
}
current.State = "stopped"
current.ExitClassification = "forced-stop"
@@ -404,7 +405,7 @@ func (supervisor *OSManagedProcessSupervisor) wait(key string, process managedPr
supervisor.drainTailersAfter(item, managedProcessOutputDrainDelay)
}
if err != nil {
log.Printf("RUN phase=process.managed status=%s job=%s pid=%d state=%s exitCode=%d classification=%s error=%s", processTerminalStatus(item.State), safeOptional(item.JobID), pid, item.State, item.ExitCode, item.ExitClassification, RedactText(err.Error()))
log.Printf("RUN phase=process.managed status=%s job=%s pid=%d state=%s exitCode=%d classification=%s error=%s", processTerminalStatus(item.State), safeOptional(item.JobID), pid, item.State, item.ExitCode, item.ExitClassification, err.Error())
return
}
log.Printf("RUN phase=process.managed status=%s job=%s pid=%d state=%s exitCode=%d classification=%s", processTerminalStatus(item.State), safeOptional(item.JobID), pid, item.State, item.ExitCode, item.ExitClassification)
@@ -516,31 +517,31 @@ func (supervisor *OSManagedProcessSupervisor) tailOutput(ctx context.Context, ta
defer supervisor.removeTailer(tailerID, tailer, identity)
file, err := os.Open(path)
if err != nil {
log.Printf("RUN phase=process.managed.output status=tail_open_failed job=%s pid=%d stream=%s path=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, safeOptional(path), RedactText(err.Error()))
log.Printf("RUN phase=process.managed.output status=tail_open_failed job=%s pid=%d stream=%s path=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, safeOptional(path), err.Error())
return
}
defer file.Close()
if offset > 0 {
if _, err := file.Seek(offset, io.SeekStart); err != nil {
log.Printf("RUN phase=process.managed.output status=tail_seek_failed job=%s pid=%d stream=%s path=%s offset=%d error=%s", safeOptional(identity.JobID), identity.PID, stream, safeOptional(path), offset, RedactText(err.Error()))
log.Printf("RUN phase=process.managed.output status=tail_seek_failed job=%s pid=%d stream=%s path=%s offset=%d error=%s", safeOptional(identity.JobID), identity.PID, stream, safeOptional(path), offset, err.Error())
return
}
}
reader := bufio.NewReader(file)
reader := bufio.NewReaderSize(file, managedProcessOutputReaderSize)
defer func() {
log.Printf("RUN phase=process.managed.output status=tail_stop job=%s pid=%d stream=%s offset=%d", safeOptional(identity.JobID), identity.PID, stream, offset)
}()
for {
line, err := reader.ReadString('\n')
line, err := reader.ReadSlice('\n')
if len(line) > 0 {
startOffset := offset
endOffset := offset + int64(len(line))
text := strings.TrimSuffix(line, "\n")
text := strings.TrimSuffix(string(line), "\n")
for {
if sinkErr := sink(identity, ManagedProcessLine{Text: text, StartOffset: startOffset, EndOffset: endOffset}); sinkErr == nil {
break
} else {
log.Printf("RUN phase=process.managed.output status=sink_retry job=%s pid=%d stream=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, RedactText(sinkErr.Error()))
log.Printf("RUN phase=process.managed.output status=sink_retry job=%s pid=%d stream=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, sinkErr.Error())
}
select {
case <-ctx.Done():
@@ -552,7 +553,7 @@ func (supervisor *OSManagedProcessSupervisor) tailOutput(ctx context.Context, ta
if offsetErr := supervisor.updateOutputOffset(identity, stream, endOffset); offsetErr == nil {
break
} else {
log.Printf("RUN phase=process.managed.output status=offset_retry job=%s pid=%d stream=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, RedactText(offsetErr.Error()))
log.Printf("RUN phase=process.managed.output status=offset_retry job=%s pid=%d stream=%s error=%s", safeOptional(identity.JobID), identity.PID, stream, offsetErr.Error())
}
select {
case <-ctx.Done():
@@ -565,6 +566,9 @@ func (supervisor *OSManagedProcessSupervisor) tailOutput(ctx context.Context, ta
if err == nil {
continue
}
if err == bufio.ErrBufferFull {
continue
}
if err != io.EOF {
return
}
@@ -589,7 +593,7 @@ func (supervisor *OSManagedProcessSupervisor) removeTailer(tailerID string, tail
delete(supervisor.retired, key)
if err := supervisor.persistLocked(); err != nil {
supervisor.retired[key] = retired
log.Printf("RUN phase=process.managed.output status=retired_prune_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), identity.PID, RedactText(err.Error()))
log.Printf("RUN phase=process.managed.output status=retired_prune_failed job=%s pid=%d error=%s", safeOptional(identity.JobID), identity.PID, err.Error())
}
}
}
+26 -7
View File
@@ -52,17 +52,36 @@ type windowsFileManagedProcess struct {
}
type managedProcessHelperSpec struct {
Command ProcessCommand `json:"command"`
StopEventName string `json:"stopEventName,omitempty"`
StdoutPath string `json:"stdoutPath,omitempty"`
StderrPath string `json:"stderrPath,omitempty"`
PIDPath string `json:"pidPath,omitempty"`
Command managedProcessHelperCommand `json:"command"`
StopEventName string `json:"stopEventName,omitempty"`
StdoutPath string `json:"stdoutPath,omitempty"`
StderrPath string `json:"stderrPath,omitempty"`
PIDPath string `json:"pidPath,omitempty"`
}
type managedProcessHelperCommand struct {
WorkDir string `json:"workDir,omitempty"`
Args []string `json:"args,omitempty"`
Env map[string]string `json:"env,omitempty"`
OutputMode string `json:"outputMode,omitempty"`
Timeout time.Duration `json:"timeout,omitempty"`
JobID string `json:"jobId,omitempty"`
Capability string `json:"capability,omitempty"`
Action string `json:"action,omitempty"`
}
func managedProcessHelperCommandFromProcess(command ProcessCommand) managedProcessHelperCommand {
return managedProcessHelperCommand{WorkDir: command.WorkDir, Args: append([]string(nil), command.Args...), Env: copyStringMap(command.Env), OutputMode: command.OutputMode, Timeout: command.Timeout, JobID: command.JobID, Capability: command.Capability, Action: command.Action}
}
func (command managedProcessHelperCommand) processCommand() ProcessCommand {
return ProcessCommand{WorkDir: command.WorkDir, Args: append([]string(nil), command.Args...), Env: copyStringMap(command.Env), OutputMode: command.OutputMode, Timeout: command.Timeout, JobID: command.JobID, Capability: command.Capability, Action: command.Action}
}
func startManagedProcess(command ProcessCommand, files managedProcessFiles, stopEventName string) (managedProcess, error) {
pidPath := files.stdout.Name() + ".pid"
_ = os.Remove(pidPath)
body, err := json.Marshal(managedProcessHelperSpec{Command: command, StopEventName: stopEventName, StdoutPath: files.stdout.Name(), StderrPath: files.stderr.Name(), PIDPath: pidPath})
body, err := json.Marshal(managedProcessHelperSpec{Command: managedProcessHelperCommandFromProcess(command), StopEventName: stopEventName, StdoutPath: files.stdout.Name(), StderrPath: files.stderr.Name(), PIDPath: pidPath})
if err != nil {
return nil, fmt.Errorf("encode managed process helper spec: %w", err)
}
@@ -189,7 +208,7 @@ func runManagedProcessHelper(spec managedProcessHelperSpec) (int, error) {
return 1, fmt.Errorf("open managed process stderr: %w", err)
}
defer closeStderr()
command := spec.Command
command := spec.Command.processCommand()
stopEventName := spec.StopEventName
// The plugin-declared pipes mode uses ordinary inherited handles. The
// durable output files are attached directly to the child process, so this
+7 -22
View File
@@ -12,17 +12,15 @@ 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"`
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"`
}
type RuntimeDiscoveryProbe struct {
@@ -39,7 +37,6 @@ type RuntimeLifecycleProfile struct {
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"`
}
@@ -84,10 +81,6 @@ type RuntimeTransportProfile struct {
Capabilities []string `json:"capabilities"`
}
type RuntimeClientManagerSpec struct {
Key string `json:"key"`
}
type RuntimeBindingSet struct {
ProfileKey string `json:"profileKey"`
Mode string `json:"mode"`
@@ -104,7 +97,6 @@ type RuntimeResolution struct {
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"`
}
@@ -144,7 +136,6 @@ func ResolveRuntimeProfile(profiles RuntimeProfiles, profileKey string, targetOS
Transports: transports,
LogSources: safeLogSources(profiles.LogSources, targetOS),
Discovery: safeDiscovery(profiles.Discovery, targetOS),
ClientManagerRef: profile.ClientManagerRef,
MissingKeys: missing,
Available: len(missing) == 0,
}, nil
@@ -173,9 +164,6 @@ func validateRuntimeProfile(profile RuntimeLifecycleProfile) error {
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
}
@@ -222,9 +210,6 @@ func missingRuntimeBindingKeys(profile RuntimeLifecycleProfile, transports []Run
logTargets[source.TargetKey] = struct{}{}
}
}
if profile.ClientManagerRef != "" {
required[profile.ClientManagerRef] = struct{}{}
}
for _, key := range binding.MissingKeys {
if _, logTarget := logTargets[key]; logTarget {
continue
@@ -266,7 +251,7 @@ func safeLogSources(sources []RuntimeLogSource, targetOS string) []RuntimeLogSou
func supportedRuntimeMode(mode string) bool {
switch mode {
case RuntimeModeLocalProcess, RuntimeModeHostedFTPRCON, RuntimeModeFTPOnly, RuntimeModeCustomClient:
case RuntimeModeLocalProcess, RuntimeModeHostedFTPRCON, RuntimeModeFTPOnly:
return true
default:
return false
+1 -1
View File
@@ -63,7 +63,7 @@ func TestSQLiteSchemaProbeRejectsUnscopedOrUnsafeRequests(t *testing.T) {
assignment.ExecutionInput.SQLiteSchemaProbe.Binding.DatabaseIdentity = "C:/host/path"
result = NewSQLiteSchemaProbeExecutor(t.TempDir()).Execute(context.Background(), assignment)
if result.ErrorCode != "invalid_request" || strings.Contains(result.Message, "C:/") {
t.Fatalf("expected redacted binding rejection, got %+v", result)
t.Fatalf("expected bounded binding rejection, got %+v", result)
}
}
+45 -45
View File
@@ -326,7 +326,7 @@ func (worker *Worker) registerUnlocked(ctx context.Context) error {
Capacity: worker.capacityReportFor(state),
})
if err != nil {
log.Printf("RUN phase=register status=failed endpoint=%s error=%s", worker.cfg.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=register status=failed endpoint=%s error=%s", worker.cfg.RunEndpointID, err.Error())
return err
}
if !response.Accepted || response.SessionToken == "" {
@@ -399,10 +399,10 @@ func (worker *Worker) HeartbeatOnce(ctx context.Context) error {
})
if err != nil {
if sessionInvalidError(err) {
log.Printf("RUN phase=heartbeat status=session_invalid endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=heartbeat status=session_invalid endpoint=%s error=%s", state.RunEndpointID, err.Error())
return worker.reregisterAndReconcile(ctx, "heartbeat_session_invalid", state.SessionToken)
}
log.Printf("RUN phase=heartbeat status=failed endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=heartbeat status=failed endpoint=%s error=%s", state.RunEndpointID, err.Error())
return err
}
if !response.Accepted {
@@ -452,10 +452,10 @@ func (worker *Worker) claimAndRunOnce(ctx context.Context, waitSeconds int) (boo
})
if err != nil {
if sessionInvalidError(err) {
log.Printf("RUN phase=claim status=session_invalid endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=claim status=session_invalid endpoint=%s error=%s", state.RunEndpointID, err.Error())
return false, worker.reregisterAndReconcile(ctx, "claim_session_invalid", state.SessionToken)
}
log.Printf("RUN phase=claim status=failed endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=claim status=failed endpoint=%s error=%s", state.RunEndpointID, err.Error())
return false, err
}
if !claim.Accepted || !claim.HasJob || claim.Job == nil {
@@ -482,7 +482,7 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
}
log.Printf("RUN phase=job status=journal_store job=%s capability=%s attempt=%d", assignment.JobID, assignment.Capability, assignment.Attempt)
if err := worker.journal.Store(assignment); err != nil {
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
log.Printf("RUN phase=job status=ack_start job=%s capability=%s", assignment.JobID, assignment.Capability)
@@ -495,7 +495,7 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
Message: "job accepted by run worker",
})
if err != nil {
log.Printf("RUN phase=job status=ack_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=ack_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
assignment = ack.Job
@@ -505,7 +505,7 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
}
log.Printf("RUN phase=job status=ack_accepted job=%s attempt=%d", assignment.JobID, assignment.Attempt)
if err := worker.journal.Store(assignment); err != nil {
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
progressSequence := worker.nextProgressSequence(assignment.ProgressSequence)
@@ -524,7 +524,7 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
Sequence: progressSequence,
})
if err != nil {
log.Printf("RUN phase=job status=progress_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=progress_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
if !progress.Accepted {
@@ -534,13 +534,13 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
assignment = progress.Job
log.Printf("RUN phase=job status=progress_accepted job=%s percent=%d", assignment.JobID, assignment.Progress.Percent)
if err := worker.journal.Store(assignment); err != nil {
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=journal_store_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
log.Printf("RUN phase=job status=execute_start job=%s capability=%s", assignment.JobID, assignment.Capability)
execution, assignment, cancelledByPlatform, err := worker.executeWithJobPolling(ctx, assignment)
if err != nil {
log.Printf("RUN phase=job status=execute_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=execute_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
log.Printf("RUN phase=job status=execute_done job=%s state=%s errorCode=%s message=%s cancelledByPlatform=%t", assignment.JobID, execution.State, safeOptional(execution.ErrorCode), safeOptional(execution.Message), cancelledByPlatform)
@@ -559,13 +559,13 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
resultRequest := LifecycleResultRequest(assignment, state.SessionToken, execution)
log.Printf("RUN phase=job status=result_store job=%s state=%s", assignment.JobID, resultRequest.State)
if err := worker.journal.StorePendingResult(resultRequest, execution.ActivationManifest); err != nil {
log.Printf("RUN phase=job status=result_store_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=result_store_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
log.Printf("RUN phase=job status=result_submit job=%s state=%s", assignment.JobID, resultRequest.State)
result, err := worker.client.CompleteJob(ctx, resultRequest)
if err != nil {
log.Printf("RUN phase=job status=result_submit_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=result_submit_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
if !result.Accepted {
@@ -574,14 +574,14 @@ func (worker *Worker) runAssignment(ctx context.Context, assignment protocol.Run
}
log.Printf("RUN phase=job status=result_accepted job=%s state=%s", assignment.JobID, resultRequest.State)
if err := worker.journal.Delete(assignment.JobID); err != nil {
log.Printf("RUN phase=job status=journal_delete_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job status=journal_delete_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
log.Printf("RUN phase=job status=complete job=%s state=%s", assignment.JobID, resultRequest.State)
if execution.ActivationManifest != "" {
log.Printf("RUN phase=self_update status=activate_start job=%s", assignment.JobID)
if err := worker.executor.selfUpdateActivator.Activate(execution.ActivationManifest); err != nil {
log.Printf("RUN phase=self_update status=activate_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=self_update status=activate_failed job=%s error=%s", assignment.JobID, err.Error())
return fmt.Errorf("launch self-update helper: %w", err)
}
worker.restartMu.Lock()
@@ -598,7 +598,7 @@ func (worker *Worker) executeWithJobPolling(ctx context.Context, assignment prot
pollCancel := func() {
state, err := worker.registeredState()
if err != nil {
log.Printf("RUN phase=job.cancel_poll status=skipped_unregistered job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job.cancel_poll status=skipped_unregistered job=%s error=%s", assignment.JobID, err.Error())
return
}
log.Printf("RUN phase=job.cancel_poll status=starting job=%s", assignment.JobID)
@@ -610,7 +610,7 @@ func (worker *Worker) executeWithJobPolling(ctx context.Context, assignment prot
Attempt: assignment.Attempt,
})
if err != nil {
log.Printf("RUN phase=job.cancel_poll status=failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job.cancel_poll status=failed job=%s error=%s", assignment.JobID, err.Error())
return
}
if err == nil && response.HasCancel {
@@ -637,7 +637,7 @@ func (worker *Worker) executeWithJobPolling(ctx context.Context, assignment prot
log.Printf("RUN phase=job.execute status=worker_finished job=%s state=%s", assignment.JobID, execution.State)
return execution, assignment, cancelledByPlatform, nil
case <-ctx.Done():
log.Printf("RUN phase=job.execute status=context_done job=%s error=%s", assignment.JobID, RedactText(ctx.Err().Error()))
log.Printf("RUN phase=job.execute status=context_done job=%s error=%s", assignment.JobID, ctx.Err().Error())
cancel()
execution := <-executionCh
return execution, assignment, cancelledByPlatform, ctx.Err()
@@ -652,7 +652,7 @@ func (worker *Worker) executeWithJobPolling(ctx context.Context, assignment prot
state, err := worker.registeredState()
if err != nil {
cancel()
log.Printf("RUN phase=job.execute status=lease_renew_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job.execute status=lease_renew_failed job=%s error=%s", assignment.JobID, err.Error())
return LifecycleExecutionResult{}, assignment, cancelledByPlatform, err
}
progress, err := worker.client.UpdateJobProgress(ctx, protocol.RunJobProgressRequest{
@@ -669,14 +669,14 @@ func (worker *Worker) executeWithJobPolling(ctx context.Context, assignment prot
if err == nil {
err = fmt.Errorf("job lease renewal was not accepted")
}
log.Printf("RUN phase=job.execute status=lease_renew_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job.execute status=lease_renew_failed job=%s error=%s", assignment.JobID, err.Error())
return LifecycleExecutionResult{}, assignment, cancelledByPlatform, err
}
assignment = progress.Job
log.Printf("RUN phase=job.execute status=lease_renewed job=%s percent=%d", assignment.JobID, assignment.Progress.Percent)
if err := worker.journal.Store(assignment); err != nil {
cancel()
log.Printf("RUN phase=job.execute status=journal_store_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=job.execute status=journal_store_failed job=%s error=%s", assignment.JobID, err.Error())
return LifecycleExecutionResult{}, assignment, cancelledByPlatform, err
}
}
@@ -880,7 +880,7 @@ func (worker *Worker) ReconcileOnce(ctx context.Context) error {
ActiveJobs: worker.journal.ReconcileEntries(),
})
if err != nil {
log.Printf("RUN phase=reconcile status=failed endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=reconcile status=failed endpoint=%s error=%s", state.RunEndpointID, err.Error())
return err
}
if !response.Accepted {
@@ -928,7 +928,7 @@ func (worker *Worker) RecoverActiveJobs(ctx context.Context) error {
pending.SessionToken = state.SessionToken
result, err := worker.client.CompleteJob(ctx, pending)
if err != nil {
log.Printf("RUN phase=recover status=result_submit_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=recover status=result_submit_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
if !result.Accepted {
@@ -950,7 +950,7 @@ func (worker *Worker) RecoverActiveJobs(ctx context.Context) error {
}
log.Printf("RUN phase=recover status=rerun_active_job job=%s capability=%s", assignment.JobID, assignment.Capability)
if err := worker.runAssignment(ctx, assignment); err != nil {
log.Printf("RUN phase=recover status=rerun_failed job=%s error=%s", assignment.JobID, RedactText(err.Error()))
log.Printf("RUN phase=recover status=rerun_failed job=%s error=%s", assignment.JobID, err.Error())
return err
}
}
@@ -973,11 +973,11 @@ func (worker *Worker) Run(ctx context.Context) error {
return err
}
if err := worker.reportAutonomousProcessObservations(ctx); err != nil {
log.Printf("RUN phase=autonomous_lifecycle.observation status=degraded error=%s", RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.observation status=degraded error=%s", err.Error())
}
worker.reportMetricsDegraded(ctx, "startup")
if err := MarkSelfUpdateHealthy(worker.cfg.UpdateHealthFile); err != nil {
log.Printf("RUN phase=self_update_health status=mark_failed error=%s", RedactText(err.Error()))
log.Printf("RUN phase=self_update_health status=mark_failed error=%s", err.Error())
return err
}
if err := worker.reportRunUpdateHealth(ctx); err != nil {
@@ -1004,7 +1004,7 @@ func (worker *Worker) Run(ctx context.Context) error {
for {
select {
case <-ctx.Done():
log.Printf("RUN phase=run status=context_done error=%s", RedactText(ctx.Err().Error()))
log.Printf("RUN phase=run status=context_done error=%s", ctx.Err().Error())
return ctx.Err()
case err := <-jobDone:
log.Printf("RUN phase=run status=job_loop_done error=%s", errorSummary(err))
@@ -1022,7 +1022,7 @@ func (worker *Worker) Run(ctx context.Context) error {
continue
}
if err := worker.reportAutonomousProcessObservations(ctx); err != nil {
log.Printf("RUN phase=autonomous_lifecycle.observation status=degraded error=%s", RedactText(err.Error()))
log.Printf("RUN phase=autonomous_lifecycle.observation status=degraded error=%s", err.Error())
}
worker.reportMetricsDegraded(ctx, "heartbeat")
heartbeatTicker.Reset(heartbeatInterval)
@@ -1054,7 +1054,7 @@ func (worker *Worker) reportRunUpdateHealth(ctx context.Context) error {
Version: worker.cfg.Version,
})
if err != nil {
log.Printf("RUN phase=self_update_health status=failed job=%s error=%s", worker.cfg.UpdateJobID, RedactText(err.Error()))
log.Printf("RUN phase=self_update_health status=failed job=%s error=%s", worker.cfg.UpdateJobID, err.Error())
return err
}
if !response.Accepted || response.JobID != worker.cfg.UpdateJobID {
@@ -1088,19 +1088,19 @@ func (worker *Worker) runControlStreamLoop(ctx context.Context, wake chan<- stru
return nil
})
if ctx.Err() != nil {
log.Printf("RUN phase=control_stream status=context_done error=%s", RedactText(ctx.Err().Error()))
log.Printf("RUN phase=control_stream status=context_done error=%s", ctx.Err().Error())
return ctx.Err()
}
if err != nil {
if sessionInvalidError(err) {
log.Printf("RUN phase=control_stream status=session_invalid endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=control_stream status=session_invalid endpoint=%s error=%s", state.RunEndpointID, err.Error())
if refreshErr := worker.reregisterAndReconcile(ctx, "control_stream_session_invalid", state.SessionToken); refreshErr != nil {
log.Printf("RUN phase=control_stream status=reregister_failed endpoint=%s error=%s", state.RunEndpointID, RedactText(refreshErr.Error()))
log.Printf("RUN phase=control_stream status=reregister_failed endpoint=%s error=%s", state.RunEndpointID, refreshErr.Error())
}
lastSeq = 0
signalControlWake(wake)
} else {
log.Printf("RUN phase=control_stream status=failed endpoint=%s error=%s", state.RunEndpointID, RedactText(err.Error()))
log.Printf("RUN phase=control_stream status=failed endpoint=%s error=%s", state.RunEndpointID, err.Error())
}
}
if waitErr := waitWorkerLoop(ctx, boundedRetryBackoff(worker.cfg.RetryBackoff)); waitErr != nil {
@@ -1121,21 +1121,21 @@ func (worker *Worker) runJobLoop(ctx context.Context, interval time.Duration, wa
for {
select {
case <-ctx.Done():
log.Printf("RUN phase=job_loop status=context_done error=%s", RedactText(ctx.Err().Error()))
log.Printf("RUN phase=job_loop status=context_done error=%s", ctx.Err().Error())
return ctx.Err()
default:
}
if worker.journal.ActiveCount() > 0 {
log.Printf("RUN phase=job_loop status=active_jobs activeJobs=%d", worker.journal.ActiveCount())
if err := worker.ReconcileOnce(ctx); err != nil {
log.Printf("RUN phase=job_loop status=reconcile_failed error=%s", RedactText(err.Error()))
log.Printf("RUN phase=job_loop status=reconcile_failed error=%s", err.Error())
if err := waitWorkerLoop(ctx, boundedRetryBackoff(worker.cfg.RetryBackoff)); err != nil {
return err
}
continue
}
if err := worker.RecoverActiveJobs(ctx); err != nil {
log.Printf("RUN phase=job_loop status=recover_failed error=%s", RedactText(err.Error()))
log.Printf("RUN phase=job_loop status=recover_failed error=%s", err.Error())
if err := waitWorkerLoop(ctx, boundedRetryBackoff(worker.cfg.RetryBackoff)); err != nil {
return err
}
@@ -1145,7 +1145,7 @@ func (worker *Worker) runJobLoop(ctx context.Context, interval time.Duration, wa
claimStartedAt := time.Now()
handled, err := worker.claimAndRunOnce(ctx, 0)
if err != nil {
log.Printf("RUN phase=job_loop status=claim_failed error=%s", RedactText(err.Error()))
log.Printf("RUN phase=job_loop status=claim_failed error=%s", err.Error())
if err := waitWorkerLoop(ctx, boundedRetryBackoff(worker.cfg.RetryBackoff)); err != nil {
return err
}
@@ -1248,7 +1248,7 @@ func (client sessionLogBatchClient) IngestLogBatch(ctx context.Context, batch pr
}
response, err := client.client.IngestLogBatch(ctx, batch)
if err != nil && logBatchNotFoundError(err) {
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=platform_not_found error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=platform_not_found error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, err.Error())
return protocol.LogBatchIngestResponse{}, spool.PermanentLogBatchRejection("platform_not_found", err)
}
if err != nil && (logBatchSequenceGapError(err) || logBatchAcknowledgedRangeConflict(err)) {
@@ -1256,15 +1256,15 @@ func (client sessionLogBatchClient) IngestLogBatch(ctx context.Context, batch pr
if logBatchAcknowledgedRangeConflict(err) {
reason = "platform_acknowledged_range_conflict"
}
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=%s error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, reason, RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=%s error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, reason, err.Error())
return protocol.LogBatchIngestResponse{}, spool.PermanentLogBatchRejection(reason, err)
}
if err != nil && logBatchLegacySessionMetadataError(err) && strings.TrimSpace(batch.LogSessionID) == "" && !batch.SessionStartedAt.IsZero() {
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=legacy_session_metadata error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=legacy_session_metadata error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, err.Error())
return protocol.LogBatchIngestResponse{}, spool.PermanentLogBatchRejection("legacy_session_metadata", err)
}
if err != nil && logBatchSessionMetadataMismatchError(err) {
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=session_metadata_mismatch error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.logs status=spool_quarantine stream=%s firstSeq=%d lastSeq=%d reason=session_metadata_mismatch error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, batch.LastSeq, err.Error())
return protocol.LogBatchIngestResponse{}, spool.PermanentLogBatchRejection("session_metadata_mismatch", err)
}
return response, err
@@ -1362,7 +1362,7 @@ func (worker *Worker) runDurableUploaders(ctx context.Context, done chan<- struc
case <-ticker.C:
state, err := worker.registeredState()
if err != nil {
log.Printf("RUN phase=durable_uploaders status=skipped_unregistered error=%s", RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders status=skipped_unregistered error=%s", err.Error())
continue
}
if hasLogSink && hasLogClient {
@@ -1372,7 +1372,7 @@ func (worker *Worker) runDurableUploaders(ctx context.Context, done chan<- struc
client := sessionLogBatchClient{client: logClient, progressClient: progressClient, runEndpointID: state.RunEndpointID, sessionToken: state.SessionToken}
flushed, err := logSink.Spool.Flush(flushCtx, client)
if err != nil {
log.Printf("RUN phase=durable_uploaders.logs status=flush_failed flushed=%d durationMs=%d error=%s", flushed, time.Since(startedAt).Milliseconds(), RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.logs status=flush_failed flushed=%d durationMs=%d error=%s", flushed, time.Since(startedAt).Milliseconds(), err.Error())
} else if flushed > 0 {
log.Printf("RUN phase=durable_uploaders.logs status=flushed count=%d durationMs=%d", flushed, time.Since(startedAt).Milliseconds())
}
@@ -1385,7 +1385,7 @@ func (worker *Worker) runDurableUploaders(ctx context.Context, done chan<- struc
client := sessionArtifactChunkClient{client: artifactClient, runEndpointID: state.RunEndpointID, sessionToken: state.SessionToken}
flushed, err := artifactHook.Queue.Flush(flushCtx, client)
if err != nil {
log.Printf("RUN phase=durable_uploaders.artifacts status=flush_failed flushed=%d durationMs=%d error=%s", flushed, time.Since(startedAt).Milliseconds(), RedactText(err.Error()))
log.Printf("RUN phase=durable_uploaders.artifacts status=flush_failed flushed=%d durationMs=%d error=%s", flushed, time.Since(startedAt).Milliseconds(), err.Error())
} else if flushed > 0 {
log.Printf("RUN phase=durable_uploaders.artifacts status=flushed count=%d durationMs=%d", flushed, time.Since(startedAt).Milliseconds())
}
@@ -1542,7 +1542,7 @@ func (sink *LiveLogSink) dispatch() {
_, err := sink.Client.RelayLiveLogBatch(ctx, batch)
cancel()
if err != nil {
log.Printf("RUN phase=live_log_relay status=dropped stream=%s sequence=%d error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, RedactText(err.Error()))
log.Printf("RUN phase=live_log_relay status=dropped stream=%s sequence=%d error=%s", safeOptional(batch.LogStreamID), batch.FirstSeq, err.Error())
}
}
}
+4 -4
View File
@@ -34,12 +34,12 @@ func MaterializeWorkspaceSeed(cfg config.Config) error {
log.Printf("RUN phase=workspace_seed status=decoding workspace=%s encodedBytes=%d componentKey=%s", safeOptional(cfg.WorkspaceRoot), len(encoded), safeOptional(cfg.ComponentKey))
payload, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
log.Printf("RUN phase=workspace_seed status=decode_failed workspace=%s error=%s", safeOptional(cfg.WorkspaceRoot), RedactText(err.Error()))
log.Printf("RUN phase=workspace_seed status=decode_failed workspace=%s error=%s", safeOptional(cfg.WorkspaceRoot), err.Error())
return fmt.Errorf("decode workspace seed: %w", err)
}
var files []workspaceSeedFile
if err := json.Unmarshal(payload, &files); err != nil {
log.Printf("RUN phase=workspace_seed status=manifest_failed workspace=%s payloadBytes=%d error=%s", safeOptional(cfg.WorkspaceRoot), len(payload), RedactText(err.Error()))
log.Printf("RUN phase=workspace_seed status=manifest_failed workspace=%s payloadBytes=%d error=%s", safeOptional(cfg.WorkspaceRoot), len(payload), err.Error())
return fmt.Errorf("decode workspace seed manifest: %w", err)
}
log.Printf("RUN phase=workspace_seed status=decoded workspace=%s payloadBytes=%d files=%d", safeOptional(cfg.WorkspaceRoot), len(payload), len(files))
@@ -53,7 +53,7 @@ func MaterializeWorkspaceSeed(cfg config.Config) error {
}
scope, err := seededWorkspaceScopeForFiles(cfg, files)
if err != nil {
log.Printf("RUN phase=workspace_seed status=scope_failed workspace=%s server=%s componentKey=%s error=%s", safeOptional(cfg.WorkspaceRoot), safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKey), RedactText(err.Error()))
log.Printf("RUN phase=workspace_seed status=scope_failed workspace=%s server=%s componentKey=%s error=%s", safeOptional(cfg.WorkspaceRoot), safeOptional(cfg.ServerInstanceID), safeOptional(cfg.ComponentKey), err.Error())
return err
}
log.Printf("RUN phase=workspace_seed status=scope_ready workspace=%s scope=%s files=%d", safeOptional(cfg.WorkspaceRoot), safeOptional(scope), len(files))
@@ -61,7 +61,7 @@ func MaterializeWorkspaceSeed(cfg config.Config) error {
for index, file := range files {
written, err := writeWorkspaceSeedFile(scope, file, index+1, len(files))
if err != nil {
log.Printf("RUN phase=workspace_seed.file status=failed index=%d total=%d path=%s error=%s", index+1, len(files), safeOptional(file.Path), RedactText(err.Error()))
log.Printf("RUN phase=workspace_seed.file status=failed index=%d total=%d path=%s error=%s", index+1, len(files), safeOptional(file.Path), err.Error())
return err
}
totalBytes += written