Skip to content

Commit c24d84d

Browse files
authored
Merge pull request #2864 from balopat/check_ast_change
Fail PR if it has a structural schema change in a released version
2 parents 73409fb + 51cabd4 commit c24d84d

File tree

8 files changed

+715
-1
lines changed

8 files changed

+715
-1
lines changed

hack/check-schema-changes.sh

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright 2019 The Skaffold Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
18+
# This check checks whether the PR compared to master contains any changes
19+
# in the config.go files under pkg/skaffold/schema. If yes, it checks if those changes
20+
# are structural changes or not.
21+
# If they are structural changes and the package is not "latest",
22+
# then we'll fail the PR as we assume anything else than latest is already released and
23+
# shouldn't be changed.
24+
# If the change is latest and it is released, we fail the PR for the same reason.
25+
# If the change is in latest and it is not released yet, it is fine to make changes.
26+
27+
28+
29+
set +x
30+
31+
go run hack/versions/cmd/schema_check/check.go

hack/checks.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ RESET='\033[0m'
2020

2121
echo "Running validation scripts..."
2222
scripts=(
23+
"hack/check-schema-changes.sh"
2324
"hack/boilerplate.sh"
2425
"hack/gofmt.sh"
2526
"hack/linter.sh"
@@ -41,4 +42,4 @@ for s in "${scripts[@]}"; do
4142
fail=1
4243
fi
4344
done
44-
exit $fail
45+
exit $fail
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2019 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"github.com/GoogleContainerTools/skaffold/hack/versions/pkg/schema"
21+
"github.com/sirupsen/logrus"
22+
)
23+
24+
func main() {
25+
if err := schema.RunSchemaCheckOnChangedFiles(); err != nil {
26+
logrus.Fatal(err)
27+
}
28+
}

hack/versions/pkg/diff/godiff.go

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
Copyright 2019 The Skaffold Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package diff
18+
19+
import (
20+
"fmt"
21+
"go/ast"
22+
"go/parser"
23+
"go/token"
24+
"sort"
25+
"strings"
26+
27+
"github.com/sirupsen/logrus"
28+
29+
"github.com/google/go-cmp/cmp"
30+
)
31+
32+
// CompareGoStructs returns an empty string iff aFile and bFile are valid go files
33+
// and the top level go struct declarations have the same fields independent of order.
34+
// It returns a semi-readable diff of the AST in case the two files are different.
35+
// Returns error when either of the go files are not parseable.
36+
func CompareGoStructs(aFile string, bFile string) (string, error) {
37+
fset := token.NewFileSet()
38+
astA, err := parser.ParseFile(fset, aFile, nil, parser.AllErrors)
39+
if err != nil {
40+
return "", err
41+
}
42+
astB, err := parser.ParseFile(fset, bFile, nil, parser.AllErrors)
43+
if err != nil {
44+
return "", err
45+
}
46+
47+
return cmp.Diff(structsMap(astA), structsMap(astB)), nil
48+
}
49+
50+
func structsMap(astB *ast.File) map[string]string {
51+
bStructs := make(map[string]string)
52+
for _, n := range astB.Decls {
53+
decl, ok := n.(*ast.GenDecl)
54+
if !ok {
55+
continue
56+
}
57+
typeSec, ok := decl.Specs[0].(*ast.TypeSpec)
58+
if !ok {
59+
continue
60+
}
61+
structType, ok := typeSec.Type.(*ast.StructType)
62+
if !ok {
63+
continue
64+
}
65+
bStructs[typeSec.Name.Name] = fieldListString(structType)
66+
}
67+
logrus.Debugf("%+v", bStructs)
68+
return bStructs
69+
}
70+
71+
func fieldListString(structType *ast.StructType) string {
72+
fieldListString := ""
73+
fields := structType.Fields.List
74+
sort.Slice(fields, func(i, j int) bool {
75+
if len(fields[i].Names) == 0 {
76+
return false
77+
}
78+
if len(fields[i].Names) > 0 && len(fields[j].Names) == 0 {
79+
return true
80+
}
81+
return strings.Compare(fields[i].Names[0].Name, fields[j].Names[0].Name) > 0
82+
})
83+
for _, field := range fields {
84+
tag := "'"
85+
if field.Tag != nil {
86+
tag = field.Tag.Value
87+
}
88+
fieldListString = fmt.Sprintf("%s %s type: %s tag: %s",
89+
fieldListString,
90+
field.Names,
91+
baseTypeName(field.Type),
92+
tag)
93+
}
94+
return fieldListString
95+
}
96+
97+
// inspired by https://github.com/golang/go/blob/9b968df17782f21cc0af14c9d3c0bcf4cf3f911f/src/go/doc/reader.go#L100
98+
func baseTypeName(x ast.Expr) (name string) {
99+
switch t := x.(type) {
100+
case *ast.Ident:
101+
return t.Name
102+
case *ast.SelectorExpr:
103+
if _, ok := t.X.(*ast.Ident); ok {
104+
return t.Sel.Name
105+
}
106+
case *ast.ArrayType:
107+
return "[]" + baseTypeName(t.Elt)
108+
case *ast.MapType:
109+
return "map[" + baseTypeName(t.Key) + "]" + baseTypeName(t.Value)
110+
case *ast.ParenExpr:
111+
return baseTypeName(t.X)
112+
case *ast.StarExpr:
113+
return "*" + baseTypeName(t.X)
114+
default:
115+
panic(fmt.Errorf("not covered %+v %+v ", t, x))
116+
}
117+
return
118+
}

0 commit comments

Comments
 (0)