1
0
mirror of https://github.com/rancher/steve.git synced 2025-08-31 06:46:25 +00:00

added delegate error / improve delegate coverage / add new mocks

This commit is contained in:
Felipe C. Gehrke
2024-11-07 20:11:53 -03:00
parent b2f2bab3c4
commit 887fc05f08
7 changed files with 1399 additions and 13 deletions

View File

@@ -239,20 +239,22 @@ func InstallStore[T runtime.Object, TList runtime.Object](
apiGroup.VersionedResourcesStorageMap[gvk.Version] = make(map[string]rest.Storage)
}
delegate := &delegate[T, TList]{
scheme: s.scheme,
delegate := &delegateError[T, TList]{
inner: &delegate[T, TList]{
scheme: s.scheme,
t: t,
tList: tList,
singularName: singularName,
gvk: gvk,
gvr: schema.GroupVersionResource{
Group: gvk.Group,
Version: gvk.Version,
Resource: resourceName,
t: t,
tList: tList,
singularName: singularName,
gvk: gvk,
gvr: schema.GroupVersionResource{
Group: gvk.Group,
Version: gvk.Version,
Resource: resourceName,
},
authorizer: s.authorizer,
store: store,
},
authorizer: s.authorizer,
store: store,
}
apiGroup.VersionedResourcesStorageMap[gvk.Version][resourceName] = delegate

View File

@@ -2,6 +2,7 @@ package ext
import (
"context"
"errors"
"fmt"
"sync"
@@ -16,6 +17,10 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
)
var (
errMissingUserInfo error = errors.New("missing user info")
)
// delegate is the bridge between k8s.io/apiserver's [rest.Storage] interface and
// our own Store interface we want developers to use
//
@@ -328,7 +333,7 @@ func (s *delegate[T, TList]) GetSingularName() string {
func (s *delegate[T, TList]) makeContext(parentCtx context.Context) (Context, error) {
userInfo, ok := request.UserFrom(parentCtx)
if !ok {
return Context{}, fmt.Errorf("missing user info")
return Context{}, errMissingUserInfo
}
ctx := Context{

110
pkg/ext/delegate_error.go Normal file
View File

@@ -0,0 +1,110 @@
package ext
import (
"context"
"k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/registry/rest"
)
// delegateError wraps an inner delegate and converts unknown errors.
type delegateError[T runtime.Object, TList runtime.Object] struct {
inner *delegate[T, TList]
}
func (d *delegateError[T, TList]) convertError(err error) error {
if _, ok := err.(errors.APIStatus); ok {
return err
}
return errors.NewInternalError(err)
}
func (d *delegateError[T, TList]) New() runtime.Object {
return d.inner.New()
}
func (d *delegateError[T, TList]) Destroy() {
d.inner.Destroy()
}
func (d *delegateError[T, TList]) NewList() runtime.Object {
return d.inner.NewList()
}
func (d *delegateError[T, TList]) List(parentCtx context.Context, internaloptions *metainternalversion.ListOptions) (runtime.Object, error) {
result, err := d.inner.List(parentCtx, internaloptions)
if err != nil {
return nil, d.convertError(err)
}
return result, nil
}
func (d *delegateError[T, TList]) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
result, err := d.inner.ConvertToTable(ctx, object, tableOptions)
if err != nil {
return nil, d.convertError(err)
}
return result, nil
}
func (d *delegateError[T, TList]) Get(parentCtx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
result, err := d.inner.Get(parentCtx, name, options)
if err != nil {
return nil, d.convertError(err)
}
return result, nil
}
func (d *delegateError[T, TList]) Delete(parentCtx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
result, completed, err := d.inner.Delete(parentCtx, name, deleteValidation, options)
if err != nil {
return nil, false, d.convertError(err)
}
return result, completed, nil
}
func (d *delegateError[T, TList]) Create(parentCtx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
result, err := d.inner.Create(parentCtx, obj, createValidation, options)
if err != nil {
return nil, d.convertError(err)
}
return result, nil
}
func (d *delegateError[T, TList]) Update(parentCtx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
result, created, err := d.inner.Update(parentCtx, name, objInfo, createValidation, updateValidation, forceAllowCreate, options)
if err != nil {
return nil, false, d.convertError(err)
}
return result, created, nil
}
func (d *delegateError[T, TList]) Watch(parentCtx context.Context, internaloptions *metainternalversion.ListOptions) (watch.Interface, error) {
result, err := d.inner.Watch(parentCtx, internaloptions)
if err != nil {
return nil, d.convertError(err)
}
return result, nil
}
func (d *delegateError[T, TList]) GroupVersionKind(groupVersion schema.GroupVersion) schema.GroupVersionKind {
return d.inner.GroupVersionKind(groupVersion)
}
func (d *delegateError[T, TList]) NamespaceScoped() bool {
return d.inner.NamespaceScoped()
}
func (d *delegateError[T, TList]) Kind() string {
return d.inner.Kind()
}
func (d *delegateError[T, TList]) GetSingularName() string {
return d.inner.GetSingularName()
}

View File

@@ -0,0 +1,60 @@
package ext
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestDelegateError_convertError(t *testing.T) {
tests := []struct {
name string
input error
output error
}{
{
name: "api status error",
input: &apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
output: &apierrors.StatusError{
ErrStatus: metav1.Status{
Code: http.StatusNotFound,
Reason: metav1.StatusReasonNotFound,
},
},
},
{
name: "generic error",
input: assert.AnError,
output: &apierrors.StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusInternalServerError,
Reason: metav1.StatusReasonInternalError,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{{Message: assert.AnError.Error()}},
},
Message: fmt.Sprintf("Internal error occurred: %v", assert.AnError),
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
delegateError := delegateError[*TestType, *TestTypeList]{
inner: &delegate[*TestType, *TestTypeList]{},
}
output := delegateError.convertError(tt.input)
assert.Equal(t, tt.output, output)
})
}
}

1012
pkg/ext/delegate_test.go Normal file

File diff suppressed because it is too large Load Diff

66
pkg/ext/rest_mock.go Normal file
View File

@@ -0,0 +1,66 @@
// Code generated by MockGen. DO NOT EDIT.
// Package ext is a generated GoMock package.
package ext
import (
context "context"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// MockUpdatedObjectInfo is a mock of UpdatedObjectInfo interface.
type MockUpdatedObjectInfo struct {
ctrl *gomock.Controller
recorder *MockUpdatedObjectInfoMockRecorder
isgomock struct{}
}
// MockUpdatedObjectInfoMockRecorder is the mock recorder for MockUpdatedObjectInfo.
type MockUpdatedObjectInfoMockRecorder struct {
mock *MockUpdatedObjectInfo
}
// NewMockUpdatedObjectInfo creates a new mock instance.
func NewMockUpdatedObjectInfo(ctrl *gomock.Controller) *MockUpdatedObjectInfo {
mock := &MockUpdatedObjectInfo{ctrl: ctrl}
mock.recorder = &MockUpdatedObjectInfoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUpdatedObjectInfo) EXPECT() *MockUpdatedObjectInfoMockRecorder {
return m.recorder
}
// Preconditions mocks base method.
func (m *MockUpdatedObjectInfo) Preconditions() *v1.Preconditions {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Preconditions")
ret0, _ := ret[0].(*v1.Preconditions)
return ret0
}
// Preconditions indicates an expected call of Preconditions.
func (mr *MockUpdatedObjectInfoMockRecorder) Preconditions() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Preconditions", reflect.TypeOf((*MockUpdatedObjectInfo)(nil).Preconditions))
}
// UpdatedObject mocks base method.
func (m *MockUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runtime.Object) (runtime.Object, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdatedObject", ctx, oldObj)
ret0, _ := ret[0].(runtime.Object)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdatedObject indicates an expected call of UpdatedObject.
func (mr *MockUpdatedObjectInfoMockRecorder) UpdatedObject(ctx, oldObj any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatedObject", reflect.TypeOf((*MockUpdatedObjectInfo)(nil).UpdatedObject), ctx, oldObj)
}

131
pkg/ext/store_mock.go Normal file
View File

@@ -0,0 +1,131 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/ext/store.go
//
// Generated by this command:
//
// mockgen -source=./pkg/ext/store.go -destination=./pkg/ext/store_mock.go -package=ext
//
// Package ext is a generated GoMock package.
package ext
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// MockStore is a mock of Store interface.
type MockStore[T runtime.Object, TList runtime.Object] struct {
ctrl *gomock.Controller
recorder *MockStoreMockRecorder[T, TList]
isgomock struct{}
}
// MockStoreMockRecorder is the mock recorder for MockStore.
type MockStoreMockRecorder[T runtime.Object, TList runtime.Object] struct {
mock *MockStore[T, TList]
}
// NewMockStore creates a new mock instance.
func NewMockStore[T runtime.Object, TList runtime.Object](ctrl *gomock.Controller) *MockStore[T, TList] {
mock := &MockStore[T, TList]{ctrl: ctrl}
mock.recorder = &MockStoreMockRecorder[T, TList]{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockStore[T, TList]) EXPECT() *MockStoreMockRecorder[T, TList] {
return m.recorder
}
// Create mocks base method.
func (m *MockStore[T, TList]) Create(ctx Context, obj T, opts *v1.CreateOptions) (T, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, obj, opts)
ret0, _ := ret[0].(T)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Create indicates an expected call of Create.
func (mr *MockStoreMockRecorder[T, TList]) Create(ctx, obj, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockStore[T, TList])(nil).Create), ctx, obj, opts)
}
// Delete mocks base method.
func (m *MockStore[T, TList]) Delete(ctx Context, name string, opts *v1.DeleteOptions) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, name, opts)
ret0, _ := ret[0].(error)
return ret0
}
// Delete indicates an expected call of Delete.
func (mr *MockStoreMockRecorder[T, TList]) Delete(ctx, name, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockStore[T, TList])(nil).Delete), ctx, name, opts)
}
// Get mocks base method.
func (m *MockStore[T, TList]) Get(ctx Context, name string, opts *v1.GetOptions) (T, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, name, opts)
ret0, _ := ret[0].(T)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockStoreMockRecorder[T, TList]) Get(ctx, name, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStore[T, TList])(nil).Get), ctx, name, opts)
}
// List mocks base method.
func (m *MockStore[T, TList]) List(ctx Context, opts *v1.ListOptions) (TList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, opts)
ret0, _ := ret[0].(TList)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// List indicates an expected call of List.
func (mr *MockStoreMockRecorder[T, TList]) List(ctx, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockStore[T, TList])(nil).List), ctx, opts)
}
// Update mocks base method.
func (m *MockStore[T, TList]) Update(ctx Context, obj T, opts *v1.UpdateOptions) (T, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, obj, opts)
ret0, _ := ret[0].(T)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
func (mr *MockStoreMockRecorder[T, TList]) Update(ctx, obj, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStore[T, TList])(nil).Update), ctx, obj, opts)
}
// Watch mocks base method.
func (m *MockStore[T, TList]) Watch(ctx Context, opts *v1.ListOptions) (<-chan WatchEvent[T], error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Watch", ctx, opts)
ret0, _ := ret[0].(<-chan WatchEvent[T])
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Watch indicates an expected call of Watch.
func (mr *MockStoreMockRecorder[T, TList]) Watch(ctx, opts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockStore[T, TList])(nil).Watch), ctx, opts)
}