Skip to content

Commit c4b044f

Browse files
authoredJul 3, 2021
Merge pull request #54 from yannh/list-support
Support for lists
2 parents 5ed3dda + df386fc commit c4b044f

File tree

8 files changed

+195
-49
lines changed

8 files changed

+195
-49
lines changed
 

Diff for: ‎acceptance.bats

+38
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,41 @@ resetCacheFolder() {
243243
[ "${lines[1]}" == 'ok 1 - fixtures/valid.yaml (ReplicationController)' ]
244244
[ "${lines[2]}" == '1..1' ]
245245
}
246+
247+
@test "Pass when parsing a file containing multiple a List" {
248+
run bin/kubeconform -summary fixtures/list_valid.yaml
249+
[ "$status" -eq 0 ]
250+
[ "$output" = "Summary: 6 resources found in 1 file - Valid: 6, Invalid: 0, Errors: 0, Skipped: 0" ]
251+
}
252+
253+
@test "Pass when parsing a List resource from stdin" {
254+
run bash -c "cat fixtures/list_valid.yaml | bin/kubeconform -summary"
255+
[ "$status" -eq 0 ]
256+
[ "$output" = 'Summary: 6 resources found parsing stdin - Valid: 6, Invalid: 0, Errors: 0, Skipped: 0' ]
257+
}
258+
259+
@test "Fail when parsing a List that contains an invalid resource" {
260+
run bin/kubeconform -summary fixtures/list_invalid.yaml
261+
[ "$status" -eq 1 ]
262+
[ "${lines[0]}" == 'fixtures/list_invalid.yaml - ReplicationController bob is invalid: For field spec.replicas: Invalid type. Expected: [integer,null], given: string' ]
263+
[ "${lines[1]}" == 'Summary: 2 resources found in 1 file - Valid: 1, Invalid: 1, Errors: 0, Skipped: 0' ]
264+
}
265+
266+
@test "Fail when parsing a List that contains an invalid resource from stdin" {
267+
run bash -c "cat fixtures/list_invalid.yaml | bin/kubeconform -summary -"
268+
[ "$status" -eq 1 ]
269+
[ "${lines[0]}" == 'stdin - ReplicationController bob is invalid: For field spec.replicas: Invalid type. Expected: [integer,null], given: string' ]
270+
[ "${lines[1]}" == 'Summary: 2 resources found parsing stdin - Valid: 1, Invalid: 1, Errors: 0, Skipped: 0' ]
271+
}
272+
273+
@test "Pass on valid, empty list" {
274+
run bin/kubeconform -summary fixtures/list_empty_valid.yaml
275+
[ "$status" -eq 0 ]
276+
[ "$output" = 'Summary: 0 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 0, Skipped: 0' ]
277+
}
278+
279+
@test "Pass on multi-yaml containing one resource, one list" {
280+
run bin/kubeconform -summary fixtures/multi_with_list.yaml
281+
[ "$status" -eq 0 ]
282+
[ "$output" = 'Summary: 2 resources found in 1 file - Valid: 2, Invalid: 0, Errors: 0, Skipped: 0' ]
283+
}

Diff for: ‎fixtures/list_empty_valid.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
apiVersion: v1
3+
kind: List
4+
items: []

Diff for: ‎fixtures/multi_with_list.yaml

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: redis-master
6+
labels:
7+
app: redis
8+
tier: backend
9+
role: master
10+
spec:
11+
ports:
12+
# the port that this service should serve on
13+
- port: 6379
14+
targetPort: 6379
15+
selector:
16+
app: redis
17+
tier: backend
18+
role: master
19+
---
20+
apiVersion: v1
21+
kind: List
22+
items:
23+
- apiVersion: v1
24+
kind: Namespace
25+
metadata:
26+
name: b

Diff for: ‎go.sum

