mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-05-08 00:03:24 +02:00
- Add idle-time cleanup for stale bind-workdir task directories instead of cleaning them on the task execution path. - Make cleanup behavior configurable with `runner.startup_cleanup_age` as the stale-age threshold (default: `24h`) and `runner.idle_cleanup_interval` as the idle cleanup cadence (default: `10m`). - Restrict cleanup scope to numeric task directory names only, to avoid touching operator-managed folders. - Document the cleanup settings in `config.example.yaml` and `README.md`. - Add tests for stale-directory cleanup, idle cleanup throttling, and config default/override parsing. ## Why When a runner or host crashes, normal per-task cleanup may not run, leaving stale task directories under the bind-workdir root. Running this cleanup only while the runner is idle recovers that disk space without adding overhead to active job execution. If you want, I can also tighten the wording around `startup_cleanup_age`, since the key name now reads a bit misleadingly relative to the actual behavior. --------- Co-authored-by: silverwind <me@silverwind.io> Reviewed-on: https://gitea.com/gitea/runner/pulls/870 Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLoadDefault_RejectsExternalServerWithoutSecret(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
cache:
|
|
enabled: true
|
|
external_server: "http://cache.invalid/"
|
|
`), 0o600))
|
|
|
|
_, err := LoadDefault(path)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "external_secret")
|
|
}
|
|
|
|
func TestLoadDefault_AcceptsExternalServerWithSecret(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
cache:
|
|
enabled: true
|
|
external_server: "http://cache.invalid/"
|
|
external_secret: "shh"
|
|
`), 0o600))
|
|
|
|
_, err := LoadDefault(path)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestLoadDefault_DefaultsWorkdirCleanupAge(t *testing.T) {
|
|
cfg, err := LoadDefault("")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 24*time.Hour, cfg.Runner.WorkdirCleanupAge)
|
|
assert.Equal(t, 10*time.Minute, cfg.Runner.IdleCleanupInterval)
|
|
}
|
|
|
|
func TestLoadDefault_UsesConfiguredWorkdirCleanupAge(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
runner:
|
|
workdir_cleanup_age: 2h30m
|
|
`), 0o600))
|
|
|
|
cfg, err := LoadDefault(path)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 150*time.Minute, cfg.Runner.WorkdirCleanupAge)
|
|
}
|
|
|
|
func TestLoadDefault_UsesConfiguredIdleCleanupInterval(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
runner:
|
|
idle_cleanup_interval: 45m
|
|
`), 0o600))
|
|
|
|
cfg, err := LoadDefault(path)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 45*time.Minute, cfg.Runner.IdleCleanupInterval)
|
|
}
|
|
|
|
func TestLoadDefault_AllowsDisablingWorkdirCleanup(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
runner:
|
|
workdir_cleanup_age: 0s
|
|
idle_cleanup_interval: 0s
|
|
`), 0o600))
|
|
|
|
cfg, err := LoadDefault(path)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Duration(0), cfg.Runner.WorkdirCleanupAge)
|
|
assert.Equal(t, time.Duration(0), cfg.Runner.IdleCleanupInterval)
|
|
}
|
|
|
|
func TestLoadDefault_AllowsNegativeWorkdirCleanupValues(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte(`
|
|
runner:
|
|
workdir_cleanup_age: -1s
|
|
idle_cleanup_interval: -1s
|
|
`), 0o600))
|
|
|
|
cfg, err := LoadDefault(path)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, -1*time.Second, cfg.Runner.WorkdirCleanupAge)
|
|
assert.Equal(t, -1*time.Second, cfg.Runner.IdleCleanupInterval)
|
|
}
|
|
|
|
// TestLoadDefault_MalformedYAMLReturnsParseError pins the error surfaced for
|
|
// invalid YAML to the canonical "parse config file" message rather than the
|
|
// "for defaults metadata" variant — i.e. the main yaml.Unmarshal runs first.
|
|
func TestLoadDefault_MalformedYAMLReturnsParseError(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "config.yaml")
|
|
require.NoError(t, os.WriteFile(path, []byte("runner:\n capacity: [unterminated\n"), 0o600))
|
|
|
|
_, err := LoadDefault(path)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse config file")
|
|
assert.NotContains(t, err.Error(), "defaults metadata")
|
|
}
|