Backport #37116 by @bircni `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 #37115 Signed-off-by: Nicolas <bircni@icloud.com> Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Zettat123 <zettat123@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -103,10 +103,20 @@ func GetEventsFromContent(content []byte) ([]*jobparser.Event, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := ValidateWorkflowContent(content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return events, nil
|
return events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateWorkflowContent catches structural errors (e.g. blank lines in run: | blocks)
|
||||||
|
// that model.ReadWorkflow alone does not detect.
|
||||||
|
func ValidateWorkflowContent(content []byte) error {
|
||||||
|
_, err := jobparser.Parse(content)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func DetectWorkflows(
|
func DetectWorkflows(
|
||||||
gitRepo *git.Repository,
|
gitRepo *git.Repository,
|
||||||
commit *git.Commit,
|
commit *git.Commit,
|
||||||
|
|||||||
@@ -9,16 +9,26 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func fullWorkflowContent(part string) []byte {
|
||||||
|
return []byte(`
|
||||||
|
name: test
|
||||||
|
` + part + `
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo hello
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsWorkflow(t *testing.T) {
|
func TestIsWorkflow(t *testing.T) {
|
||||||
oldDirs := setting.Actions.WorkflowDirs
|
defer test.MockVariableValue(&setting.Actions.WorkflowDirs)()
|
||||||
defer func() {
|
|
||||||
setting.Actions.WorkflowDirs = oldDirs
|
|
||||||
}()
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -218,7 +228,7 @@ func TestDetectMatched(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
|
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, evts, 1)
|
assert.Len(t, evts, 1)
|
||||||
assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
|
assert.Equal(t, tc.expected, detectMatched(nil, tc.commit, tc.triggedEvent, tc.payload, evts[0]))
|
||||||
@@ -373,7 +383,7 @@ func TestMatchIssuesEvent(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
evts, err := GetEventsFromContent([]byte(tc.yamlOn))
|
evts, err := GetEventsFromContent(fullWorkflowContent(tc.yamlOn))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, evts, 1)
|
assert.Len(t, evts, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -151,6 +151,11 @@ func prepareWorkflowTemplate(ctx *context.Context, commit *git.Commit) (workflow
|
|||||||
workflows = append(workflows, workflow)
|
workflows = append(workflows, workflow)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if err := actions.ValidateWorkflowContent(content); err != nil {
|
||||||
|
workflow.ErrMsg = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
|
||||||
|
workflows = append(workflows, workflow)
|
||||||
|
continue
|
||||||
|
}
|
||||||
workflow.Workflow = wf
|
workflow.Workflow = wf
|
||||||
// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
|
// The workflow must contain at least one job without "needs". Otherwise, a deadlock will occur and no jobs will be able to run.
|
||||||
hasJobWithoutNeeds := false
|
hasJobWithoutNeeds := false
|
||||||
@@ -315,6 +320,10 @@ func prepareWorkflowList(ctx *context.Context, workflows []WorkflowInfo) {
|
|||||||
if !job.Status.IsWaiting() {
|
if !job.Status.IsWaiting() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if err := actions.ValidateWorkflowContent(job.WorkflowPayload); err != nil {
|
||||||
|
runErrors[run.ID] = ctx.Locale.TrString("actions.runs.invalid_workflow_helper", err.Error())
|
||||||
|
break
|
||||||
|
}
|
||||||
hasOnlineRunner := false
|
hasOnlineRunner := false
|
||||||
for _, runner := range runners {
|
for _, runner := range runners {
|
||||||
if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) {
|
if !runner.IsDisabled && runner.CanMatchLabels(job.RunsOn) {
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
|
|
||||||
"github.com/nektos/act/pkg/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepareLatestCommitInfo(ctx *context.Context) bool {
|
func prepareLatestCommitInfo(ctx *context.Context) bool {
|
||||||
@@ -184,8 +182,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("actions.GetContentFromEntry: %v", err)
|
log.Error("actions.GetContentFromEntry: %v", err)
|
||||||
}
|
}
|
||||||
_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
|
if workFlowErr := actions.ValidateWorkflowContent(content); workFlowErr != nil {
|
||||||
if workFlowErr != nil {
|
|
||||||
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
|
||||||
}
|
}
|
||||||
} else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) {
|
} else if issue_service.IsCodeOwnerFile(ctx.Repo.TreePath) {
|
||||||
|
|||||||
Reference in New Issue
Block a user