+4-33
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,23 @@
1-
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
21
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
32
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
43
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6-
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
7-
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
8-
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
9-
github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357 h1:Rem2+U35z1QtPQc6r+WolF7yXiefXqDKyk+lN2pE164=
10-
github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
11-
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0 h1:j30noezaCfvNLcdMYSvHLv81DxYRSt1grlpseG67vhU=
12-
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
13-
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
14-
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
15-
github.com/instrumenta/kubeval v0.0.0-20200515185822-7721cbec724c h1:tF3B96upB2wECZMXZxrAMLiVUgT22sNNxhuOhrcg28s=
16-
github.com/instrumenta/kubeval v0.0.0-20200515185822-7721cbec724c/go.mod h1:cD+P/oZrBwOnaIHXrqvKPuN353KPxGomnsXSXf8pFJs=
17-
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
18-
github.com/mattn/go-colorable v0.1.0 h1:v2XXALHHh6zHfYTJ+cSkwtyffnaOyR1MXaA91mTrb8o=
19-
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
20-
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
21-
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
22-
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
23-
github.com/pelletier/go-toml v0.0.0-20180724185102-c2dbbc24a979/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
24-
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
6+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
257
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26-
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
27-
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
28-
github.com/spf13/cobra v0.0.0-20180820174524-ff0d02e85550 h1:LB9SHuuXO8gnsHtexOQSpsJrrAHYA35lvHUaE74kznU=
29-
github.com/spf13/cobra v0.0.0-20180820174524-ff0d02e85550/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
30-
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
31-
github.com/spf13/pflag v0.0.0-20180821114517-d929dcbb1086 h1:iU+nPfqRqK8ShQqnpZLv8cZ9oklo6NFUcmX1JT5Rudg=
32-
github.com/spf13/pflag v0.0.0-20180821114517-d929dcbb1086/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
33-
github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
348
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9+
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
3510
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
3611
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
3712
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
3813
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
3914
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
40-
github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
4115
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
4216
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
43-
golang.org/x/sys v0.0.0-20180821044426-4ea2f632f6e9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
44-
golang.org/x/text v0.0.0-20180810153555-6e3c4e7365dd/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4518
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
46-
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
4719
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
4820
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
4921
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
50-
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
5122
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
5223
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

