diff --git a/controller/konnect/ops/kongupstream.go b/controller/konnect/ops/kongupstream.go index f780e2b7a..3b15fe1f4 100644 --- a/controller/konnect/ops/kongupstream.go +++ b/controller/konnect/ops/kongupstream.go @@ -12,4 +12,5 @@ type UpstreamsSDK interface { CreateUpstream(ctx context.Context, controlPlaneID string, upstream sdkkonnectcomp.UpstreamInput, opts ...sdkkonnectops.Option) (*sdkkonnectops.CreateUpstreamResponse, error) UpsertUpstream(ctx context.Context, req sdkkonnectops.UpsertUpstreamRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.UpsertUpstreamResponse, error) DeleteUpstream(ctx context.Context, controlPlaneID, upstreamID string, opts ...sdkkonnectops.Option) (*sdkkonnectops.DeleteUpstreamResponse, error) + ListUpstream(ctx context.Context, request sdkkonnectops.ListUpstreamRequest, opts ...sdkkonnectops.Option) (*sdkkonnectops.ListUpstreamResponse, error) } diff --git a/controller/konnect/ops/kongupstream_mock.go b/controller/konnect/ops/kongupstream_mock.go index 790cb5232..c39927bc5 100644 --- a/controller/konnect/ops/kongupstream_mock.go +++ b/controller/konnect/ops/kongupstream_mock.go @@ -175,6 +175,80 @@ func (_c *MockUpstreamsSDK_DeleteUpstream_Call) RunAndReturn(run func(context.Co return _c } +// ListUpstream provides a mock function with given fields: ctx, request, opts +func (_m *MockUpstreamsSDK) ListUpstream(ctx context.Context, request operations.ListUpstreamRequest, opts ...operations.Option) (*operations.ListUpstreamResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, request) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ListUpstream") + } + + var r0 *operations.ListUpstreamResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, operations.ListUpstreamRequest, ...operations.Option) (*operations.ListUpstreamResponse, error)); ok { + return rf(ctx, request, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, operations.ListUpstreamRequest, ...operations.Option) *operations.ListUpstreamResponse); ok { + r0 = rf(ctx, request, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*operations.ListUpstreamResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, operations.ListUpstreamRequest, ...operations.Option) error); ok { + r1 = rf(ctx, request, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockUpstreamsSDK_ListUpstream_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListUpstream' +type MockUpstreamsSDK_ListUpstream_Call struct { + *mock.Call +} + +// ListUpstream is a helper method to define mock.On call +// - ctx context.Context +// - request operations.ListUpstreamRequest +// - opts ...operations.Option +func (_e *MockUpstreamsSDK_Expecter) ListUpstream(ctx interface{}, request interface{}, opts ...interface{}) *MockUpstreamsSDK_ListUpstream_Call { + return &MockUpstreamsSDK_ListUpstream_Call{Call: _e.mock.On("ListUpstream", + append([]interface{}{ctx, request}, opts...)...)} +} + +func (_c *MockUpstreamsSDK_ListUpstream_Call) Run(run func(ctx context.Context, request operations.ListUpstreamRequest, opts ...operations.Option)) *MockUpstreamsSDK_ListUpstream_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.ListUpstreamRequest), variadicArgs...) + }) + return _c +} + +func (_c *MockUpstreamsSDK_ListUpstream_Call) Return(_a0 *operations.ListUpstreamResponse, _a1 error) *MockUpstreamsSDK_ListUpstream_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockUpstreamsSDK_ListUpstream_Call) RunAndReturn(run func(context.Context, operations.ListUpstreamRequest, ...operations.Option) (*operations.ListUpstreamResponse, error)) *MockUpstreamsSDK_ListUpstream_Call { + _c.Call.Return(run) + return _c +} + // UpsertUpstream provides a mock function with given fields: ctx, req, opts func (_m *MockUpstreamsSDK) UpsertUpstream(ctx context.Context, req operations.UpsertUpstreamRequest, opts ...operations.Option) (*operations.UpsertUpstreamResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/controller/konnect/ops/ops.go b/controller/konnect/ops/ops.go index 79b9c5ee7..88f4ea63a 100644 --- a/controller/konnect/ops/ops.go +++ b/controller/konnect/ops/ops.go @@ -115,15 +115,17 @@ func Create[ case *configurationv1alpha1.KongRoute: id, err = getKongRouteForUID(ctx, sdk.GetRoutesSDK(), ent) case *configurationv1alpha1.KongSNI: - id, err = getSNIForUID(ctx, sdk.GetSNIsSDK(), ent) + id, err = getKongSNIForUID(ctx, sdk.GetSNIsSDK(), ent) case *configurationv1.KongConsumer: - id, err = getConsumerForUID(ctx, sdk.GetConsumersSDK(), ent) + id, err = getKongConsumerForUID(ctx, sdk.GetConsumersSDK(), ent) case *configurationv1beta1.KongConsumerGroup: - id, err = getConsumerGroupForUID(ctx, sdk.GetConsumerGroupsSDK(), ent) + id, err = getKongConsumerGroupForUID(ctx, sdk.GetConsumerGroupsSDK(), ent) case *configurationv1alpha1.KongKeySet: id, err = getKongKeySetForUID(ctx, sdk.GetKeySetsSDK(), ent) case *configurationv1alpha1.KongKey: id, err = getKongKeyForUID(ctx, sdk.GetKeysSDK(), ent) + case *configurationv1alpha1.KongUpstream: + id, err = getKongUpstreamForUID(ctx, sdk.GetUpstreamsSDK(), ent) // --------------------------------------------------------------------- // TODO: add other Konnect types default: diff --git a/controller/konnect/ops/ops_controlplane.go b/controller/konnect/ops/ops_controlplane.go index e86e6e3f6..9459f5747 100644 --- a/controller/konnect/ops/ops_controlplane.go +++ b/controller/konnect/ops/ops_controlplane.go @@ -35,9 +35,6 @@ func createControlPlane( resp, err := sdk.CreateControlPlane(ctx, req) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, cp); errWrap != nil { return errWrap } diff --git a/controller/konnect/ops/ops_credentialacl.go b/controller/konnect/ops/ops_credentialacl.go index 9e0402435..8ac30a881 100644 --- a/controller/konnect/ops/ops_credentialacl.go +++ b/controller/konnect/ops/ops_credentialacl.go @@ -3,13 +3,11 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,7 +20,7 @@ func createKongCredentialACL( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: CreateOp} } resp, err := sdk.CreateACLWithConsumer(ctx, @@ -58,7 +56,7 @@ func updateKongCredentialACL( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: UpdateOp} } _, err := sdk.UpsertACLWithConsumer(ctx, diff --git a/controller/konnect/ops/ops_credentialapikey.go b/controller/konnect/ops/ops_credentialapikey.go index bebbf56b8..445fda564 100644 --- a/controller/konnect/ops/ops_credentialapikey.go +++ b/controller/konnect/ops/ops_credentialapikey.go @@ -3,13 +3,11 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,7 +20,7 @@ func createKongCredentialAPIKey( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: CreateOp} } resp, err := sdk.CreateKeyAuthWithConsumer(ctx, @@ -58,7 +56,7 @@ func updateKongCredentialAPIKey( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: UpdateOp} } _, err := sdk.UpsertKeyAuthWithConsumer(ctx, diff --git a/controller/konnect/ops/ops_credentialbasicauth.go b/controller/konnect/ops/ops_credentialbasicauth.go index 82cd4324a..cac1d77aa 100644 --- a/controller/konnect/ops/ops_credentialbasicauth.go +++ b/controller/konnect/ops/ops_credentialbasicauth.go @@ -3,13 +3,11 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,7 +20,7 @@ func createKongCredentialBasicAuth( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: CreateOp} } resp, err := sdk.CreateBasicAuthWithConsumer(ctx, @@ -58,7 +56,7 @@ func updateKongCredentialBasicAuth( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: UpdateOp} } _, err := sdk.UpsertBasicAuthWithConsumer(ctx, sdkkonnectops.UpsertBasicAuthWithConsumerRequest{ diff --git a/controller/konnect/ops/ops_credentialhmac.go b/controller/konnect/ops/ops_credentialhmac.go index b5dd6f0b8..2a80c4af9 100644 --- a/controller/konnect/ops/ops_credentialhmac.go +++ b/controller/konnect/ops/ops_credentialhmac.go @@ -3,12 +3,10 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -21,7 +19,7 @@ func createKongCredentialHMAC( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: CreateOp} } resp, err := sdk.CreateHmacAuthWithConsumer(ctx, @@ -57,7 +55,7 @@ func updateKongCredentialHMAC( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: UpdateOp} } _, err := sdk.UpsertHmacAuthWithConsumer(ctx, diff --git a/controller/konnect/ops/ops_credentialjwt.go b/controller/konnect/ops/ops_credentialjwt.go index 4685a1712..420a3a289 100644 --- a/controller/konnect/ops/ops_credentialjwt.go +++ b/controller/konnect/ops/ops_credentialjwt.go @@ -3,12 +3,10 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -21,7 +19,7 @@ func createKongCredentialJWT( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: CreateOp} } resp, err := sdk.CreateJwtWithConsumer(ctx, @@ -57,7 +55,7 @@ func updateKongCredentialJWT( ) error { cpID := cred.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", cred, client.ObjectKeyFromObject(cred)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cred, Op: UpdateOp} } _, err := sdk.UpsertJwtWithConsumer(ctx, diff --git a/controller/konnect/ops/ops_errors.go b/controller/konnect/ops/ops_errors.go index 405b395aa..b87a9b447 100644 --- a/controller/konnect/ops/ops_errors.go +++ b/controller/konnect/ops/ops_errors.go @@ -31,6 +31,21 @@ func (e EntityWithMatchingUIDNotFoundError) Error() string { ) } +// CantPerformOperationWithoutControlPlaneIDError is an error indicating that an +// operation cannot be performed without a ControlPlane ID. +type CantPerformOperationWithoutControlPlaneIDError struct { + Entity entity + Op Op +} + +// Error implements the error interface. +func (e CantPerformOperationWithoutControlPlaneIDError) Error() string { + return fmt.Sprintf( + "can't %s %s %s without a Konnect ControlPlane ID", + e.Op, e.Entity.GetTypeName(), client.ObjectKeyFromObject(e.Entity), + ) +} + type sdkErrorBody struct { Code int `json:"code"` Message string `json:"message"` diff --git a/controller/konnect/ops/ops_kongcacertificate.go b/controller/konnect/ops/ops_kongcacertificate.go index f950c01f1..a56a8ef9c 100644 --- a/controller/konnect/ops/ops_kongcacertificate.go +++ b/controller/konnect/ops/ops_kongcacertificate.go @@ -3,12 +3,10 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -23,7 +21,7 @@ func createCACertificate( ) error { cpID := cert.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cert, client.ObjectKeyFromObject(cert)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cert, Op: CreateOp} } resp, err := sdk.CreateCaCertificate(ctx, @@ -55,7 +53,7 @@ func updateCACertificate( ) error { cpID := cert.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T without a ControlPlaneID", cert) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cert, Op: UpdateOp} } _, err := sdk.UpsertCaCertificate(ctx, diff --git a/controller/konnect/ops/ops_kongcertificate.go b/controller/konnect/ops/ops_kongcertificate.go index cf03c6743..489856df1 100644 --- a/controller/konnect/ops/ops_kongcertificate.go +++ b/controller/konnect/ops/ops_kongcertificate.go @@ -3,12 +3,10 @@ package ops import ( "context" "errors" - "fmt" 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" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -23,7 +21,7 @@ func createCertificate( ) error { cpID := cert.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cert, client.ObjectKeyFromObject(cert)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cert, Op: CreateOp} } resp, err := sdk.CreateCertificate(ctx, @@ -55,7 +53,7 @@ func updateCertificate( ) error { cpID := cert.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T without a ControlPlaneID", cert) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cert, Op: UpdateOp} } _, err := sdk.UpsertCertificate(ctx, diff --git a/controller/konnect/ops/ops_kongconsumer.go b/controller/konnect/ops/ops_kongconsumer.go index 087fda304..9ad864be1 100644 --- a/controller/konnect/ops/ops_kongconsumer.go +++ b/controller/konnect/ops/ops_kongconsumer.go @@ -33,16 +33,14 @@ func createConsumer( ) error { cpID := consumer.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", consumer, client.ObjectKeyFromObject(consumer)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: consumer, Op: CreateOp} } resp, err := sdk.CreateConsumer(ctx, cpID, kongConsumerToSDKConsumerInput(consumer), ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 + if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, consumer); errWrap != nil { return errWrap } @@ -78,7 +76,7 @@ func updateConsumer( ) error { cpID := consumer.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T without a ControlPlaneID", consumer) + return CantPerformOperationWithoutControlPlaneIDError{Entity: consumer, Op: UpdateOp} } id := consumer.GetKonnectStatus().GetKonnectID() @@ -90,9 +88,6 @@ func updateConsumer( }, ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, consumer); errWrap != nil { return errWrap } @@ -337,8 +332,8 @@ func kongConsumerToSDKConsumerInput( } } -// getConsumerForUID lists consumers in Konnect with given k8s uid as its tag. -func getConsumerForUID( +// getKongConsumerForUID lists consumers in Konnect with given k8s uid as its tag. +func getKongConsumerForUID( ctx context.Context, sdk ConsumersSDK, consumer *configurationv1.KongConsumer, diff --git a/controller/konnect/ops/ops_kongconsumergroup.go b/controller/konnect/ops/ops_kongconsumergroup.go index c5e7e77a6..c0efd0834 100644 --- a/controller/konnect/ops/ops_kongconsumergroup.go +++ b/controller/konnect/ops/ops_kongconsumergroup.go @@ -9,7 +9,6 @@ import ( sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1beta1 "github.com/kong/kubernetes-configuration/api/configuration/v1beta1" @@ -21,16 +20,14 @@ func createConsumerGroup( group *configurationv1beta1.KongConsumerGroup, ) error { if group.GetControlPlaneID() == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", group, client.ObjectKeyFromObject(group)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: group, Op: CreateOp} } resp, err := sdk.CreateConsumerGroup(ctx, group.Status.Konnect.ControlPlaneID, kongConsumerGroupToSDKConsumerGroupInput(group), ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 + if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, group); errWrap != nil { return errWrap } @@ -55,7 +52,7 @@ func updateConsumerGroup( ) error { cpID := group.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", group, client.ObjectKeyFromObject(group)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: group, Op: UpdateOp} } _, err := sdk.UpsertConsumerGroup(ctx, @@ -66,9 +63,6 @@ func updateConsumerGroup( }, ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, group); errWrap != nil { return errWrap } @@ -120,8 +114,8 @@ func kongConsumerGroupToSDKConsumerGroupInput( } } -// getConsumerGroupForUID lists consumer groups in Konnect with given k8s uid as its tag. -func getConsumerGroupForUID( +// getKongConsumerGroupForUID lists consumer groups in Konnect with given k8s uid as its tag. +func getKongConsumerGroupForUID( ctx context.Context, sdk ConsumerGroupSDK, cg *configurationv1beta1.KongConsumerGroup, diff --git a/controller/konnect/ops/ops_kongdataplaneclientcertificate.go b/controller/konnect/ops/ops_kongdataplaneclientcertificate.go index 547ff796a..d4d582eaf 100644 --- a/controller/konnect/ops/ops_kongdataplaneclientcertificate.go +++ b/controller/konnect/ops/ops_kongdataplaneclientcertificate.go @@ -3,11 +3,9 @@ package ops import ( "context" "errors" - "fmt" sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,7 +20,7 @@ func createKongDataPlaneClientCertificate( ) error { cpID := cert.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", cert, client.ObjectKeyFromObject(cert)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: cert, Op: CreateOp} } resp, err := sdk.CreateDataplaneCertificate(ctx, diff --git a/controller/konnect/ops/ops_kongkey.go b/controller/konnect/ops/ops_kongkey.go index dfece8d5f..be2f8fe20 100644 --- a/controller/konnect/ops/ops_kongkey.go +++ b/controller/konnect/ops/ops_kongkey.go @@ -9,7 +9,6 @@ import ( sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -24,7 +23,7 @@ func createKey( ) error { cpID := key.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", key, client.ObjectKeyFromObject(key)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: key, Op: CreateOp} } resp, err := sdk.CreateKey(ctx, @@ -58,7 +57,7 @@ func updateKey( ) error { cpID := key.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T without a ControlPlaneID", key) + return CantPerformOperationWithoutControlPlaneIDError{Entity: key, Op: UpdateOp} } _, err := sdk.UpsertKey(ctx, diff --git a/controller/konnect/ops/ops_kongkeyset.go b/controller/konnect/ops/ops_kongkeyset.go index 2bd408800..5809a72ee 100644 --- a/controller/konnect/ops/ops_kongkeyset.go +++ b/controller/konnect/ops/ops_kongkeyset.go @@ -9,7 +9,6 @@ import ( sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -24,7 +23,7 @@ func createKeySet( ) error { cpID := keySet.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", keySet, client.ObjectKeyFromObject(keySet)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: keySet} } resp, err := sdk.CreateKeySet(ctx, @@ -59,7 +58,7 @@ func updateKeySet( ) error { cpID := keySet.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T without a ControlPlaneID", keySet) + return CantPerformOperationWithoutControlPlaneIDError{Entity: keySet, Op: UpdateOp} } _, err := sdk.UpsertKeySet(ctx, diff --git a/controller/konnect/ops/ops_kongpluginbinding.go b/controller/konnect/ops/ops_kongpluginbinding.go index ad3f8e867..0c5d40842 100644 --- a/controller/konnect/ops/ops_kongpluginbinding.go +++ b/controller/konnect/ops/ops_kongpluginbinding.go @@ -34,7 +34,7 @@ func createPlugin( ) error { controlPlaneID := pb.GetControlPlaneID() if controlPlaneID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", pb, client.ObjectKeyFromObject(pb)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: pb, Op: CreateOp} } pluginInput, err := kongPluginBindingToSDKPluginInput(ctx, cl, pb) if err != nil { @@ -72,7 +72,7 @@ func updatePlugin( ) error { controlPlaneID := pb.GetControlPlaneID() if controlPlaneID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", pb, client.ObjectKeyFromObject(pb)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: pb, Op: UpdateOp} } pluginInput, err := kongPluginBindingToSDKPluginInput(ctx, cl, pb) diff --git a/controller/konnect/ops/ops_kongroute.go b/controller/konnect/ops/ops_kongroute.go index af9347458..c65c6f23b 100644 --- a/controller/konnect/ops/ops_kongroute.go +++ b/controller/konnect/ops/ops_kongroute.go @@ -10,7 +10,6 @@ import ( sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -22,13 +21,11 @@ func createRoute( route *configurationv1alpha1.KongRoute, ) error { if route.GetControlPlaneID() == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", route, client.ObjectKeyFromObject(route)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: route, Op: CreateOp} } resp, err := sdk.CreateRoute(ctx, route.Status.Konnect.ControlPlaneID, kongRouteToSDKRouteInput(route)) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 + if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, route); errWrap != nil { return errWrap } @@ -53,7 +50,7 @@ func updateRoute( ) error { cpID := route.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", route, client.ObjectKeyFromObject(route)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: route, Op: UpdateOp} } id := route.GetKonnectStatus().GetKonnectID() @@ -63,9 +60,6 @@ func updateRoute( Route: kongRouteToSDKRouteInput(route), }) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, route); errWrap != nil { // Route update operation returns an SDKError instead of a NotFoundError. var sdkError *sdkkonnecterrs.SDKError diff --git a/controller/konnect/ops/ops_kongservice.go b/controller/konnect/ops/ops_kongservice.go index 2c3974bc6..f4f77490a 100644 --- a/controller/konnect/ops/ops_kongservice.go +++ b/controller/konnect/ops/ops_kongservice.go @@ -9,7 +9,6 @@ import ( sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" sdkkonnecterrs "github.com/Kong/sdk-konnect-go/models/sdkerrors" "github.com/samber/lo" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -21,10 +20,7 @@ func createService( 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 CantPerformOperationWithoutControlPlaneIDError{Entity: svc, Op: CreateOp} } resp, err := sdk.CreateService(ctx, @@ -32,9 +28,6 @@ func createService( kongServiceToSDKServiceInput(svc), ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, svc); errWrap != nil { return errWrap } @@ -43,7 +36,6 @@ func createService( return fmt.Errorf("failed creating %s: %w", svc.GetTypeName(), ErrNilResponse) } - // At this point, the Service has been created in Konnect. svc.SetKonnectID(*resp.Service.ID) return nil @@ -59,9 +51,7 @@ func updateService( svc *configurationv1alpha1.KongService, ) error { if svc.GetControlPlaneID() == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", - svc, client.ObjectKeyFromObject(svc), - ) + return CantPerformOperationWithoutControlPlaneIDError{Entity: svc, Op: UpdateOp} } id := svc.GetKonnectStatus().GetKonnectID() @@ -73,9 +63,6 @@ func updateService( }, ) - // Can't adopt it as it will cause conflicts between the controller - // that created that entity and already manages it. - // TODO: implement entity adoption https://github.com/Kong/gateway-operator/issues/460 if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, svc); errWrap != nil { // Service update operation returns an SDKError instead of a NotFoundError. var sdkError *sdkkonnecterrs.SDKError diff --git a/controller/konnect/ops/ops_kongservice_test.go b/controller/konnect/ops/ops_kongservice_test.go index b17ad1ff6..214b94739 100644 --- a/controller/konnect/ops/ops_kongservice_test.go +++ b/controller/konnect/ops/ops_kongservice_test.go @@ -92,9 +92,8 @@ func TestCreateKongService(t *testing.T) { }, 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. }, - expectedErrContains: `can't create *v1alpha1.KongService default/svc-1 without a Konnect ControlPlane ID`, + expectedErrContains: "can't create KongService default/svc-1 without a Konnect ControlPlane ID", }, { name: "fail", diff --git a/controller/konnect/ops/ops_kongsni.go b/controller/konnect/ops/ops_kongsni.go index edd13e954..f6a276e88 100644 --- a/controller/konnect/ops/ops_kongsni.go +++ b/controller/konnect/ops/ops_kongsni.go @@ -23,7 +23,7 @@ func createSNI( ) error { cpID := sni.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", sni, client.ObjectKeyFromObject(sni)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: sni, Op: CreateOp} } if sni.Status.Konnect == nil || sni.Status.Konnect.CertificateID == "" { return fmt.Errorf("can't create %T %s without a Konnect Certificate ID", sni, client.ObjectKeyFromObject(sni)) @@ -55,7 +55,7 @@ func updateSNI( ) error { cpID := sni.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", sni, client.ObjectKeyFromObject(sni)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: sni, Op: UpdateOp} } if sni.Status.Konnect == nil || sni.Status.Konnect.CertificateID == "" { return fmt.Errorf("can't update %T %s without a Konnect Certificate ID", sni, client.ObjectKeyFromObject(sni)) @@ -154,8 +154,8 @@ func kongSNIToSNIWithoutParents(sni *configurationv1alpha1.KongSNI) sdkkonnectco } } -// getSNIForUID returns the Konnect ID of the Konnect SNI that matches the UID of the provided SNI. -func getSNIForUID(ctx context.Context, sdk SNIsSDK, sni *configurationv1alpha1.KongSNI) (string, error) { +// getKongSNIForUID returns the Konnect ID of the Konnect SNI that matches the UID of the provided SNI. +func getKongSNIForUID(ctx context.Context, sdk SNIsSDK, sni *configurationv1alpha1.KongSNI) (string, error) { resp, err := sdk.ListSni(ctx, sdkkonnectops.ListSniRequest{ ControlPlaneID: sni.GetControlPlaneID(), Tags: lo.ToPtr(UIDLabelForObject(sni)), diff --git a/controller/konnect/ops/ops_kongtarget.go b/controller/konnect/ops/ops_kongtarget.go index b6dedfc8b..d172d6116 100644 --- a/controller/konnect/ops/ops_kongtarget.go +++ b/controller/konnect/ops/ops_kongtarget.go @@ -23,8 +23,9 @@ func createTarget( ) error { cpID := target.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't create %T %s without a Konnect ControlPlane ID", target, client.ObjectKeyFromObject(target)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: target, Op: CreateOp} } + if target.Status.Konnect == nil || target.Status.Konnect.UpstreamID == "" { return fmt.Errorf("can't create %T %s without a Konnect Upstream ID", target, client.ObjectKeyFromObject(target)) } @@ -53,7 +54,7 @@ func updateTarget( ) error { cpID := target.GetControlPlaneID() if cpID == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", target, client.ObjectKeyFromObject(target)) + return CantPerformOperationWithoutControlPlaneIDError{Entity: target, Op: UpdateOp} } if target.Status.Konnect == nil || target.Status.Konnect.UpstreamID == "" { return fmt.Errorf("can't update %T %s without a Konnect Upstream ID", target, client.ObjectKeyFromObject(target)) diff --git a/controller/konnect/ops/ops_kongupstream.go b/controller/konnect/ops/ops_kongupstream.go index 18d2193f0..9652bcd6f 100644 --- a/controller/konnect/ops/ops_kongupstream.go +++ b/controller/konnect/ops/ops_kongupstream.go @@ -8,7 +8,7 @@ import ( 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" - "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/samber/lo" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" @@ -20,10 +20,7 @@ func createUpstream( upstream *configurationv1alpha1.KongUpstream, ) error { if upstream.GetControlPlaneID() == "" { - return fmt.Errorf( - "can't create %T %s without a Konnect ControlPlane ID", - upstream, client.ObjectKeyFromObject(upstream), - ) + return CantPerformOperationWithoutControlPlaneIDError{Entity: upstream, Op: CreateOp} } resp, err := sdk.CreateUpstream(ctx, @@ -31,16 +28,15 @@ func createUpstream( kongUpstreamToSDKUpstreamInput(upstream), ) - // 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 if errWrap := wrapErrIfKonnectOpFailed(err, CreateOp, upstream); errWrap != nil { - SetKonnectEntityProgrammedConditionFalse(upstream, "FailedToCreate", errWrap.Error()) return errWrap } - upstream.Status.Konnect.SetKonnectID(*resp.Upstream.ID) - SetKonnectEntityProgrammedCondition(upstream) + if resp == nil || resp.Upstream == nil || resp.Upstream.ID == nil { + return fmt.Errorf("failed creating %s: %w", upstream.GetTypeName(), ErrNilResponse) + } + + upstream.SetKonnectID(*resp.Upstream.ID) return nil } @@ -55,9 +51,7 @@ func updateUpstream( upstream *configurationv1alpha1.KongUpstream, ) error { if upstream.GetControlPlaneID() == "" { - return fmt.Errorf("can't update %T %s without a Konnect ControlPlane ID", - upstream, client.ObjectKeyFromObject(upstream), - ) + return CantPerformOperationWithoutControlPlaneIDError{Entity: upstream, Op: UpdateOp} } id := upstream.GetKonnectStatus().GetKonnectID() @@ -69,9 +63,6 @@ func updateUpstream( }, ) - // 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 if errWrap := wrapErrIfKonnectOpFailed(err, UpdateOp, upstream); errWrap != nil { // Upstream update operation returns an SDKError instead of a NotFoundError. var sdkError *sdkkonnecterrs.SDKError @@ -95,12 +86,9 @@ func updateUpstream( } } - SetKonnectEntityProgrammedConditionFalse(upstream, "FailedToUpdate", errWrap.Error()) return errWrap } - SetKonnectEntityProgrammedCondition(upstream) - return nil } @@ -165,3 +153,30 @@ func kongUpstreamToSDKUpstreamInput( UseSrvName: upstream.Spec.UseSrvName, } } + +// getKongUpstreamForUID lists upstreams in Konnect with given k8s uid as its tag. +func getKongUpstreamForUID( + ctx context.Context, + sdk UpstreamsSDK, + u *configurationv1alpha1.KongUpstream, +) (string, error) { + cpID := u.GetControlPlaneID() + + reqList := sdkkonnectops.ListUpstreamRequest{ + // NOTE: only filter on object's UID. + // Other fields like name might have changed in the meantime but that's OK. + // Those will be enforced via subsequent updates. + ControlPlaneID: cpID, + Tags: lo.ToPtr(UIDLabelForObject(u)), + } + + resp, err := sdk.ListUpstream(ctx, reqList) + if err != nil { + return "", fmt.Errorf("failed listing %s: %w", u.GetTypeName(), err) + } + if resp == nil || resp.Object == nil { + return "", fmt.Errorf("failed listing %s: %w", u.GetTypeName(), ErrNilResponse) + } + + return getMatchingEntryFromListResponseData(sliceToEntityWithIDSlice(resp.Object.Data), u) +} diff --git a/controller/konnect/ops/ops_kongupstream_test.go b/controller/konnect/ops/ops_kongupstream_test.go index 2ae2f73b0..0b1f71cfc 100644 --- a/controller/konnect/ops/ops_kongupstream_test.go +++ b/controller/konnect/ops/ops_kongupstream_test.go @@ -15,7 +15,6 @@ import ( k8stypes "k8s.io/apimachinery/pkg/types" konnectconsts "github.com/kong/gateway-operator/controller/konnect/consts" - 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" @@ -67,11 +66,6 @@ func TestCreateKongUpstream(t *testing.T) { }, assertions: func(t *testing.T, svc *configurationv1alpha1.KongUpstream) { assert.Equal(t, "12345", svc.GetKonnectStatus().GetKonnectID()) - cond, ok := k8sutils.GetCondition(konnectv1alpha1.KonnectEntityProgrammedConditionType, svc) - require.True(t, ok, "Programmed condition not set on KongUpstream") - assert.Equal(t, metav1.ConditionTrue, cond.Status) - assert.Equal(t, konnectv1alpha1.KonnectEntityProgrammedReasonProgrammed, cond.Reason) - assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) }, }, { @@ -134,12 +128,6 @@ func TestCreateKongUpstream(t *testing.T) { }, assertions: func(t *testing.T, svc *configurationv1alpha1.KongUpstream) { assert.Equal(t, "", svc.GetKonnectStatus().GetKonnectID()) - cond, ok := k8sutils.GetCondition(konnectv1alpha1.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 KongUpstream default/svc-1: {"status":400,"title":"","instance":"","detail":"bad request","invalid_parameters":null}`, cond.Message) }, expectedErr: true, }, @@ -343,12 +331,6 @@ func TestUpdateKongUpstream(t *testing.T) { }, assertions: func(t *testing.T, svc *configurationv1alpha1.KongUpstream) { assert.Equal(t, "123456789", svc.GetKonnectStatus().GetKonnectID()) - cond, ok := k8sutils.GetCondition(konnectv1alpha1.KonnectEntityProgrammedConditionType, svc) - require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") - assert.Equal(t, metav1.ConditionTrue, cond.Status) - assert.Equal(t, konnectv1alpha1.KonnectEntityProgrammedReasonProgrammed, cond.Reason) - assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) - assert.Equal(t, "", cond.Message) }, }, { @@ -394,15 +376,9 @@ func TestUpdateKongUpstream(t *testing.T) { return sdk, svc }, assertions: func(t *testing.T, svc *configurationv1alpha1.KongUpstream) { - // TODO: When we fail to update a KongUpstream, do we want to clear - // the Konnect ID from the status? Probably not. - // assert.Equal(t, "", svc.GetKonnectStatus().GetKonnectID()) - cond, ok := k8sutils.GetCondition(konnectv1alpha1.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 KongUpstream default/svc-1: {"status":400,"title":"bad request","instance":"","detail":"","invalid_parameters":null}`, cond.Message) + assert.Equal(t, "123456789", svc.GetKonnectStatus().GetKonnectID(), + "Konnect ID should be retained after a failed update", + ) }, expectedErr: true, }, @@ -463,12 +439,6 @@ func TestUpdateKongUpstream(t *testing.T) { }, assertions: func(t *testing.T, svc *configurationv1alpha1.KongUpstream) { assert.Equal(t, "123456789", svc.GetKonnectStatus().GetKonnectID()) - cond, ok := k8sutils.GetCondition(konnectv1alpha1.KonnectEntityProgrammedConditionType, svc) - require.True(t, ok, "Programmed condition not set on KonnectGatewayControlPlane") - assert.Equal(t, metav1.ConditionTrue, cond.Status) - assert.Equal(t, konnectv1alpha1.KonnectEntityProgrammedReasonProgrammed, cond.Reason) - assert.Equal(t, svc.GetGeneration(), cond.ObservedGeneration) - assert.Equal(t, "", cond.Message) }, }, } diff --git a/controller/konnect/ops/ops_kongvault.go b/controller/konnect/ops/ops_kongvault.go index ef15ab1ce..f64da4d08 100644 --- a/controller/konnect/ops/ops_kongvault.go +++ b/controller/konnect/ops/ops_kongvault.go @@ -19,10 +19,7 @@ import ( func createVault(ctx context.Context, sdk VaultSDK, vault *configurationv1alpha1.KongVault) error { cpID := vault.GetControlPlaneID() if cpID == "" { - return fmt.Errorf( - "can't create %T %s without a Konnect ControlPlane ID", - vault, client.ObjectKeyFromObject(vault), - ) + return CantPerformOperationWithoutControlPlaneIDError{Entity: vault, Op: CreateOp} } vaultInput, err := kongVaultToVaultInput(vault) @@ -44,11 +41,9 @@ func createVault(ctx context.Context, sdk VaultSDK, vault *configurationv1alpha1 func updateVault(ctx context.Context, sdk VaultSDK, vault *configurationv1alpha1.KongVault) error { cpID := vault.GetControlPlaneID() if cpID == "" { - return fmt.Errorf( - "can't update %T %s without a Konnect ControlPlane ID", - vault, client.ObjectKeyFromObject(vault), - ) + return CantPerformOperationWithoutControlPlaneIDError{Entity: vault, Op: UpdateOp} } + id := vault.GetKonnectID() vaultInput, err := kongVaultToVaultInput(vault) if err != nil { diff --git a/test/envtest/konnect_entities_gatewaycontrolplane_test.go b/test/envtest/konnect_entities_gatewaycontrolplane_test.go index 6fd6af883..8755d6861 100644 --- a/test/envtest/konnect_entities_gatewaycontrolplane_test.go +++ b/test/envtest/konnect_entities_gatewaycontrolplane_test.go @@ -170,7 +170,12 @@ var konnectGatewayControlPlaneTestCases = []konnectEntityReconcilerTestCase{ ID: lo.ToPtr("12346"), }, }, - nil) + nil). + // NOTE: UpdateControlPlane can be called depending on the order + // of the events in the queue: either the group itself or the member + // control plane can be created first. + Maybe() + // verify that mock SDK is called as expected. t.Cleanup(func() { require.True(t, sdk.ControlPlaneSDK.AssertExpectations(t)) diff --git a/test/envtest/konnect_entities_kongupstream_test.go b/test/envtest/konnect_entities_kongupstream_test.go new file mode 100644 index 000000000..636879ff1 --- /dev/null +++ b/test/envtest/konnect_entities_kongupstream_test.go @@ -0,0 +1,143 @@ +package envtest + +import ( + "context" + "testing" + + sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components" + sdkkonnectops "github.com/Kong/sdk-konnect-go/models/operations" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/watch" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kong/gateway-operator/controller/konnect" + "github.com/kong/gateway-operator/controller/konnect/ops" + "github.com/kong/gateway-operator/modules/manager/scheme" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" + "github.com/kong/gateway-operator/test/helpers/deploy" + + configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" +) + +func TestKongUpstream(t *testing.T) { + t.Parallel() + ctx, cancel := Context(t, context.Background()) + defer cancel() + cfg, ns := Setup(t, ctx, scheme.Get()) + + t.Log("Setting up the manager with reconcilers") + mgr, logs := NewManager(t, ctx, cfg, scheme.Get()) + factory := ops.NewMockSDKFactory(t) + sdk := factory.SDK + StartReconcilers(ctx, t, mgr, logs, + konnect.NewKonnectEntityReconciler(factory, false, mgr.GetClient(), + konnect.WithKonnectEntitySyncPeriod[configurationv1alpha1.KongUpstream](konnectInfiniteSyncTime), + ), + ) + + t.Log("Setting up clients") + cl, err := client.NewWithWatch(mgr.GetConfig(), client.Options{ + Scheme: scheme.Get(), + }) + require.NoError(t, err) + clientNamespaced := client.NewNamespacedClient(mgr.GetClient(), ns.Name) + + t.Log("Creating KonnectAPIAuthConfiguration and KonnectGatewayControlPlane") + apiAuth := deploy.KonnectAPIAuthConfigurationWithProgrammed(t, ctx, clientNamespaced) + cp := deploy.KonnectGatewayControlPlaneWithID(t, ctx, clientNamespaced, apiAuth) + + t.Run("adding, patching and deleting KongUpstream", func(t *testing.T) { + const ( + upstreamID = "upstream-12345" + ) + + t.Log("Setting up a watch for KongUpstream events") + w := setupWatch[configurationv1alpha1.KongUpstreamList](t, ctx, cl, client.InNamespace(ns.Name)) + + t.Log("Setting up SDK expectations on Upstream creation") + sdk.UpstreamsSDK.EXPECT(). + CreateUpstream( + mock.Anything, + cp.GetKonnectID(), + mock.MatchedBy(func(req sdkkonnectcomp.UpstreamInput) bool { + return req.Algorithm != nil && *req.Algorithm == "round-robin" + }), + ). + Return( + &sdkkonnectops.CreateUpstreamResponse{ + Upstream: &sdkkonnectcomp.Upstream{ + ID: lo.ToPtr(upstreamID), + }, + }, + nil, + ) + + t.Log("Creating a KongUpstream") + createdUpstream := deploy.KongUpstreamAttachedToCP(t, ctx, clientNamespaced, cp, + func(obj client.Object) { + s := obj.(*configurationv1alpha1.KongUpstream) + s.Spec.KongUpstreamAPISpec.Algorithm = sdkkonnectcomp.UpstreamAlgorithmRoundRobin.ToPointer() + }, + ) + + t.Log("Checking SDK KongUpstream operations") + require.EventuallyWithT(t, func(c *assert.CollectT) { + assert.True(c, factory.SDK.UpstreamsSDK.AssertExpectations(t)) + }, waitTime, tickTime) + + t.Log("Waiting for Upstream to be programmed and get Konnect ID") + watchFor(t, ctx, w, watch.Modified, func(r *configurationv1alpha1.KongUpstream) bool { + return r.GetKonnectID() == upstreamID && k8sutils.IsProgrammed(r) + }, "KongUpstream didn't get Programmed status condition or didn't get the correct (upstream-12345) Konnect ID assigned") + + t.Log("Setting up SDK expectations on Upstream update") + sdk.UpstreamsSDK.EXPECT(). + UpsertUpstream( + mock.Anything, + mock.MatchedBy(func(req sdkkonnectops.UpsertUpstreamRequest) bool { + return req.UpstreamID == upstreamID && + req.Upstream.HashFallback != nil && + *req.Upstream.HashFallback == sdkkonnectcomp.HashFallbackHeader + }), + ). + Return(&sdkkonnectops.UpsertUpstreamResponse{}, nil) + + t.Log("Patching KongUpstream") + upstreamToPatch := createdUpstream.DeepCopy() + upstreamToPatch.Spec.HashFallback = sdkkonnectcomp.HashFallbackHeader.ToPointer() + upstreamToPatch.Spec.HashFallbackHeader = lo.ToPtr("X-Hash-Header") + require.NoError(t, clientNamespaced.Patch(ctx, upstreamToPatch, client.MergeFrom(createdUpstream))) + + t.Log("Waiting for Upstream to be updated in the SDK") + assert.EventuallyWithT(t, func(c *assert.CollectT) { + assert.True(c, factory.SDK.UpstreamsSDK.AssertExpectations(t)) + }, waitTime, tickTime) + + t.Log("Setting up SDK expectations on Upstream deletion") + sdk.UpstreamsSDK.EXPECT(). + DeleteUpstream( + mock.Anything, + cp.GetKonnectID(), + upstreamID, + ). + Return(&sdkkonnectops.DeleteUpstreamResponse{}, nil) + + t.Log("Deleting KongUpstream") + require.NoError(t, clientNamespaced.Delete(ctx, createdUpstream)) + + t.Log("Waiting for KongUpstream to disappear") + assert.EventuallyWithT(t, func(c *assert.CollectT) { + err := clientNamespaced.Get(ctx, client.ObjectKeyFromObject(createdUpstream), createdUpstream) + assert.True(c, err != nil && k8serrors.IsNotFound(err)) + }, waitTime, tickTime) + + t.Log("Waiting for Upstream to be deleted in the SDK") + assert.EventuallyWithT(t, func(c *assert.CollectT) { + assert.True(c, factory.SDK.UpstreamsSDK.AssertExpectations(t)) + }, waitTime, tickTime) + }) +}