Skip to content

Commit a1844ed

Browse files
authored
Validate action definitions against the JSON schema (#171)
This fixes #155.
1 parent eaea421 commit a1844ed

File tree

8 files changed

+194
-0
lines changed

8 files changed

+194
-0
lines changed

.goreleaser.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ before:
66
hooks:
77
- go mod download
88
- go mod tidy
9+
- go generate ./schema
910
builds:
1011
-
1112
main: ./cmd/src/

cmd/src/actions_exec.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ import (
1818
"time"
1919

2020
"github.com/fatih/color"
21+
multierror "github.com/hashicorp/go-multierror"
2122
"github.com/mattn/go-isatty"
2223
"github.com/pkg/errors"
2324
"github.com/sourcegraph/go-diff/diff"
25+
"github.com/sourcegraph/src-cli/schema"
26+
"github.com/xeipuuv/gojsonschema"
2427
)
2528

2629
type Action struct {
@@ -179,6 +182,11 @@ Format of the action JSON files:
179182
return err
180183
}
181184

185+
err = validateActionDefinition(actionFile)
186+
if err != nil {
187+
return err
188+
}
189+
182190
var action Action
183191
if err := jsonxUnmarshal(string(actionFile), &action); err != nil {
184192
return errors.Wrap(err, "invalid JSON action file")
@@ -307,6 +315,46 @@ Format of the action JSON files:
307315
})
308316
}
309317

