mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
feat: add cli resource loader package (#8488)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
4046315dac
commit
86b752e2fb
3 changed files with 217 additions and 0 deletions
|
@ -4,3 +4,5 @@ metadata:
|
|||
name: prod-bus-app1
|
||||
labels:
|
||||
purpose: production
|
||||
spec: {}
|
||||
status: {}
|
||||
|
|
39
cmd/cli/kubectl-kyverno/resource/loader/loader.go
Normal file
39
cmd/cli/kubectl-kyverno/resource/loader/loader.go
Normal file
|
@ -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
cmd/cli/kubectl-kyverno/resource/loader/loader_test.go
Normal file
176
cmd/cli/kubectl-kyverno/resource/loader/loader_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue