Skip to content

Commit 3cbcd68

Browse files
committed
Update to sigs.k8s.io/yaml
This updates the current YAML library from gopkg.in/yaml.v2 to sigs.k8s.io/yaml, a wrapper around the former. The k8s library provides better functionality with regards to unmarshaling a YAML file into a JSON-friendly data structure. This commit also includes various cleanups related to the change: * `convertToStringKeys` has been removed * functions `determineKind` and `determineAPIVersion` have been replaced by the more generic `getString` * functions `in`, `detectLineBreak` and `getString` have been moved to kubeval/utils.go Lastly, unit tests have been written for the new `getString` function
1 parent 70e32d6 commit 3cbcd68

File tree

5 files changed

+95
-83
lines changed

5 files changed

+95
-83
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ require (
2828
github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609
2929
golang.org/x/sys v0.0.0-20180821044426-4ea2f632f6e9 // indirect
3030
golang.org/x/text v0.0.0-20180810153555-6e3c4e7365dd // indirect
31-
gopkg.in/yaml.v2 v2.2.1
31+
gopkg.in/yaml.v2 v2.2.1 // indirect
32+
sigs.k8s.io/yaml v1.1.0
3233
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
5656
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5757
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
5858
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
59+
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
60+
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

kubeval/kubeval.go

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,14 @@ package kubeval
22

33
import (
44
"bytes"
5-
"errors"
65
"fmt"
76
"regexp"
8-
"runtime"
97
"strings"
108

119
"github.com/hashicorp/go-multierror"
1210
"github.com/spf13/viper"
1311
"github.com/xeipuuv/gojsonschema"
14-
"gopkg.in/yaml.v2"
12+
"sigs.k8s.io/yaml"
1513

1614
"github.com/instrumenta/kubeval/log"
1715
)
@@ -51,16 +49,6 @@ var ExitOnError bool
5149
// schema validation
5250
var KindsToSkip []string
5351

54-
// in is a method which tests whether the `key` is in the set
55-
func in(set []string, key string) bool {
56-
for _, k := range set {
57-
if k == key {
58-
return true
59-
}
60-
}
61-
return false
62-
}
63-
6452
// ValidFormat is a type for quickly forcing
6553
// new formats on the gojsonschema loader
6654
type ValidFormat struct{}
@@ -81,15 +69,6 @@ type ValidationResult struct {
8169
Errors []gojsonschema.ResultError
8270
}
8371

84-
// detectLineBreak returns the relevant platform specific line ending
85-
func detectLineBreak(haystack []byte) string {
86-
windowsLineEnding := bytes.Contains(haystack, []byte("\r\n"))
87-
if windowsLineEnding && runtime.GOOS == "windows" {
88-
return "\r\n"
89-
}
90-
return "\n"
91-
}
92-
9372
func determineSchema(kind string, apiVersion string) string {
9473
// We have both the upstream Kubernetes schemas and the OpenShift schemas available
9574
// the tool can toggle between then using the --openshift boolean flag and here we
@@ -148,57 +127,26 @@ func determineSchema(kind string, apiVersion string) string {
148127
return fmt.Sprintf("%s/%s-standalone%s/%s%s.json", baseURL, normalisedVersion, strictSuffix, strings.ToLower(kind), kindSuffix)
149128
}
150129

151-
func determineKind(body interface{}) (string, error) {
152-
cast, _ := body.(map[string]interface{})
153-
if _, ok := cast["kind"]; !ok {
154-
return "", errors.New("Missing a kind key")
155-
}
156-
if cast["kind"] == nil {
157-
return "", errors.New("Missing a kind value")
158-
}
159-
return cast["kind"].(string), nil
160-
}
161-
162-
func determineAPIVersion(body interface{}) (string, error) {
163-
cast, _ := body.(map[string]interface{})
164-
if _, ok := cast["apiVersion"]; !ok {
165-
return "", errors.New("Missing a apiVersion key")
166-
}
167-
if cast["apiVersion"] == nil {
168-
return "", errors.New("Missing a apiVersion value")
169-
}
170-
return cast["apiVersion"].(string), nil
171-
}
172-
173130
// validateResource validates a single Kubernetes resource against
174131
// the relevant schema, detecting the type of resource automatically
175132
func validateResource(data []byte, fileName string, schemaCache map[string]*gojsonschema.Schema) (ValidationResult, error) {
176-
var spec interface{}
177133
result := ValidationResult{}
178134
result.FileName = fileName
179-
err := yaml.Unmarshal(data, &spec)
135+
var body map[string]interface{}
136+
err := yaml.Unmarshal(data, &body)
180137
if err != nil {
181-
return result, errors.New("Failed to decode YAML from " + fileName)
182-
}
183-
184-
body := convertToStringKeys(spec)
185-
186-
if body == nil {
138+
return result, fmt.Errorf("Failed to decode YAML from %s: %s", fileName, err.Error())
139+
} else if body == nil {
187140
return result, nil
188141
}
189142

190-
cast, _ := body.(map[string]interface{})
191-
if len(cast) == 0 {
192-
return result, nil
193-
}
194-
195-
kind, err := determineKind(body)
143+
kind, err := getString(body, "kind")
196144
if err != nil {
197145
return result, err
198146
}
199147
result.Kind = kind
200148

201-
apiVersion, err := determineAPIVersion(body)
149+
apiVersion, err := getString(body, "apiVersion")
202150
if err != nil {
203151
return result, err
204152
}
@@ -216,7 +164,6 @@ func validateResource(data []byte, fileName string, schemaCache map[string]*gojs
216164
return result, nil
217165
}
218166

219-
220167
func validateAgainstSchema(body interface{}, resource *ValidationResult, schemaCache map[string]*gojsonschema.Schema) ([]gojsonschema.ResultError, error) {
221168
if IgnoreMissingSchemas {
222169
log.Warn("Warning: Set to ignore missing schemas")

kubeval/kubeval_test.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,55 @@ func TestDetermineSchemaForSchemaLocation(t *testing.T) {
211211
}
212212
}
213213

214-
func TestDetermineKind(t *testing.T) {
215-
_, err := determineKind("sample")
216-
if err == nil {
217-
t.Errorf("Shouldn't be able to find a kind when passed a blank string")
214+
func TestGetString(t *testing.T) {
215+
var tests = []struct{
216+
body map[string]interface{}
217+
key string
218+
expectedVal string
219+
expectError bool
220+
}{
221+
{
222+
body: map[string]interface{}{"goodKey": "goodVal"},
223+
key: "goodKey",
224+
expectedVal: "goodVal",
225+
expectError: false,
226+
},
227+
{
228+
body: map[string]interface{}{},
229+
key: "missingKey",
230+
expectedVal: "",
231+
expectError: true,
232+
},
233+
{
234+
body: map[string]interface{}{"nilKey": nil},
235+
key: "nilKey",
236+
expectedVal: "",
237+
expectError: true,
238+
},
239+
{
240+
body: map[string]interface{}{"badKey": 5},
241+
key: "badKey",
242+
expectedVal: "",
243+
expectError: true,
244+
},
245+
}
246+
247+
for _, test := range tests {
248+
actualVal, err := getString(test.body, test.key)
249+
if err != nil {
250+
if !test.expectError {
251+
t.Errorf("Unexpected error: %s", err.Error())
252+
}
253+
// We expected this error, so move to the next test
254+
continue
255+
}
256+
if test.expectError {
257+
t.Errorf("Expected an error, but didn't receive one")
258+
continue
259+
}
260+
if actualVal != test.expectedVal {
261+
t.Errorf("Expected %s, got %s", test.expectedVal, actualVal)
262+
}
218263
}
219264
}
220265

kubeval/utils.go

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
package kubeval
22

3-
import "fmt"
3+
import (
4+
"bytes"
5+
"fmt"
6+
"runtime"
7+
)
48

5-
// Based on https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang
6-
// We unmarshal yaml into a value of type interface{},
7-
// go through the result recursively, and convert each encountered
8-
// map[interface{}]interface{} to a map[string]interface{} value
9-
// required to marshall to JSON.
10-
func convertToStringKeys(i interface{}) interface{} {
11-
switch x := i.(type) {
12-
case map[interface{}]interface{}:
13-
m2 := map[string]interface{}{}
14-
for k, v := range x {
15-
m2[fmt.Sprintf("%v", k)] = convertToStringKeys(v)
16-
}
17-
return m2
18-
case []interface{}:
19-
for i, v := range x {
20-
x[i] = convertToStringKeys(v)
9+
func getString(body map[string]interface{}, key string) (string, error) {
10+
value, found := body[key]
11+
if !found {
12+
return "", fmt.Errorf("Missing '%s' key", key)
13+
}
14+
if value == nil {
15+
return "", fmt.Errorf("Missing '%s' value", key)
16+
}
17+
typedValue, ok := value.(string)
18+
if !ok {
19+
return "", fmt.Errorf("Expected string value for key '%s'", key)
20+
}
21+
return typedValue, nil
22+
}
23+
24+
// detectLineBreak returns the relevant platform specific line ending
25+
func detectLineBreak(haystack []byte) string {
26+
windowsLineEnding := bytes.Contains(haystack, []byte("\r\n"))
27+
if windowsLineEnding && runtime.GOOS == "windows" {
28+
return "\r\n"
29+
}
30+
return "\n"
31+
}
32+
33+
// in is a method which tests whether the `key` is in the set
34+
func in(set []string, key string) bool {
35+
for _, k := range set {
36+
if k == key {
37+
return true
2138
}
2239
}
23-
return i
40+
return false
2441
}

0 commit comments

Comments
 (0)