Skip to content

Commit 0be6a81

Browse files
committed
flattening parallel steps so we can run our existing rego rules without change
1 parent 7fb15a6 commit 0be6a81

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

models/github_actions.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,14 @@ func (o *GithubActionsSteps) UnmarshalYAML(node *yaml.Node) error {
428428
}
429429

430430
for _, item := range node.Content {
431+
// flatten parallel steps
432+
if p := mappingValue(item, "parallel"); p != nil {
433+
var nested GithubActionsSteps
434+
_ = p.Decode(&nested) // recurses; lenient, handles nested parallel
435+
*o = append(*o, nested...)
436+
continue
437+
}
438+
431439
var step GithubActionsStep
432440
if err := item.Decode(&step); err != nil {
433441
continue
@@ -438,6 +446,19 @@ func (o *GithubActionsSteps) UnmarshalYAML(node *yaml.Node) error {
438446
return nil
439447
}
440448

449+
// mappingValue returns the value node for key in a mapping node, or nil.
450+
func mappingValue(node *yaml.Node, key string) *yaml.Node {
451+
if node.Kind != yaml.MappingNode {
452+
return nil
453+
}
454+
for i := 0; i+1 < len(node.Content); i += 2 {
455+
if node.Content[i].Value == key {
456+
return node.Content[i+1]
457+
}
458+
}
459+
return nil
460+
}
461+
441462
func (o *GithubActionsStep) UnmarshalYAML(node *yaml.Node) error {
442463
type Alias GithubActionsStep
443464
t := Alias{

models/github_actions_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,54 @@ jobs:
711711
assert.Equal(t, "make build", good.Steps[1].Run)
712712
}
713713

714+
// TestGithubActionsParallelStepsFlattened verifies that `parallel:` step blocks
715+
// are flattened inline so their nested run/uses sinks remain visible to rules.
716+
func TestGithubActionsParallelStepsFlattened(t *testing.T) {
717+
input := `build:
718+
runs-on: ubuntu-latest
719+
steps:
720+
- uses: actions/checkout@v6
721+
- parallel:
722+
- name: Build frontend
723+
run: npm run build:frontend
724+
- name: Build backend
725+
run: npm run build:backend
726+
- name: Run tests
727+
run: npm test
728+
`
729+
var jobs GithubActionsJobs
730+
require.NoError(t, yaml.Unmarshal([]byte(input), &jobs))
731+
require.Len(t, jobs, 1)
732+
733+
steps := jobs[0].Steps
734+
require.Len(t, steps, 4)
735+
assert.Equal(t, "actions/checkout@v6", steps[0].Uses)
736+
assert.Equal(t, "npm run build:frontend", steps[1].Run)
737+
assert.Equal(t, "npm run build:backend", steps[2].Run)
738+
assert.Equal(t, "npm test", steps[3].Run)
739+
// Each flattened child keeps its own line number.
740+
assert.Equal(t, 6, steps[1].Line)
741+
assert.Equal(t, 8, steps[2].Line)
742+
743+
t.Run("nested parallel", func(t *testing.T) {
744+
nested := `build:
745+
steps:
746+
- parallel:
747+
- parallel:
748+
- run: a
749+
- run: b
750+
- run: c
751+
`
752+
var jobs GithubActionsJobs
753+
require.NoError(t, yaml.Unmarshal([]byte(nested), &jobs))
754+
runs := make([]string, 0, len(jobs[0].Steps))
755+
for _, s := range jobs[0].Steps {
756+
runs = append(runs, s.Run)
757+
}
758+
assert.Equal(t, []string{"a", "b", "c"}, runs)
759+
})
760+
}
761+
714762
func TestGithubActionMetadata(t *testing.T) {
715763
var actionMetadata GithubActionsMetadata
716764
subject := `name: "My GitHub Action"

0 commit comments

Comments
 (0)