Support for CRDs

This commit is contained in:
Yann Hamon 2020-10-16 23:53:41 +02:00
parent f7bfd2c960
commit b4995aa02c
11 changed files with 237 additions and 39 deletions

View file

@ -7,8 +7,8 @@ import (
)
type KubernetesRegistry struct {
baseURL string
strict bool
schemaPathTemplate string
strict bool
}
type downloadError struct {
@ -22,15 +22,18 @@ func newDownloadError(err error, isRetryable bool) *downloadError {
func (e *downloadError) IsRetryable() bool { return e.isRetryable }
func (e *downloadError) Error() string { return e.err.Error() }
func NewKubernetesRegistry(strict bool) *KubernetesRegistry {
func NewHTTPRegistry(schemaPathTemplate string, strict bool) *KubernetesRegistry {
return &KubernetesRegistry{
baseURL: "https://kubernetesjsonschema.dev",
strict: strict,
schemaPathTemplate: schemaPathTemplate,
strict: strict,
}
}
func (r KubernetesRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) {
url := r.baseURL + "/" + schemaPath(resourceKind, resourceAPIVersion, k8sVersion, r.strict)
url, err := schemaPath(r.schemaPathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict)
if err != nil {
return nil, err
}
resp, err := http.Get(url)
if err != nil {

View file

@ -4,30 +4,47 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
)
type LocalRegistry struct {
folder string
strict bool
pathTemplate string
strict bool
}
type fileNotFoundError struct {
err error
isRetryable bool
}
func newFileNotFoundError(err error, isRetryable bool) *fileNotFoundError {
return &fileNotFoundError{err, isRetryable}
}
func (e *fileNotFoundError) IsRetryable() bool { return e.isRetryable }
func (e *fileNotFoundError) Error() string { return e.err.Error() }
// NewLocalSchemas creates a new "registry", that will serve schemas from files, given a list of schema filenames
func NewLocalRegistry(folder string, strict bool) (*LocalRegistry, error) {
func NewLocalRegistry(pathTemplate string, strict bool) *LocalRegistry {
return &LocalRegistry{
folder,
pathTemplate,
strict,
}, nil
}
}
// DownloadSchema retrieves the schema from a file for the resource
func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) {
schemaFile := path.Join(r.folder, schemaPath(resourceKind, resourceAPIVersion, k8sVersion, r.strict))
schemaFile, err := schemaPath(r.pathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict)
if err != nil {
return []byte{}, nil
}
f, err := os.Open(schemaFile)
if err != nil {
if os.IsNotExist(err) {
return nil, newFileNotFoundError(fmt.Errorf("no schema found"), false)
}
return nil, fmt.Errorf("failed to open schema %s", schemaFile)
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {

View file

@ -1,8 +1,9 @@
package registry
import (
"fmt"
"bytes"
"strings"
"text/template"
)
type Manifest struct {
@ -19,7 +20,7 @@ type Retryable interface {
IsRetryable() bool
}
func schemaPath(resourceKind, resourceAPIVersion, k8sVersion string, strict bool) string {
func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict bool) (string, error) {
normalisedVersion := k8sVersion
if normalisedVersion != "master" {
normalisedVersion = "v" + normalisedVersion
@ -38,5 +39,28 @@ func schemaPath(resourceKind, resourceAPIVersion, k8sVersion string, strict bool
kindSuffix += "-" + strings.ToLower(groupParts[1])
}
return fmt.Sprintf("%s-standalone%s/%s%s.json", normalisedVersion, strictSuffix, strings.ToLower(resourceKind), kindSuffix)
tmpl, err := template.New("tpl").Parse(tpl)
if err != nil {
return "", err
}
tplData := struct {
NormalizedVersion string
StrictSuffix string
ResourceKind string
KindSuffix string
}{
normalisedVersion,
strictSuffix,
strings.ToLower(resourceKind),
kindSuffix,
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, tplData)
if err != nil {
return "", err
}
return buf.String(), nil
}

View file

@ -6,39 +6,52 @@ import (
func TestSchemaPath(t *testing.T) {
for i, testCase := range []struct {
resourceKind, resourceAPIVersion, k8sVersion, expected string
strict bool
tpl, resourceKind, resourceAPIVersion, k8sVersion, expected string
strict bool
errExpected error
}{
{
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Deployment",
"apps/v1",
"1.16.0",
"v1.16.0-standalone-strict/deployment-apps-v1.json",
"https://kubernetesjsonschema.dev/v1.16.0-standalone-strict/deployment-apps-v1.json",
true,
nil,
},
{
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Deployment",
"apps/v1",
"1.16.0",
"v1.16.0-standalone/deployment-apps-v1.json",
"https://kubernetesjsonschema.dev/v1.16.0-standalone/deployment-apps-v1.json",
false,
nil,
},
{
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Service",
"v1",
"1.18.0",
"v1.18.0-standalone/service-v1.json",
"https://kubernetesjsonschema.dev/v1.18.0-standalone/service-v1.json",
false,
nil,
},
{
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Service",
"v1",
"master",
"master-standalone/service-v1.json",
"https://kubernetesjsonschema.dev/master-standalone/service-v1.json",
false,
nil,
},
} {
if got := schemaPath(testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sVersion, testCase.strict); got != testCase.expected {
got, err := schemaPath(testCase.tpl, testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sVersion, testCase.strict)
if err != testCase.errExpected {
t.Errorf("%d - got error %s, expected %s", i+1, err, testCase.errExpected)
}
if got != testCase.expected {
t.Errorf("%d - got %s, expected %s", i+1, got, testCase.expected)
}
}

View file

@ -65,6 +65,31 @@ lastName: bar
}`),
fmt.Errorf("Invalid type. Expected: number, given: string"),
},
{
"missing required field",
[]byte(`
firstName: foo
`),
[]byte(`{
"title": "Example Schema",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
}
},
"required": ["firstName", "lastName"]
}`),
fmt.Errorf("lastName is required"),
},
{
"resource has invalid yaml",
[]byte(`