-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(konnect): add generic reconciler scaffolding (#446)
* feat(konnect): add generic reconciler scaffolding * chore: add note about moving Konnect related status conditions * refactor: split APIAuthRefValid into 2 conditions: APIAuthValid and APIAuthResolvedRef * Update controller/konnect/constraints.go Co-authored-by: Mattia Lavacca <[email protected]> * Update controller/konnect/constraints.go * Update controller/konnect/constraints.go * chore: go mod tidy --------- Co-authored-by: Mattia Lavacca <[email protected]>
- Loading branch information
Showing
13 changed files
with
663 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package konnect | ||
|
||
// 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 | ||
|
||
const ( | ||
// KonnectEntityProgrammedConditionType is the type of the condition that | ||
// indicates whether the entity has been programmed in Konnect. | ||
KonnectEntityProgrammedConditionType = "Programmed" | ||
|
||
// KonnectEntityProgrammedReason is the reason for the Programmed condition. | ||
// It is set when the entity has been programmed in Konnect. | ||
KonnectEntityProgrammedReason = "Programmed" | ||
) | ||
|
||
const ( | ||
// KonnectEntityAPIAuthConfigurationResolvedRefConditionType is the type of the | ||
// condition that indicates whether the APIAuth configuration reference is | ||
// valid and points to an existing APIAuth configuration. | ||
KonnectEntityAPIAuthConfigurationResolvedRefConditionType = "APIAuthResolvedRef" | ||
|
||
// KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef is the reason | ||
// used with the APIAuthResolvedRef condition type indicating that the APIAuth | ||
// configuration reference has been resolved. | ||
KonnectEntityAPIAuthConfigurationResolvedRefReasonResolvedRef = "ResolvedRef" | ||
// KonnectEntityAPIAuthConfigurationResolvedRefReasonRefNotFound is the reason | ||
// used with the APIAuthResolvedRef condition type indicating that the APIAuth | ||
// configuration reference could not be resolved. | ||
KonnectEntityAPIAuthConfigurationResolvedRefReasonRefNotFound = "RefNotFound" | ||
// KonnectEntityAPIAuthConfigurationResolvedRefReasonRefNotFound is the reason | ||
// used with the APIAuthResolvedRef condition type indicating that the APIAuth | ||
// configuration reference is invalid and could not be resolved. | ||
// Condition message can contain more information about the error. | ||
KonnectEntityAPIAuthConfigurationResolvedRefReasonRefInvalid = "RefInvalid" | ||
) | ||
|
||
const ( | ||
// KonnectEntityAPIAuthConfigurationValidConditionType is the type of the | ||
// condition that indicates whether the referenced APIAuth configuration is | ||
// valid. | ||
KonnectEntityAPIAuthConfigurationValidConditionType = "APIAuthValid" | ||
|
||
// KonnectEntityAPIAuthConfigurationReasonValid is the reason used with the | ||
// APIAuthRefValid condition type indicating that the APIAuth configuration | ||
// referenced by the entity is valid. | ||
KonnectEntityAPIAuthConfigurationReasonValid = "Valid" | ||
// KonnectEntityAPIAuthConfigurationReasonInvalid is the reason used with the | ||
// APIAuthRefValid condition type indicating that the APIAuth configuration | ||
// referenced by the entity is invalid. | ||
KonnectEntityAPIAuthConfigurationReasonInvalid = "Invalid" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package konnect | ||
|
||
import ( | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
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" | ||
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1" | ||
) | ||
|
||
// SupportedKonnectEntityType is an interface that all Konnect entity types | ||
// must implement. | ||
type SupportedKonnectEntityType interface { | ||
configurationv1alpha1.KongService | | ||
configurationv1alpha1.KongRoute | | ||
configurationv1.KongConsumer | | ||
configurationv1beta1.KongConsumerGroup | ||
// TODO: add other types | ||
|
||
GetTypeName() string | ||
} | ||
|
||
// EntityType is an interface that all Konnect entity types must implement. | ||
// Separating this from SupportedKonnectEntityType allows us to use EntityType | ||
// where client.Object is required, since it embeds client.Object and uses pointer | ||
// to refer to the SupportedKonnectEntityType. | ||
type EntityType[ | ||
T SupportedKonnectEntityType, | ||
] interface { | ||
*T | ||
|
||
// Kubernetes Object methods | ||
GetObjectMeta() metav1.Object | ||
client.Object | ||
|
||
// Additional methods which are used in reconciling Konnect entities. | ||
GetConditions() []metav1.Condition | ||
SetConditions([]metav1.Condition) | ||
GetKonnectStatus() *configurationv1alpha1.KonnectEntityStatus | ||
GetKonnectAPIAuthConfigurationRef() konnectv1alpha1.KonnectAPIAuthConfigurationRef | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package konnect | ||
|
||
func entityTypeName[T SupportedKonnectEntityType]() string { | ||
var e T | ||
return e.GetTypeName() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package konnect | ||
|
||
import "fmt" | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package konnect | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
sdkkonnectgo "github.com/Kong/sdk-konnect-go" | ||
"github.com/go-logr/logr" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/kong/gateway-operator/controller/pkg/log" | ||
k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" | ||
) | ||
|
||
// Op is the Konnect operation type. | ||
type Op string | ||
|
||
const ( | ||
// CreateOp is the Konnect create operation. | ||
CreateOp Op = "create" | ||
// UpdateOp is the Konnect update operation. | ||
UpdateOp Op = "update" | ||
// DeleteOp is the Konnect delete operation. | ||
DeleteOp Op = "delete" | ||
) | ||
|
||
// Create creates a Konnect entity. | ||
func Create[ | ||
T SupportedKonnectEntityType, | ||
TEnt EntityType[T], | ||
](ctx context.Context, sdk *sdkkonnectgo.SDK, logger logr.Logger, cl client.Client, e *T) (*T, error) { | ||
defer logOpComplete[T, TEnt](logger, time.Now(), CreateOp, e) | ||
|
||
switch ent := any(e).(type) { //nolint:gocritic | ||
// --------------------------------------------------------------------- | ||
// TODO: add other Konnect types | ||
|
||
default: | ||
return nil, fmt.Errorf("unsupported entity type %T", ent) | ||
} | ||
} | ||
|
||
// Delete deletes a Konnect entity. | ||
func Delete[ | ||
T SupportedKonnectEntityType, | ||
TEnt EntityType[T], | ||
](ctx context.Context, sdk *sdkkonnectgo.SDK, logger logr.Logger, cl client.Client, e *T) error { | ||
defer logOpComplete[T, TEnt](logger, time.Now(), DeleteOp, e) | ||
|
||
switch ent := any(e).(type) { //nolint:gocritic | ||
// --------------------------------------------------------------------- | ||
// TODO: add other Konnect types | ||
|
||
default: | ||
return fmt.Errorf("unsupported entity type %T", ent) | ||
} | ||
} | ||
|
||
// Update updates a Konnect entity. | ||
func Update[ | ||
T SupportedKonnectEntityType, | ||
TEnt EntityType[T], | ||
](ctx context.Context, sdk *sdkkonnectgo.SDK, logger logr.Logger, cl client.Client, e *T) (ctrl.Result, error) { | ||
var ( | ||
ent = TEnt(e) | ||
condProgrammed, ok = k8sutils.GetCondition(KonnectEntityProgrammedConditionType, ent) | ||
now = time.Now() | ||
timeFromLastUpdate = time.Since(condProgrammed.LastTransitionTime.Time) | ||
) | ||
// If the entity is already programmed and the last update was less than | ||
// the configured sync period, requeue after the remaining time. | ||
if ok && | ||
condProgrammed.Status == metav1.ConditionTrue && | ||
condProgrammed.Reason == KonnectEntityProgrammedReason && | ||
condProgrammed.ObservedGeneration == ent.GetObjectMeta().GetGeneration() && | ||
timeFromLastUpdate <= configurableSyncPeriod { | ||
requeueAfter := configurableSyncPeriod - timeFromLastUpdate | ||
log.Debug(logger, "no need for update, requeueing after configured sync period", e, | ||
"last_update", condProgrammed.LastTransitionTime.Time, | ||
"time_from_last_update", timeFromLastUpdate, | ||
"requeue_after", requeueAfter, | ||
"requeue_at", now.Add(requeueAfter), | ||
) | ||
return ctrl.Result{ | ||
RequeueAfter: requeueAfter, | ||
}, nil | ||
} | ||
|
||
defer logOpComplete[T, TEnt](logger, now, UpdateOp, e) | ||
|
||
switch ent := any(e).(type) { //nolint:gocritic | ||
// --------------------------------------------------------------------- | ||
// TODO: add other Konnect types | ||
|
||
default: | ||
return ctrl.Result{}, fmt.Errorf("unsupported entity type %T", ent) | ||
} | ||
} | ||
|
||
func logOpComplete[ | ||
T SupportedKonnectEntityType, | ||
TEnt EntityType[T], | ||
](logger logr.Logger, start time.Time, op Op, e TEnt) { | ||
logger.Info("operation in Konnect API complete", | ||
"op", op, | ||
"duration", time.Since(start), | ||
"type", entityTypeName[T](), | ||
"konnect_id", e.GetKonnectStatus().GetKonnectID(), | ||
) | ||
} |
Oops, something went wrong.