mirror of
https://github.com/rancher/steve.git
synced 2025-04-27 02:51:10 +00:00
573 lines
15 KiB
Go
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))
|
|
})
|
|
}
|
|
|
|
}
|