Skip to content

Commit 4143017

Browse files
fix: schema validation for job if functions (#2446)
* fix: schema validation for job if functions * Add Tests * Update pkg/schema/schema.go Co-authored-by: Josh Soref <[email protected]> * Update pkg/schema/schema.go --------- Co-authored-by: Josh Soref <[email protected]>
1 parent d8b6f61 commit 4143017

File tree

2 files changed

+117
-7
lines changed

2 files changed

+117
-7
lines changed

pkg/schema/schema.go

+25-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"math"
89
"regexp"
10+
"strconv"
911
"strings"
1012

1113
"github.com/rhysd/actionlint"
@@ -18,6 +20,8 @@ var workflowSchema string
1820
//go:embed action_schema.json
1921
var actionSchema string
2022

23+
var functions = regexp.MustCompile(`^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$`)
24+
2125
type Schema struct {
2226
Definitions map[string]Definition
2327
}
@@ -138,10 +142,10 @@ func (s *Node) checkSingleExpression(exprNode actionlint.ExprNode) error {
138142
for _, v := range *funcs {
139143
if strings.EqualFold(funcCallNode.Callee, v.name) {
140144
if v.min > len(funcCallNode.Args) {
141-
err = errors.Join(err, fmt.Errorf("Missing parameters for %s expected > %v got %v", funcCallNode.Callee, v.min, len(funcCallNode.Args)))
145+
err = errors.Join(err, fmt.Errorf("Missing parameters for %s expected >= %v got %v", funcCallNode.Callee, v.min, len(funcCallNode.Args)))
142146
}
143147
if v.max < len(funcCallNode.Args) {
144-
err = errors.Join(err, fmt.Errorf("To many parameters for %s expected < %v got %v", funcCallNode.Callee, v.max, len(funcCallNode.Args)))
148+
err = errors.Join(err, fmt.Errorf("Too many parameters for %s expected <= %v got %v", funcCallNode.Callee, v.max, len(funcCallNode.Args)))
145149
}
146150
return
147151
}
@@ -174,11 +178,22 @@ func (s *Node) GetFunctions() *[]FunctionInfo {
174178
if i == -1 {
175179
continue
176180
}
177-
fun := FunctionInfo{
178-
name: v[:i],
179-
}
180-
if n, err := fmt.Sscanf(v[i:], "(%d,%d)", &fun.min, &fun.max); n == 2 && err == nil {
181-
*funcs = append(*funcs, fun)
181+
smatch := functions.FindStringSubmatch(v)
182+
if len(smatch) > 0 {
183+
functionName := smatch[1]
184+
minParameters, _ := strconv.ParseInt(smatch[2], 10, 32)
185+
maxParametersRaw := smatch[3]
186+
var maxParameters int64
187+
if strings.EqualFold(maxParametersRaw, "MAX") {
188+
maxParameters = math.MaxInt32
189+
} else {
190+
maxParameters, _ = strconv.ParseInt(maxParametersRaw, 10, 32)
191+
}
192+
*funcs = append(*funcs, FunctionInfo{
193+
name: functionName,
194+
min: int(minParameters),
195+
max: int(maxParameters),
196+
})
182197
}
183198
}
184199
return funcs
@@ -220,6 +235,9 @@ func AddFunction(funcs *[]FunctionInfo, s string, i1, i2 int) {
220235
}
221236

222237
func (s *Node) UnmarshalYAML(node *yaml.Node) error {
238+
if node != nil && node.Kind == yaml.DocumentNode {
239+
return s.UnmarshalYAML(node.Content[0])
240+
}
223241
def := s.Schema.GetDefinition(s.Definition)
224242
if s.Context == nil {
225243
s.Context = def.Context

pkg/schema/schema_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package schema
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"gopkg.in/yaml.v3"
8+
)
9+
10+
func TestAdditionalFunctions(t *testing.T) {
11+
var node yaml.Node
12+
err := yaml.Unmarshal([]byte(`
13+
on: push
14+
jobs:
15+
job-with-condition:
16+
runs-on: self-hosted
17+
if: success() || success('joba', 'jobb') || failure() || failure('joba', 'jobb') || always() || cancelled()
18+
steps:
19+
- run: exit 0
20+
`), &node)
21+
if !assert.NoError(t, err) {
22+
return
23+
}
24+
err = (&Node{
25+
Definition: "workflow-root-strict",
26+
Schema: GetWorkflowSchema(),
27+
}).UnmarshalYAML(&node)
28+
assert.NoError(t, err)
29+
}
30+
31+
func TestAdditionalFunctionsFailure(t *testing.T) {
32+
var node yaml.Node
33+
err := yaml.Unmarshal([]byte(`
34+
on: push
35+
jobs:
36+
job-with-condition:
37+
runs-on: self-hosted
38+
if: success() || success('joba', 'jobb') || failure() || failure('joba', 'jobb') || always('error')
39+
steps:
40+
- run: exit 0
41+
`), &node)
42+
if !assert.NoError(t, err) {
43+
return
44+
}
45+
err = (&Node{
46+
Definition: "workflow-root-strict",
47+
Schema: GetWorkflowSchema(),
48+
}).UnmarshalYAML(&node)
49+
assert.Error(t, err)
50+
}
51+
52+
func TestAdditionalFunctionsSteps(t *testing.T) {
53+
var node yaml.Node
54+
err := yaml.Unmarshal([]byte(`
55+
on: push
56+
jobs:
57+
job-with-condition:
58+
runs-on: self-hosted
59+
steps:
60+
- run: exit 0
61+
if: success() || failure() || always()
62+
`), &node)
63+
if !assert.NoError(t, err) {
64+
return
65+
}
66+
err = (&Node{
67+
Definition: "workflow-root-strict",
68+
Schema: GetWorkflowSchema(),
69+
}).UnmarshalYAML(&node)
70+
assert.NoError(t, err)
71+
}
72+
73+
func TestAdditionalFunctionsStepsExprSyntax(t *testing.T) {
74+
var node yaml.Node
75+
err := yaml.Unmarshal([]byte(`
76+
on: push
77+
jobs:
78+
job-with-condition:
79+
runs-on: self-hosted
80+
steps:
81+
- run: exit 0
82+
if: ${{ success() || failure() || always() }}
83+
`), &node)
84+
if !assert.NoError(t, err) {
85+
return
86+
}
87+
err = (&Node{
88+
Definition: "workflow-root-strict",
89+
Schema: GetWorkflowSchema(),
90+
}).UnmarshalYAML(&node)
91+
assert.NoError(t, err)
92+
}

0 commit comments

Comments
 (0)