Skip to content

Commit 052999e

Browse files
authored
Fix Yaml Parse Errors (#414)
1 parent ea261b1 commit 052999e

6 files changed

Lines changed: 114 additions & 27 deletions

File tree

models/azure_pipelines.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,18 @@ func (v *AzurePipelineVariables) UnmarshalYAML(value *yaml.Node) error {
147147
return nil
148148
}
149149

150-
var listFormat []AzurePipelineVariable
151-
if err := value.Decode(&listFormat); err == nil {
152-
for _, variable := range listFormat {
153-
v.Map[variable.Name] = variable.Value
150+
// Variables lists can contain name/value pairs, group references,
151+
// and template references. Decode each item individually and
152+
// skip non-variable entries (group, template).
153+
if value.Kind == yaml.SequenceNode {
154+
for _, item := range value.Content {
155+
var variable AzurePipelineVariable
156+
if err := item.Decode(&variable); err == nil && variable.Name != "" {
157+
v.Map[variable.Name] = variable.Value
158+
}
154159
}
155160
return nil
156161
}
157162

158-
return fmt.Errorf("variables must be either a map or a list of objects")
163+
return nil
159164
}

models/azure_pipelines_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,21 @@ func TestAzurePipeline(t *testing.T) {
5050
}},
5151
},
5252
},
53+
{
54+
input: "variables:\n - group: my-variable-group\n - name: foo\n value: bar\n - template: vars.yml",
55+
expected: AzurePipeline{
56+
Stages: []AzureStage{
57+
{
58+
Jobs: []AzureJob{
59+
{},
60+
},
61+
},
62+
},
63+
Variables: AzurePipelineVariables{map[string]string{
64+
"foo": "bar",
65+
}},
66+
},
67+
},
5368
{
5469
input: `stages: [{stage: build, jobs: [{job: test, steps: [bash: asdf]}]}]`,
5570
expected: AzurePipeline{

models/github_actions.go

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package models
22

33
import (
44
"encoding/json"
5-
"errors"
65
"fmt"
7-
"gopkg.in/yaml.v3"
86
"strings"
7+
8+
"gopkg.in/yaml.v3"
99
)
1010

1111
const (
@@ -64,11 +64,28 @@ type GithubActionsWith = GithubActionsEnvs
6464
type GithubActionsJobRunsOn StringList
6565
type StringList []string
6666

67+
// StringBool accepts both YAML booleans and quoted strings like "false".
68+
type StringBool bool
69+
70+
func (b *StringBool) UnmarshalYAML(node *yaml.Node) error {
71+
var boolVal bool
72+
if err := node.Decode(&boolVal); err == nil {
73+
*b = StringBool(boolVal)
74+
return nil
75+
}
76+
var strVal string
77+
if err := node.Decode(&strVal); err != nil {
78+
return fmt.Errorf("error parsing string value for string bool: %s", node.Value)
79+
}
80+
*b = StringBool(strVal == "true")
81+
return nil
82+
}
83+
6784
type GithubActionsInput struct {
68-
Name string `json:"name"`
69-
Description string `json:"description,omitempty"`
70-
Required bool `json:"required"`
71-
Type string `json:"type"`
85+
Name string `json:"name"`
86+
Description string `json:"description,omitempty"`
87+
Required StringBool `json:"required"`
88+
Type string `json:"type"`
7289
}
7390

7491
type GithubActionsOutput struct {
@@ -553,27 +570,34 @@ type GithubActionsStrategy struct {
553570
Matrix map[string]StringList `json:"matrix,omitempty" yaml:"matrix"`
554571
}
555572

556-
// UnmarshalYAML parses the `strategy` block and extracts `matrix`
573+
// UnmarshalYAML parses the `strategy` block and extracts `matrix` dimensions.
574+
// It is intentionally lenient: unsupported constructs (expressions, include/exclude,
575+
// scalar dimensions, nested sequences) are silently skipped so that the rest of
576+
// the workflow still parses successfully.
557577
func (o *GithubActionsStrategy) UnmarshalYAML(node *yaml.Node) error {
558578
if node.Kind != yaml.MappingNode {
559-
return errors.New("invalid yaml node type for strategy")
579+
return nil
560580
}
561581
for i := 0; i < len(node.Content); i += 2 {
562582
key := node.Content[i].Value
563583
value := node.Content[i+1]
564584
if key != "matrix" {
565585
continue
566586
}
587+
// matrix may be an expression like ${{ fromJSON(...) }}
567588
if value.Kind != yaml.MappingNode {
568-
return errors.New("matrix must be a mapping")
589+
continue
569590
}
570591
m := make(map[string]StringList, len(value.Content)/2)
571-
// walk each matrix dimension
572592
for j := 0; j < len(value.Content); j += 2 {
573593
dim := value.Content[j].Value
574594
listNode := value.Content[j+1]
595+
// skip include/exclude (special GHA keys) and non-sequence dimensions
596+
if dim == "include" || dim == "exclude" {
597+
continue
598+
}
575599
if listNode.Kind != yaml.SequenceNode {
576-
return fmt.Errorf("matrix.%s must be a sequence", dim)
600+
continue
577601
}
578602
var items StringList
579603
for _, item := range listNode.Content {
@@ -583,15 +607,15 @@ func (o *GithubActionsStrategy) UnmarshalYAML(node *yaml.Node) error {
583607
case yaml.MappingNode:
584608
var obj map[string]interface{}
585609
if err := item.Decode(&obj); err != nil {
586-
return fmt.Errorf("failed to decode matrix item: %w", err)
610+
continue
587611
}
588612
b, err := json.Marshal(obj)
589613
if err != nil {
590-
return fmt.Errorf("failed to marshal matrix item: %w", err)
614+
continue
591615
}
592616
items = append(items, string(b))
593617
default:
594-
return fmt.Errorf("unsupported node kind %v in matrix.%s", item.Kind, dim)
618+
continue
595619
}
596620
}
597621
m[dim] = items

models/github_actions_test.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,50 @@ func TestGithubActionsWorkflowJobs(t *testing.T) {
234234
},
235235
},
236236
},
237+
{
238+
Name: "matrix with expression value",
239+
Input: `example_matrix: { strategy: { matrix: "${{ fromJSON(needs.config.outputs.matrix) }}" } }`,
240+
Expected: GithubActionsJob{
241+
ID: "example_matrix",
242+
},
243+
},
244+
{
245+
Name: "matrix with scalar dimension skipped",
246+
Input: `example_matrix: { strategy: { matrix: { rust: stable, os: [ubuntu-latest, macos-latest] } } }`,
247+
Expected: GithubActionsJob{
248+
ID: "example_matrix",
249+
Strategy: GithubActionsStrategy{
250+
Matrix: map[string]StringList{
251+
"os": {"ubuntu-latest", "macos-latest"},
252+
},
253+
},
254+
},
255+
},
256+
{
257+
Name: "matrix with include and exclude skipped",
258+
Input: `example_matrix: { strategy: { matrix: { os: [ubuntu-latest], include: [{ os: windows-latest, experimental: true }], exclude: [{ os: macos-latest }] } } }`,
259+
Expected: GithubActionsJob{
260+
ID: "example_matrix",
261+
Strategy: GithubActionsStrategy{
262+
Matrix: map[string]StringList{
263+
"os": {"ubuntu-latest"},
264+
},
265+
},
266+
},
267+
},
268+
{
269+
Name: "matrix with nested sequences skipped",
270+
Input: "example_matrix:\n strategy:\n matrix:\n target:\n - [a, b]\n - [c, d]\n os: [ubuntu-latest]",
271+
Expected: GithubActionsJob{
272+
ID: "example_matrix",
273+
Strategy: GithubActionsStrategy{
274+
Matrix: map[string]StringList{
275+
"target": nil,
276+
"os": {"ubuntu-latest"},
277+
},
278+
},
279+
},
280+
},
237281
}
238282

239283
for _, tt := range tests {
@@ -489,12 +533,12 @@ jobs:
489533

490534
assert.Equal(t, "workflow_call", workflow.Events[1].Name)
491535
assert.Equal(t, "string", workflow.Events[1].Inputs[0].Type)
492-
assert.Equal(t, true, workflow.Events[1].Inputs[0].Required)
536+
assert.Equal(t, StringBool(true), workflow.Events[1].Inputs[0].Required)
493537
assert.Equal(t, "build", workflow.Events[1].Outputs[0].Name)
494538
assert.Equal(t, "build_id", workflow.Events[1].Outputs[0].Description)
495539
assert.Equal(t, "${{ jobs.build.outputs.build }}", workflow.Events[1].Outputs[0].Value)
496540
assert.Equal(t, "BOARD_TOKEN", workflow.Events[1].Secrets[0].Name)
497-
assert.Equal(t, true, workflow.Events[1].Secrets[0].Required)
541+
assert.Equal(t, StringBool(true), workflow.Events[1].Secrets[0].Required)
498542

499543
assert.Equal(t, "schedule", workflow.Events[2].Name)
500544
assert.Equal(t, "0 0 * * 0", workflow.Events[2].Cron[0])
@@ -583,7 +627,7 @@ runs:
583627
assert.Equal(t, "John Doe", actionMetadata.Author)
584628
assert.Equal(t, "Analyze git sha", actionMetadata.Description)
585629
assert.Equal(t, "git_sha", actionMetadata.Inputs[0].Name)
586-
assert.Equal(t, true, actionMetadata.Inputs[0].Required)
630+
assert.Equal(t, StringBool(true), actionMetadata.Inputs[0].Required)
587631
assert.Equal(t, "string", actionMetadata.Inputs[0].Type)
588632
assert.Equal(t, "response", actionMetadata.Outputs[0].Name)
589633
assert.Equal(t, "Response from the command executed", actionMetadata.Outputs[0].Description)

models/gitlab.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,8 @@ func (o *GitlabciStringRef) UnmarshalYAML(node *yaml.Node) error {
395395
if node.Tag == "!reference" {
396396
val, _ := yaml.Marshal(node)
397397
*o = GitlabciStringRef(val)
398-
} else {
399-
return fmt.Errorf("unexpected string or reference")
400398
}
399+
// skip non-reference sequences
401400
case yaml.ScalarNode:
402401
var s string
403402
if err := node.Decode(&s); err != nil {

scanner/parsers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package scanner
22

33
import (
4-
"fmt"
54
"os"
65
"path/filepath"
76
"regexp"
@@ -18,7 +17,7 @@ type GithubActionsMetadataParser struct {
1817

1918
func NewGithubActionsMetadataParser() *GithubActionsMetadataParser {
2019
return &GithubActionsMetadataParser{
21-
pattern: regexp.MustCompile(`(\b|/)action\.ya?ml$`),
20+
pattern: regexp.MustCompile(`(^|/)action\.ya?ml$`),
2221
}
2322
}
2423

@@ -214,7 +213,8 @@ func (p *GitlabCiParser) Parse(filePath string, scanningPath string, pkgInsights
214213
func (p *GitlabCiParser) ParseFromMemory(data []byte, filePath string, pkgInsights *models.PackageInsights) error {
215214
config, err := models.ParseGitlabciConfig(data)
216215
if err != nil {
217-
return fmt.Errorf("failed to parse gitlabci config: %w", err)
216+
log.Debug().Err(err).Str("file", filePath).Msg("failed to parse gitlabci config")
217+
return nil
218218
}
219219
config.Path = filePath
220220

0 commit comments

Comments
 (0)