Skip to content

Commit

Permalink
feat: add cli resource loader package (kyverno#8488)
Browse files Browse the repository at this point in the history
Signed-off-by: Charles-Edouard Brétéché <[email protected]>
  • Loading branch information
eddycharly authored Sep 21, 2023
1 parent 4046315 commit 86b752e
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
2 changes: 2 additions & 0 deletions cmd/cli/kubectl-kyverno/_testdata/resources/namespace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ metadata:
name: prod-bus-app1
labels:
purpose: production
spec: {}
status: {}
39 changes: 39 additions & 0 deletions cmd/cli/kubectl-kyverno/resource/loader/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package loader

import (
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/openapi"
"sigs.k8s.io/kubectl-validate/pkg/validator"
)

type Loader interface {
Load([]byte) (unstructured.Unstructured, error)
}

type loader struct {
validator *validator.Validator
}

func New(client openapi.Client) (Loader, error) {
factory, err := validator.New(client)
if err != nil {
return nil, err
}
return &loader{
validator: factory,
}, nil
}

func (l *loader) Load(document []byte) (unstructured.Unstructured, error) {
_, result, err := l.validator.Parse(document)
if err != nil {
return unstructured.Unstructured{}, fmt.Errorf("failed to parse document (%w)", err)
}
// TODO: remove DeepCopy when fixed upstream
if err := l.validator.Validate(result.DeepCopy()); err != nil {
return unstructured.Unstructured{}, fmt.Errorf("failed to validate resource (%w)", err)
}
return *result, nil
}
176 changes: 176 additions & 0 deletions cmd/cli/kubectl-kyverno/resource/loader/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package loader

import (
"errors"
"os"
"reflect"
"testing"

"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/data"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/openapi"
"sigs.k8s.io/kubectl-validate/pkg/openapiclient"
"sigs.k8s.io/kubectl-validate/pkg/validator"
"sigs.k8s.io/yaml"
)

type errClient struct{}

func (errClient) Paths() (map[string]openapi.GroupVersion, error) {
return nil, errors.New("error")
}

func TestNew(t *testing.T) {
tests := []struct {
name string
client openapi.Client
want Loader
wantErr bool
}{{
name: "err client",
client: errClient{},
wantErr: true,
}, {
name: "builtin",
client: openapiclient.NewHardcodedBuiltins("1.27"),
want: func() Loader {
validator, err := validator.New(openapiclient.NewHardcodedBuiltins("1.27"))
require.NoError(t, err)
return &loader{
validator: validator,
}
}(),
}, {
name: "invalid local",
client: openapiclient.NewLocalSchemaFiles(data.Crds(), "blam"),
want: func() Loader {
validator, err := validator.New(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam"))
require.NoError(t, err)
return &loader{
validator: validator,
}
}(),
}, {
name: "composite - no clients",
client: openapiclient.NewComposite(),
want: func() Loader {
validator, err := validator.New(openapiclient.NewComposite())
require.NoError(t, err)
return &loader{
validator: validator,
}
}(),
}, {
name: "composite - err client",
client: openapiclient.NewComposite(errClient{}),
wantErr: true,
}, {
name: "composite - with err client",
client: openapiclient.NewComposite(openapiclient.NewHardcodedBuiltins("1.27"), errClient{}),
wantErr: true,
}, {
name: "composite - invalid local",
client: openapiclient.NewComposite(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam")),
want: func() Loader {
validator, err := validator.New(openapiclient.NewComposite(openapiclient.NewLocalSchemaFiles(data.Crds(), "blam")))
require.NoError(t, err)
return &loader{
validator: validator,
}
}(),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.client)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}

func Test_loader_Load(t *testing.T) {
loadFile := func(path string) []byte {
bytes, err := os.ReadFile(path)
require.NoError(t, err)
return bytes
}
newLoader := func(client openapi.Client) Loader {
loader, err := New(client)
require.NoError(t, err)
return loader
}
toUnstructured := func(data []byte) unstructured.Unstructured {
json, err := yaml.YAMLToJSON(data)
require.NoError(t, err)
var result unstructured.Unstructured
require.NoError(t, result.UnmarshalJSON(json))
if result.GetCreationTimestamp().Time.IsZero() {
require.NoError(t, unstructured.SetNestedField(result.UnstructuredContent(), nil, "metadata", "creationTimestamp"))
}
return result
}
tests := []struct {
name string
loader Loader
document []byte
want unstructured.Unstructured
wantErr bool
}{{
name: "nil",
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
wantErr: true,
}, {
name: "empty GVK",
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
document: []byte(`foo: bar`),
wantErr: true,
}, {
name: "not yaml",
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
document: []byte(`
foo
bar
- baz`),
wantErr: true,
}, {
name: "unknown GVK",
loader: newLoader(openapiclient.NewLocalSchemaFiles(data.Crds(), "schemas")),
document: loadFile("../../_testdata/resources/namespace.yaml"),
wantErr: true,
}, {
name: "bad schema",
loader: newLoader(openapiclient.NewHardcodedBuiltins("1.27")),
document: []byte(`
apiVersion: v1
kind: Namespace
bad: field
metadata:
name: prod-bus-app1
labels:
purpose: production`),
wantErr: true,
}, {
name: "ok",
loader: newLoader(openapiclient.NewHardcodedBuiltins("1.27")),
document: loadFile("../../_testdata/resources/namespace.yaml"),
want: toUnstructured(loadFile("../../_testdata/resources/namespace.yaml")),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.loader.Load(tt.document)
if (err != nil) != tt.wantErr {
t.Errorf("loader.Load() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("loader.Load() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 86b752e

Please sign in to comment.