Procházet zdrojové kódy

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

James Read před 1 měsícem
rodič
revize
ec1aa3952f

+ 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{