Skip to content

Commit 2593420

Browse files
authored
Support Parallel Workflow Steps + More Lenient Parsing (#445)
* making parser more lenient so we don';t lose data when parsing * flattening parallel steps so we can run our existing rego rules without change
1 parent a41f05a commit 2593420

3 files changed

Lines changed: 322 additions & 51 deletions

File tree

models/github_actions.go

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ type GithubActionsStep struct {
114114
Line int `json:"line" yaml:"-"`
115115
Action string `json:"action,omitempty" yaml:"-"`
116116

117+
// Set when the step comes from a flattened `parallel:` block (steps that
118+
// run concurrently). No order index: parallel steps have no defined order.
119+
Parallel bool `json:"parallel,omitempty" yaml:"-"`
120+
117121
Lines map[string]int `json:"lines" yaml:"-"`
118122
}
119123

@@ -216,7 +220,7 @@ func (o GithubActionsMetadata) IsValid() bool {
216220

217221
func (o *GithubActionsJobs) UnmarshalYAML(node *yaml.Node) error {
218222
if node.Kind != yaml.MappingNode {
219-
return fmt.Errorf("invalid yaml node type for jobs")
223+
return nil
220224
}
221225

222226
*o = make(GithubActionsJobs, 0, len(node.Content)/2)
@@ -233,7 +237,8 @@ func (o *GithubActionsJobs) UnmarshalYAML(node *yaml.Node) error {
233237
}
234238
err := value.Decode(&job)
235239
if err != nil {
236-
return err
240+
// Skip the offending job, keep the rest of the workflow.
241+
continue
237242
}
238243

239244
for j := 0; j < len(value.Content); j += 2 {
@@ -261,7 +266,7 @@ func (o *GithubActionsJobSecrets) UnmarshalYAML(node *yaml.Node) error {
261266
}
262267

263268
if node.Kind != yaml.MappingNode {
264-
return fmt.Errorf("invalid yaml node type for secrets")
269+
return nil
265270
}
266271

267272
for i := 0; i < len(node.Content); i += 2 {
@@ -280,13 +285,15 @@ func (o *StringList) UnmarshalYAML(node *yaml.Node) error {
280285
}
281286

282287
if node.Kind != yaml.SequenceNode {
283-
return fmt.Errorf("invalid yaml node type %v for string list", node.Kind)
288+
// Lenient: unexpected shapes leave an empty list rather than failing
289+
// the entire workflow parse.
290+
return nil
284291
}
285292

286-
var l []string = make([]string, len(node.Content))
293+
l := make([]string, len(node.Content))
287294
err := node.Decode(&l)
288295
if err != nil {
289-
return err
296+
return nil
290297
}
291298

292299
*o = l
@@ -319,20 +326,23 @@ func (o *GithubActionsEvents) UnmarshalYAML(node *yaml.Node) error {
319326

320327
err := value.Decode(&crons)
321328
if err != nil {
322-
return err
329+
// Skip a malformed schedule block, keep other events.
330+
continue
323331
}
324332

325333
for _, c := range crons {
326334
if c.Cron == "" {
327-
return fmt.Errorf("invalid cron object")
335+
// Skip empty/invalid cron entries individually.
336+
continue
328337
}
329338

330339
event.Cron = append(event.Cron, c.Cron)
331340
}
332341
} else {
333342
err := value.Decode(&event)
334343
if err != nil {
335-
return err
344+
// Skip the offending event, keep the rest.
345+
continue
336346
}
337347
}
338348

@@ -345,7 +355,7 @@ func (o *GithubActionsEvents) UnmarshalYAML(node *yaml.Node) error {
345355

346356
func (o *GithubActionsOutputs) UnmarshalYAML(node *yaml.Node) error {
347357
if node.Kind != yaml.MappingNode {
348-
return fmt.Errorf("invalid yaml node type for outputs")
358+
return nil
349359
}
350360

351361
for i := 0; i < len(node.Content); i += 2 {
@@ -360,7 +370,8 @@ func (o *GithubActionsOutputs) UnmarshalYAML(node *yaml.Node) error {
360370
output = GithubActionsOutput{Name: name}
361371
err := value.Decode(&output)
362372
if err != nil {
363-
return err
373+
// Skip the offending output, keep the rest.
374+
continue
364375
}
365376
*o = append(*o, output)
366377
}
@@ -372,7 +383,7 @@ func (o *GithubActionsOutputs) UnmarshalYAML(node *yaml.Node) error {
372383

373384
func (o *GithubActionsInputs) UnmarshalYAML(node *yaml.Node) error {
374385
if node.Kind != yaml.MappingNode {
375-
return fmt.Errorf("invalid yaml node type for inputs")
386+
return nil
376387
}
377388

378389
for i := 0; i < len(node.Content); i += 2 {
@@ -382,7 +393,8 @@ func (o *GithubActionsInputs) UnmarshalYAML(node *yaml.Node) error {
382393
err := value.Decode(&input)
383394

384395
if err != nil {
385-
return err
396+
// Skip the offending input, keep the rest.
397+
continue
386398
}
387399

388400
*o = append(*o, input)
@@ -400,7 +412,7 @@ func (o *GithubActionsEnvs) UnmarshalYAML(node *yaml.Node) error {
400412
}
401413

402414
if node.Kind != yaml.MappingNode {
403-
return fmt.Errorf("invalid yaml node type for env")
415+
return nil
404416
}
405417

406418
for i := 0; i < len(node.Content); i += 2 {
@@ -412,6 +424,48 @@ func (o *GithubActionsEnvs) UnmarshalYAML(node *yaml.Node) error {
412424
return nil
413425
}
414426

427+
func (o *GithubActionsSteps) UnmarshalYAML(node *yaml.Node) error {
428+
// Be lenient: a single malformed step should only drop that step, not the
429+
// whole job (and therefore not the whole workflow file).
430+
if node.Kind != yaml.SequenceNode {
431+
return nil
432+
}
433+
434+
for _, item := range node.Content {
435+
// flatten parallel steps
436+
if p := mappingValue(item, "parallel"); p != nil {
437+
var nested GithubActionsSteps
438+
_ = p.Decode(&nested) // recurses; handles nested parallel
439+
for k := range nested {
440+
nested[k].Parallel = true
441+
}
442+
*o = append(*o, nested...)
443+
continue
444+
}
445+
446+
var step GithubActionsStep
447+
if err := item.Decode(&step); err != nil {
448+
continue
449+
}
450+
*o = append(*o, step)
451+
}
452+
453+
return nil
454+
}
455+
456+
// mappingValue returns the value node for key in a mapping node, or nil.
457+
func mappingValue(node *yaml.Node, key string) *yaml.Node {
458+
if node.Kind != yaml.MappingNode {
459+
return nil
460+
}
461+
for i := 0; i+1 < len(node.Content); i += 2 {
462+
if node.Content[i].Value == key {
463+
return node.Content[i+1]
464+
}
465+
}
466+
return nil
467+
}
468+
415469
func (o *GithubActionsStep) UnmarshalYAML(node *yaml.Node) error {
416470
type Alias GithubActionsStep
417471
t := Alias{
@@ -467,7 +521,9 @@ func (o *GithubActionsPermissions) UnmarshalYAML(node *yaml.Node) error {
467521
case "read-all":
468522
permission = PermissionRead
469523
default:
470-
return fmt.Errorf("invalid permission %s", node.Value)
524+
// Unknown scalar (e.g. a typo): leave permissions empty rather
525+
// than failing the whole workflow parse.
526+
return nil
471527
}
472528

473529
*o = make(GithubActionsPermissions, 0, len(AllScopes))
@@ -478,7 +534,7 @@ func (o *GithubActionsPermissions) UnmarshalYAML(node *yaml.Node) error {
478534
}
479535

480536
if node.Kind != yaml.MappingNode {
481-
return fmt.Errorf("invalid yaml node type for permissions")
537+
return nil
482538
}
483539

484540
*o = make(GithubActionsPermissions, 0, len(node.Content)/2)
@@ -497,7 +553,7 @@ func (o *GithubActionsJobRunsOn) UnmarshalYAML(node *yaml.Node) error {
497553
var runsOn StringList
498554
err := node.Decode(&runsOn)
499555
if err != nil {
500-
return err
556+
return nil
501557
}
502558
*o = GithubActionsJobRunsOn(runsOn)
503559
}
@@ -510,18 +566,19 @@ func (o *GithubActionsJobRunsOn) UnmarshalYAML(node *yaml.Node) error {
510566
var runsOn RunsOn
511567
err := node.Decode(&runsOn)
512568
if err != nil {
513-
return err
569+
return nil
514570
}
515571
for _, group := range runsOn.Group {
516572
if group == "" {
517-
return fmt.Errorf("unexpected empty group")
573+
// Skip empty entries individually instead of failing.
574+
continue
518575
}
519576
*o = append(*o, fmt.Sprintf("group:%s", group))
520577
}
521578

522579
for _, label := range runsOn.Labels {
523580
if label == "" {
524-
return fmt.Errorf("unexpected empty label")
581+
continue
525582
}
526583
*o = append(*o, fmt.Sprintf("label:%s", label))
527584
}
@@ -540,7 +597,8 @@ func (o *GithubActionsJobContainer) UnmarshalYAML(node *yaml.Node) error {
540597
var c container
541598
err := node.Decode(&c)
542599
if err != nil {
543-
return err
600+
// Lenient: leave the container empty rather than failing the parse.
601+
return nil
544602
}
545603
*o = GithubActionsJobContainer(c)
546604
return nil
@@ -553,13 +611,13 @@ func (o *GithubActionsJobEnvironments) UnmarshalYAML(node *yaml.Node) error {
553611
}
554612

555613
if node.Kind != yaml.MappingNode {
556-
return fmt.Errorf("invalid yaml node type for environment")
614+
return nil
557615
}
558616

559617
var env GithubActionsJobEnvironment
560618
err := node.Decode(&env)
561619
if err != nil {
562-
return err
620+
return nil
563621
}
564622

565623
*o = GithubActionsJobEnvironments{env}

0 commit comments

Comments
 (0)