Forbid duplicate keys in strict mode

Prevents specifying duplicate keys using
UnmarshallStrict: https://pkg.go.dev/gopkg.in/yaml.v2#UnmarshalStrict
This commit is contained in:
Bill Franklin 2022-07-15 10:56:15 +01:00
parent 7bf1e01dec
commit 78ed040b7d
6 changed files with 81 additions and 8 deletions

View file

@ -98,7 +98,7 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
-skip string -skip string
comma-separated list of kinds to ignore comma-separated list of kinds to ignore
-strict -strict
disallow additional properties not in schema disallow additional properties not in schema or duplicated keys
-summary -summary
print a summary at the end (ignored for junit output) print a summary at the end (ignored for junit output)
-v show version information -v show version information

View file

@ -72,7 +72,7 @@ func FromFlags(progName string, args []string) (Config, string, error) {
flags.Var(&ignoreFilenamePatterns, "ignore-filename-pattern", "regular expression specifying paths to ignore (can be specified multiple times)") flags.Var(&ignoreFilenamePatterns, "ignore-filename-pattern", "regular expression specifying paths to ignore (can be specified multiple times)")
flags.BoolVar(&c.Summary, "summary", false, "print a summary at the end (ignored for junit output)") flags.BoolVar(&c.Summary, "summary", false, "print a summary at the end (ignored for junit output)")
flags.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently") flags.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently")
flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema") flags.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema or duplicated keys")
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, tap, text") flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, tap, text")
flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)") flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)")
flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure") flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure")

View file

@ -112,7 +112,12 @@ func (val *v) ValidateResource(res resource.Resource) Result {
} }
var r map[string]interface{} var r map[string]interface{}
if err := yaml.Unmarshal(res.Bytes, &r); err != nil { unmarshaller := yaml.Unmarshal
if val.opts.Strict {
unmarshaller = yaml.UnmarshalStrict
}
if err := unmarshaller(res.Bytes, &r); err != nil {
return Result{Resource: res, Status: Error, Err: fmt.Errorf("error unmarshalling resource: %s", err)} return Result{Resource: res, Status: Error, Err: fmt.Errorf("error unmarshalling resource: %s", err)}
} }

View file

@ -27,6 +27,7 @@ func TestValidate(t *testing.T) {
rawResource, schemaRegistry1 []byte rawResource, schemaRegistry1 []byte
schemaRegistry2 []byte schemaRegistry2 []byte
ignoreMissingSchema bool ignoreMissingSchema bool
strict bool
expect Status expect Status
}{ }{
{ {
@ -60,6 +61,7 @@ lastName: bar
}`), }`),
nil, nil,
false, false,
false,
Valid, Valid,
}, },
{ {
@ -93,6 +95,7 @@ lastName: bar
}`), }`),
nil, nil,
false, false,
false,
Invalid, Invalid,
}, },
{ {
@ -125,8 +128,61 @@ firstName: foo
}`), }`),
nil, nil,
false, false,
false,
Invalid, Invalid,
}, },
{
"key \"firstName\" already set in map",
[]byte(`
kind: name
apiVersion: v1
firstName: foo
firstName: bar
`),
[]byte(`{
"title": "Example Schema",
"type": "object",
"properties": {
"kind": {
"type": "string"
},
"firstName": {
"type": "string"
}
},
"required": ["firstName"]
}`),
nil,
false,
true,
Error,
},
{
"key firstname already set in map in non-strict mode",
[]byte(`
kind: name
apiVersion: v1
firstName: foo
firstName: bar
`),
[]byte(`{
"title": "Example Schema",
"type": "object",
"properties": {
"kind": {
"type": "string"
},
"firstName": {
"type": "string"
}
},
"required": ["firstName"]
}`),
nil,
false,
false,
Valid,
},
{ {
"resource has invalid yaml", "resource has invalid yaml",
[]byte(` []byte(`
@ -161,6 +217,7 @@ lastName: bar
}`), }`),
nil, nil,
false, false,
false,
Error, Error,
}, },
{ {
@ -196,6 +253,7 @@ lastName: bar
}, },
"required": ["firstName", "lastName"] "required": ["firstName", "lastName"]
}`), }`),
false,
false, false,
Valid, Valid,
}, },
@ -232,6 +290,7 @@ lastName: bar
}, },
"required": ["firstName", "lastName"] "required": ["firstName", "lastName"]
}`), }`),
false,
false, false,
Valid, Valid,
}, },
@ -246,6 +305,7 @@ lastName: bar
nil, nil,
nil, nil,
true, true,
false,
Skipped, Skipped,
}, },
{ {
@ -259,6 +319,7 @@ lastName: bar
nil, nil,
nil, nil,
false, false,
false,
Error, Error,
}, },
{ {
@ -272,6 +333,7 @@ lastName: bar
[]byte(`<html>error page</html>`), []byte(`<html>error page</html>`),
[]byte(`<html>error page</html>`), []byte(`<html>error page</html>`),
true, true,
false,
Skipped, Skipped,
}, },
{ {
@ -285,6 +347,7 @@ lastName: bar
[]byte(`<html>error page</html>`), []byte(`<html>error page</html>`),
[]byte(`<html>error page</html>`), []byte(`<html>error page</html>`),
false, false,
false,
Error, Error,
}, },
} { } {
@ -293,6 +356,7 @@ lastName: bar
SkipKinds: map[string]struct{}{}, SkipKinds: map[string]struct{}{},
RejectKinds: map[string]struct{}{}, RejectKinds: map[string]struct{}{},
IgnoreMissingSchemas: testCase.ignoreMissingSchema, IgnoreMissingSchemas: testCase.ignoreMissingSchema,
Strict: testCase.strict,
}, },
schemaCache: nil, schemaCache: nil,
schemaDownload: downloadSchema, schemaDownload: downloadSchema,
@ -306,7 +370,11 @@ lastName: bar
}, },
} }
if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect { if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error()) if got.Err != nil {
t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error())
} else {
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
}
} }
} }
} }

View file

@ -34,7 +34,7 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
-skip string -skip string
comma-separated list of kinds to ignore comma-separated list of kinds to ignore
-strict -strict
disallow additional properties not in schema disallow additional properties not in schema or duplicated keys
-summary -summary
print a summary at the end (ignored for junit output) print a summary at the end (ignored for junit output)
-v show version information -v show version information

View file

@ -59,7 +59,7 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
-skip string -skip string
comma-separated list of kinds to ignore comma-separated list of kinds to ignore
-strict -strict
disallow additional properties not in schema disallow additional properties not in schema or duplicated keys
-summary -summary
print a summary at the end (ignored for junit output) print a summary at the end (ignored for junit output)
-v show version information -v show version information