mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-09-30 19:22:08 +00:00
[12.0/forgejo] fix: de-duplicate Forgejo Actions job names when needed (#8883)
**Backport: https://codeberg.org/forgejo/forgejo/pulls/8864**
The status of two jobs by the same name shadow each other, they need to be distinct. If two jobs by the same name are found, they are made distinct by adding a -<occurence number> suffix.
Resolves forgejo/forgejo#8648
(cherry picked from commit 6bc1803c70
)
```
Conflicts:
services/actions/notifier_helper.go
services/actions/schedule_tasks.go
services/actions/workflows.go
trivial context conflicts
services/actions/job_parser.go
use "github.com/nektos/act/pkg/jobparser"
```
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8883
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Earl Warren <contact@earl-warren.org>
Co-committed-by: Earl Warren <contact@earl-warren.org>
This commit is contained in:
parent
53c4c6bda8
commit
aca70e89b6
5 changed files with 246 additions and 3 deletions
31
services/actions/job_parser.go
Normal file
31
services/actions/job_parser.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nektos/act/pkg/jobparser"
|
||||
)
|
||||
|
||||
func jobParser(workflow []byte, options ...jobparser.ParseOption) ([]*jobparser.SingleWorkflow, error) {
|
||||
singleWorkflows, err := jobparser.Parse(workflow, options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nameToSingleWorkflows := make(map[string][]*jobparser.SingleWorkflow, len(singleWorkflows))
|
||||
duplicates := make(map[string]int, len(singleWorkflows))
|
||||
for _, singleWorkflow := range singleWorkflows {
|
||||
id, job := singleWorkflow.Job()
|
||||
nameToSingleWorkflows[job.Name] = append(nameToSingleWorkflows[job.Name], singleWorkflow)
|
||||
if len(nameToSingleWorkflows[job.Name]) > 1 {
|
||||
duplicates[job.Name]++
|
||||
job.Name = fmt.Sprintf("%s-%d", job.Name, duplicates[job.Name])
|
||||
if err := singleWorkflow.SetJob(id, job); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return singleWorkflows, nil
|
||||
}
|
212
services/actions/job_parser_test.go
Normal file
212
services/actions/job_parser_test.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServiceActions_jobParser(t *testing.T) {
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
workflow string
|
||||
singleWorkflows []string
|
||||
}{
|
||||
{
|
||||
name: "OneJobNoDuplicate",
|
||||
workflow: `
|
||||
jobs:
|
||||
job1:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
singleWorkflows: []string{
|
||||
`jobs:
|
||||
job1:
|
||||
name: job1
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MatrixTwoJobsWithSameJobName",
|
||||
workflow: `
|
||||
name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: shadowdefaultmatrixgeneratednames
|
||||
strategy:
|
||||
matrix:
|
||||
version: [1.17, 1.19]
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
singleWorkflows: []string{
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: shadowdefaultmatrixgeneratednames
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.17
|
||||
`,
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: shadowdefaultmatrixgeneratednames-1
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.19
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MatrixTwoJobsWithMatrixGeneratedNames",
|
||||
workflow: `
|
||||
name: test
|
||||
jobs:
|
||||
job1:
|
||||
strategy:
|
||||
matrix:
|
||||
version: [1.17, 1.19]
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
singleWorkflows: []string{
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: job1 (1.17)
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.17
|
||||
`,
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: job1 (1.19)
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.19
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MatrixTwoJobsWithDistinctInterpolatedNames",
|
||||
workflow: `
|
||||
name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname-${{ matrix.version }}
|
||||
strategy:
|
||||
matrix:
|
||||
version: [1.17, 1.19]
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
singleWorkflows: []string{
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname-1.17
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.17
|
||||
`,
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname-1.19
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.19
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MatrixTwoJobsWithIdenticalInterpolatedNames",
|
||||
workflow: `
|
||||
name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname-${{ matrix.typo }}
|
||||
strategy:
|
||||
matrix:
|
||||
version: [1.17, 1.19]
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
`,
|
||||
singleWorkflows: []string{
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname-
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.17
|
||||
`,
|
||||
`name: test
|
||||
jobs:
|
||||
job1:
|
||||
name: myname--1
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo OK
|
||||
strategy:
|
||||
matrix:
|
||||
version:
|
||||
- 1.19
|
||||
`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
sw, err := jobParser([]byte(testCase.workflow))
|
||||
require.NoError(t, err)
|
||||
for i, sw := range sw {
|
||||
actual, err := sw.Marshal()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testCase.singleWorkflows[i], string(actual))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -372,7 +372,7 @@ func handleWorkflows(
|
|||
continue
|
||||
}
|
||||
|
||||
jobs, err := jobparser.Parse(dwf.Content, jobparser.WithVars(vars))
|
||||
jobs, err := jobParser(dwf.Content, jobparser.WithVars(vars))
|
||||
if err != nil {
|
||||
run.Status = actions_model.StatusFailure
|
||||
log.Info("jobparser.Parse: invalid workflow, setting job status to failed: %v", err)
|
||||
|
|
|
@ -153,7 +153,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
|
|||
run.NotifyEmail = notifications
|
||||
|
||||
// Parse the workflow specification from the cron schedule
|
||||
workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars))
|
||||
workflows, err := jobParser(cron.Content, jobparser.WithVars(vars))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
jobs, err := jobparser.Parse(content, jobparser.WithVars(vars))
|
||||
jobs, err := jobParser(content, jobparser.WithVars(vars))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue