fix: avoid 'filename too long' on matrix jobs by hashing container names (#853)

## Bug fixes

- Fixed "file name too long" errors when running matrix jobs with many or
  long-valued matrix parameters. Docker volume and container names are now
  bounded to a safe length by hashing the full name with SHA-256, keeping
  the result well within the filesystem `NAME_MAX` limit (255 bytes) even
  after Docker appends its own suffixes (`-env`, `-network`, etc.).

## Upgrade notes

This change renames the Docker containers, volumes and networks created by
the runner. Resources created by a previous version will **not** be cleaned
up automatically after upgrade and will become orphans.

After upgrading, you can remove the legacy resources with:

```sh
# volumes
docker volume ls -q | grep -E '^GITEA-ACTIONS-TASK-[0-9]+_' | xargs -r docker volume rm

# networks
docker network ls --format '{{.Name}}' | grep -E '^GITEA-ACTIONS-TASK-[0-9]+_.*-network$' | xargs -r docker network rm
```

> **Note:** If multiple act_runner instances share the same Docker daemon,
> make sure no runner using the old version is running before executing the
> cleanup commands above.

Fixes #686

---------

Co-authored-by: Nicolas <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/853
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Morgan Peyre <195218+peyremorgan@noreply.gitea.com>
Co-committed-by: Morgan Peyre <195218+peyremorgan@noreply.gitea.com>
This commit is contained in:
Morgan Peyre
2026-04-28 00:30:27 +00:00
committed by silverwind
parent 3f05040438
commit 801e5cf4d5
4 changed files with 15 additions and 40 deletions

View File

@@ -429,7 +429,7 @@ func newStepContainer(ctx context.Context, step step, image string, cmd, entrypo
Image: image, Image: image,
Username: rc.Config.Secrets["DOCKER_USERNAME"], Username: rc.Config.Secrets["DOCKER_USERNAME"],
Password: rc.Config.Secrets["DOCKER_PASSWORD"], Password: rc.Config.Secrets["DOCKER_PASSWORD"],
Name: createSimpleContainerName(rc.jobContainerName(), "STEP-"+stepModel.ID), Name: createContainerName(rc.jobContainerName(), "STEP-"+stepModel.ID),
Env: envList, Env: envList,
Mounts: mounts, Mounts: mounts,
NetworkMode: networkMode, NetworkMode: networkMode,

View File

@@ -101,8 +101,7 @@ func (rc *RunContext) jobContainerName() string {
if rc.caller != nil { if rc.caller != nil {
nameParts = append(nameParts, "CALLED-BY-"+rc.caller.runContext.JobName) nameParts = append(nameParts, "CALLED-BY-"+rc.caller.runContext.JobName)
} }
// return createSimpleContainerName(rc.Config.ContainerNamePrefix, "WORKFLOW-"+rc.Run.Workflow.Name, "JOB-"+rc.Name) return createContainerName(nameParts...) // For Gitea
return createSimpleContainerName(nameParts...) // For Gitea
} }
// networkNameForGitea return the name of the network // networkNameForGitea return the name of the network
@@ -769,7 +768,6 @@ func mergeMaps(maps ...map[string]string) map[string]string {
return rtnMap return rtnMap
} }
// Deprecated: use createSimpleContainerName
func createContainerName(parts ...string) string { func createContainerName(parts ...string) string {
name := strings.Join(parts, "-") name := strings.Join(parts, "-")
pattern := regexp.MustCompile("[^a-zA-Z0-9]") pattern := regexp.MustCompile("[^a-zA-Z0-9]")
@@ -783,22 +781,6 @@ func createContainerName(parts ...string) string {
return fmt.Sprintf("%s-%x", trimmedName, hash) return fmt.Sprintf("%s-%x", trimmedName, hash)
} }
func createSimpleContainerName(parts ...string) string {
pattern := regexp.MustCompile("[^a-zA-Z0-9-]")
name := make([]string, 0, len(parts))
for _, v := range parts {
v = pattern.ReplaceAllString(v, "-")
v = strings.Trim(v, "-")
for strings.Contains(v, "--") {
v = strings.ReplaceAll(v, "--", "-")
}
if v != "" {
name = append(name, v)
}
}
return strings.Join(name, "_")
}
func trimToLen(s string, l int) string { func trimToLen(s string, l int) string {
if l < 0 { if l < 0 {
l = 0 l = 0

View File

@@ -622,23 +622,16 @@ func TestRunContextGetEnv(t *testing.T) {
} }
} }
func Test_createSimpleContainerName(t *testing.T) { func TestCreateContainerNameBoundedForLongMatrixInput(t *testing.T) {
tests := []struct { longMatrixValue := strings.Repeat("os=ubuntu-latest-go=1.24-node=22-", 20)
parts []string name := createContainerName(
want string "gitea",
}{ "WORKFLOW-super-long-workflow-name",
{ "JOB-build-matrix-"+longMatrixValue,
parts: []string{"a--a", "BB正", "c-C"}, )
want: "a-a_BB_c-C",
}, assert.LessOrEqual(t, len(name), 128)
{ assert.LessOrEqual(t, len(name+"-env"), 255)
parts: []string{"a-a", "", "-"}, assert.LessOrEqual(t, len(name+"-network"), 255)
want: "a-a", assert.LessOrEqual(t, len(name+"-job1234567890"), 255)
},
}
for _, tt := range tests {
t.Run(strings.Join(tt.parts, " "), func(t *testing.T) {
assert.Equalf(t, tt.want, createSimpleContainerName(tt.parts...), "createSimpleContainerName(%v)", tt.parts)
})
}
} }

View File

@@ -127,7 +127,7 @@ func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd, e
Image: image, Image: image,
Username: rc.Config.Secrets["DOCKER_USERNAME"], Username: rc.Config.Secrets["DOCKER_USERNAME"],
Password: rc.Config.Secrets["DOCKER_PASSWORD"], Password: rc.Config.Secrets["DOCKER_PASSWORD"],
Name: createSimpleContainerName(rc.jobContainerName(), "STEP-"+step.ID), Name: createContainerName(rc.jobContainerName(), "STEP-"+step.ID),
Env: envList, Env: envList,
Mounts: mounts, Mounts: mounts,
NetworkMode: networkMode, NetworkMode: networkMode,