-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
eaa28d9
commit 97bb45a
Showing
4 changed files
with
671 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
package definitions | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
openapi_v2 "github.com/google/gnostic/openapiv2" | ||
"github.com/rancher/apiserver/pkg/apierror" | ||
"github.com/rancher/apiserver/pkg/types" | ||
wschemas "github.com/rancher/wrangler/pkg/schemas" | ||
"github.com/stretchr/testify/require" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apimachinery/pkg/version" | ||
"k8s.io/client-go/discovery" | ||
"k8s.io/client-go/openapi" | ||
restclient "k8s.io/client-go/rest" | ||
) | ||
|
||
var globalRoleObject = types.APIObject{ | ||
ID: "management.cattle.io.globalrole", | ||
Type: "schemaDefinition", | ||
Object: schemaDefinition{ | ||
DefinitionType: "io.cattle.management.v2.GlobalRole", | ||
Definitions: map[string]definition{ | ||
"io.cattle.management.v2.GlobalRole": { | ||
ResourceFields: map[string]definitionField{ | ||
"apiVersion": { | ||
Type: "string", | ||
Description: "The APIVersion of this resource", | ||
}, | ||
"kind": { | ||
Type: "string", | ||
Description: "The kind", | ||
}, | ||
"metadata": { | ||
Type: "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", | ||
Description: "The metadata", | ||
}, | ||
"spec": { | ||
Type: "io.cattle.management.v2.GlobalRole.spec", Description: "The spec for the project", | ||
}, | ||
}, | ||
Type: "io.cattle.management.v2.GlobalRole", | ||
Description: "A Global Role V2 provides Global Permissions in Rancher", | ||
}, | ||
"io.cattle.management.v2.GlobalRole.spec": { | ||
ResourceFields: map[string]definitionField{ | ||
"clusterName": { | ||
Type: "string", | ||
Description: "The name of the cluster", | ||
Required: true, | ||
}, | ||
"displayName": { | ||
Type: "string", | ||
Description: "The UI readable name", | ||
Required: true, | ||
}, | ||
"newField": { | ||
Type: "string", | ||
Description: "A new field not present in v1", | ||
}, | ||
"notRequired": { | ||
Type: "boolean", | ||
Description: "Some field that isn't required", | ||
}, | ||
}, | ||
Type: "io.cattle.management.v2.GlobalRole.spec", | ||
Description: "The spec for the project", | ||
}, | ||
"io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { | ||
ResourceFields: map[string]definitionField{ | ||
"annotations": { | ||
Type: "map", | ||
SubType: "string", | ||
Description: "annotations of the resource", | ||
}, | ||
"name": { | ||
Type: "string", | ||
SubType: "", | ||
Description: "name of the resource", | ||
}, | ||
}, | ||
Type: "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", | ||
Description: "Object Metadata", | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
func TestByID(t *testing.T) { | ||
schemas := types.EmptyAPISchemas() | ||
addBaseSchema := func(names ...string) { | ||
for _, name := range names { | ||
schemas.MustAddSchema(types.APISchema{ | ||
Schema: &wschemas.Schema{ | ||
ID: name, | ||
CollectionMethods: []string{"get"}, | ||
ResourceMethods: []string{"get"}, | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
intPtr := func(input int) *int { | ||
return &input | ||
} | ||
|
||
addBaseSchema("management.cattle.io.globalrole", "management.cattle.io.missingfrommodel") | ||
|
||
tests := []struct { | ||
name string | ||
schemaName string | ||
needsRefresh bool | ||
openapiError error | ||
serverGroupsResourcesErr error | ||
useBadOpenApiDoc bool | ||
unparseableGV bool | ||
wantObject *types.APIObject | ||
wantError bool | ||
wantErrorCode *int | ||
}{ | ||
{ | ||
name: "global role definition", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
wantObject: &globalRoleObject, | ||
}, | ||
{ | ||
name: "missing definition", | ||
schemaName: "management.cattle.io.cluster", | ||
needsRefresh: true, | ||
wantError: true, | ||
wantErrorCode: intPtr(404), | ||
}, | ||
{ | ||
name: "not refreshed", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: false, | ||
wantError: true, | ||
wantErrorCode: intPtr(503), | ||
}, | ||
{ | ||
name: "missing from model", | ||
schemaName: "management.cattle.io.missingfrommodel", | ||
needsRefresh: true, | ||
wantError: true, | ||
wantErrorCode: intPtr(503), | ||
}, | ||
{ | ||
name: "refresh error - openapi doc unavailable", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
openapiError: fmt.Errorf("server unavailable"), | ||
wantError: true, | ||
wantErrorCode: intPtr(500), | ||
}, | ||
{ | ||
name: "refresh error - unable to parse openapi doc", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
useBadOpenApiDoc: true, | ||
wantError: true, | ||
wantErrorCode: intPtr(500), | ||
}, | ||
{ | ||
name: "refresh error - unable to retrieve groups and resources", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
serverGroupsResourcesErr: fmt.Errorf("server not available"), | ||
wantError: true, | ||
wantErrorCode: intPtr(500), | ||
}, | ||
{ | ||
name: "refresh error - unable to retrieve all groups and resources", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
serverGroupsResourcesErr: &discovery.ErrGroupDiscoveryFailed{ | ||
Groups: map[schema.GroupVersion]error{ | ||
{ | ||
Group: "other.cattle.io", | ||
Version: "v1", | ||
}: fmt.Errorf("some group error"), | ||
}, | ||
}, | ||
wantError: true, | ||
wantErrorCode: intPtr(500), | ||
}, | ||
{ | ||
name: "refresh error - unparesable gv", | ||
schemaName: "management.cattle.io.globalrole", | ||
needsRefresh: true, | ||
unparseableGV: true, | ||
wantError: true, | ||
wantErrorCode: intPtr(500), | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
test := test | ||
t.Run(test.name, func(t *testing.T) { | ||
t.Parallel() | ||
client, err := buildDefaultDiscovery() | ||
client.DocumentErr = test.openapiError | ||
client.GroupResourcesErr = test.serverGroupsResourcesErr | ||
if test.useBadOpenApiDoc { | ||
schema := client.Document.Definitions.AdditionalProperties[0] | ||
schema.Value.Type = &openapi_v2.TypeItem{ | ||
Value: []string{"multiple", "entries"}, | ||
} | ||
} | ||
if test.unparseableGV { | ||
client.Resources = append(client.Resources, &metav1.APIResourceList{ | ||
GroupVersion: "not/parse/able", | ||
}) | ||
} | ||
require.Nil(t, err) | ||
handler := SchemaDefinitionHandler{ | ||
client: client, | ||
} | ||
if !test.needsRefresh { | ||
handler.lastRefresh = time.Now() | ||
handler.refreshStale = time.Minute * 1 | ||
} | ||
request := types.APIRequest{ | ||
Schemas: schemas, | ||
Name: test.schemaName, | ||
} | ||
response, err := handler.ByIDHandler(&request) | ||
if test.wantError { | ||
require.Error(t, err) | ||
if test.wantErrorCode != nil { | ||
require.True(t, apierror.IsAPIError(err)) | ||
apiErr, _ := err.(*apierror.APIError) | ||
require.Equal(t, *test.wantErrorCode, apiErr.Code.Status) | ||
} | ||
} else { | ||
require.NoError(t, err) | ||
require.Equal(t, *test.wantObject, response) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func buildDefaultDiscovery() (*fakeDiscovery, error) { | ||
document, err := openapi_v2.ParseDocument([]byte(openapi_raw)) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to parse openapi document %w", err) | ||
} | ||
groups := []*metav1.APIGroup{ | ||
{ | ||
Name: "management.cattle.io", | ||
PreferredVersion: metav1.GroupVersionForDiscovery{ | ||
Version: "v2", | ||
}, | ||
}, | ||
} | ||
resources := []*metav1.APIResourceList{ | ||
{ | ||
GroupVersion: schema.GroupVersion{ | ||
Group: "management.cattle.io", | ||
Version: "v2", | ||
}.String(), | ||
APIResources: []metav1.APIResource{ | ||
{ | ||
Group: "management.cattle.io", | ||
Kind: "GlobalRole", | ||
Version: "v2", | ||
}, | ||
}, | ||
}, | ||
{ | ||
GroupVersion: schema.GroupVersion{ | ||
Group: "management.cattle.io", | ||
Version: "v1", | ||
}.String(), | ||
APIResources: []metav1.APIResource{ | ||
{ | ||
Group: "management.cattle.io", | ||
Kind: "GlobalRole", | ||
Version: "v2", | ||
}, | ||
}, | ||
}, | ||
nil, | ||
} | ||
return &fakeDiscovery{ | ||
Groups: groups, | ||
Resources: resources, | ||
Document: document, | ||
}, nil | ||
} | ||
|
||
type fakeDiscovery struct { | ||
Groups []*metav1.APIGroup | ||
Resources []*metav1.APIResourceList | ||
Document *openapi_v2.Document | ||
GroupResourcesErr error | ||
DocumentErr error | ||
} | ||
|
||
// ServerGroupsAndResources is the only method we actually need for the test - just returns what is on the struct | ||
func (f *fakeDiscovery) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { | ||
return f.Groups, f.Resources, f.GroupResourcesErr | ||
} | ||
|
||
// The rest of these methods are just here to conform to discovery.DiscoveryInterface | ||
func (f *fakeDiscovery) RESTClient() restclient.Interface { return nil } | ||
func (f *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) { return nil, nil } | ||
func (f *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) { | ||
return nil, nil | ||
} | ||
func (f *fakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) { | ||
return nil, nil | ||
} | ||
func (f *fakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) { | ||
return nil, nil | ||
} | ||
func (f *fakeDiscovery) ServerVersion() (*version.Info, error) { return nil, nil } | ||
func (f *fakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) { | ||
return f.Document, f.DocumentErr | ||
} | ||
func (f *fakeDiscovery) OpenAPIV3() openapi.Client { return nil } | ||
func (f *fakeDiscovery) WithLegacy() discovery.DiscoveryInterface { return f } |
Oops, something went wrong.