318+
func formatValidationErrs(es []error) string {
319+
points := make([]string, len(es))
320+
for i, err := range es {
321+
points[i] = fmt.Sprintf("- %s", err)
322+
}
323+
324+
return fmt.Sprintf(
325+
"Validating action definition failed:\n%s\n",
326+
strings.Join(points, "\n"))
327+
}
328+
329+
func validateActionDefinition(def []byte) error {
330+
sl := gojsonschema.NewSchemaLoader()
331+
sc, err := sl.Compile(gojsonschema.NewStringLoader(schema.ActionSchemaJSON))
332+
if err != nil {
333+
return errors.Wrapf(err, "failed to compile actions schema")
334+
}
335+
336+
normalized, err := jsonxToJSON(string(def))
337+
if err != nil {
338+
return err
339+
}
340+
341+
res, err := sc.Validate(gojsonschema.NewBytesLoader(normalized))
342+
if err != nil {
343+
return errors.Wrap(err, "failed to validate config against schema")
344+
}
345+
346+
errs := &multierror.Error{ErrorFormat: formatValidationErrs}
347+
for _, err := range res.Errors() {
348+
e := err.String()
349+
// Remove `(root): ` from error formatting since these errors are
350+
// presented to users.
351+
e = strings.TrimPrefix(e, "(root): ")
352+
errs = multierror.Append(errs, errors.New(e))
353+
}
354+
355+
return errs.ErrorOrNil()
356+
}
357+
310358
func validateAction(ctx context.Context, action Action) error {
311359
for _, step := range action.Steps {
312360
if step.Type == "docker" {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ require (
1818
github.com/pkg/errors v0.9.1
1919
github.com/segmentio/textio v1.2.0
2020
github.com/sourcegraph/go-diff v0.5.1
21+
github.com/sourcegraph/go-jsonschema v0.0.0-20191222043427-cdbee60427af // indirect
2122
github.com/sourcegraph/jsonx v0.0.0-20190114210550-ba8cb36a8614
2223
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
24+
github.com/xeipuuv/gojsonschema v1.2.0
2325
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa
2426
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
2527
jaytaylor.com/html2text v0.0.0-20190408195923-01ec452cbe43

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
22
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
45
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
56
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
@@ -16,6 +17,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
1617
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
1718
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
1819
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
20+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
21+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
22+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1923
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
2024
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
2125
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -35,8 +39,10 @@ github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn
3539
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
3640
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
3741
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
42+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3843
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
3944
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
45+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4046
github.com/segmentio/textio v1.2.0 h1:Ug4IkV3kh72juJbG8azoSBlgebIbUUxVNrfFcKHfTSQ=
4147
github.com/segmentio/textio v1.2.0/go.mod h1:+Rb7v0YVODP+tK5F7FD9TCkV7gOYx9IgLHWiqtvY8ag=
4248
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM=
@@ -45,10 +51,20 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo
4551
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
4652
github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs=
4753
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
54+
github.com/sourcegraph/go-jsonschema v0.0.0-20191222043427-cdbee60427af h1:VmLIjAWL5tLYoqW7l1Q/E3CLuqdLyBuSyQJYOOS01JA=
55+
github.com/sourcegraph/go-jsonschema v0.0.0-20191222043427-cdbee60427af/go.mod h1:SJwWIH9fe2RW2FouXEXM4Cm4ZczlewF2xNQAL2VaU1M=
4856
github.com/sourcegraph/jsonx v0.0.0-20190114210550-ba8cb36a8614 h1:MrlKMpoGse4bCneDoK/c+ZbPGqOP5Hme5ulatc8smbQ=
4957
github.com/sourcegraph/jsonx v0.0.0-20190114210550-ba8cb36a8614/go.mod h1:7jkSQ2sdxwXMaIDxKJotTt+hwKnT9b/wbJFU7/ObUEY=
5058
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
5159
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
60+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
61+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
62+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
63+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
64+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
65+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
66+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
67+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
5268
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
5369
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
5470
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

schema/action_stringdata.go

Lines changed: 71 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

actions.schema.json renamed to schema/actions.schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"steps": {
2020
"description": "A list of action steps to execute in each repository.",
2121
"type": "array",
22+
"minItems": 1,
2223
"items": {
2324
"type": "object",
2425
"required": ["type"],

schema/gen.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package schema
2+
3+
//go:generate env GO111MODULE=on go run stringdata.go -i actions.schema.json -name ActionSchemaJSON -pkg schema -o action_stringdata.go
4+
//go:generate gofmt -s -w action_stringdata.go

schema/stringdata.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// +build ignore
2+
3+
package main
4+
5+
import (
6+
"flag"
7+
"fmt"
8+
"io/ioutil"
9+
"os"
10+
"strings"
11+
)
12+
13+
var (
14+
inputFile = flag.String("i", "", "input file")
15+
outputFile = flag.String("o", "", "output file")
16+
constName = flag.String("name", "stringdata", "name of Go const")
17+
pkgName = flag.String("pkg", "main", "Go package name")
18+
)
19+
20+
func main() {
21+
flag.Parse()
22+
23+
if *inputFile == "" || *outputFile == "" {
24+
flag.Usage()
25+
os.Exit(1)
26+
}
27+
28+
data, err := ioutil.ReadFile(*inputFile)
29+
if err != nil {
30+
fmt.Fprintln(os.Stderr, err)
31+
os.Exit(1)
32+
}
33+
34+
output, err := os.Create(*outputFile)
35+
if err != nil {
36+
fmt.Fprintln(os.Stderr, err)
37+
os.Exit(1)
38+
}
39+
defer output.Close()
40+
fmt.Fprintln(output, "// Code generated by stringdata. DO NOT EDIT.")
41+
fmt.Fprintln(output)
42+
fmt.Fprintf(output, "package %s\n", *pkgName)
43+
fmt.Fprintln(output)
44+
fmt.Fprintf(output, "// %s is the content of the file %q.\n", *constName, *inputFile)
45+
fmt.Fprintf(output, "const %s = %s", *constName, backtickStringLiteral(string(data)))
46+
fmt.Fprintln(output)
47+
}
48+
49+
func backtickStringLiteral(data string) string {
50+
return "`" + strings.Replace(data, "`", "` + \"`\" + `", -1) + "`"
51+
}

0 commit comments

Comments
 (0)