소스 검색

fix: persist blocked executions to saveLogs files (#1111)

James Read 3 일 전
부모
커밋
ec1aa3952f
3개의 변경된 파일81개의 추가작업 그리고 1개의 파일을 삭제
  1. 2 0
      docs/modules/ROOT/pages/logs/saving.adoc
  2. 1 1
      service/internal/executor/executor.go
  3. 78 0
      service/internal/executor/executor_test.go

+ 2 - 0
docs/modules/ROOT/pages/logs/saving.adoc

@@ -3,6 +3,8 @@
 
 
 By default, OliveTin only keeps logs in memory, meaning that if you restart OliveTin your logs will be lost. For some use cases this is acceptable, but you can configure OliveTin to save logs for you.
 By default, OliveTin only keeps logs in memory, meaning that if you restart OliveTin your logs will be lost. For some use cases this is acceptable, but you can configure OliveTin to save logs for you.
 
 
+When `saveLogs` is enabled, OliveTin writes a file for each configured directory when an execution finishes — a results YAML when `resultsDirectory` is set, and an output log when `outputDirectory` is set. If only one directory is configured, only that file type is written. This includes blocked runs (for example when concurrency or rate limits prevent the action from starting).
+
 You can configure the global setting for saving logs, or override it on a per-action basis;
 You can configure the global setting for saving logs, or override it on a per-action basis;
 
 
 [source,yaml]
 [source,yaml]

+ 1 - 1
service/internal/executor/executor.go

@@ -184,7 +184,6 @@ func DefaultExecutor(cfg *config.Config) *Executor {
 		stepExec,
 		stepExec,
 		stepExecAfter,
 		stepExecAfter,
 		stepLogFinish,
 		stepLogFinish,
-		stepSaveLog,
 		stepTrigger,
 		stepTrigger,
 	}
 	}
 
 
@@ -688,6 +687,7 @@ func (e *Executor) finishExecChain(req *ExecutionRequest) {
 	recordExecutionMetrics(req.logEntry)
 	recordExecutionMetrics(req.logEntry)
 
 
 	notifyListenersFinished(req)
 	notifyListenersFinished(req)
+	stepSaveLog(req)
 	e.drainGroupQueue()
 	e.drainGroupQueue()
 }
 }
 
 

+ 78 - 0
service/internal/executor/executor_test.go

@@ -1042,6 +1042,84 @@ func TestStepSaveLogSanitizesNULInTitle(t *testing.T) {
 	assert.Equal(t, "nul ok", string(output))
 	assert.Equal(t, "nul ok", string(output))
 }
 }
 
 
+func TestBlockedExecutionPersistsSaveLogs(t *testing.T) {
+	t.Parallel()
+
+	resultsDir := t.TempDir()
+	outputDir := t.TempDir()
+
+	action := &config.Action{
+		Title:         "Blocked report",
+		Shell:         "sleep 1",
+		MaxConcurrent: 1,
+		SaveLogs: config.SaveLogsConfig{
+			ResultsDirectory: resultsDir,
+			OutputDirectory:  outputDir,
+		},
+	}
+
+	e, cfg := testGroupExecutor([]*config.Action{action}, nil)
+	binding := e.FindBindingWithNoEntity(action)
+
+	wg1, tracking1 := e.ExecRequest(&ExecutionRequest{
+		Binding:           binding,
+		Cfg:               cfg,
+		AuthenticatedUser: auth.UserFromSystem(cfg, "testuser"),
+	})
+
+	waitUntilExecutionStarted(t, e, tracking1)
+
+	wg2, tracking2 := e.ExecRequest(&ExecutionRequest{
+		Binding:           binding,
+		Cfg:               cfg,
+		AuthenticatedUser: auth.UserFromSystem(cfg, "testuser"),
+	})
+
+	wg1.Wait()
+	wg2.Wait()
+
+	snapshot, ok := e.SnapshotLog(tracking2)
+	require.True(t, ok)
+	require.True(t, snapshot.Blocked)
+
+	resultsEntries, err := os.ReadDir(resultsDir)
+	require.NoError(t, err)
+
+	var resultsPath string
+
+	for _, entry := range resultsEntries {
+		if strings.Contains(entry.Name(), tracking2) {
+			resultsPath = filepath.Join(resultsDir, entry.Name())
+			break
+		}
+	}
+
+	require.NotEmpty(t, resultsPath)
+
+	outputEntries, err := os.ReadDir(outputDir)
+	require.NoError(t, err)
+
+	var outputPath string
+
+	for _, entry := range outputEntries {
+		if strings.Contains(entry.Name(), tracking2) {
+			outputPath = filepath.Join(outputDir, entry.Name())
+			break
+		}
+	}
+
+	require.NotEmpty(t, outputPath)
+
+	resultsData, err := os.ReadFile(resultsPath)
+	require.NoError(t, err)
+	assert.Contains(t, string(resultsData), "blocked: true")
+	assert.Contains(t, string(resultsData), tracking2)
+
+	outputData, err := os.ReadFile(outputPath)
+	require.NoError(t, err)
+	assert.Contains(t, string(outputData), "Blocked from executing due to concurrency limit")
+}
+
 func TestStepSaveLogReturnsFalseWhenDependenciesMissing(t *testing.T) {
 func TestStepSaveLogReturnsFalseWhenDependenciesMissing(t *testing.T) {
 	started := time.Unix(1714333384, 0)
 	started := time.Unix(1714333384, 0)
 	valid := &ExecutionRequest{
 	valid := &ExecutionRequest{