diff --git a/Readme.md b/Readme.md index dbffd67..e0353a4 100644 --- a/Readme.md +++ b/Readme.md @@ -102,6 +102,8 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]... cache schemas downloaded via HTTP to this folder -debug print debug information + -delims string + the delims for go template parsing -exit-on-error immediately stop execution when the first error is encountered -h show help information @@ -211,6 +213,7 @@ in each of them, in order, stopping as soon as a matching file is found. * if the `-schema-location` value ends with `.json` - Kubeconform assumes the value is a **Go templated string** that indicates how to search for JSON schemas. * the `-schema-location` value of `default` is an alias for `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{.NormalizedKubernetesVersion}}-standalone{{.StrictSuffix}}/{{.ResourceKind}}{{.KindSuffix}}.json`. +* the `-delims` could use an alternative delims when parsing the URL. Such as, when you pass `-delims=[[,,]]`, then it could parse `https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/[[.NormalizedKubernetesVersion]]-standalone[[.StrictSuffix]]/[[.ResourceKind]][[.KindSuffix]].json` **The following command lines are equivalent:** ```bash diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/main.go index e8a93e4..22a01bd 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/main.go @@ -108,6 +108,7 @@ func realMain() int { KubernetesVersion: cfg.KubernetesVersion, Strict: cfg.Strict, IgnoreMissingSchemas: cfg.IgnoreMissingSchemas, + Delims: cfg.Delims, }) if err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/pkg/config/config.go b/pkg/config/config.go index b64a3c1..3feeca5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -24,6 +24,7 @@ type Config struct { Verbose bool IgnoreMissingSchemas bool IgnoreFilenamePatterns []string + Delims string Help bool Version bool } @@ -78,6 +79,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { 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.StringVar(&c.Cache, "cache", "", "cache schemas downloaded via HTTP to this folder") + flags.StringVar(&c.Delims, "delims", "", "the delims for go template parsing") flags.BoolVar(&c.Help, "h", false, "show help information") flags.BoolVar(&c.Version, "v", false, "show version information") flags.Usage = func() { diff --git a/pkg/registry/http.go b/pkg/registry/http.go index db49f96..eb5c9d3 100644 --- a/pkg/registry/http.go +++ b/pkg/registry/http.go @@ -24,9 +24,10 @@ type SchemaRegistry struct { cache cache.Cache strict bool debug bool + delims string } -func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool, debug bool) (*SchemaRegistry, error) { +func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool, debug bool, delims string) (*SchemaRegistry, error) { reghttp := &http.Transport{ MaxIdleConns: 100, IdleConnTimeout: 3 * time.Second, @@ -57,12 +58,13 @@ func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, cache: filecache, strict: strict, debug: debug, + delims: delims, }, nil } // DownloadSchema downloads the schema for a particular resource from an HTTP server func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) { - url, err := schemaPath(r.schemaPathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict) + url, err := schemaPath(r.schemaPathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict, r.delims) if err != nil { return nil, err } diff --git a/pkg/registry/local.go b/pkg/registry/local.go index 81d69e2..17c6239 100644 --- a/pkg/registry/local.go +++ b/pkg/registry/local.go @@ -12,20 +12,22 @@ type LocalRegistry struct { pathTemplate string strict bool debug bool + delims string } // NewLocalSchemas creates a new "registry", that will serve schemas from files, given a list of schema filenames -func newLocalRegistry(pathTemplate string, strict bool, debug bool) (*LocalRegistry, error) { +func newLocalRegistry(pathTemplate string, strict bool, debug bool, delims string) (*LocalRegistry, error) { return &LocalRegistry{ pathTemplate, strict, debug, + delims, }, nil } // DownloadSchema retrieves the schema from a file for the resource func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) { - schemaFile, err := schemaPath(r.pathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict) + schemaFile, err := schemaPath(r.pathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict, r.delims) if err != nil { return []byte{}, nil } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index afde801..483e066 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -32,7 +32,7 @@ func newNotFoundError(err error) *NotFoundError { func (e *NotFoundError) Error() string { return e.err.Error() } func (e *NotFoundError) Retryable() bool { return false } -func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict bool) (string, error) { +func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict bool, delims string) (string, error) { normalisedVersion := k8sVersion if normalisedVersion != "master" { normalisedVersion = "v" + normalisedVersion @@ -51,7 +51,15 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict kindSuffix += "-" + strings.ToLower(groupParts[1]) } - tmpl, err := template.New("tpl").Parse(tpl) + tmpl := template.New("tpl") + + // in case of some special cases + delimsPairs := strings.Split(delims, ",") + if len(delimsPairs) == 2 { + tmpl = tmpl.Delims(delimsPairs[0], delimsPairs[1]) + } + + tmpl, err := tmpl.Parse(tpl) if err != nil { return "", err } @@ -81,7 +89,7 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict return buf.String(), nil } -func New(schemaLocation string, cache string, strict bool, skipTLS bool, debug bool) (Registry, error) { +func New(schemaLocation string, cache string, strict bool, skipTLS bool, debug bool, delims string) (Registry, error) { if schemaLocation == "default" { schemaLocation = "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json" } else if !strings.HasSuffix(schemaLocation, "json") { // If we dont specify a full templated path, we assume the paths of our fork of kubernetes-json-schema @@ -89,13 +97,13 @@ func New(schemaLocation string, cache string, strict bool, skipTLS bool, debug b } // try to compile the schemaLocation template to ensure it is valid - if _, err := schemaPath(schemaLocation, "Deployment", "v1", "master", true); err != nil { + if _, err := schemaPath(schemaLocation, "Deployment", "v1", "master", true, delims); err != nil { return nil, fmt.Errorf("failed initialising schema location registry: %s", err) } if strings.HasPrefix(schemaLocation, "http") { - return newHTTPRegistry(schemaLocation, cache, strict, skipTLS, debug) + return newHTTPRegistry(schemaLocation, cache, strict, skipTLS, debug, delims) } - return newLocalRegistry(schemaLocation, strict, debug) + return newLocalRegistry(schemaLocation, strict, debug, delims) } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 3f560c8..03e73e0 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -8,6 +8,7 @@ func TestSchemaPath(t *testing.T) { for i, testCase := range []struct { tpl, resourceKind, resourceAPIVersion, k8sVersion, expected string strict bool + delims string errExpected error }{ { @@ -17,6 +18,7 @@ func TestSchemaPath(t *testing.T) { "1.16.0", "https://kubernetesjsonschema.dev/v1.16.0-standalone-strict/deployment-apps-v1.json", true, + "", nil, }, { @@ -26,6 +28,7 @@ func TestSchemaPath(t *testing.T) { "1.16.0", "https://kubernetesjsonschema.dev/v1.16.0-standalone/deployment-apps-v1.json", false, + "", nil, }, { @@ -35,6 +38,7 @@ func TestSchemaPath(t *testing.T) { "1.18.0", "https://kubernetesjsonschema.dev/v1.18.0-standalone/service-v1.json", false, + "", nil, }, { @@ -44,10 +48,21 @@ func TestSchemaPath(t *testing.T) { "master", "https://kubernetesjsonschema.dev/master-standalone/service-v1.json", false, + "", + nil, + }, + { + "https://kubernetesjsonschema.dev/[[ .NormalizedKubernetesVersion ]]-standalone[[ .StrictSuffix ]]/[[ .ResourceKind ]][[ .KindSuffix ]].json", + "Service", + "v1", + "master", + "https://kubernetesjsonschema.dev/master-standalone/service-v1.json", + false, + "[[,]]", nil, }, } { - got, err := schemaPath(testCase.tpl, testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sVersion, testCase.strict) + got, err := schemaPath(testCase.tpl, testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sVersion, testCase.strict, testCase.delims) if err != testCase.errExpected { t.Errorf("%d - got error %s, expected %s", i+1, err, testCase.errExpected) } diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 09eeb53..cc7bd0f 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -49,7 +49,8 @@ type Opts struct { RejectKinds map[string]struct{} // List of resource Kinds to reject KubernetesVersion string // Kubernetes Version - has to match one in https://github.com/instrumenta/kubernetes-json-schema Strict bool // thros an error if resources contain undocumented fields - IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found + Delims string + IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found } // New returns a new Validator @@ -62,7 +63,7 @@ func New(schemaLocations []string, opts Opts) (Validator, error) { registries := []registry.Registry{} for _, schemaLocation := range schemaLocations { - reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS, opts.Debug) + reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS, opts.Debug, opts.Delims) if err != nil { return nil, err }