Skip to content

Commit 8dc53a0

Browse files
bircniclaudeZettat123GiteaBotwxiaoguang
committed
Report structurally invalid workflows to users (go-gitea#37116)
`model.ReadWorkflow` succeeds for YAML that is syntactically valid but fails deeper parsing in `jobparser.Parse` (e.g. blank lines inside `run: |` blocks cause a SetJob round-trip error). Add `ValidateWorkflowContent` which runs the full `jobparser.Parse` to catch these cases, and use it in the file view, the actions workflow list, and the workflow detection loop so users see the error instead of silently getting a 500 or a dropped workflow. Fixes go-gitea#37115 Signed-off-by: Nicolas <[email protected]> Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Zettat123 <[email protected]> Co-authored-by: Giteabot <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent a2283a0 commit 8dc53a0

File tree

4 files changed

+36
-10
lines changed

4 files changed

+36
-10
lines changed

modules/actions/workflows.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,20 @@ func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) {
103103
if err != nil {
104104
return nil, err
105105
}
106+
if err := ValidateWorkflowContent(content); err != nil {
107+
return nil, err
108+
}
106109

107110
return events, nil
108111
}
109112

113+
// ValidateWorkflowContent catches structural errors (e.g. blank lines in run: | blocks)
114+
// that model.ReadWorkflow alone does not detect.
115+
func ValidateWorkflowContent(content []byte) error {
116+
_, err := jobparser.Parse(content)
117+
return err
118+
}
119+
110120
func DetectWorkflows(
111121
gitRepo *git.Repository,
112122
commit *git.Commit,

modules/actions/workflows_test.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,26 @@ import (
99
"code.gitea.io/gitea/modules/git"
1010
"code.gitea.io/gitea/modules/setting"
1111
api "code.gitea.io/gitea/modules/structs"
12+
"code.gitea.io/gitea/modules/test"
1213
webhook_module "code.gitea.io/gitea/modules/webhook"
1314

1415
"github.com/stretchr/testify/assert"
1516
)
1617

18+
func fullWorkflowContent(part string) []byte {
19+
return []byte(`
20+
name: test
21+
` + part + `
22+
jobs:
23+
test:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- run: echo hello
27+
`)
28+
}
29+
1730
func TestIsWorkflow(t *testing.T) {
18-
oldDirs := setting.Actions.WorkflowDirs
19-
defer func() {
20-
setting.Actions.WorkflowDirs = oldDirs
21-
}()
31+
defer test.MockVariableValue(&setting.Actions.WorkflowDirs)()
2232

2333
tests := []struct {
2434
name string
@@ -218,7 +228,7 @@ func TestDetectMatched(t *testing.T) {
218228

219229
for _, tc := range testCases {
220230
t.Run(tc.desc, func(t *testing.T) {
221-
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
231+
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
222232
assert.NoError(t, err)
223233
assert.Len(t, evts, 1)
224234
assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
@@ -373,7 +383,7 @@ func TestMatchIssuesEvent(t *testing.T) {
373383

374384
for _, tc := range testCases {
375385
t.Run(tc.desc, func(t *testing.T) {
376-
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
386+
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
377387
assert.NoError(t, err)
378388
assert.Len(t, evts, 1)
379389

routers/web/repo/actions/actions.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow
151151
workflows = append(workflows, workflow)
152152
continue
153153
}
154+
if err := actions.ValidateWorkflowContent(content); err != nil {
155+
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
156+
workflows = append(workflows, workflow)
157+
continue
158+
}
154159
workflow.Workflow = wf
155160
// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
156161
hasJobWithoutNeeds := false
@@ -315,6 +320,10 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) {
315320
if !job.Status.IsWaiting() {
316321
continue
317322
}
323+
if err := actions.ValidateWorkflowContent(job.WorkflowPayload); err != nil {
324+
runErrors[run.ID] = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
325+
break
326+
}
318327
hasOnlineRunner := false
319328
for _, runner := range runners {
320329
if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) {

routers/web/repo/view_file.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import (
2525
"code.gitea.io/gitea/modules/util"
2626
"code.gitea.io/gitea/services/context"
2727
issue_service "code.gitea.io/gitea/services/issue"
28-
29-
"github.com/nektos/act/pkg/model"
3028
)
3129

3230
func prepareLatestCommitInfo(ctx *context.Context) bool {
@@ -184,8 +182,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
184182
if err != nil {
185183
log.Error("actions.GetContentFromEntry: %v", err)
186184
}
187-
_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
188-
if workFlowErr != nil {
185+
if workFlowErr := actions.ValidateWorkflowContent(content); workFlowErr != nil {
189186
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
190187
}
191188
} else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) {

0 commit comments

Comments
 (0)