Preserve runtime log evidence verbatim
This commit is contained in:
+13
-5
@@ -264,7 +264,7 @@ func (executor LifecycleExecutor) runDependencyProbe(ctx context.Context, probe
|
|||||||
}
|
}
|
||||||
return "missing", "declared executable is not available", nil
|
return "missing", "declared executable is not available", nil
|
||||||
}
|
}
|
||||||
version := dependencyVersionEvidence(result.Stdout + "\n" + result.Stderr)
|
version := dependencyVersionEvidence(processOutputEvidence(result.Stdout, result.Stderr))
|
||||||
if probe.MinimumVersion != "" && !dependencyVersionAtLeast(version, probe.MinimumVersion) {
|
if probe.MinimumVersion != "" && !dependencyVersionAtLeast(version, probe.MinimumVersion) {
|
||||||
return "missing", "declared executable version is below minimum", nil
|
return "missing", "declared executable version is below minimum", nil
|
||||||
}
|
}
|
||||||
@@ -450,7 +450,6 @@ func serviceProbeCommand(targetOS, service string) (string, []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dependencySuccess(assignment protocol.RunJobAssignment, input protocol.DependencyExecutionInputResponse, state, evidence string, completed int) LifecycleExecutionResult {
|
func dependencySuccess(assignment protocol.RunJobAssignment, input protocol.DependencyExecutionInputResponse, state, evidence string, completed int) LifecycleExecutionResult {
|
||||||
evidence = RedactText(strings.TrimSpace(evidence))
|
|
||||||
payload, _ := json.Marshal(protocol.DependencyExecutionEvidence{ProbeKey: input.Probe.Key, PlanKey: input.Plan.Key, PlanDigest: input.PlanDigest, State: state, Evidence: evidence, CompletedSteps: completed})
|
payload, _ := json.Marshal(protocol.DependencyExecutionEvidence{ProbeKey: input.Probe.Key, PlanKey: input.Plan.Key, PlanDigest: input.PlanDigest, State: state, Evidence: evidence, CompletedSteps: completed})
|
||||||
kind := "dependency.check"
|
kind := "dependency.check"
|
||||||
message := "dependency probe completed"
|
message := "dependency probe completed"
|
||||||
@@ -461,6 +460,16 @@ func dependencySuccess(assignment protocol.RunJobAssignment, input protocol.Depe
|
|||||||
return LifecycleExecutionResult{State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: message}, ResultRef: fmt.Sprintf("artifact://jobs/%s/dependencies-result", url.PathEscape(assignment.JobID)), Message: message, ExecutionResult: protocol.RunJobExecutionResult{Kind: kind, Checksum: input.PlanDigest, Summary: message, Content: string(payload)}}
|
return LifecycleExecutionResult{State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: message}, ResultRef: fmt.Sprintf("artifact://jobs/%s/dependencies-result", url.PathEscape(assignment.JobID)), Message: message, ExecutionResult: protocol.RunJobExecutionResult{Kind: kind, Checksum: input.PlanDigest, Summary: message, Content: string(payload)}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processOutputEvidence(stdout string, stderr string) string {
|
||||||
|
if stdout == "" {
|
||||||
|
return stderr
|
||||||
|
}
|
||||||
|
if stderr == "" {
|
||||||
|
return stdout
|
||||||
|
}
|
||||||
|
return stdout + "\n" + stderr
|
||||||
|
}
|
||||||
|
|
||||||
func loadDependencyJournal(path string, assignment protocol.RunJobAssignment, digest string) (dependencyJournal, error) {
|
func loadDependencyJournal(path string, assignment protocol.RunJobAssignment, digest string) (dependencyJournal, error) {
|
||||||
journal := dependencyJournal{Version: 1, JobID: assignment.JobID, Attempt: assignment.Attempt, PlanDigest: digest, State: "pending", UpdatedAt: time.Now().UTC()}
|
journal := dependencyJournal{Version: 1, JobID: assignment.JobID, Attempt: assignment.Attempt, PlanDigest: digest, State: "pending", UpdatedAt: time.Now().UTC()}
|
||||||
body, err := os.ReadFile(path)
|
body, err := os.ReadFile(path)
|
||||||
@@ -530,11 +539,10 @@ func validSHA256(value string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dependencyVersionEvidence(output string) string {
|
func dependencyVersionEvidence(output string) string {
|
||||||
lines := splitBoundedLines(output)
|
if output == "" {
|
||||||
if len(lines) == 0 {
|
|
||||||
return "version available"
|
return "version available"
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(lines[0])
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func dependencyVersionAtLeast(actual, minimum string) bool {
|
func dependencyVersionAtLeast(actual, minimum string) bool {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (supervisor *dependencyTestSupervisor) Run(ctx context.Context, command Pro
|
|||||||
if !present {
|
if !present {
|
||||||
return ProcessResult{ExitCode: 1}, errors.New("not installed")
|
return ProcessResult{ExitCode: 1}, errors.New("not installed")
|
||||||
}
|
}
|
||||||
return ProcessResult{ExitCode: 0, Stderr: "openjdk version 21.0.2"}, nil
|
return ProcessResult{ExitCode: 0, Stderr: "openjdk version 21.0.2\npassword=visible\n/Users/operator/private"}, nil
|
||||||
}
|
}
|
||||||
return ProcessResult{ExitCode: 0}, nil
|
return ProcessResult{ExitCode: 0}, nil
|
||||||
}
|
}
|
||||||
@@ -102,8 +102,8 @@ func TestDependencyInstallExecutesTypedPlanAndResumesCompletedSteps(t *testing.T
|
|||||||
if runner.count("apt-get") != 1 {
|
if runner.count("apt-get") != 1 {
|
||||||
t.Fatalf("completed package step must not repeat, calls=%+v", runner.calls)
|
t.Fatalf("completed package step must not repeat, calls=%+v", runner.calls)
|
||||||
}
|
}
|
||||||
if !strings.Contains(first.ExecutionResult.Content, `"completedSteps":1`) || strings.Contains(first.ExecutionResult.Content, "/Users/") {
|
if !strings.Contains(first.ExecutionResult.Content, `"completedSteps":1`) || !strings.Contains(first.ExecutionResult.Content, "password=visible") || !strings.Contains(first.ExecutionResult.Content, "/Users/operator/private") {
|
||||||
t.Fatalf("dependency evidence is not safe: %s", first.ExecutionResult.Content)
|
t.Fatalf("dependency evidence was not preserved verbatim: %s", first.ExecutionResult.Content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1235,20 +1235,6 @@ func RedactText(value string) string {
|
|||||||
return redacted
|
return redacted
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitBoundedLines(value string) []string {
|
|
||||||
value = RedactText(value)
|
|
||||||
lines := strings.Split(value, "\n")
|
|
||||||
out := make([]string, 0, len(lines))
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimRight(line, "\r")
|
|
||||||
if strings.TrimSpace(line) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, line)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// splitRawLogLines only removes the newline framing used by LogEntry. It
|
// splitRawLogLines only removes the newline framing used by LogEntry. It
|
||||||
// deliberately preserves every other byte, including blank lines and spaces.
|
// deliberately preserves every other byte, including blank lines and spaces.
|
||||||
func splitRawLogLines(value string) []string {
|
func splitRawLogLines(value string) []string {
|
||||||
|
|||||||
Reference in New Issue
Block a user