1
0
mirror of https://github.com/rancher/steve.git synced 2025-04-27 02:51:10 +00:00
steve/pkg/schema/converter/k8stonorman_test.go
vardhaman22 dae842ea98 updated wrangler from v2 to v3
also updated k8s dependencies to v0.30.1
2024-06-05 22:53:08 +05:30

573 lines
15 KiB
Go

package converter
import (
"fmt"
"testing"
"github.com/golang/mock/gomock"
openapiv2 "github.com/google/gnostic-models/openapiv2"
"github.com/rancher/apiserver/pkg/types"
"github.com/rancher/steve/pkg/schema/table"
"github.com/rancher/wrangler/v3/pkg/generic/fake"
wranglerSchema "github.com/rancher/wrangler/v3/pkg/schemas"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/util/proto"
)
func TestToSchemas(t *testing.T) {
gvkExtensionMap := map[any]any{
gvkExtensionGroup: "TestGroup",
gvkExtensionVersion: "v1",
gvkExtensionKind: "TestResource",
}
gvkExtensionSlice := []any{gvkExtensionMap}
extensionSliceYaml, err := yaml.Marshal(gvkExtensionSlice)
require.NoError(t, err)
gvkSchema := openapiv2.NamedSchema{
Name: "TestResources",
Value: &openapiv2.Schema{
Description: "TestResources are test resource created for unit tests",
Type: &openapiv2.TypeItem{
Value: []string{"object"},
},
Properties: &openapiv2.Properties{
AdditionalProperties: []*openapiv2.NamedSchema{},
},
VendorExtension: []*openapiv2.NamedAny{
{
Name: gvkExtensionName,
Value: &openapiv2.Any{
Yaml: string(extensionSliceYaml),
},
},
},
},
}
tests := []struct {
name string
groups []schema.GroupVersion
resources map[schema.GroupVersion][]metav1.APIResource
crds []v1.CustomResourceDefinition
document *openapiv2.Document
discoveryErr error
documentErr error
crdErr error
wantError bool
desiredSchema map[string]*types.APISchema
}{
{
name: "crd listed in discovery, defined in crds",
groups: []schema.GroupVersion{{Group: "TestGroup", Version: "v1"}},
resources: map[schema.GroupVersion][]metav1.APIResource{
{Group: "TestGroup", Version: "v1"}: {
{
Name: "testResources",
SingularName: "testResource",
Kind: "TestResource",
Namespaced: true,
Verbs: metav1.Verbs{"get"},
},
},
},
crds: []v1.CustomResourceDefinition{
{
Status: v1.CustomResourceDefinitionStatus{
AcceptedNames: v1.CustomResourceDefinitionNames{
Plural: "testResources",
Singular: "testResource",
Kind: "TestResource",
},
},
Spec: v1.CustomResourceDefinitionSpec{
Group: "TestGroup",
Versions: []v1.CustomResourceDefinitionVersion{
{
Name: "v1",
AdditionalPrinterColumns: []v1.CustomResourceColumnDefinition{
{
Name: "TestColumn",
JSONPath: "TestPath",
Type: "TestType",
Format: "TestFormat",
},
},
Schema: &v1.CustomResourceValidation{
OpenAPIV3Schema: &v1.JSONSchemaProps{
Description: "Test Resource for unit tests",
Required: []string{"required"},
Properties: map[string]v1.JSONSchemaProps{
"required": {
Description: "Required Property",
Type: "string",
},
"numberField": {
Description: "NumberField - Not Required Property",
Type: "number",
},
"nullArrayField": {
Description: "ArrayField with no type - Not Required Property",
Type: "array",
},
"nullObjectField": {
Description: "ObjectField with no type - Not Required Property",
Type: "object",
},
"actions": {
Description: "Reserved field - Not Required Property",
Type: "string",
},
},
},
},
},
},
Names: v1.CustomResourceDefinitionNames{
Plural: "testResources",
Singular: "testResource",
Kind: "TestResource",
},
},
},
},
wantError: false,
desiredSchema: map[string]*types.APISchema{
"testgroup.v1.testresource": {
Schema: &wranglerSchema.Schema{
ID: "testgroup.v1.testresource",
PluralName: "TestGroup.v1.testResources",
Attributes: map[string]interface{}{
"group": "TestGroup",
"version": "v1",
"kind": "TestResource",
"resource": "testResources",
"verbs": []string{"get"},
"namespaced": true,
"columns": []table.Column{
{
Name: "TestColumn",
Field: "TestPath",
Type: "TestType",
Format: "TestFormat",
},
},
},
Description: "Test Resource for unit tests",
},
},
},
},
{
name: "listed in discovery, not defined in crds",
crds: []v1.CustomResourceDefinition{},
groups: []schema.GroupVersion{{Group: "TestGroup", Version: "v1"}},
resources: map[schema.GroupVersion][]metav1.APIResource{
{Group: "TestGroup", Version: "v1"}: {
{
Name: "testResources",
SingularName: "testResource",
Kind: "TestResource",
Namespaced: true,
Verbs: metav1.Verbs{"get"},
},
},
},
wantError: false,
desiredSchema: map[string]*types.APISchema{
"testgroup.v1.testresource": {
Schema: &wranglerSchema.Schema{
ID: "testgroup.v1.testresource",
PluralName: "TestGroup.v1.testResources",
Attributes: map[string]interface{}{
"group": "TestGroup",
"version": "v1",
"kind": "TestResource",
"resource": "testResources",
"verbs": []string{"get"},
"namespaced": true,
},
},
},
},
},
{
name: "defined in crds, but not in discovery",
groups: []schema.GroupVersion{},
resources: map[schema.GroupVersion][]metav1.APIResource{},
crds: []v1.CustomResourceDefinition{
{
Status: v1.CustomResourceDefinitionStatus{
AcceptedNames: v1.CustomResourceDefinitionNames{
Plural: "testResources",
Singular: "testResource",
Kind: "TestResource",
},
},
Spec: v1.CustomResourceDefinitionSpec{
Group: "TestGroup",
Versions: []v1.CustomResourceDefinitionVersion{
{
Name: "v1",
AdditionalPrinterColumns: []v1.CustomResourceColumnDefinition{
{
Name: "TestColumn",
JSONPath: "TestPath",
Type: "TestType",
Format: "TestFormat",
},
},
Schema: &v1.CustomResourceValidation{
OpenAPIV3Schema: &v1.JSONSchemaProps{
Description: "Test Resource for unit tests",
Required: []string{"required"},
Properties: map[string]v1.JSONSchemaProps{
"required": {
Description: "Required Property",
Type: "string",
},
"numberField": {
Description: "NumberField - Not Required Property",
Type: "number",
},
"nullArrayField": {
Description: "ArrayField with no type - Not Required Property",
Type: "array",
},
"nullObjectField": {
Description: "ObjectField with no type - Not Required Property",
Type: "object",
},
"actions": {
Description: "Reserved field - Not Required Property",
Type: "string",
},
},
},
},
},
},
Names: v1.CustomResourceDefinitionNames{
Plural: "testResources",
Singular: "testResource",
Kind: "TestResource",
},
},
},
},
wantError: false,
desiredSchema: map[string]*types.APISchema{},
},
{
name: "discovery error",
groups: []schema.GroupVersion{},
resources: map[schema.GroupVersion][]metav1.APIResource{},
discoveryErr: fmt.Errorf("server is down, can't use discovery"),
crds: []v1.CustomResourceDefinition{},
wantError: true,
desiredSchema: nil,
},
{
name: "crd error",
groups: []schema.GroupVersion{{Group: "TestGroup", Version: "v1"}},
resources: map[schema.GroupVersion][]metav1.APIResource{
{Group: "TestGroup", Version: "v1"}: {
{
Name: "testResources",
SingularName: "testResource",
Kind: "TestResource",
Namespaced: true,
Verbs: metav1.Verbs{"get"},
},
},
},
crdErr: fmt.Errorf("unable to use crd client, insufficient permissions"),
crds: []v1.CustomResourceDefinition{},
wantError: false,
desiredSchema: map[string]*types.APISchema{
"testgroup.v1.testresource": {
Schema: &wranglerSchema.Schema{
ID: "testgroup.v1.testresource",
PluralName: "TestGroup.v1.testResources",
Attributes: map[string]interface{}{
"group": "TestGroup",
"version": "v1",
"kind": "TestResource",
"resource": "testResources",
"verbs": []string{"get"},
"namespaced": true,
},
},
},
},
},
{
name: "adding descriptions",
groups: []schema.GroupVersion{{Group: "TestGroup", Version: "v1"}},
resources: map[schema.GroupVersion][]metav1.APIResource{
{Group: "TestGroup", Version: "v1"}: {
{
Name: "testResources",
SingularName: "testResource",
Kind: "TestResource",
Namespaced: true,
Verbs: metav1.Verbs{"get"},
},
},
},
crdErr: nil,
crds: []v1.CustomResourceDefinition{},
document: &openapiv2.Document{
Definitions: &openapiv2.Definitions{
AdditionalProperties: []*openapiv2.NamedSchema{&gvkSchema},
},
},
wantError: false,
desiredSchema: map[string]*types.APISchema{
"testgroup.v1.testresource": {
Schema: &wranglerSchema.Schema{
ID: "testgroup.v1.testresource",
Description: gvkSchema.Value.Description,
PluralName: "TestGroup.v1.testResources",
Attributes: map[string]interface{}{
"group": "TestGroup",
"version": "v1",
"kind": "TestResource",
"resource": "testResources",
"verbs": []string{"get"},
"namespaced": true,
},
},
},
},
},
{
name: "descriptions error",
groups: []schema.GroupVersion{{Group: "TestGroup", Version: "v1"}},
resources: map[schema.GroupVersion][]metav1.APIResource{
{Group: "TestGroup", Version: "v1"}: {
{
Name: "testResources",
SingularName: "testResource",
Kind: "TestResource",
Namespaced: true,
Verbs: metav1.Verbs{"get"},
},
},
},
crdErr: nil,
crds: []v1.CustomResourceDefinition{},
document: nil,
documentErr: fmt.Errorf("can't get document"),
wantError: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
testDiscovery := fakeDiscovery{}
for _, gvr := range test.groups {
gvr := gvr
testDiscovery.AddGroup(gvr.Group, gvr.Version, false)
}
testDiscovery.Document = test.document
testDiscovery.DocumentErr = test.documentErr
for gvr, resourceSlice := range test.resources {
for _, resource := range resourceSlice {
resource := resource
testDiscovery.AddResource(gvr.Group, gvr.Version, resource)
}
}
testDiscovery.GroupResourcesErr = test.discoveryErr
var crds *v1.CustomResourceDefinitionList
if test.crds != nil {
crds = &v1.CustomResourceDefinitionList{
Items: test.crds,
}
}
fakeClient := fake.NewMockNonNamespacedClientInterface[*v1.CustomResourceDefinition, *v1.CustomResourceDefinitionList](ctrl)
fakeClient.EXPECT().List(gomock.Any()).Return(crds, test.crdErr).AnyTimes()
schemas, err := ToSchemas(fakeClient, &testDiscovery)
if test.wantError {
assert.Error(t, err, "wanted error but didn't get one")
} else {
assert.NoError(t, err, "got an error but did not want one")
}
assert.Equal(t, test.desiredSchema, schemas, "did not get the desired schemas")
})
}
}
func TestGVKToVersionedSchemaID(t *testing.T) {
tests := []struct {
name string
gvk schema.GroupVersionKind
want string
}{
{
name: "basic gvk",
gvk: schema.GroupVersionKind{
Group: "TestGroup",
Version: "v1",
Kind: "TestKind",
},
want: "testgroup.v1.testkind",
},
{
name: "core resource",
gvk: schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "TestKind",
},
want: "core.v1.testkind",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.want, GVKToVersionedSchemaID(test.gvk))
})
}
}
func TestGVKToSchemaID(t *testing.T) {
tests := []struct {
name string
gvk schema.GroupVersionKind
want string
}{
{
name: "basic gvk",
gvk: schema.GroupVersionKind{
Group: "TestGroup",
Version: "v1",
Kind: "TestKind",
},
want: "testgroup.testkind",
},
{
name: "core resource",
gvk: schema.GroupVersionKind{
Group: "",
Version: "v1",
Kind: "TestKind",
},
want: "testkind",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.want, GVKToSchemaID(test.gvk))
})
}
}
func TestGVRToPluralName(t *testing.T) {
tests := []struct {
name string
gvr schema.GroupVersionResource
want string
}{
{
name: "basic gvk",
gvr: schema.GroupVersionResource{
Group: "TestGroup",
Version: "v1",
Resource: "TestResources",
},
want: "TestGroup.TestResources",
},
{
name: "core resource",
gvr: schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "TestResources",
},
want: "TestResources",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.want, GVRToPluralName(test.gvr))
})
}
}
func TestGetGVKForKind(t *testing.T) {
tests := []struct {
name string
kind *proto.Kind
wantGVK *schema.GroupVersionKind
}{
{
name: "basic kind",
kind: &proto.Kind{
BaseSchema: proto.BaseSchema{
Extensions: map[string]any{
gvkExtensionName: []any{
"some other extension",
map[any]any{
gvkExtensionGroup: "TestGroup",
gvkExtensionVersion: "v1",
gvkExtensionKind: "TestKind",
},
},
},
},
},
wantGVK: &schema.GroupVersionKind{
Group: "TestGroup",
Version: "v1",
Kind: "TestKind",
},
},
{
name: "kind missing gvkExtension",
kind: &proto.Kind{
BaseSchema: proto.BaseSchema{
Extensions: map[string]any{},
},
},
wantGVK: nil,
},
{
name: "kind missing gvk map",
kind: &proto.Kind{
BaseSchema: proto.BaseSchema{
Extensions: map[string]any{
gvkExtensionName: []any{"some value"},
},
},
},
wantGVK: nil,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, test.wantGVK, GetGVKForKind(test.kind))
})
}
}