diff --git a/.gitattributes b/.gitattributes index 8f1c1fee6..5a81cdb4f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ **/zz_generated*.go linguist-generated=true pkg/clientset/** linguist-generated=true +controller/konnect/ops/*_mock_test.go linguist-generated=true diff --git a/.golangci.yaml b/.golangci.yaml index c99fe7bc8..d4074e251 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -62,6 +62,13 @@ linters-settings: - pkg: github.com/kong/gateway-operator/internal/types alias: gwtypes + + - pkg: github.com/Kong/sdk-konnect-go/models/components + alias: sdkkonnectcomp + - pkg: github.com/Kong/sdk-konnect-go/models/operations + alias: sdkkonnectops + - pkg: github.com/Kong/sdk-konnect-go/models/sdkerrors + alias: sdkkonnecterrs revive: rules: - name: errorf diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 000000000..36d799f63 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,15 @@ +quiet: False +inpackage: True +disable-version-string: True +with-expecter: True + +filename: "{{ trimSuffix .InterfaceFile \".go\" | base | lower }}_mock_test.go" +dir: "{{ .InterfaceDir }}" +mockname: "Mock{{ .InterfaceName }}" +outpkg: "{{ .PackageName }}" + +packages: + github.com/kong/gateway-operator/controller/konnect/ops: + interfaces: + ControlPlaneSDK: + ServicesSDK: diff --git a/.tools_versions.yaml b/.tools_versions.yaml index d7daa6871..c94f35896 100644 --- a/.tools_versions.yaml +++ b/.tools_versions.yaml @@ -14,3 +14,5 @@ dlv: "1.23.0" gotestsum: "1.12.0" # renovate: datasource=github-releases depName=elastic/crd-ref-docs crd-ref-docs: "0.1.0" +# renovate: datasource=github-releases depName=vektra/mockery +mockery: "2.44.2" diff --git a/Makefile b/Makefile index cd972bbe3..62dc511fc 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,13 @@ skaffold: mise yq ## Download skaffold locally if necessary. @$(MISE) plugin install --yes -q skaffold @$(MISE) install -q skaffold@$(SKAFFOLD_VERSION) +MOCKERY_VERSION = $(shell $(YQ) -r '.mockery' < $(TOOLS_VERSIONS_FILE)) +MOCKERY = $(PROJECT_DIR)/bin/installs/mockery/$(MOCKERY_VERSION)/bin/mockery +.PHONY: mockery +mockery: mise yq ## Download mockery locally if necessary. + @$(MISE) plugin install --yes -q mockery https://github.com/cabify/asdf-mockery.git + @$(MISE) install -q mockery@$(MOCKERY_VERSION) + # ------------------------------------------------------------------------------ # Build # ------------------------------------------------------------------------------ @@ -191,7 +198,7 @@ verify.generators: verify.repo generate verify.diff API_DIR ?= api .PHONY: generate -generate: generate.api generate.clientsets generate.rbacs generate.gateway-api-urls generate.docs generate.k8sio-gomod-replace generate.testcases-registration generate.kic-webhook-config +generate: generate.api generate.clientsets generate.rbacs generate.gateway-api-urls generate.docs generate.k8sio-gomod-replace generate.testcases-registration generate.kic-webhook-config generate.mocks .PHONY: generate.api generate.api: controller-gen @@ -388,6 +395,11 @@ test.conformance: test.samples: kustomize find ./config/samples -not -name "kustomization.*" -type f | sort | xargs -I{} bash -c "kubectl apply -f {}; kubectl delete -f {}" +# https://github.com/vektra/mockery/issues/803#issuecomment-2287198024 +.PHONY: generate.mocks +generate.mocks: mockery + GODEBUG=gotypesalias=0 $(MOCKERY) + # ------------------------------------------------------------------------------ # Gateway API # ------------------------------------------------------------------------------ diff --git a/controller/konnect/conditions.go b/controller/konnect/conditions/conditions.go similarity index 99% rename from controller/konnect/conditions.go rename to controller/konnect/conditions/conditions.go index 0a0e1e226..bce9d594c 100644 --- a/controller/konnect/conditions.go +++ b/controller/konnect/conditions/conditions.go @@ -1,4 +1,4 @@ -package konnect +package conditions // TODO(pmalek): move this to Konnect API directory so that it's part of the API contract. // https://github.com/Kong/kubernetes-configuration/issues/14 diff --git a/controller/konnect/constraints.go b/controller/konnect/constraints/constraints.go similarity index 91% rename from controller/konnect/constraints.go rename to controller/konnect/constraints/constraints.go index 52bba73e5..d3775a688 100644 --- a/controller/konnect/constraints.go +++ b/controller/konnect/constraints/constraints.go @@ -1,4 +1,4 @@ -package konnect +package constraints import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -25,9 +25,9 @@ type SupportedKonnectEntityType interface { } // EntityType is an interface that all Konnect entity types must implement. -// Separating this from SupportedKonnectEntityType allows us to use EntityType +// Separating this from constraints.SupportedKonnectEntityType allows us to use EntityType // where client.Object is required, since it embeds client.Object and uses pointer -// to refer to the SupportedKonnectEntityType. +// to refer to the constraints.SupportedKonnectEntityType. type EntityType[T SupportedKonnectEntityType] interface { *T // Kubernetes Object methods diff --git a/controller/konnect/constraints/entitytypename.go b/controller/konnect/constraints/entitytypename.go new file mode 100644 index 000000000..4dfc22b33 --- /dev/null +++ b/controller/konnect/constraints/entitytypename.go @@ -0,0 +1,7 @@ +package constraints + +// EntityTypeName returns the name of the entity type. +func EntityTypeName[T SupportedKonnectEntityType]() string { + var e T + return e.GetTypeName() +} diff --git a/controller/konnect/entitytypename.go b/controller/konnect/entitytypename.go deleted file mode 100644 index a75f73ff7..000000000 --- a/controller/konnect/entitytypename.go +++ /dev/null @@ -1,6 +0,0 @@ -package konnect - -func entityTypeName[T SupportedKonnectEntityType]() string { - var e T - return e.GetTypeName() -} diff --git a/controller/konnect/errors.go b/controller/konnect/errors.go index d95e93523..07c11aa2c 100644 --- a/controller/konnect/errors.go +++ b/controller/konnect/errors.go @@ -6,25 +6,6 @@ import ( "k8s.io/apimachinery/pkg/types" ) -// FailedKonnectOpError is an error type that is returned when an operation against -// Konnect API fails. -type FailedKonnectOpError[T SupportedKonnectEntityType] struct { - Op Op - Err error -} - -// Error implements the error interface. -func (e FailedKonnectOpError[T]) Error() string { - return fmt.Sprintf("failed to %s %s on Konnect: %v", - e.Op, entityTypeName[T](), e.Err, - ) -} - -// Unwrap returns the underlying error. -func (e FailedKonnectOpError[T]) Unwrap() error { - return e.Err -} - // ReferencedControlPlaneDoesNotExistError is an error type that is returned when // a Konnect entity references a KonnectGatewayControlPlane that does not exist. type ReferencedControlPlaneDoesNotExistError struct { diff --git a/controller/konnect/index.go b/controller/konnect/index.go index 5314b61f1..b021a29cf 100644 --- a/controller/konnect/index.go +++ b/controller/konnect/index.go @@ -3,6 +3,8 @@ package konnect import ( "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/kong/gateway-operator/controller/konnect/constraints" + configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" ) @@ -14,8 +16,8 @@ type ReconciliationIndexOption struct { } // ReconciliationIndexOptionsForEntity returns required index options for controller reconciliing the entity. -func ReconciliationIndexOptionsForEntity[T SupportedKonnectEntityType, - TEnt EntityType[T]](ent TEnt) []ReconciliationIndexOption { +func ReconciliationIndexOptionsForEntity[T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T]](ent TEnt) []ReconciliationIndexOption { switch any(ent).(type) { //nolint:gocritic // TODO: add index options required for other entities case *configurationv1alpha1.KongPluginBinding: return IndexOptionsForKongPluginBinding() diff --git a/controller/konnect/ops/controlplane.go b/controller/konnect/ops/controlplane.go new file mode 100644 index 000000000..4da05c1bd --- /dev/null +++ b/controller/konnect/ops/controlplane.go @@ -0,0 +1,15 @@ +package ops + +import ( + "context" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" +) + +// ControlPlaneSDK is the interface for the Konnect ControlPlaneSDK SDK. +type ControlPlaneSDK interface { + CreateControlPlane(ctx context.Context, req sdkkonnectcomp.CreateControlPlaneRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateControlPlaneResponse, error) + DeleteControlPlane(ctx context.Context, id string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteControlPlaneResponse, error) + UpdateControlPlane(ctx context.Context, id string, req sdkkonnectcomp.UpdateControlPlaneRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpdateControlPlaneResponse, error) +} diff --git a/controller/konnect/ops/controlplane_mock_test.go b/controller/konnect/ops/controlplane_mock_test.go new file mode 100644 index 000000000..006ab5a77 --- /dev/null +++ b/controller/konnect/ops/controlplane_mock_test.go @@ -0,0 +1,263 @@ +// Code generated by mockery. DO NOT EDIT. + +package ops + +import ( + context "context" + + components "github.com/Kong/sdk-konnect-go/models/components" + + mock "github.com/stretchr/testify/mock" + + operations "github.com/Kong/sdk-konnect-go/models/operations" +) + +// MockControlPlaneSDK is an autogenerated mock type for the ControlPlaneSDK type +type MockControlPlaneSDK struct { + mock.Mock +} + +type MockControlPlaneSDK_Expecter struct { + mock *mock.Mock +} + +func (_m *MockControlPlaneSDK) EXPECT() *MockControlPlaneSDK_Expecter { + return &MockControlPlaneSDK_Expecter{mock: &_m.Mock} +} + +// CreateControlPlane provides a mock function with given fields: ctx, req, opts +func (_m *MockControlPlaneSDK) CreateControlPlane(ctx context.Context, req components.CreateControlPlaneRequest, opts ...operations.Option) (*operations.CreateControlPlaneResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateControlPlane") + } + + var r0 *operations.CreateControlPlaneResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, components.CreateControlPlaneRequest, ...operations.Option) (*operations.CreateControlPlaneResponse, error)); ok { + return rf(ctx, req, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, components.CreateControlPlaneRequest, ...operations.Option) *operations.CreateControlPlaneResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.CreateControlPlaneResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, components.CreateControlPlaneRequest, ...operations.Option) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockControlPlaneSDK_CreateControlPlane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateControlPlane' +type MockControlPlaneSDK_CreateControlPlane_Call struct { + *mock.Call +} + +// CreateControlPlane is a helper method to define mock.On call +// - ctx context.Context +// - req components.CreateControlPlaneRequest +// - opts ...operations.Option +func (_e *MockControlPlaneSDK_Expecter) CreateControlPlane(ctx interface{}, req interface{}, opts ...interface{}) *MockControlPlaneSDK_CreateControlPlane_Call { + return &MockControlPlaneSDK_CreateControlPlane_Call{Call: _e.mock.On("CreateControlPlane", + append([]interface{}{ctx, req}, opts...)...)} +} + +func (_c *MockControlPlaneSDK_CreateControlPlane_Call) Run(run func(ctx context.Context, req components.CreateControlPlaneRequest, opts ...operations.Option)) *MockControlPlaneSDK_CreateControlPlane_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(components.CreateControlPlaneRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockControlPlaneSDK_CreateControlPlane_Call) Return(_a0 *operations.CreateControlPlaneResponse, _a1 error) *MockControlPlaneSDK_CreateControlPlane_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockControlPlaneSDK_CreateControlPlane_Call) RunAndReturn(run func(context.Context, components.CreateControlPlaneRequest, ...operations.Option) (*operations.CreateControlPlaneResponse, error)) *MockControlPlaneSDK_CreateControlPlane_Call { + _c.Call.Return(run) + return _c +} + +// DeleteControlPlane provides a mock function with given fields: ctx, id, opts +func (_m *MockControlPlaneSDK) DeleteControlPlane(ctx context.Context, id string, opts ...operations.Option) (*operations.DeleteControlPlaneResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, id) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for DeleteControlPlane") + } + + var r0 *operations.DeleteControlPlaneResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...operations.Option) (*operations.DeleteControlPlaneResponse, error)); ok { + return rf(ctx, id, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...operations.Option) *operations.DeleteControlPlaneResponse); ok { + r0 = rf(ctx, id, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.DeleteControlPlaneResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...operations.Option) error); ok { + r1 = rf(ctx, id, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockControlPlaneSDK_DeleteControlPlane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteControlPlane' +type MockControlPlaneSDK_DeleteControlPlane_Call struct { + *mock.Call +} + +// DeleteControlPlane is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - opts ...operations.Option +func (_e *MockControlPlaneSDK_Expecter) DeleteControlPlane(ctx interface{}, id interface{}, opts ...interface{}) *MockControlPlaneSDK_DeleteControlPlane_Call { + return &MockControlPlaneSDK_DeleteControlPlane_Call{Call: _e.mock.On("DeleteControlPlane", + append([]interface{}{ctx, id}, opts...)...)} +} + +func (_c *MockControlPlaneSDK_DeleteControlPlane_Call) Run(run func(ctx context.Context, id string, opts ...operations.Option)) *MockControlPlaneSDK_DeleteControlPlane_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *MockControlPlaneSDK_DeleteControlPlane_Call) Return(_a0 *operations.DeleteControlPlaneResponse, _a1 error) *MockControlPlaneSDK_DeleteControlPlane_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockControlPlaneSDK_DeleteControlPlane_Call) RunAndReturn(run func(context.Context, string, ...operations.Option) (*operations.DeleteControlPlaneResponse, error)) *MockControlPlaneSDK_DeleteControlPlane_Call { + _c.Call.Return(run) + return _c +} + +// UpdateControlPlane provides a mock function with given fields: ctx, id, req, opts +func (_m *MockControlPlaneSDK) UpdateControlPlane(ctx context.Context, id string, req components.UpdateControlPlaneRequest, opts ...operations.Option) (*operations.UpdateControlPlaneResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, id, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for UpdateControlPlane") + } + + var r0 *operations.UpdateControlPlaneResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, components.UpdateControlPlaneRequest, ...operations.Option) (*operations.UpdateControlPlaneResponse, error)); ok { + return rf(ctx, id, req, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, components.UpdateControlPlaneRequest, ...operations.Option) *operations.UpdateControlPlaneResponse); ok { + r0 = rf(ctx, id, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.UpdateControlPlaneResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, components.UpdateControlPlaneRequest, ...operations.Option) error); ok { + r1 = rf(ctx, id, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockControlPlaneSDK_UpdateControlPlane_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateControlPlane' +type MockControlPlaneSDK_UpdateControlPlane_Call struct { + *mock.Call +} + +// UpdateControlPlane is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - req components.UpdateControlPlaneRequest +// - opts ...operations.Option +func (_e *MockControlPlaneSDK_Expecter) UpdateControlPlane(ctx interface{}, id interface{}, req interface{}, opts ...interface{}) *MockControlPlaneSDK_UpdateControlPlane_Call { + return &MockControlPlaneSDK_UpdateControlPlane_Call{Call: _e.mock.On("UpdateControlPlane", + append([]interface{}{ctx, id, req}, opts...)...)} +} + +func (_c *MockControlPlaneSDK_UpdateControlPlane_Call) Run(run func(ctx context.Context, id string, req components.UpdateControlPlaneRequest, opts ...operations.Option)) *MockControlPlaneSDK_UpdateControlPlane_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-3) + for i, a := range args[3:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(string), args[2].(components.UpdateControlPlaneRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockControlPlaneSDK_UpdateControlPlane_Call) Return(_a0 *operations.UpdateControlPlaneResponse, _a1 error) *MockControlPlaneSDK_UpdateControlPlane_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockControlPlaneSDK_UpdateControlPlane_Call) RunAndReturn(run func(context.Context, string, components.UpdateControlPlaneRequest, ...operations.Option) (*operations.UpdateControlPlaneResponse, error)) *MockControlPlaneSDK_UpdateControlPlane_Call { + _c.Call.Return(run) + return _c +} + +// NewMockControlPlaneSDK creates a new instance of MockControlPlaneSDK. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockControlPlaneSDK(t interface { + mock.TestingT + Cleanup(func()) +}) *MockControlPlaneSDK { + mock := &MockControlPlaneSDK{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/controller/konnect/ops/errors.go b/controller/konnect/ops/errors.go new file mode 100644 index 000000000..e4034c64e --- /dev/null +++ b/controller/konnect/ops/errors.go @@ -0,0 +1,26 @@ +package ops + +import ( + "fmt" + + "github.com/kong/gateway-operator/controller/konnect/constraints" +) + +// FailedKonnectOpError is an error type that is returned when an operation against +// Konnect API fails. +type FailedKonnectOpError[T constraints.SupportedKonnectEntityType] struct { + Op Op + Err error +} + +// Error implements the error interface. +func (e FailedKonnectOpError[T]) Error() string { + return fmt.Sprintf("failed to %s %s on Konnect: %v", + e.Op, constraints.EntityTypeName[T](), e.Err, + ) +} + +// Unwrap returns the underlying error. +func (e FailedKonnectOpError[T]) Unwrap() error { + return e.Err +} diff --git a/controller/konnect/ops/kongconsumer.go b/controller/konnect/ops/kongconsumer.go new file mode 100644 index 000000000..547ac6480 --- /dev/null +++ b/controller/konnect/ops/kongconsumer.go @@ -0,0 +1,15 @@ +package ops + +import ( + "context" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" +) + +// ConsumersSDK is the interface for the Konnect Consumers SDK. +type ConsumersSDK interface { + CreateConsumer(ctx context.Context, controlPlaneID string, consumerInput sdkkonnectcomp.ConsumerInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateConsumerResponse, error) + UpsertConsumer(ctx context.Context, upsertConsumerRequest sdkkonnectops.UpsertConsumerRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertConsumerResponse, error) + DeleteConsumer(ctx context.Context, controlPlaneID string, consumerID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteConsumerResponse, error) +} diff --git a/controller/konnect/ops/kongconsumergroup.go b/controller/konnect/ops/kongconsumergroup.go new file mode 100644 index 000000000..afebcd8fd --- /dev/null +++ b/controller/konnect/ops/kongconsumergroup.go @@ -0,0 +1,15 @@ +package ops + +import ( + "context" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" +) + +// ConsumerGroupSDK is the interface for the Konnect ConsumerGroups SDK. +type ConsumerGroupSDK interface { + CreateConsumerGroup(ctx context.Context, controlPlaneID string, consumerInput sdkkonnectcomp.ConsumerGroupInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateConsumerGroupResponse, error) + UpsertConsumerGroup(ctx context.Context, upsertConsumerRequest sdkkonnectops.UpsertConsumerGroupRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertConsumerGroupResponse, error) + DeleteConsumerGroup(ctx context.Context, controlPlaneID string, consumerID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteConsumerGroupResponse, error) +} diff --git a/controller/konnect/ops/kongroute.go b/controller/konnect/ops/kongroute.go new file mode 100644 index 000000000..809382866 --- /dev/null +++ b/controller/konnect/ops/kongroute.go @@ -0,0 +1,15 @@ +package ops + +import ( + "context" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" +) + +// RoutesSDK is the interface for the Konnect Routes SDK. +type RoutesSDK interface { + CreateRoute(ctx context.Context, controlPlaneID string, route sdkkonnectcomp.RouteInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateRouteResponse, error) + UpsertRoute(ctx context.Context, req sdkkonnectops.UpsertRouteRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertRouteResponse, error) + DeleteRoute(ctx context.Context, controlPlaneID, routeID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteRouteResponse, error) +} diff --git a/controller/konnect/ops/kongservice.go b/controller/konnect/ops/kongservice.go new file mode 100644 index 000000000..502ae5649 --- /dev/null +++ b/controller/konnect/ops/kongservice.go @@ -0,0 +1,15 @@ +package ops + +import ( + "context" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" +) + +// ServicesSDK is the interface for the Konnect Service SDK. +type ServicesSDK interface { + CreateService(ctx context.Context, controlPlaneID string, service sdkkonnectcomp.ServiceInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateServiceResponse, error) + UpsertService(ctx context.Context, req sdkkonnectops.UpsertServiceRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertServiceResponse, error) + DeleteService(ctx context.Context, controlPlaneID, serviceID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteServiceResponse, error) +} diff --git a/controller/konnect/ops/kongservice_mock_test.go b/controller/konnect/ops/kongservice_mock_test.go new file mode 100644 index 000000000..9340df0f0 --- /dev/null +++ b/controller/konnect/ops/kongservice_mock_test.go @@ -0,0 +1,264 @@ +// Code generated by mockery. DO NOT EDIT. + +package ops + +import ( + context "context" + + components "github.com/Kong/sdk-konnect-go/models/components" + + mock "github.com/stretchr/testify/mock" + + operations "github.com/Kong/sdk-konnect-go/models/operations" +) + +// MockServicesSDK is an autogenerated mock type for the ServicesSDK type +type MockServicesSDK struct { + mock.Mock +} + +type MockServicesSDK_Expecter struct { + mock *mock.Mock +} + +func (_m *MockServicesSDK) EXPECT() *MockServicesSDK_Expecter { + return &MockServicesSDK_Expecter{mock: &_m.Mock} +} + +// CreateService provides a mock function with given fields: ctx, controlPlaneID, service, opts +func (_m *MockServicesSDK) CreateService(ctx context.Context, controlPlaneID string, service components.ServiceInput, opts ...operations.Option) (*operations.CreateServiceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, controlPlaneID, service) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateService") + } + + var r0 *operations.CreateServiceResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, components.ServiceInput, ...operations.Option) (*operations.CreateServiceResponse, error)); ok { + return rf(ctx, controlPlaneID, service, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, components.ServiceInput, ...operations.Option) *operations.CreateServiceResponse); ok { + r0 = rf(ctx, controlPlaneID, service, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.CreateServiceResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, components.ServiceInput, ...operations.Option) error); ok { + r1 = rf(ctx, controlPlaneID, service, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockServicesSDK_CreateService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateService' +type MockServicesSDK_CreateService_Call struct { + *mock.Call +} + +// CreateService is a helper method to define mock.On call +// - ctx context.Context +// - controlPlaneID string +// - service components.ServiceInput +// - opts ...operations.Option +func (_e *MockServicesSDK_Expecter) CreateService(ctx interface{}, controlPlaneID interface{}, service interface{}, opts ...interface{}) *MockServicesSDK_CreateService_Call { + return &MockServicesSDK_CreateService_Call{Call: _e.mock.On("CreateService", + append([]interface{}{ctx, controlPlaneID, service}, opts...)...)} +} + +func (_c *MockServicesSDK_CreateService_Call) Run(run func(ctx context.Context, controlPlaneID string, service components.ServiceInput, opts ...operations.Option)) *MockServicesSDK_CreateService_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-3) + for i, a := range args[3:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(string), args[2].(components.ServiceInput), variadicArgs...) + }) + return _c +} + +func (_c *MockServicesSDK_CreateService_Call) Return(_a0 *operations.CreateServiceResponse, _a1 error) *MockServicesSDK_CreateService_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockServicesSDK_CreateService_Call) RunAndReturn(run func(context.Context, string, components.ServiceInput, ...operations.Option) (*operations.CreateServiceResponse, error)) *MockServicesSDK_CreateService_Call { + _c.Call.Return(run) + return _c +} + +// DeleteService provides a mock function with given fields: ctx, controlPlaneID, serviceID, opts +func (_m *MockServicesSDK) DeleteService(ctx context.Context, controlPlaneID string, serviceID string, opts ...operations.Option) (*operations.DeleteServiceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, controlPlaneID, serviceID) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for DeleteService") + } + + var r0 *operations.DeleteServiceResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...operations.Option) (*operations.DeleteServiceResponse, error)); ok { + return rf(ctx, controlPlaneID, serviceID, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, ...operations.Option) *operations.DeleteServiceResponse); ok { + r0 = rf(ctx, controlPlaneID, serviceID, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.DeleteServiceResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, ...operations.Option) error); ok { + r1 = rf(ctx, controlPlaneID, serviceID, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockServicesSDK_DeleteService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteService' +type MockServicesSDK_DeleteService_Call struct { + *mock.Call +} + +// DeleteService is a helper method to define mock.On call +// - ctx context.Context +// - controlPlaneID string +// - serviceID string +// - opts ...operations.Option +func (_e *MockServicesSDK_Expecter) DeleteService(ctx interface{}, controlPlaneID interface{}, serviceID interface{}, opts ...interface{}) *MockServicesSDK_DeleteService_Call { + return &MockServicesSDK_DeleteService_Call{Call: _e.mock.On("DeleteService", + append([]interface{}{ctx, controlPlaneID, serviceID}, opts...)...)} +} + +func (_c *MockServicesSDK_DeleteService_Call) Run(run func(ctx context.Context, controlPlaneID string, serviceID string, opts ...operations.Option)) *MockServicesSDK_DeleteService_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-3) + for i, a := range args[3:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(string), args[2].(string), variadicArgs...) + }) + return _c +} + +func (_c *MockServicesSDK_DeleteService_Call) Return(_a0 *operations.DeleteServiceResponse, _a1 error) *MockServicesSDK_DeleteService_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockServicesSDK_DeleteService_Call) RunAndReturn(run func(context.Context, string, string, ...operations.Option) (*operations.DeleteServiceResponse, error)) *MockServicesSDK_DeleteService_Call { + _c.Call.Return(run) + return _c +} + +// UpsertService provides a mock function with given fields: ctx, req, opts +func (_m *MockServicesSDK) UpsertService(ctx context.Context, req operations.UpsertServiceRequest, opts ...operations.Option) (*operations.UpsertServiceResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, req) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for UpsertService") + } + + var r0 *operations.UpsertServiceResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operations.UpsertServiceRequest, ...operations.Option) (*operations.UpsertServiceResponse, error)); ok { + return rf(ctx, req, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, operations.UpsertServiceRequest, ...operations.Option) *operations.UpsertServiceResponse); ok { + r0 = rf(ctx, req, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.UpsertServiceResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, operations.UpsertServiceRequest, ...operations.Option) error); ok { + r1 = rf(ctx, req, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockServicesSDK_UpsertService_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertService' +type MockServicesSDK_UpsertService_Call struct { + *mock.Call +} + +// UpsertService is a helper method to define mock.On call +// - ctx context.Context +// - req operations.UpsertServiceRequest +// - opts ...operations.Option +func (_e *MockServicesSDK_Expecter) UpsertService(ctx interface{}, req interface{}, opts ...interface{}) *MockServicesSDK_UpsertService_Call { + return &MockServicesSDK_UpsertService_Call{Call: _e.mock.On("UpsertService", + append([]interface{}{ctx, req}, opts...)...)} +} + +func (_c *MockServicesSDK_UpsertService_Call) Run(run func(ctx context.Context, req operations.UpsertServiceRequest, opts ...operations.Option)) *MockServicesSDK_UpsertService_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]operations.Option, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(operations.Option) + } + } + run(args[0].(context.Context), args[1].(operations.UpsertServiceRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockServicesSDK_UpsertService_Call) Return(_a0 *operations.UpsertServiceResponse, _a1 error) *MockServicesSDK_UpsertService_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockServicesSDK_UpsertService_Call) RunAndReturn(run func(context.Context, operations.UpsertServiceRequest, ...operations.Option) (*operations.UpsertServiceResponse, error)) *MockServicesSDK_UpsertService_Call { + _c.Call.Return(run) + return _c +} + +// NewMockServicesSDK creates a new instance of MockServicesSDK. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockServicesSDK(t interface { + mock.TestingT + Cleanup(func()) +}) *MockServicesSDK { + mock := &MockServicesSDK{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/controller/konnect/ops.go b/controller/konnect/ops/ops.go similarity index 74% rename from controller/konnect/ops.go rename to controller/konnect/ops/ops.go index eba53f885..a5158a574 100644 --- a/controller/konnect/ops.go +++ b/controller/konnect/ops/ops.go @@ -1,4 +1,4 @@ -package konnect +package ops import ( "context" @@ -11,6 +11,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" + "github.com/kong/gateway-operator/controller/konnect/constraints" "github.com/kong/gateway-operator/controller/pkg/log" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" @@ -40,8 +42,8 @@ const ( // Create creates a Konnect entity. func Create[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ]( ctx context.Context, sdk *sdkkonnectgo.SDK, @@ -52,15 +54,15 @@ func Create[ switch ent := any(e).(type) { case *konnectv1alpha1.KonnectGatewayControlPlane: - return e, createControlPlane(ctx, sdk, ent) + return e, createControlPlane(ctx, sdk.ControlPlanes, ent) case *configurationv1alpha1.KongService: - return e, createService(ctx, sdk, ent) + return e, createService(ctx, sdk.Services, ent) case *configurationv1alpha1.KongRoute: - return e, createRoute(ctx, sdk, ent) + return e, createRoute(ctx, sdk.Routes, ent) case *configurationv1.KongConsumer: - return e, createConsumer(ctx, sdk, ent) + return e, createConsumer(ctx, sdk.Consumers, ent) case *configurationv1beta1.KongConsumerGroup: - return e, createConsumerGroup(ctx, sdk, ent) + return e, createConsumerGroup(ctx, sdk.ConsumerGroups, ent) case *configurationv1alpha1.KongPluginBinding: return e, createPlugin(ctx, cl, sdk, ent) @@ -75,8 +77,8 @@ func Create[ // Delete deletes a Konnect entity. // It returns an error if the entity does not have a Konnect ID or if the operation fails. func Delete[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ](ctx context.Context, sdk *sdkkonnectgo.SDK, cl client.Client, e *T) error { ent := TEnt(e) if ent.GetKonnectStatus().GetKonnectID() == "" { @@ -90,15 +92,15 @@ func Delete[ switch ent := any(e).(type) { case *konnectv1alpha1.KonnectGatewayControlPlane: - return deleteControlPlane(ctx, sdk, ent) + return deleteControlPlane(ctx, sdk.ControlPlanes, ent) case *configurationv1alpha1.KongService: - return deleteService(ctx, sdk, ent) + return deleteService(ctx, sdk.Services, ent) case *configurationv1alpha1.KongRoute: - return deleteRoute(ctx, sdk, ent) + return deleteRoute(ctx, sdk.Routes, ent) case *configurationv1.KongConsumer: - return deleteConsumer(ctx, sdk, ent) + return deleteConsumer(ctx, sdk.Consumers, ent) case *configurationv1beta1.KongConsumerGroup: - return deleteConsumerGroup(ctx, sdk, ent) + return deleteConsumerGroup(ctx, sdk.ConsumerGroups, ent) case *configurationv1alpha1.KongPluginBinding: return deletePlugin(ctx, sdk, ent) @@ -113,12 +115,12 @@ func Delete[ // Update updates a Konnect entity. // It returns an error if the entity does not have a Konnect ID or if the operation fails. func Update[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ](ctx context.Context, sdk *sdkkonnectgo.SDK, syncPeriod time.Duration, cl client.Client, e *T) (ctrl.Result, error) { var ( ent = TEnt(e) - condProgrammed, ok = k8sutils.GetCondition(KonnectEntityProgrammedConditionType, ent) + condProgrammed, ok = k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, ent) now = time.Now() timeFromLastUpdate = time.Since(condProgrammed.LastTransitionTime.Time) ) @@ -126,7 +128,7 @@ func Update[ // the configured sync period, requeue after the remaining time. if ok && condProgrammed.Status == metav1.ConditionTrue && - condProgrammed.Reason == KonnectEntityProgrammedReasonProgrammed && + condProgrammed.Reason == conditions.KonnectEntityProgrammedReasonProgrammed && condProgrammed.ObservedGeneration == ent.GetObjectMeta().GetGeneration() && timeFromLastUpdate <= syncPeriod { requeueAfter := syncPeriod - timeFromLastUpdate @@ -153,15 +155,15 @@ func Update[ switch ent := any(e).(type) { case *konnectv1alpha1.KonnectGatewayControlPlane: - return ctrl.Result{}, updateControlPlane(ctx, sdk, ent) + return ctrl.Result{}, updateControlPlane(ctx, sdk.ControlPlanes, ent) case *configurationv1alpha1.KongService: - return ctrl.Result{}, updateService(ctx, sdk, cl, ent) + return ctrl.Result{}, updateService(ctx, sdk.Services, ent) case *configurationv1alpha1.KongRoute: - return ctrl.Result{}, updateRoute(ctx, sdk, cl, ent) + return ctrl.Result{}, updateRoute(ctx, sdk.Routes, cl, ent) case *configurationv1.KongConsumer: - return ctrl.Result{}, updateConsumer(ctx, sdk, cl, ent) + return ctrl.Result{}, updateConsumer(ctx, sdk.Consumers, cl, ent) case *configurationv1beta1.KongConsumerGroup: - return ctrl.Result{}, updateConsumerGroup(ctx, sdk, cl, ent) + return ctrl.Result{}, updateConsumerGroup(ctx, sdk.ConsumerGroups, cl, ent) case *configurationv1alpha1.KongPluginBinding: return ctrl.Result{}, updatePlugin(ctx, sdk, cl, ent) @@ -174,8 +176,8 @@ func Update[ } func logOpComplete[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ](ctx context.Context, start time.Time, op Op, e TEnt) { s := e.GetKonnectStatus() if s == nil { @@ -186,7 +188,7 @@ func logOpComplete[ Info("operation in Konnect API complete", "op", op, "duration", time.Since(start), - "type", entityTypeName[T](), + "type", constraints.EntityTypeName[T](), "konnect_id", s.GetKonnectID(), ) } @@ -194,17 +196,18 @@ func logOpComplete[ // wrapErrIfKonnectOpFailed checks the response from the Konnect API and returns a uniform // error for all Konnect entities if the operation failed. func wrapErrIfKonnectOpFailed[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ](err error, op Op, e TEnt) error { if err != nil { + entityTypeName := constraints.EntityTypeName[T]() if e == nil { - return fmt.Errorf("failed to %s for %T: %w", - op, e, err, + return fmt.Errorf("failed to %s %s: %w", + op, entityTypeName, err, ) } - return fmt.Errorf("failed to %s for %T %q: %w", - op, client.ObjectKeyFromObject(e), e, err, + return fmt.Errorf("failed to %s %s %s: %w", + op, entityTypeName, client.ObjectKeyFromObject(e), err, ) } return nil diff --git a/controller/konnect/ops_controlplane.go b/controller/konnect/ops/ops_controlplane.go similarity index 76% rename from controller/konnect/ops_controlplane.go rename to controller/konnect/ops/ops_controlplane.go index 7c07a6e24..a848d71c4 100644 --- a/controller/konnect/ops_controlplane.go +++ b/controller/konnect/ops/ops_controlplane.go @@ -1,15 +1,16 @@ -package konnect +package ops import ( "context" "errors" sdkkonnectgo "github.com/Kong/sdk-konnect-go" - "github.com/Kong/sdk-konnect-go/models/components" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" @@ -17,10 +18,10 @@ import ( func createControlPlane( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ControlPlaneSDK, cp *konnectv1alpha1.KonnectGatewayControlPlane, ) error { - resp, err := sdk.ControlPlanes.CreateControlPlane(ctx, cp.Spec.CreateControlPlaneRequest) + resp, err := sdk.CreateControlPlane(ctx, cp.Spec.CreateControlPlaneRequest) // TODO: handle already exists // Can't adopt it as it will cause conflicts between the controller // that created that entity and already manages it, hm @@ -28,7 +29,7 @@ func createControlPlane( if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, cp); errWrap != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrap.Error(), @@ -42,9 +43,9 @@ func createControlPlane( cp.Status.SetKonnectID(resp.ControlPlane.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", cp.GetGeneration(), ), @@ -58,13 +59,13 @@ func createControlPlane( // It is assumed that the Konnect ControlPlane has a Konnect ID. func deleteControlPlane( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ControlPlaneSDK, cp *konnectv1alpha1.KonnectGatewayControlPlane, ) error { id := cp.GetKonnectStatus().GetKonnectID() - _, err := sdk.ControlPlanes.DeleteControlPlane(ctx, id) + _, err := sdk.DeleteControlPlane(ctx, id) if errWrap := wrapErrIfKonnectOpFailed(err, DeleteOp, cp); errWrap != nil { - var sdkNotFoundError *sdkerrors.NotFoundError + var sdkNotFoundError *sdkkonnecterrs.NotFoundError if errors.As(err, &sdkNotFoundError) { ctrllog.FromContext(ctx). Info("entity not found in Konnect, skipping delete", @@ -72,7 +73,7 @@ func deleteControlPlane( ) return nil } - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrap, &sdkError) { return FailedKonnectOpError[konnectv1alpha1.KonnectGatewayControlPlane]{ Op: DeleteOp, @@ -93,20 +94,20 @@ func deleteControlPlane( // It returns an error if the operation fails. func updateControlPlane( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ControlPlaneSDK, cp *konnectv1alpha1.KonnectGatewayControlPlane, ) error { id := cp.GetKonnectStatus().GetKonnectID() - req := components.UpdateControlPlaneRequest{ + req := sdkkonnectcomp.UpdateControlPlaneRequest{ Name: sdkkonnectgo.String(cp.Spec.Name), Description: cp.Spec.Description, - AuthType: (*components.UpdateControlPlaneRequestAuthType)(cp.Spec.AuthType), + AuthType: (*sdkkonnectcomp.UpdateControlPlaneRequestAuthType)(cp.Spec.AuthType), ProxyUrls: cp.Spec.ProxyUrls, Labels: cp.Spec.Labels, } - resp, err := sdk.ControlPlanes.UpdateControlPlane(ctx, id, req) - var sdkError *sdkerrors.NotFoundError + resp, err := sdk.UpdateControlPlane(ctx, id, req) + var sdkError *sdkkonnecterrs.NotFoundError if errors.As(err, &sdkError) { ctrllog.FromContext(ctx). Info("entity not found in Konnect, trying to recreate", @@ -126,7 +127,7 @@ func updateControlPlane( if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, cp); errWrap != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToUpdate", errWrap.Error(), @@ -143,9 +144,9 @@ func updateControlPlane( cp.Status.SetKonnectID(resp.ControlPlane.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", cp.GetGeneration(), ), diff --git a/controller/konnect/ops/ops_controlplane_test.go b/controller/konnect/ops/ops_controlplane_test.go new file mode 100644 index 000000000..afff2cbce --- /dev/null +++ b/controller/konnect/ops/ops_controlplane_test.go @@ -0,0 +1,450 @@ +package ops + +import ( + "context" + "testing" + + sdkkonnectgo "github.com/Kong/sdk-konnect-go" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kong/gateway-operator/controller/konnect/conditions" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" + + konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" +) + +func TestCreateControlPlane(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockCPPair func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) + expectedErr bool + assertions func(*testing.T, *konnectv1alpha1.KonnectGatewayControlPlane) + }{ + { + name: "success", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + } + + sdk. + EXPECT(). + CreateControlPlane(ctx, cp.Spec.CreateControlPlaneRequest). + Return( + &sdkkonnectops.CreateControlPlaneResponse{ + ControlPlane: &sdkkonnectcomp.ControlPlane{ + ID: "12345", + }, + }, + nil, + ) + + return sdk, cp + }, + assertions: func(t *testing.T, cp *konnectv1alpha1.KonnectGatewayControlPlane) { + assert.Equal(t, "12345", cp.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, cp.GetGeneration(), cond.ObservedGeneration) + }, + }, + { + name: "fail", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cp-1", + Namespace: "default", + }, + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + } + + sdk. + EXPECT(). + CreateControlPlane(ctx, cp.Spec.CreateControlPlaneRequest). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Detail: "bad request", + }, + ) + + return sdk, cp + }, + assertions: func(t *testing.T, cp *konnectv1alpha1.KonnectGatewayControlPlane) { + assert.Equal(t, "", cp.Status.GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, "FailedToCreate", cond.Reason) + assert.Equal(t, cp.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, `failed to create KonnectGatewayControlPlane default/cp-1: {"status":400,"title":"","instance":"","detail":"bad request","invalid_parameters":null}`, cond.Message) + }, + expectedErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, cp := tc.mockCPPair() + + err := createControlPlane(ctx, sdk, cp) + t.Cleanup(func() { + assert.True(t, sdk.AssertExpectations(t)) + }) + + tc.assertions(t, cp) + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} + +func TestDeleteControlPlane(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockCPPair func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) + expectedErr bool + assertions func(*testing.T, *konnectv1alpha1.KonnectGatewayControlPlane) + }{ + { + name: "success", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + sdk. + EXPECT(). + DeleteControlPlane(ctx, "12345"). + Return( + &sdkkonnectops.DeleteControlPlaneResponse{ + StatusCode: 200, + }, + nil, + ) + + return sdk, cp + }, + }, + { + name: "fail", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cp-1", + Namespace: "default", + }, + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + sdk. + EXPECT(). + DeleteControlPlane(ctx, "12345"). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Detail: "bad request", + }, + ) + + return sdk, cp + }, + expectedErr: true, + }, + { + name: "not found error is ignored and considered a success when trying to delete", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cp-1", + Namespace: "default", + }, + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + sdk. + EXPECT(). + DeleteControlPlane(ctx, "12345"). + Return( + nil, + &sdkkonnecterrs.NotFoundError{ + Status: 404, + Detail: "not found", + }, + ) + + return sdk, cp + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, cp := tc.mockCPPair() + + err := deleteControlPlane(ctx, sdk, cp) + + if tc.assertions != nil { + tc.assertions(t, cp) + } + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.True(t, sdk.AssertExpectations(t)) + }) + } +} + +func TestUpdateControlPlane(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockCPPair func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) + expectedErr bool + assertions func(*testing.T, *konnectv1alpha1.KonnectGatewayControlPlane) + }{ + { + name: "success", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + sdk. + EXPECT(). + UpdateControlPlane(ctx, "12345", + sdkkonnectcomp.UpdateControlPlaneRequest{ + Name: sdkkonnectgo.String(cp.Spec.Name), + Description: cp.Spec.Description, + AuthType: (*sdkkonnectcomp.UpdateControlPlaneRequestAuthType)(cp.Spec.AuthType), + ProxyUrls: cp.Spec.ProxyUrls, + Labels: cp.Spec.Labels, + }, + ). + Return( + &sdkkonnectops.UpdateControlPlaneResponse{ + ControlPlane: &sdkkonnectcomp.ControlPlane{ + ID: "12345", + }, + }, + nil, + ) + + return sdk, cp + }, + assertions: func(t *testing.T, cp *konnectv1alpha1.KonnectGatewayControlPlane) { + assert.Equal(t, "12345", cp.Status.GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, cp.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, "", cond.Message) + }, + }, + { + name: "fail", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cp-1", + Namespace: "default", + }, + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + + sdk. + EXPECT(). + UpdateControlPlane(ctx, "12345", + sdkkonnectcomp.UpdateControlPlaneRequest{ + Name: sdkkonnectgo.String(cp.Spec.Name), + Description: cp.Spec.Description, + AuthType: (*sdkkonnectcomp.UpdateControlPlaneRequestAuthType)(cp.Spec.AuthType), + ProxyUrls: cp.Spec.ProxyUrls, + Labels: cp.Spec.Labels, + }, + ). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Detail: "bad request", + }, + ) + + return sdk, cp + }, + assertions: func(t *testing.T, cp *konnectv1alpha1.KonnectGatewayControlPlane) { + assert.Equal(t, "12345", cp.Status.GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, "FailedToUpdate", cond.Reason) + assert.Equal(t, cp.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, `failed to update KonnectGatewayControlPlane default/cp-1: {"status":400,"title":"","instance":"","detail":"bad request","invalid_parameters":null}`, cond.Message) + }, + expectedErr: true, + }, + { + name: "when not found then try to create", + mockCPPair: func() (*MockControlPlaneSDK, *konnectv1alpha1.KonnectGatewayControlPlane) { + sdk := &MockControlPlaneSDK{} + cp := &konnectv1alpha1.KonnectGatewayControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cp-1", + Namespace: "default", + }, + Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ + Name: "cp-1", + }, + }, + Status: konnectv1alpha1.KonnectGatewayControlPlaneStatus{ + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "12345", + }, + }, + } + + sdk. + EXPECT(). + UpdateControlPlane(ctx, "12345", + sdkkonnectcomp.UpdateControlPlaneRequest{ + Name: sdkkonnectgo.String(cp.Spec.Name), + Description: cp.Spec.Description, + AuthType: (*sdkkonnectcomp.UpdateControlPlaneRequestAuthType)(cp.Spec.AuthType), + ProxyUrls: cp.Spec.ProxyUrls, + Labels: cp.Spec.Labels, + }, + ). + Return( + nil, + &sdkkonnecterrs.NotFoundError{ + Status: 404, + Detail: "not found", + }, + ) + + sdk. + EXPECT(). + CreateControlPlane(ctx, cp.Spec.CreateControlPlaneRequest). + Return( + &sdkkonnectops.CreateControlPlaneResponse{ + ControlPlane: &sdkkonnectcomp.ControlPlane{ + ID: "12345", + }, + }, + nil, + ) + + return sdk, cp + }, + assertions: func(t *testing.T, cp *konnectv1alpha1.KonnectGatewayControlPlane) { + assert.Equal(t, "12345", cp.Status.GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, cp.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, "", cond.Message) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, cp := tc.mockCPPair() + + err := updateControlPlane(ctx, sdk, cp) + + if tc.assertions != nil { + tc.assertions(t, cp) + } + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.True(t, sdk.AssertExpectations(t)) + }) + } +} diff --git a/controller/konnect/ops_kongconsumer.go b/controller/konnect/ops/ops_kongconsumer.go similarity index 82% rename from controller/konnect/ops_kongconsumer.go rename to controller/konnect/ops/ops_kongconsumer.go index cfa628af5..51890d7b3 100644 --- a/controller/konnect/ops_kongconsumer.go +++ b/controller/konnect/ops/ops_kongconsumer.go @@ -1,19 +1,19 @@ -package konnect +package ops import ( "context" "errors" "fmt" - sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" @@ -23,14 +23,14 @@ import ( func createConsumer( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumersSDK, consumer *configurationv1.KongConsumer, ) error { if consumer.GetControlPlaneID() == "" { return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", consumer, client.ObjectKeyFromObject(consumer)) } - resp, err := sdk.Consumers.CreateConsumer(ctx, + resp, err := sdk.CreateConsumer(ctx, consumer.Status.Konnect.ControlPlaneID, kongConsumerToSDKConsumerInput(consumer), ) @@ -41,7 +41,7 @@ func createConsumer( if errWrapped := wrapErrIfKonnectOpFailed(err, CreateOp, consumer); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -55,9 +55,9 @@ func createConsumer( consumer.Status.Konnect.SetKonnectID(*resp.Consumer.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", consumer.GetGeneration(), ), @@ -72,7 +72,7 @@ func createConsumer( // It returns an error if the KongConsumer does not have a ControlPlaneRef. func updateConsumer( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumersSDK, cl client.Client, consumer *configurationv1.KongConsumer, ) error { @@ -100,8 +100,8 @@ func updateConsumer( ) } - resp, err := sdk.Consumers.UpsertConsumer(ctx, - sdkkonnectgoops.UpsertConsumerRequest{ + resp, err := sdk.UpsertConsumer(ctx, + sdkkonnectops.UpsertConsumerRequest{ ControlPlaneID: cp.Status.ID, ConsumerID: consumer.GetKonnectStatus().GetKonnectID(), Consumer: kongConsumerToSDKConsumerInput(consumer), @@ -114,7 +114,7 @@ func updateConsumer( if errWrapped := wrapErrIfKonnectOpFailed(err, UpdateOp, consumer); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -129,9 +129,9 @@ func updateConsumer( consumer.Status.Konnect.SetControlPlaneID(cp.Status.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", consumer.GetGeneration(), ), @@ -146,14 +146,14 @@ func updateConsumer( // It returns an error if the operation fails. func deleteConsumer( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumersSDK, consumer *configurationv1.KongConsumer, ) error { id := consumer.Status.Konnect.GetKonnectID() - _, err := sdk.Consumers.DeleteConsumer(ctx, consumer.Status.Konnect.ControlPlaneID, id) + _, err := sdk.DeleteConsumer(ctx, consumer.Status.Konnect.ControlPlaneID, id) if errWrapped := wrapErrIfKonnectOpFailed(err, DeleteOp, consumer); errWrapped != nil { // Consumer delete operation returns an SDKError instead of a NotFoundError. - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrapped, &sdkError) { if sdkError.StatusCode == 404 { ctrllog.FromContext(ctx). @@ -178,8 +178,8 @@ func deleteConsumer( func kongConsumerToSDKConsumerInput( consumer *configurationv1.KongConsumer, -) sdkkonnectgocomp.ConsumerInput { - return sdkkonnectgocomp.ConsumerInput{ +) sdkkonnectcomp.ConsumerInput { + return sdkkonnectcomp.ConsumerInput{ CustomID: &consumer.CustomID, Tags: metadata.ExtractTags(consumer), Username: &consumer.Username, diff --git a/controller/konnect/ops_kongconsumergroup.go b/controller/konnect/ops/ops_kongconsumergroup.go similarity index 82% rename from controller/konnect/ops_kongconsumergroup.go rename to controller/konnect/ops/ops_kongconsumergroup.go index fd5a184b8..979802007 100644 --- a/controller/konnect/ops_kongconsumergroup.go +++ b/controller/konnect/ops/ops_kongconsumergroup.go @@ -1,19 +1,19 @@ -package konnect +package ops import ( "context" "errors" "fmt" - sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" configurationv1beta1 "github.com/kong/kubernetes-configuration/api/configuration/v1beta1" @@ -23,14 +23,14 @@ import ( func createConsumerGroup( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumerGroupSDK, group *configurationv1beta1.KongConsumerGroup, ) error { if group.GetControlPlaneID() == "" { return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", group, client.ObjectKeyFromObject(group)) } - resp, err := sdk.ConsumerGroups.CreateConsumerGroup(ctx, + resp, err := sdk.CreateConsumerGroup(ctx, group.Status.Konnect.ControlPlaneID, kongConsumerGroupToSDKConsumerGroupInput(group), ) @@ -41,7 +41,7 @@ func createConsumerGroup( if errWrapped := wrapErrIfKonnectOpFailed(err, CreateOp, group); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -55,9 +55,9 @@ func createConsumerGroup( group.Status.Konnect.SetKonnectID(*resp.ConsumerGroup.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", group.GetGeneration(), ), @@ -72,7 +72,7 @@ func createConsumerGroup( // It returns an error if the KongConsumerGroup does not have a ControlPlaneRef. func updateConsumerGroup( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumerGroupSDK, cl client.Client, group *configurationv1beta1.KongConsumerGroup, ) error { @@ -100,8 +100,8 @@ func updateConsumerGroup( ) } - resp, err := sdk.ConsumerGroups.UpsertConsumerGroup(ctx, - sdkkonnectgoops.UpsertConsumerGroupRequest{ + resp, err := sdk.UpsertConsumerGroup(ctx, + sdkkonnectops.UpsertConsumerGroupRequest{ ControlPlaneID: cp.Status.ID, ConsumerGroupID: group.GetKonnectStatus().GetKonnectID(), ConsumerGroup: kongConsumerGroupToSDKConsumerGroupInput(group), @@ -114,7 +114,7 @@ func updateConsumerGroup( if errWrapped := wrapErrIfKonnectOpFailed(err, UpdateOp, group); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -129,9 +129,9 @@ func updateConsumerGroup( group.Status.Konnect.SetControlPlaneID(cp.Status.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", group.GetGeneration(), ), @@ -146,14 +146,14 @@ func updateConsumerGroup( // It returns an error if the operation fails. func deleteConsumerGroup( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ConsumerGroupSDK, consumer *configurationv1beta1.KongConsumerGroup, ) error { id := consumer.Status.Konnect.GetKonnectID() - _, err := sdk.ConsumerGroups.DeleteConsumerGroup(ctx, consumer.Status.Konnect.ControlPlaneID, id) + _, err := sdk.DeleteConsumerGroup(ctx, consumer.Status.Konnect.ControlPlaneID, id) if errWrapped := wrapErrIfKonnectOpFailed(err, DeleteOp, consumer); errWrapped != nil { // Consumer delete operation returns an SDKError instead of a NotFoundError. - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrapped, &sdkError) { if sdkError.StatusCode == 404 { ctrllog.FromContext(ctx). @@ -178,8 +178,8 @@ func deleteConsumerGroup( func kongConsumerGroupToSDKConsumerGroupInput( group *configurationv1beta1.KongConsumerGroup, -) sdkkonnectgocomp.ConsumerGroupInput { - return sdkkonnectgocomp.ConsumerGroupInput{ +) sdkkonnectcomp.ConsumerGroupInput { + return sdkkonnectcomp.ConsumerGroupInput{ Tags: metadata.ExtractTags(group), Name: group.Spec.Name, } diff --git a/controller/konnect/ops_kongpluginbinding.go b/controller/konnect/ops/ops_kongpluginbinding.go similarity index 90% rename from controller/konnect/ops_kongpluginbinding.go rename to controller/konnect/ops/ops_kongpluginbinding.go index 556d03fb8..d04c6faab 100644 --- a/controller/konnect/ops_kongpluginbinding.go +++ b/controller/konnect/ops/ops_kongpluginbinding.go @@ -1,4 +1,4 @@ -package konnect +package ops import ( "context" @@ -7,15 +7,16 @@ import ( "fmt" sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" @@ -54,7 +55,7 @@ func createPlugin( if errWrapped := wrapErrIfKonnectOpFailed[configurationv1alpha1.KongPluginBinding](err, CreateOp, pluginBinding); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -68,9 +69,9 @@ func createPlugin( pluginBinding.SetKonnectID(*resp.Plugin.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", pluginBinding.GetGeneration(), ), @@ -121,7 +122,7 @@ func updatePlugin( } resp, err := sdk.Plugins.UpsertPlugin(ctx, - sdkkonnectgoops.UpsertPluginRequest{ + sdkkonnectops.UpsertPluginRequest{ ControlPlaneID: controlPlaneID, PluginID: pb.GetKonnectID(), Plugin: *pluginInput, @@ -134,7 +135,7 @@ func updatePlugin( if errWrapped := wrapErrIfKonnectOpFailed[configurationv1alpha1.KongPluginBinding](err, UpdateOp, pb); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -149,9 +150,9 @@ func updatePlugin( pb.Status.Konnect.SetControlPlaneID(cp.Status.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", pb.GetGeneration(), ), @@ -173,7 +174,7 @@ func deletePlugin( _, err := sdk.Plugins.DeletePlugin(ctx, pb.GetControlPlaneID(), id) if errWrapped := wrapErrIfKonnectOpFailed[configurationv1alpha1.KongPluginBinding](err, DeleteOp, pb); errWrapped != nil { // plugin delete operation returns an SDKError instead of a NotFoundError. - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrapped, &sdkError) && sdkError.StatusCode == 404 { ctrllog.FromContext(ctx). Info("entity not found in Konnect, skipping delete", @@ -195,7 +196,7 @@ func deletePlugin( // ----------------------------------------------------------------------------- // getPluginInput returns the SDK PluginInput for the KongPluginBinding. -func getPluginInput(ctx context.Context, cl client.Client, pluginBinding *configurationv1alpha1.KongPluginBinding) (*sdkkonnectgocomp.PluginInput, error) { +func getPluginInput(ctx context.Context, cl client.Client, pluginBinding *configurationv1alpha1.KongPluginBinding) (*sdkkonnectcomp.PluginInput, error) { plugin, err := getReferencedPlugin(ctx, cl, pluginBinding) if err != nil { return nil, err @@ -256,7 +257,7 @@ func getReferencedPlugin(ctx context.Context, cl client.Client, pluginBinding *c func kongPluginBindingToSDKPluginInput( plugin *configurationv1.KongPlugin, targets []client.Object, -) (*sdkkonnectgocomp.PluginInput, error) { +) (*sdkkonnectcomp.PluginInput, error) { if len(targets) == 0 { return nil, fmt.Errorf("no targets found for KongPluginBinding %s", client.ObjectKeyFromObject(plugin)) } @@ -266,7 +267,7 @@ func kongPluginBindingToSDKPluginInput( return nil, err } - pluginInput := &sdkkonnectgocomp.PluginInput{ + pluginInput := &sdkkonnectcomp.PluginInput{ Name: lo.ToPtr(plugin.PluginName), Config: pluginConfig, Enabled: lo.ToPtr(!plugin.Disabled), @@ -281,7 +282,7 @@ func kongPluginBindingToSDKPluginInput( if id == "" { return nil, fmt.Errorf("KongService %s is not configured in Konnect yet", client.ObjectKeyFromObject(t)) } - pluginInput.Service = &sdkkonnectgocomp.PluginService{ + pluginInput.Service = &sdkkonnectcomp.PluginService{ ID: lo.ToPtr(t.GetKonnectStatus().ID), } // TODO(mlavacca): add support for KongRoute diff --git a/controller/konnect/ops_kongroute.go b/controller/konnect/ops/ops_kongroute.go similarity index 84% rename from controller/konnect/ops_kongroute.go rename to controller/konnect/ops/ops_kongroute.go index f6e5aff99..bf857a53a 100644 --- a/controller/konnect/ops_kongroute.go +++ b/controller/konnect/ops/ops_kongroute.go @@ -1,4 +1,4 @@ -package konnect +package ops import ( "context" @@ -6,14 +6,15 @@ import ( "fmt" sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,14 +23,14 @@ import ( func createRoute( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk RoutesSDK, route *configurationv1alpha1.KongRoute, ) error { if route.GetControlPlaneID() == "" { return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", route, client.ObjectKeyFromObject(route)) } - resp, err := sdk.Routes.CreateRoute(ctx, route.Status.Konnect.ControlPlaneID, kongRouteToSDKRouteInput(route)) + resp, err := sdk.CreateRoute(ctx, route.Status.Konnect.ControlPlaneID, kongRouteToSDKRouteInput(route)) // TODO: handle already exists // Can't adopt it as it will cause conflicts between the controller @@ -37,7 +38,7 @@ func createRoute( if errWrapped := wrapErrIfKonnectOpFailed(err, CreateOp, route); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -51,9 +52,9 @@ func createRoute( route.Status.Konnect.SetKonnectID(*resp.Route.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", route.GetGeneration(), ), @@ -69,7 +70,8 @@ func createRoute( // if the operation fails. func updateRoute( ctx context.Context, - sdk *sdkkonnectgo.SDK, + // sdk *sdkkonnectgo.SDK, + sdk RoutesSDK, cl client.Client, route *configurationv1alpha1.KongRoute, ) error { @@ -114,7 +116,8 @@ func updateRoute( ) } - resp, err := sdk.Routes.UpsertRoute(ctx, sdkkonnectgoops.UpsertRouteRequest{ + resp, err := sdk.UpsertRoute(ctx, sdkkonnectops.UpsertRouteRequest{ + // resp, err := sdk.UpsertRoute(ctx, sdkkonnectops.UpsertRouteRequest{ ControlPlaneID: cp.Status.ID, RouteID: route.Status.Konnect.ID, Route: kongRouteToSDKRouteInput(route), @@ -127,7 +130,7 @@ func updateRoute( if errWrapped := wrapErrIfKonnectOpFailed(err, UpdateOp, route); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -142,9 +145,9 @@ func updateRoute( route.Status.Konnect.SetControlPlaneID(cp.Status.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", route.GetGeneration(), ), @@ -159,14 +162,14 @@ func updateRoute( // It returns an error if the operation fails. func deleteRoute( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk RoutesSDK, route *configurationv1alpha1.KongRoute, ) error { id := route.GetKonnectStatus().GetKonnectID() - _, err := sdk.Routes.DeleteRoute(ctx, route.Status.Konnect.ControlPlaneID, id) + _, err := sdk.DeleteRoute(ctx, route.Status.Konnect.ControlPlaneID, id) if errWrapped := wrapErrIfKonnectOpFailed(err, DeleteOp, route); errWrapped != nil { // Service delete operation returns an SDKError instead of a NotFoundError. - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrapped, &sdkError) { if sdkError.StatusCode == 404 { ctrllog.FromContext(ctx). @@ -191,8 +194,8 @@ func deleteRoute( func kongRouteToSDKRouteInput( route *configurationv1alpha1.KongRoute, -) sdkkonnectgocomp.RouteInput { - return sdkkonnectgocomp.RouteInput{ +) sdkkonnectcomp.RouteInput { + return sdkkonnectcomp.RouteInput{ Destinations: route.Spec.KongRouteAPISpec.Destinations, Headers: route.Spec.KongRouteAPISpec.Headers, Hosts: route.Spec.KongRouteAPISpec.Hosts, @@ -210,7 +213,7 @@ func kongRouteToSDKRouteInput( Sources: route.Spec.KongRouteAPISpec.Sources, StripPath: route.Spec.KongRouteAPISpec.StripPath, Tags: route.Spec.KongRouteAPISpec.Tags, - Service: &sdkkonnectgocomp.RouteService{ + Service: &sdkkonnectcomp.RouteService{ ID: sdkkonnectgo.String(route.Status.Konnect.ServiceID), }, } diff --git a/controller/konnect/ops_kongservice.go b/controller/konnect/ops/ops_kongservice.go similarity index 67% rename from controller/konnect/ops_kongservice.go rename to controller/konnect/ops/ops_kongservice.go index 5aa872e09..44559029c 100644 --- a/controller/konnect/ops_kongservice.go +++ b/controller/konnect/ops/ops_kongservice.go @@ -1,35 +1,36 @@ -package konnect +package ops import ( "context" "errors" "fmt" - sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" - "github.com/Kong/sdk-konnect-go/models/sdkerrors" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" - konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" ) func createService( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ServicesSDK, svc *configurationv1alpha1.KongService, ) error { if svc.GetControlPlaneID() == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", svc, client.ObjectKeyFromObject(svc)) + return fmt.Errorf( + "can't create %T %s without a Konnect ControlPlane ID", + svc, client.ObjectKeyFromObject(svc), + ) } - resp, err := sdk.Services.CreateService(ctx, + resp, err := sdk.CreateService(ctx, svc.Status.Konnect.ControlPlaneID, kongServiceToSDKServiceInput(svc), ) @@ -40,7 +41,7 @@ func createService( if errWrapped := wrapErrIfKonnectOpFailed(err, CreateOp, svc); errWrapped != nil { k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, "FailedToCreate", errWrapped.Error(), @@ -54,9 +55,9 @@ func createService( svc.Status.Konnect.SetKonnectID(*resp.Service.ID) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", svc.GetGeneration(), ), @@ -72,38 +73,20 @@ func createService( // if the operation fails. func updateService( ctx context.Context, - sdk *sdkkonnectgo.SDK, - cl client.Client, + sdk ServicesSDK, svc *configurationv1alpha1.KongService, ) error { - if svc.Spec.ControlPlaneRef == nil { - return fmt.Errorf("can't update %T without a ControlPlaneRef", svc) - } - - // TODO(pmalek) handle other types of CP ref - // TODO(pmalek) handle cross namespace refs - nnCP := types.NamespacedName{ - Namespace: svc.Namespace, - Name: svc.Spec.ControlPlaneRef.KonnectNamespacedRef.Name, - } - var cp konnectv1alpha1.KonnectGatewayControlPlane - if err := cl.Get(ctx, nnCP, &cp); err != nil { - return fmt.Errorf("failed to get KonnectGatewayControlPlane %s: for %T %s: %w", - nnCP, svc, client.ObjectKeyFromObject(svc), err, - ) - } - - if cp.Status.ID == "" { - return fmt.Errorf( - "can't update %T when referenced KonnectGatewayControlPlane %s does not have the Konnect ID", - svc, nnCP, + if svc.GetControlPlaneID() == "" { + return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", + svc, client.ObjectKeyFromObject(svc), ) } - resp, err := sdk.Services.UpsertService(ctx, - sdkkonnectgoops.UpsertServiceRequest{ - ControlPlaneID: cp.Status.ID, - ServiceID: svc.GetKonnectStatus().GetKonnectID(), + id := svc.GetKonnectStatus().GetKonnectID() + resp, err := sdk.UpsertService(ctx, + sdkkonnectops.UpsertServiceRequest{ + ControlPlaneID: svc.GetControlPlaneID(), + ServiceID: id, Service: kongServiceToSDKServiceInput(svc), }, ) @@ -112,11 +95,33 @@ func updateService( // Can't adopt it as it will cause conflicts between the controller // that created that entity and already manages it, hm if errWrapped := wrapErrIfKonnectOpFailed(err, UpdateOp, svc); errWrapped != nil { + // Service update operation returns an SDKError instead of a NotFoundError. + var sdkError *sdkkonnecterrs.SDKError + if errors.As(errWrapped, &sdkError) { + switch sdkError.StatusCode { + case 404: + if err := createService(ctx, sdk, svc); err != nil { + return FailedKonnectOpError[configurationv1alpha1.KongService]{ + Op: UpdateOp, + Err: err, + } + } + // Create succeeded, createService sets the status so no need to do this here. + + return nil + default: + return FailedKonnectOpError[configurationv1alpha1.KongService]{ + Op: UpdateOp, + Err: sdkError, + } + } + } + k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, - "FailedToCreate", + "FailedToUpdate", errWrapped.Error(), svc.GetGeneration(), ), @@ -126,12 +131,12 @@ func updateService( } svc.Status.Konnect.SetKonnectID(*resp.Service.ID) - svc.Status.Konnect.SetControlPlaneID(cp.Status.ID) + svc.Status.Konnect.SetControlPlaneID(svc.GetControlPlaneID()) k8sutils.SetCondition( k8sutils.NewConditionWithGeneration( - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionTrue, - KonnectEntityProgrammedReasonProgrammed, + conditions.KonnectEntityProgrammedReasonProgrammed, "", svc.GetGeneration(), ), @@ -146,14 +151,14 @@ func updateService( // It returns an error if the operation fails. func deleteService( ctx context.Context, - sdk *sdkkonnectgo.SDK, + sdk ServicesSDK, svc *configurationv1alpha1.KongService, ) error { id := svc.GetKonnectStatus().GetKonnectID() - _, err := sdk.Services.DeleteService(ctx, svc.Status.Konnect.ControlPlaneID, id) + _, err := sdk.DeleteService(ctx, svc.Status.Konnect.ControlPlaneID, id) if errWrapped := wrapErrIfKonnectOpFailed(err, DeleteOp, svc); errWrapped != nil { // Service delete operation returns an SDKError instead of a NotFoundError. - var sdkError *sdkerrors.SDKError + var sdkError *sdkkonnecterrs.SDKError if errors.As(errWrapped, &sdkError) { switch sdkError.StatusCode { case 404: @@ -180,8 +185,8 @@ func deleteService( func kongServiceToSDKServiceInput( svc *configurationv1alpha1.KongService, -) sdkkonnectgocomp.ServiceInput { - return sdkkonnectgocomp.ServiceInput{ +) sdkkonnectcomp.ServiceInput { + return sdkkonnectcomp.ServiceInput{ URL: svc.Spec.KongServiceAPISpec.URL, ConnectTimeout: svc.Spec.KongServiceAPISpec.ConnectTimeout, Enabled: svc.Spec.KongServiceAPISpec.Enabled, diff --git a/controller/konnect/ops/ops_kongservice_test.go b/controller/konnect/ops/ops_kongservice_test.go new file mode 100644 index 000000000..fa0e6421e --- /dev/null +++ b/controller/konnect/ops/ops_kongservice_test.go @@ -0,0 +1,501 @@ +package ops + +import ( + "context" + "testing" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kong/gateway-operator/controller/konnect/conditions" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" + + configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" + konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" +) + +func TestCreateKongService(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockServicePair func() (*MockServicesSDK, *configurationv1alpha1.KongService) + expectedErr bool + assertions func(*testing.T, *configurationv1alpha1.KongService) + }{ + { + name: "success", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Namespace: "default", + }, + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + Host: "example.com", + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "123456789", + }, + }, + } + + sdk. + EXPECT(). + CreateService(ctx, "123456789", kongServiceToSDKServiceInput(svc)). + Return( + &sdkkonnectops.CreateServiceResponse{ + Service: &sdkkonnectcomp.Service{ + ID: lo.ToPtr("12345"), + Host: "example.com", + Name: lo.ToPtr("svc-1"), + }, + }, + nil, + ) + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + assert.Equal(t, "12345", svc.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, svc) + require.True(t, ok, "Programmed condition not set on KongService") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) + }, + }, + { + name: "fail - no control plane ID in status returns an error and does not create the Service in Konnect", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Namespace: "default", + }, + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + Host: "example.com", + }, + }, + } + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + assert.Equal(t, "", svc.GetKonnectStatus().GetKonnectID()) + // TODO: we should probably set a condition when the control plane ID is missing in the status. + }, + expectedErr: true, + }, + { + name: "fail", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Namespace: "default", + }, + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + Host: "example.com", + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "123456789", + }, + }, + } + + sdk. + EXPECT(). + CreateService(ctx, "123456789", kongServiceToSDKServiceInput(svc)). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Detail: "bad request", + }, + ) + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + assert.Equal(t, "", svc.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, svc) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, "FailedToCreate", cond.Reason) + assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, `failed to create KongService default/svc-1: {"status":400,"title":"","instance":"","detail":"bad request","invalid_parameters":null}`, cond.Message) + }, + expectedErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, svc := tc.mockServicePair() + + err := createService(ctx, sdk, svc) + t.Cleanup(func() { + assert.True(t, sdk.AssertExpectations(t)) + }) + + tc.assertions(t, svc) + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + }) + } +} + +func TestDeleteKongService(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockServicePair func() (*MockServicesSDK, *configurationv1alpha1.KongService) + expectedErr bool + assertions func(*testing.T, *configurationv1alpha1.KongService) + }{ + { + name: "success", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + DeleteService(ctx, "12345", "123456789"). + Return( + &sdkkonnectops.DeleteServiceResponse{ + StatusCode: 200, + }, + nil, + ) + + return sdk, svc + }, + }, + { + name: "fail", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + DeleteService(ctx, "12345", "123456789"). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Detail: "bad request", + }, + ) + + return sdk, svc + }, + expectedErr: true, + }, + { + name: "not found error is ignored and considered a success when trying to delete", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + DeleteService(ctx, "12345", "123456789"). + Return( + nil, + &sdkkonnecterrs.SDKError{ + Message: "not found", + StatusCode: 404, + }, + ) + + return sdk, svc + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, svc := tc.mockServicePair() + + err := deleteService(ctx, sdk, svc) + + if tc.assertions != nil { + tc.assertions(t, svc) + } + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.True(t, sdk.AssertExpectations(t)) + }) + } +} + +func TestUpdateKongService(t *testing.T) { + ctx := context.Background() + testCases := []struct { + name string + mockServicePair func() (*MockServicesSDK, *configurationv1alpha1.KongService) + expectedErr bool + assertions func(*testing.T, *configurationv1alpha1.KongService) + }{ + { + name: "success", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + UpsertService(ctx, + sdkkonnectops.UpsertServiceRequest{ + ControlPlaneID: "12345", + ServiceID: "123456789", + Service: kongServiceToSDKServiceInput(svc), + }, + ). + Return( + &sdkkonnectops.UpsertServiceResponse{ + StatusCode: 200, + Service: &sdkkonnectcomp.Service{ + ID: lo.ToPtr("123456789"), + Name: lo.ToPtr("svc-1"), + }, + }, + nil, + ) + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + assert.Equal(t, "123456789", svc.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, svc) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, "", cond.Message) + }, + }, + { + name: "fail", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Namespace: "default", + }, + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + UpsertService(ctx, + sdkkonnectops.UpsertServiceRequest{ + ControlPlaneID: "12345", + ServiceID: "123456789", + Service: kongServiceToSDKServiceInput(svc), + }, + ). + Return( + nil, + &sdkkonnecterrs.BadRequestError{ + Status: 400, + Title: "bad request", + }, + ) + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + // TODO: When we fail to update a KongService, do we want to clear + // the Konnect ID from the status? Probably not. + // assert.Equal(t, "", svc.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, svc) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionFalse, cond.Status) + assert.Equal(t, "FailedToUpdate", cond.Reason) + assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, `failed to update KongService default/svc-1: {"status":400,"title":"bad request","instance":"","detail":"","invalid_parameters":null}`, cond.Message) + }, + expectedErr: true, + }, + { + name: "when not found then try to create", + mockServicePair: func() (*MockServicesSDK, *configurationv1alpha1.KongService) { + sdk := &MockServicesSDK{} + svc := &configurationv1alpha1.KongService{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc-1", + Namespace: "default", + }, + Spec: configurationv1alpha1.KongServiceSpec{ + KongServiceAPISpec: configurationv1alpha1.KongServiceAPISpec{ + Name: lo.ToPtr("svc-1"), + }, + }, + Status: configurationv1alpha1.KongServiceStatus{ + Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{ + ControlPlaneID: "12345", + KonnectEntityStatus: konnectv1alpha1.KonnectEntityStatus{ + ID: "123456789", + }, + }, + }, + } + sdk. + EXPECT(). + UpsertService(ctx, + sdkkonnectops.UpsertServiceRequest{ + ControlPlaneID: "12345", + ServiceID: "123456789", + Service: kongServiceToSDKServiceInput(svc), + }, + ). + Return( + nil, + &sdkkonnecterrs.SDKError{ + StatusCode: 404, + Message: "not found", + }, + ) + + sdk. + EXPECT(). + CreateService(ctx, "12345", kongServiceToSDKServiceInput(svc)). + Return( + &sdkkonnectops.CreateServiceResponse{ + Service: &sdkkonnectcomp.Service{ + ID: lo.ToPtr("123456789"), + Name: lo.ToPtr("svc-1"), + }, + }, + nil, + ) + + return sdk, svc + }, + assertions: func(t *testing.T, svc *configurationv1alpha1.KongService) { + assert.Equal(t, "123456789", svc.GetKonnectStatus().GetKonnectID()) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, svc) + require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") + assert.Equal(t, metav1.ConditionTrue, cond.Status) + assert.Equal(t, conditions.KonnectEntityProgrammedReasonProgrammed, cond.Reason) + assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) + assert.Equal(t, "", cond.Message) + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + sdk, svc := tc.mockServicePair() + + err := updateService(ctx, sdk, svc) + + if tc.assertions != nil { + tc.assertions(t, svc) + } + + if tc.expectedErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.True(t, sdk.AssertExpectations(t)) + }) + } +} diff --git a/controller/konnect/reconciler_generic.go b/controller/konnect/reconciler_generic.go index 8720c2953..fcd16b455 100644 --- a/controller/konnect/reconciler_generic.go +++ b/controller/konnect/reconciler_generic.go @@ -16,6 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/kong/gateway-operator/controller/konnect/conditions" + "github.com/kong/gateway-operator/controller/konnect/constraints" + "github.com/kong/gateway-operator/controller/konnect/ops" "github.com/kong/gateway-operator/controller/pkg/log" "github.com/kong/gateway-operator/pkg/consts" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" @@ -35,7 +38,7 @@ const ( // KonnectEntityReconciler reconciles a Konnect entities. // It uses the generic type constraints to constrain the supported types. -type KonnectEntityReconciler[T SupportedKonnectEntityType, TEnt EntityType[T]] struct { +type KonnectEntityReconciler[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]] struct { sdkFactory SDKFactory DevelopmentMode bool Client client.Client @@ -44,12 +47,12 @@ type KonnectEntityReconciler[T SupportedKonnectEntityType, TEnt EntityType[T]] s // KonnectEntityReconcilerOption is a functional option for the KonnectEntityReconciler. type KonnectEntityReconcilerOption[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ] func(*KonnectEntityReconciler[T, TEnt]) // WithKonnectEntitySyncPeriod sets the sync period for the reconciler. -func WithKonnectEntitySyncPeriod[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func WithKonnectEntitySyncPeriod[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( d time.Duration, ) KonnectEntityReconcilerOption[T, TEnt] { return func(r *KonnectEntityReconciler[T, TEnt]) { @@ -60,8 +63,8 @@ func WithKonnectEntitySyncPeriod[T SupportedKonnectEntityType, TEnt EntityType[T // NewKonnectEntityReconciler returns a new KonnectEntityReconciler for the given // Konnect entity type. func NewKonnectEntityReconciler[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ]( sdkFactory SDKFactory, developmentMode bool, @@ -92,7 +95,7 @@ func (r *KonnectEntityReconciler[T, TEnt]) SetupWithManager(mgr ctrl.Manager) er e T ent = TEnt(&e) b = ctrl.NewControllerManagedBy(mgr). - Named(entityTypeName[T]()). + Named(constraints.EntityTypeName[T]()). WithOptions(controller.Options{ MaxConcurrentReconciles: MaxConcurrentReconciles, }) @@ -116,7 +119,7 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( ctx context.Context, req ctrl.Request, ) (ctrl.Result, error) { var ( - entityTypeName = entityTypeName[T]() + entityTypeName = constraints.EntityTypeName[T]() logger = log.GetLogger(ctx, entityTypeName, r.DevelopmentMode) ) @@ -185,9 +188,9 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( if k8serrors.IsNotFound(err) { if res, err := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityAPIAuthConfigurationResolvedRefConditionType, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationResolvedRefReasonRefNotFound, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefReasonRefNotFound, fmt.Sprintf("Referenced KonnectAPIAuthConfiguration %s not found", apiAuthRef), ); err != nil || res.Requeue { return ctrl.Result{}, err @@ -198,9 +201,9 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( if res, err := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityAPIAuthConfigurationResolvedRefConditionType, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationResolvedRefReasonRefInvalid, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefReasonRefInvalid, fmt.Sprintf("KonnectAPIAuthConfiguration reference %s is invalid: %v", apiAuthRef, err), ); err != nil || res.Requeue { return ctrl.Result{}, err @@ -210,15 +213,15 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( } // Update the status if the reference is resolved and it's not as expected. - if cond, present := k8sutils.GetCondition(KonnectEntityAPIAuthConfigurationResolvedRefConditionType, ent); !present || + if cond, present := k8sutils.GetCondition(conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType, ent); !present || cond.Status != metav1.ConditionTrue || cond.ObservedGeneration != ent.GetGeneration() || - cond.Reason != KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef { + cond.Reason != conditions.KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef { if res, err := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityAPIAuthConfigurationResolvedRefConditionType, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType, metav1.ConditionTrue, - KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef, fmt.Sprintf("KonnectAPIAuthConfiguration reference %s is resolved", apiAuthRef), ); err != nil || res.Requeue { return res, err @@ -227,17 +230,17 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( } // Check if the referenced APIAuthConfiguration is valid. - if cond, present := k8sutils.GetCondition(KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !present || + if cond, present := k8sutils.GetCondition(conditions.KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !present || cond.Status != metav1.ConditionTrue || - cond.Reason != KonnectEntityAPIAuthConfigurationReasonValid { + cond.Reason != conditions.KonnectEntityAPIAuthConfigurationReasonValid { // If it's invalid then set the "APIAuthValid" status condition on // the entity to False with "Invalid" reason. if res, err := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationReasonInvalid, + conditions.KonnectEntityAPIAuthConfigurationReasonInvalid, conditionMessageReferenceKonnectAPIAuthConfigurationInvalid(apiAuthRef), ); err != nil || res.Requeue { return res, err @@ -249,17 +252,17 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( // If the referenced APIAuthConfiguration is valid, set the "APIAuthValid" // condition to True with "Valid" reason. // Only perform the update if the condition is not as expected. - if cond, present := k8sutils.GetCondition(KonnectEntityAPIAuthConfigurationValidConditionType, ent); !present || + if cond, present := k8sutils.GetCondition(conditions.KonnectEntityAPIAuthConfigurationValidConditionType, ent); !present || cond.Status != metav1.ConditionTrue || - cond.Reason != KonnectEntityAPIAuthConfigurationReasonValid || + cond.Reason != conditions.KonnectEntityAPIAuthConfigurationReasonValid || cond.ObservedGeneration != ent.GetGeneration() || cond.Message != conditionMessageReferenceKonnectAPIAuthConfigurationValid(apiAuthRef) { if res, err := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionTrue, - KonnectEntityAPIAuthConfigurationReasonValid, + conditions.KonnectEntityAPIAuthConfigurationReasonValid, conditionMessageReferenceKonnectAPIAuthConfigurationValid(apiAuthRef), ); err != nil || res.Requeue { return res, err @@ -271,9 +274,9 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( if err != nil { if res, errStatus := updateStatusWithCondition( ctx, r.Client, &apiAuth, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationReasonInvalid, + conditions.KonnectEntityAPIAuthConfigurationReasonInvalid, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -303,12 +306,12 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( } if controllerutil.RemoveFinalizer(ent, KonnectCleanupFinalizer) { - if err := Delete[T, TEnt](ctx, sdk, r.Client, ent); err != nil { + if err := ops.Delete[T, TEnt](ctx, sdk, r.Client, ent); err != nil { if res, errStatus := updateStatusWithCondition( ctx, r.Client, ent, - KonnectEntityProgrammedConditionType, + conditions.KonnectEntityProgrammedConditionType, metav1.ConditionFalse, - KonnectEntityProgrammedReasonKonnectAPIOpFailed, + conditions.KonnectEntityProgrammedReasonKonnectAPIOpFailed, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -333,7 +336,7 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( // We should look at the "expectations" for this: // https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/controller_utils.go if status := ent.GetKonnectStatus(); status == nil || status.GetKonnectID() == "" { - _, err := Create[T, TEnt](ctx, sdk, r.Client, ent) + _, err := ops.Create[T, TEnt](ctx, sdk, r.Client, ent) if err != nil { // TODO(pmalek): this is actually not 100% error prone because when status // update fails we don't store the Konnect ID and hence the reconciler @@ -345,8 +348,9 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( return ctrl.Result{}, fmt.Errorf("failed to update status after creating object: %w", err) } - return ctrl.Result{}, FailedKonnectOpError[T]{ - Op: CreateOp, + return ctrl.Result{}, ops.FailedKonnectOpError[T]{ + Op: ops.CreateOp, + Err: err, } } @@ -373,7 +377,7 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile( return ctrl.Result{}, nil } - if res, err := Update[T, TEnt](ctx, sdk, r.SyncPeriod, r.Client, ent); err != nil { + if res, err := ops.Update[T, TEnt](ctx, sdk, r.SyncPeriod, r.Client, ent); err != nil { return ctrl.Result{}, fmt.Errorf("failed to update object: %w", err) } else if res.Requeue || res.RequeueAfter > 0 { return res, nil @@ -431,7 +435,7 @@ func updateStatusWithCondition[T interface { } return ctrl.Result{}, fmt.Errorf( "failed to update status with %s condition: %w", - KonnectEntityAPIAuthConfigurationResolvedRefConditionType, err, + conditions.KonnectEntityAPIAuthConfigurationResolvedRefConditionType, err, ) } @@ -478,7 +482,7 @@ func getCPAuthRefForRef( }, nil } -func getAPIAuthRefNN[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func getAPIAuthRefNN[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( ctx context.Context, cl client.Client, ent TEnt, @@ -515,7 +519,7 @@ func getAPIAuthRefNN[T SupportedKonnectEntityType, TEnt EntityType[T]]( return getCPAuthRefForRef(ctx, cl, cpRef, ent.GetNamespace()) } - if ref, ok := any(ent).(EntityWithKonnectAPIAuthConfigurationRef); ok { + if ref, ok := any(ent).(constraints.EntityWithKonnectAPIAuthConfigurationRef); ok { return types.NamespacedName{ Name: ref.GetKonnectAPIAuthConfigurationRef().Name, // TODO(pmalek): enable if cross namespace refs are allowed @@ -529,7 +533,7 @@ func getAPIAuthRefNN[T SupportedKonnectEntityType, TEnt EntityType[T]]( ) } -func getServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func getServiceRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( e TEnt, ) mo.Option[configurationv1alpha1.ServiceRef] { switch e := any(e).(type) { @@ -551,7 +555,7 @@ func getServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( // handleKongServiceRef handles the ServiceRef for the given entity. // It sets the owner reference to the referenced KongService and updates the // status of the entity based on the referenced KongService status. -func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func handleKongServiceRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( ctx context.Context, cl client.Client, ent TEnt, @@ -572,9 +576,9 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if err := cl.Get(ctx, nn, &svc); err != nil { if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - KongServiceRefValidConditionType, + conditions.KongServiceRefValidConditionType, metav1.ConditionFalse, - KongServiceRefReasonInvalid, + conditions.KongServiceRefReasonInvalid, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -591,14 +595,14 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( } } - cond, ok := k8sutils.GetCondition(KonnectEntityProgrammedConditionType, &svc) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, &svc) if !ok || cond.Status != metav1.ConditionTrue { ent.SetKonnectID("") if res, err := updateStatusWithCondition( ctx, cl, ent, - KongServiceRefValidConditionType, + conditions.KongServiceRefValidConditionType, metav1.ConditionFalse, - KongServiceRefReasonInvalid, + conditions.KongServiceRefReasonInvalid, fmt.Sprintf("Referenced KongService %s is not programmed yet", nn), ); err != nil || res.Requeue { return ctrl.Result{}, err @@ -629,9 +633,9 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - KongServiceRefValidConditionType, + conditions.KongServiceRefValidConditionType, metav1.ConditionTrue, - KongServiceRefReasonValid, + conditions.KongServiceRefReasonValid, fmt.Sprintf("Referenced KongService %s programmed", nn), ); errStatus != nil || res.Requeue { return res, errStatus @@ -648,9 +652,9 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if err != nil { if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionFalse, - ControlPlaneRefReasonInvalid, + conditions.ControlPlaneRefReasonInvalid, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -664,13 +668,13 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( return ctrl.Result{}, err } - cond, ok = k8sutils.GetCondition(KonnectEntityProgrammedConditionType, cp) + cond, ok = k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, cp) if !ok || cond.Status != metav1.ConditionTrue || cond.ObservedGeneration != cp.GetGeneration() { if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionFalse, - ControlPlaneRefReasonInvalid, + conditions.ControlPlaneRefReasonInvalid, fmt.Sprintf("Referenced ControlPlane %s is not programmed yet", nn), ); errStatus != nil || res.Requeue { return res, errStatus @@ -688,9 +692,9 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionTrue, - ControlPlaneRefReasonValid, + conditions.ControlPlaneRefReasonValid, fmt.Sprintf("Referenced ControlPlane %s is programmed", nn), ); errStatus != nil || res.Requeue { return res, errStatus @@ -703,7 +707,7 @@ func handleKongServiceRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( return ctrl.Result{}, nil } -func getControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func getControlPlaneRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( e TEnt, ) mo.Option[configurationv1alpha1.ControlPlaneRef] { switch e := any(e).(type) { @@ -737,7 +741,7 @@ func getControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( // handleControlPlaneRef handles the ControlPlaneRef for the given entity. // It sets the owner reference to the referenced ControlPlane and updates the // status of the entity based on the referenced ControlPlane status. -func handleControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( +func handleControlPlaneRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]]( ctx context.Context, cl client.Client, ent TEnt, @@ -758,9 +762,9 @@ func handleControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if err := cl.Get(ctx, nn, &cp); err != nil { if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionFalse, - ControlPlaneRefReasonInvalid, + conditions.ControlPlaneRefReasonInvalid, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -774,13 +778,13 @@ func handleControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( return ctrl.Result{}, err } - cond, ok := k8sutils.GetCondition(KonnectEntityProgrammedConditionType, &cp) + cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, &cp) if !ok || cond.Status != metav1.ConditionTrue || cond.ObservedGeneration != cp.GetGeneration() { if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionFalse, - ControlPlaneRefReasonInvalid, + conditions.ControlPlaneRefReasonInvalid, fmt.Sprintf("Referenced ControlPlane %s is not programmed yet", nn), ); errStatus != nil || res.Requeue { return res, errStatus @@ -809,9 +813,9 @@ func handleControlPlaneRef[T SupportedKonnectEntityType, TEnt EntityType[T]]( if res, errStatus := updateStatusWithCondition( ctx, cl, ent, - ControlPlaneRefValidConditionType, + conditions.ControlPlaneRefValidConditionType, metav1.ConditionTrue, - ControlPlaneRefReasonValid, + conditions.ControlPlaneRefReasonValid, fmt.Sprintf("Referenced ControlPlane %s is programmed", nn), ); errStatus != nil || res.Requeue { return res, errStatus diff --git a/controller/konnect/reconciler_generic_test.go b/controller/konnect/reconciler_generic_test.go index 39655d04e..92ac0af91 100644 --- a/controller/konnect/reconciler_generic_test.go +++ b/controller/konnect/reconciler_generic_test.go @@ -8,6 +8,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/kong/gateway-operator/controller/konnect/constraints" "github.com/kong/gateway-operator/modules/manager/scheme" configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" @@ -29,8 +30,8 @@ func TestNewKonnectEntityReconciler(t *testing.T) { } func testNewKonnectEntityReconciler[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ]( t *testing.T, ent T, diff --git a/controller/konnect/reconciler_konnectapiauth.go b/controller/konnect/reconciler_konnectapiauth.go index 07b84bf6d..8e8823044 100644 --- a/controller/konnect/reconciler_konnectapiauth.go +++ b/controller/konnect/reconciler_konnectapiauth.go @@ -7,7 +7,7 @@ import ( "strings" "time" - sdkkonnectgoops "github.com/Kong/sdk-konnect-go/models/operations" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -18,6 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" + "github.com/kong/gateway-operator/controller/konnect/conditions" "github.com/kong/gateway-operator/controller/pkg/log" k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" @@ -121,9 +122,9 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( if err != nil { if res, errStatus := updateStatusWithCondition( ctx, r.Client, &apiAuth, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationReasonInvalid, + conditions.KonnectEntityAPIAuthConfigurationReasonInvalid, err.Error(), ); errStatus != nil || res.Requeue { return res, errStatus @@ -149,12 +150,12 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( // NOTE: This is needed because currently the SDK only lists the prod global API as supported: // https://github.com/Kong/sdk-konnect-go/blob/999d9a987e1aa7d2e09ac11b1450f4563adf21ea/models/operations/getorganizationsme.go#L10-L12 - respOrg, err := sdk.Me.GetOrganizationsMe(ctx, sdkkonnectgoops.WithServerURL("https://"+apiAuth.Spec.ServerURL)) + respOrg, err := sdk.Me.GetOrganizationsMe(ctx, sdkkonnectops.WithServerURL("https://"+apiAuth.Spec.ServerURL)) if err != nil { logger.Error(err, "failed to get organization info from Konnect") - if cond, ok := k8sutils.GetCondition(KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !ok || + if cond, ok := k8sutils.GetCondition(conditions.KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !ok || cond.Status != metav1.ConditionFalse || - cond.Reason != KonnectEntityAPIAuthConfigurationReasonInvalid || + cond.Reason != conditions.KonnectEntityAPIAuthConfigurationReasonInvalid || cond.ObservedGeneration != apiAuth.GetGeneration() || apiAuth.Status.OrganizationID != "" || apiAuth.Status.ServerURL != apiAuth.Spec.ServerURL { @@ -164,9 +165,9 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( res, errUpdate := updateStatusWithCondition( ctx, r.Client, &apiAuth, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionFalse, - KonnectEntityAPIAuthConfigurationReasonInvalid, + conditions.KonnectEntityAPIAuthConfigurationReasonInvalid, err.Error(), ) if errUpdate != nil || res.Requeue { @@ -189,10 +190,10 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( } condMessage = fmt.Sprintf("Token from Secret %s is valid", nn) } - if cond, ok := k8sutils.GetCondition(KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !ok || + if cond, ok := k8sutils.GetCondition(conditions.KonnectEntityAPIAuthConfigurationValidConditionType, &apiAuth); !ok || cond.Status != metav1.ConditionTrue || cond.Message != condMessage || - cond.Reason != KonnectEntityAPIAuthConfigurationReasonValid || + cond.Reason != conditions.KonnectEntityAPIAuthConfigurationReasonValid || cond.ObservedGeneration != apiAuth.GetGeneration() || apiAuth.Status.OrganizationID != *respOrg.MeOrganization.ID || apiAuth.Status.ServerURL != apiAuth.Spec.ServerURL { @@ -202,9 +203,9 @@ func (r *KonnectAPIAuthConfigurationReconciler) Reconcile( res, err := updateStatusWithCondition( ctx, r.Client, &apiAuth, - KonnectEntityAPIAuthConfigurationValidConditionType, + conditions.KonnectEntityAPIAuthConfigurationValidConditionType, metav1.ConditionTrue, - KonnectEntityAPIAuthConfigurationReasonValid, + conditions.KonnectEntityAPIAuthConfigurationReasonValid, condMessage, ) if err != nil || res.Requeue { diff --git a/controller/konnect/sdkfactory.go b/controller/konnect/sdkfactory.go index bf34e1164..07b6fb7cd 100644 --- a/controller/konnect/sdkfactory.go +++ b/controller/konnect/sdkfactory.go @@ -2,7 +2,7 @@ package konnect import ( sdkkonnectgo "github.com/Kong/sdk-konnect-go" - sdkkonnectgocomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" ) // SDKToken is a token used to authenticate with the Konnect SDK. @@ -24,7 +24,7 @@ func NewSDKFactory() SDKFactory { func (f sdkFactory) NewKonnectSDK(serverURL string, token SDKToken) *sdkkonnectgo.SDK { return sdkkonnectgo.New( sdkkonnectgo.WithSecurity( - sdkkonnectgocomp.Security{ + sdkkonnectcomp.Security{ PersonalAccessToken: sdkkonnectgo.String(string(token)), }, ), diff --git a/controller/konnect/watch.go b/controller/konnect/watch.go index da50897b0..4b9001f70 100644 --- a/controller/konnect/watch.go +++ b/controller/konnect/watch.go @@ -6,6 +6,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/kong/gateway-operator/controller/konnect/constraints" + configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" configurationv1beta1 "github.com/kong/kubernetes-configuration/api/configuration/v1beta1" @@ -15,8 +17,8 @@ import ( // ReconciliationWatchOptionsForEntity returns the watch options for the given // Konnect entity type. func ReconciliationWatchOptionsForEntity[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ]( cl client.Client, ent TEnt, diff --git a/controller/konnect/watch_test.go b/controller/konnect/watch_test.go index df7f618ff..a3d62e59d 100644 --- a/controller/konnect/watch_test.go +++ b/controller/konnect/watch_test.go @@ -6,6 +6,8 @@ import ( "github.com/stretchr/testify/require" fakectrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/kong/gateway-operator/controller/konnect/constraints" + configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" @@ -19,8 +21,8 @@ func TestWatchOptions(t *testing.T) { } func testReconciliationWatchOptionsForEntity[ - T SupportedKonnectEntityType, - TEnt EntityType[T], + T constraints.SupportedKonnectEntityType, + TEnt constraints.EntityType[T], ]( t *testing.T, ent TEnt, diff --git a/go.mod b/go.mod index b26476829..df5743961 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/texttheater/golang-levenshtein v1.0.1 // indirect github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/test/integration/test_konnect_entities.go b/test/integration/test_konnect_entities.go index a542fd76c..6094132f7 100644 --- a/test/integration/test_konnect_entities.go +++ b/test/integration/test_konnect_entities.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" "github.com/google/uuid" "github.com/samber/lo" "github.com/stretchr/testify/assert" @@ -61,9 +61,9 @@ func TestKonnectEntities(t *testing.T) { Namespace: ns.Name, }, Spec: konnectv1alpha1.KonnectGatewayControlPlaneSpec{ - CreateControlPlaneRequest: components.CreateControlPlaneRequest{ + CreateControlPlaneRequest: sdkkonnectcomp.CreateControlPlaneRequest{ Name: cpName, - ClusterType: lo.ToPtr(components.ClusterTypeClusterTypeControlPlane), + ClusterType: lo.ToPtr(sdkkonnectcomp.ClusterTypeClusterTypeControlPlane), Labels: map[string]string{"test_id": testID}, }, KonnectConfiguration: konnectv1alpha1.KonnectConfiguration{