Diff for: ‎pkg/resource/files.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,11 @@ func findResourcesInReader(p string, f io.Reader, resources chan<- Resource, err
9494
nRes := 0
9595
for scanner.Scan() {
9696
if len(scanner.Text()) > 0 {
97-
resources <- Resource{Path: p, Bytes: []byte(scanner.Text())}
98-
nRes++
97+
res := Resource{Path: p, Bytes: []byte(scanner.Text())}
98+
for _, subres := range res.Resources() {
99+
resources <- subres
100+
nRes++
101+
}
99102
}
100103
}
101104
if err := scanner.Err(); err != nil {

Diff for: ‎pkg/resource/resource.go

+45-13
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ package resource
22

33
import (
44
"fmt"
5+
"strings"
56

67
"sigs.k8s.io/yaml"
78
)
89

910
// Resource represents a Kubernetes resource within a file
1011
type Resource struct {
11-
Path string
12-
Bytes []byte
13-
sig *Signature
12+
Path string
13+
Bytes []byte
14+
sig *Signature // Cache signature parsing
15+
sigErr error // Cache potential signature parsing error
1416
}
1517

1618
// Signature is a key representing a Kubernetes resource
@@ -21,7 +23,7 @@ type Signature struct {
2123
// Signature computes a signature for a resource, based on its Kind, Version, Namespace & Name
2224
func (res *Resource) Signature() (*Signature, error) {
2325
if res.sig != nil {
24-
return res.sig, nil
26+
return res.sig, res.sigErr
2527
}
2628

2729
resource := struct {
@@ -44,33 +46,38 @@ func (res *Resource) Signature() (*Signature, error) {
4446
res.sig = &Signature{Kind: resource.Kind, Version: resource.APIVersion, Namespace: resource.Metadata.Namespace, Name: name}
4547

4648
if err != nil { // Exit if there was an error unmarshalling
47-
return res.sig, err
49+
res.sigErr = err
50+
return res.sig, res.sigErr
4851
}
4952

50-
if resource.Kind == "" {
51-
return res.sig, fmt.Errorf("missing 'kind' key")
53+
if res.sig.Kind == "" {
54+
res.sigErr = fmt.Errorf("missing 'kind' key")
55+
return res.sig, res.sigErr
5256
}
5357

54-
if resource.APIVersion == "" {
55-
return res.sig, fmt.Errorf("missing 'apiVersion' key")
58+
if res.sig.Version == "" {
59+
res.sigErr = fmt.Errorf("missing 'apiVersion' key")
60+
return res.sig, res.sigErr
5661
}
5762

58-
return res.sig, err
63+
return res.sig, res.sigErr
5964
}
6065

6166
func (res *Resource) SignatureFromMap(m map[string]interface{}) (*Signature, error) {
6267
if res.sig != nil {
63-
return res.sig, nil
68+
return res.sig, res.sigErr
6469
}
6570

6671
Kind, ok := m["kind"].(string)
6772
if !ok {
68-
return res.sig, fmt.Errorf("missing 'kind' key")
73+
res.sigErr = fmt.Errorf("missing 'kind' key")
74+
return res.sig, res.sigErr
6975
}
7076

7177
APIVersion, ok := m["apiVersion"].(string)
7278
if !ok {
73-
return res.sig, fmt.Errorf("missing 'apiVersion' key")
79+
res.sigErr = fmt.Errorf("missing 'apiVersion' key")
80+
return res.sig, res.sigErr
7481
}
7582

7683
var name, ns string
@@ -87,3 +94,28 @@ func (res *Resource) SignatureFromMap(m map[string]interface{}) (*Signature, err
8794
res.sig = &Signature{Kind: Kind, Version: APIVersion, Namespace: ns, Name: name}
8895
return res.sig, nil
8996
}
97+
98+
// Resources returns a list of resources if the resource is of type List, a single resource otherwise
99+
// See https://github.com/yannh/kubeconform/issues/53
100+
func (res *Resource) Resources() []Resource {
101+
resources := []Resource{}
102+
if s, err := res.Signature(); err == nil && strings.ToLower(s.Kind) == "list" {
103+
// A single file of type List
104+
list := struct {
105+
Version string
106+
Kind string
107+
Items []interface{}
108+
}{}
109+
110+
yaml.Unmarshal(res.Bytes, &list)
111+
112+
for _, item := range list.Items {
113+
r := Resource{Path: res.Path}
114+
r.Bytes, _ = yaml.Marshal(item)
115+
resources = append(resources, r)
116+
}
117+
return resources
118+
}
119+
120+
return []Resource{*res}
121+
}

Diff for: ‎pkg/resource/resource_test.go

+69
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,72 @@ func TestSignatureFromMap(t *testing.T) {
8686
}
8787
}
8888
}
89+
90+
func TestResources(t *testing.T) {
91+
testCases := []struct {
92+
b string
93+
expected int
94+
}{
95+
{
96+
`
97+
apiVersion: v1
98+
kind: List
99+
`,
100+
0,
101+
},
102+
{
103+
`
104+
apiVersion: v1
105+
kind: List
106+
Items: []
107+
`,
108+
0,
109+
},
110+
{
111+
`
112+
apiVersion: v1
113+
kind: List
114+
Items:
115+
- apiVersion: v1
116+
kind: ReplicationController
117+
metadata:
118+
name: "bob"
119+
spec:
120+
replicas: 2
121+
`,
122+
1,
123+
},
124+
{
125+
`
126+
apiVersion: v1
127+
kind: List
128+
Items:
129+
- apiVersion: v1
130+
kind: ReplicationController
131+
metadata:
132+
name: "bob"
133+
spec:
134+
replicas: 2
135+
- apiVersion: v1
136+
kind: ReplicationController
137+
metadata:
138+
name: "Jim"
139+
spec:
140+
replicas: 2
141+
`,
142+
2,
143+
},
144+
}
145+
146+
for i, testCase := range testCases {
147+
res := resource.Resource{
148+
Path: "foo",
149+
Bytes: []byte(testCase.b),
150+
}
151+
152+
subres := res.Resources()
153+
if len(subres) != testCase.expected {
154+
t.Errorf("test %d: expected to find %d resources, found %d", i, testCase.expected, len(subres))
155+
}
156+
}
157+
}

Diff for: ‎pkg/resource/stream.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ func FromStream(ctx context.Context, path string, r io.Reader) (<-chan Resource,
6060
break SCAN
6161
default:
6262
}
63-
resources <- Resource{Path: path, Bytes: scanner.Bytes()}
63+
res := Resource{Path: path, Bytes: scanner.Bytes()}
64+
for _, subres := range res.Resources() {
65+
resources <- subres
66+
}
6467
}
6568

6669
close(resources)

0 commit comments

Comments
 (0)
Please sign in to comment.