diff --git a/apiserver/organization/mock/namespace.go b/apiserver/organization/mock/namespace.go new file mode 100644 index 00000000..94330504 --- /dev/null +++ b/apiserver/organization/mock/namespace.go @@ -0,0 +1,127 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: namespace.go + +// Package mock_organization is a generated GoMock package. +package mock_organization + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "k8s.io/api/core/v1" + internalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" + v10 "k8s.io/apimachinery/pkg/apis/meta/v1" + watch "k8s.io/apimachinery/pkg/watch" +) + +// MocknamespaceProvider is a mock of namespaceProvider interface. +type MocknamespaceProvider struct { + ctrl *gomock.Controller + recorder *MocknamespaceProviderMockRecorder +} + +// MocknamespaceProviderMockRecorder is the mock recorder for MocknamespaceProvider. +type MocknamespaceProviderMockRecorder struct { + mock *MocknamespaceProvider +} + +// NewMocknamespaceProvider creates a new mock instance. +func NewMocknamespaceProvider(ctrl *gomock.Controller) *MocknamespaceProvider { + mock := &MocknamespaceProvider{ctrl: ctrl} + mock.recorder = &MocknamespaceProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MocknamespaceProvider) EXPECT() *MocknamespaceProviderMockRecorder { + return m.recorder +} + +// CreateNamespace mocks base method. +func (m *MocknamespaceProvider) CreateNamespace(ctx context.Context, ns *v1.Namespace, options *v10.CreateOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNamespace", ctx, ns, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNamespace indicates an expected call of CreateNamespace. +func (mr *MocknamespaceProviderMockRecorder) CreateNamespace(ctx, ns, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNamespace", reflect.TypeOf((*MocknamespaceProvider)(nil).CreateNamespace), ctx, ns, options) +} + +// DeleteNamespace mocks base method. +func (m *MocknamespaceProvider) DeleteNamespace(ctx context.Context, name string, options *v10.DeleteOptions) (*v1.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNamespace", ctx, name, options) + ret0, _ := ret[0].(*v1.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteNamespace indicates an expected call of DeleteNamespace. +func (mr *MocknamespaceProviderMockRecorder) DeleteNamespace(ctx, name, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNamespace", reflect.TypeOf((*MocknamespaceProvider)(nil).DeleteNamespace), ctx, name, options) +} + +// GetNamespace mocks base method. +func (m *MocknamespaceProvider) GetNamespace(ctx context.Context, name string, options *v10.GetOptions) (*v1.Namespace, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNamespace", ctx, name, options) + ret0, _ := ret[0].(*v1.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNamespace indicates an expected call of GetNamespace. +func (mr *MocknamespaceProviderMockRecorder) GetNamespace(ctx, name, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNamespace", reflect.TypeOf((*MocknamespaceProvider)(nil).GetNamespace), ctx, name, options) +} + +// ListNamespaces mocks base method. +func (m *MocknamespaceProvider) ListNamespaces(ctx context.Context, options *internalversion.ListOptions) (*v1.NamespaceList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNamespaces", ctx, options) + ret0, _ := ret[0].(*v1.NamespaceList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNamespaces indicates an expected call of ListNamespaces. +func (mr *MocknamespaceProviderMockRecorder) ListNamespaces(ctx, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNamespaces", reflect.TypeOf((*MocknamespaceProvider)(nil).ListNamespaces), ctx, options) +} + +// UpdateNamespace mocks base method. +func (m *MocknamespaceProvider) UpdateNamespace(ctx context.Context, ns *v1.Namespace, options *v10.UpdateOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateNamespace", ctx, ns, options) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNamespace indicates an expected call of UpdateNamespace. +func (mr *MocknamespaceProviderMockRecorder) UpdateNamespace(ctx, ns, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNamespace", reflect.TypeOf((*MocknamespaceProvider)(nil).UpdateNamespace), ctx, ns, options) +} + +// WatchNamespaces mocks base method. +func (m *MocknamespaceProvider) WatchNamespaces(ctx context.Context, options *internalversion.ListOptions) (watch.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WatchNamespaces", ctx, options) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WatchNamespaces indicates an expected call of WatchNamespaces. +func (mr *MocknamespaceProviderMockRecorder) WatchNamespaces(ctx, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchNamespaces", reflect.TypeOf((*MocknamespaceProvider)(nil).WatchNamespaces), ctx, options) +} diff --git a/apiserver/organization/namespace.go b/apiserver/organization/namespace.go index 84d1ba1f..589109ba 100644 --- a/apiserver/organization/namespace.go +++ b/apiserver/organization/namespace.go @@ -13,12 +13,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +// namespaceProvider is an abstraction for interacting with the Kubernetes API +//go:generate mockgen -source=$GOFILE -destination=./mock/$GOFILE +type namespaceProvider interface { + GetNamespace(ctx context.Context, name string, options *metav1.GetOptions) (*corev1.Namespace, error) + DeleteNamespace(ctx context.Context, name string, options *metav1.DeleteOptions) (*corev1.Namespace, error) + CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error + UpdateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error + ListNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (*corev1.NamespaceList, error) + WatchNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) +} + type loopbackNamespaceProvider struct { initOnce sync.Once client client.WithWatch } func (p *loopbackNamespaceProvider) init() error { + // The LoopbackMasterClientConfig is initialized lazily by the runtime + // We initialize the client once from which ever method is called first var err error p.initOnce.Do(func() { if p.client == nil { @@ -28,7 +41,7 @@ func (p *loopbackNamespaceProvider) init() error { return err } -func (p *loopbackNamespaceProvider) getNamespace(ctx context.Context, name string, options *metav1.GetOptions) (*corev1.Namespace, error) { +func (p *loopbackNamespaceProvider) GetNamespace(ctx context.Context, name string, options *metav1.GetOptions) (*corev1.Namespace, error) { err := p.init() if err != nil { return nil, err @@ -38,7 +51,7 @@ func (p *loopbackNamespaceProvider) getNamespace(ctx context.Context, name strin return &ns, err } -func (p *loopbackNamespaceProvider) deleteNamespace(ctx context.Context, name string, options *metav1.DeleteOptions) (*corev1.Namespace, error) { +func (p *loopbackNamespaceProvider) DeleteNamespace(ctx context.Context, name string, options *metav1.DeleteOptions) (*corev1.Namespace, error) { err := p.init() if err != nil { return nil, err @@ -51,7 +64,7 @@ func (p *loopbackNamespaceProvider) deleteNamespace(ctx context.Context, name st return &ns, err } -func (p *loopbackNamespaceProvider) createNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error { +func (p *loopbackNamespaceProvider) CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error { err := p.init() if err != nil { return err @@ -61,7 +74,7 @@ func (p *loopbackNamespaceProvider) createNamespace(ctx context.Context, ns *cor }) } -func (p *loopbackNamespaceProvider) updateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error { +func (p *loopbackNamespaceProvider) UpdateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error { err := p.init() if err != nil { return err @@ -71,7 +84,7 @@ func (p *loopbackNamespaceProvider) updateNamespace(ctx context.Context, ns *cor }) } -func (p *loopbackNamespaceProvider) listNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (*corev1.NamespaceList, error) { +func (p *loopbackNamespaceProvider) ListNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (*corev1.NamespaceList, error) { err := p.init() if err != nil { return nil, err @@ -89,7 +102,7 @@ func (p *loopbackNamespaceProvider) listNamespaces(ctx context.Context, options return &nl, nil } -func (p *loopbackNamespaceProvider) watchNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { +func (p *loopbackNamespaceProvider) WatchNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) { err := p.init() if err != nil { return nil, err diff --git a/apiserver/organization/organization.go b/apiserver/organization/organization.go index 9b72c56b..22272c4b 100644 --- a/apiserver/organization/organization.go +++ b/apiserver/organization/organization.go @@ -2,15 +2,18 @@ package organization import ( "context" + "errors" "fmt" orgv1 "github.com/appuio/control-api/apis/organization/v1" corev1 "k8s.io/api/core/v1" + apierrors "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/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/watch" genericregistry "k8s.io/apiserver/pkg/registry/generic" @@ -30,14 +33,6 @@ func New() restbuilder.ResourceHandlerProvider { type organizationStorage struct { namepaces namespaceProvider } -type namespaceProvider interface { - getNamespace(ctx context.Context, name string, options *metav1.GetOptions) (*corev1.Namespace, error) - deleteNamespace(ctx context.Context, name string, options *metav1.DeleteOptions) (*corev1.Namespace, error) - createNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error - updateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error - listNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (*corev1.NamespaceList, error) - watchNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) -} func (s organizationStorage) New() runtime.Object { return &orgv1.Organization{} @@ -52,13 +47,17 @@ func (s *organizationStorage) NamespaceScoped() bool { var _ rest.Getter = &organizationStorage{} func (s *organizationStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - ns, err := s.namepaces.getNamespace(ctx, name, options) + org := &orgv1.Organization{} + ns, err := s.namepaces.GetNamespace(ctx, name, options) if err != nil { - return nil, err + return nil, convertNamespaceError(err) } - - // TODO(glrf) Check that this is actually an organization and not a random namespace - return orgv1.NewOrganizationFromNS(ns), nil + org = orgv1.NewOrganizationFromNS(ns) + if org == nil { + // This namespace is not an organization + return nil, apierrors.NewNotFound(org.GetGroupVersionResource().GroupResource(), name) + } + return org, nil } var _ rest.Creater = &organizationStorage{} @@ -74,8 +73,8 @@ func (s *organizationStorage) Create(ctx context.Context, obj runtime.Object, cr return nil, err } - if err := s.namepaces.createNamespace(ctx, org.ToNamespace(), options); err != nil { - return nil, err + if err := s.namepaces.CreateNamespace(ctx, org.ToNamespace(), options); err != nil { + return nil, convertNamespaceError(err) } return org, nil } @@ -92,7 +91,7 @@ func (s *organizationStorage) List(ctx context.Context, options *metainternalver return nil, err } options.LabelSelector = options.LabelSelector.Add(*orgNamspace) - namespaces, err := s.namepaces.listNamespaces(ctx, options) + namespaces, err := s.namepaces.ListNamespaces(ctx, options) if err != nil { return nil, err } @@ -141,7 +140,7 @@ func (s *organizationStorage) Update(ctx context.Context, name string, objInfo r } } - return newOrg, false, s.namepaces.updateNamespace(ctx, newOrg.ToNamespace(), options) + return newOrg, false, s.namepaces.UpdateNamespace(ctx, newOrg.ToNamespace(), options) } var _ rest.GracefulDeleter = &organizationStorage{} @@ -159,7 +158,7 @@ func (s *organizationStorage) Delete(ctx context.Context, name string, deleteVal } } - ns, err := s.namepaces.deleteNamespace(ctx, name, options) + ns, err := s.namepaces.DeleteNamespace(ctx, name, options) return orgv1.NewOrganizationFromNS(ns), false, err } @@ -172,7 +171,7 @@ func (s *organizationStorage) Watch(ctx context.Context, options *metainternalve } options.LabelSelector = options.LabelSelector.Add(*orgNamspace) - nsWatcher, err := s.namepaces.watchNamespaces(ctx, options) + nsWatcher, err := s.namepaces.WatchNamespaces(ctx, options) if err != nil { return nil, err } @@ -194,3 +193,21 @@ func (s *organizationStorage) Watch(ctx context.Context, options *metainternalve return in, true }), nil } + +func convertNamespaceError(err error) error { + groupResource := schema.GroupResource{ + Group: orgv1.GroupVersion.Group, + Resource: "organizations", + } + statusErr := &apierrors.StatusError{} + + if errors.As(err, &statusErr) { + switch { + case apierrors.IsNotFound(err): + return apierrors.NewNotFound(groupResource, statusErr.ErrStatus.Details.Name) + case apierrors.IsAlreadyExists(err): + return apierrors.NewAlreadyExists(groupResource, statusErr.ErrStatus.Details.Name) + } + } + return err +} diff --git a/apiserver/organization/organization_test.go b/apiserver/organization/organization_test.go new file mode 100644 index 00000000..58714ccf --- /dev/null +++ b/apiserver/organization/organization_test.go @@ -0,0 +1,164 @@ +package organization + +import ( + "context" + "testing" + + mock "github.com/appuio/control-api/apiserver/organization/mock" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + orgv1 "github.com/appuio/control-api/apis/organization/v1" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestOrganizationStorage_Get(t *testing.T) { + tests := map[string]struct { + name string + + namespace *corev1.Namespace + namespaceErr error + + organization *orgv1.Organization + err error + }{ + "GivenOrgNS_ThenOrg": { + name: "foo", + namespace: fooNs, + organization: fooOrg, + }, + "GivenErrNotFound_ThenErrNotFound": { + name: "not-found", + namespaceErr: apierrors.NewNotFound(schema.GroupResource{ + Resource: "namepaces", + }, "not-found"), + err: apierrors.NewNotFound(schema.GroupResource{ + Group: orgv1.GroupVersion.Group, + Resource: "organizations", + }, "not-found"), + }, + "GivenNonOrgNs_ThenErrNotFound": { + name: "default", + namespace: defaultNs, + err: apierrors.NewNotFound(schema.GroupResource{ + Group: orgv1.GroupVersion.Group, + Resource: "organizations", + }, "default"), + }, + } + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mnp := mock.NewMocknamespaceProvider(ctrl) + os := organizationStorage{ + namepaces: mnp, + } + + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + mnp.EXPECT(). + GetNamespace(gomock.Any(), tc.name, gomock.Any()). + Return(tc.namespace, tc.namespaceErr). + Times(1) + org, err := os.Get(context.TODO(), tc.name, nil) + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.organization, org) + }) + } +} + +func TestOrganizationStorage_Create(t *testing.T) { + tests := map[string]struct { + organizationIn *orgv1.Organization + + namespaceErr error + + organizationOut *orgv1.Organization + err error + }{ + "GivenCreateOrg_ThenSuccess": { + organizationIn: fooOrg, + organizationOut: fooOrg, + }, + "GivenNsExists_ThenFail": { + organizationIn: fooOrg, + namespaceErr: apierrors.NewAlreadyExists(schema.GroupResource{ + Resource: "namepaces", + }, "foo"), + err: apierrors.NewAlreadyExists(schema.GroupResource{ + Group: orgv1.GroupVersion.Group, + Resource: "organizations", + }, "foo"), + }, + } + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mnp := mock.NewMocknamespaceProvider(ctrl) + os := organizationStorage{ + namepaces: mnp, + } + + for n, tc := range tests { + t.Run(n, func(t *testing.T) { + mnp.EXPECT(). + CreateNamespace(gomock.Any(), gomock.Any(), gomock.Any()). + Return(tc.namespaceErr). + Times(1) + + nopValidate := func(ctx context.Context, obj runtime.Object) error { + return nil + } + org, err := os.Create(context.TODO(), tc.organizationIn, nopValidate, nil) + + if tc.err != nil { + assert.EqualError(t, err, tc.err.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, tc.organizationOut, org) + }) + } +} + +// Some common test organizations +var ( + fooOrg = &orgv1.Organization{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{}, + Annotations: map[string]string{}, + }, + Spec: orgv1.OrganizationSpec{ + DisplayName: "Foo Inc.", + }, + } + fooNs = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Labels: map[string]string{ + orgv1.TypeKey: orgv1.OrgType, + }, + Annotations: map[string]string{ + orgv1.DisplayNameKey: "Foo Inc.", + }, + }, + } + defaultNs = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + } +) diff --git a/go.mod b/go.mod index f7b98d80..f59513f7 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.17 require ( github.com/go-logr/logr v1.2.2 + github.com/golang/mock v1.6.0 github.com/stretchr/testify v1.7.0 go.uber.org/zap v1.19.1 + k8s.io/api v0.23.0 k8s.io/apimachinery v0.23.1 + k8s.io/apiserver v0.23.0 sigs.k8s.io/apiserver-runtime v1.0.3-0.20210913073608-0663f60bfee2 sigs.k8s.io/controller-runtime v0.11.0 sigs.k8s.io/controller-tools v0.7.0 @@ -105,9 +108,7 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.23.0 // indirect k8s.io/apiextensions-apiserver v0.23.0 // indirect - k8s.io/apiserver v0.23.0 // indirect k8s.io/client-go v0.23.0 // indirect k8s.io/component-base v0.23.0 // indirect k8s.io/klog v1.0.0 // indirect diff --git a/go.sum b/go.sum index fb0560fd..378eeb3c 100644 --- a/go.sum +++ b/go.sum @@ -345,7 +345,10 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -1284,6 +1287,7 @@ golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1294,6 +1298,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= diff --git a/tools.go b/tools.go index 94de6a8f..6070a51f 100644 --- a/tools.go +++ b/tools.go @@ -10,4 +10,6 @@ import ( _ "sigs.k8s.io/controller-tools/cmd/controller-gen" // To have Kustomize updated via Renovate. _ "sigs.k8s.io/kustomize/kustomize/v4" + // To generate mocks + _ "github.com/golang/mock/mockgen" )