Skip to content

Commit

Permalink
feat: DataPlaneKonnectExtension customization
Browse files Browse the repository at this point in the history
Signed-off-by: Mattia Lavacca <[email protected]>
  • Loading branch information
mlavacca committed Oct 8, 2024
1 parent 733f221 commit 369c606
Show file tree
Hide file tree
Showing 17 changed files with 423 additions and 47 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@
[#669](https://github.com/Kong/gateway-operator/pull/669)
- Allow setting `KonnectGatewayControlPlane`s group membership
[#697](https://github.com/Kong/gateway-operator/pull/697)
- Apply konnect-related customization to `DataPlane`s that properly reference `DataPlaneKonnectExtension`
resources.
[#714](https://github.com/Kong/gateway-operator/pull/714)

### Fixed

Expand Down
1 change: 1 addition & 0 deletions api/v1alpha1/dataplane_konnect_extension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type DataPlaneKonnectExtensionList struct {
type DataPlaneKonnectExtensionSpec struct {
// ControlPlaneRef is a reference to a ControlPlane this DataPlaneKonnectExtension is associated with.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self.type == 'konnectID'", message="Only konnectID type currently supported as controlPlaneRef."
ControlPlaneRef configurationv1alpha1.ControlPlaneRef `json:"controlPlaneRef"`

// ControlPlaneRegion is the region of the Konnect Control Plane.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ spec:
- type
type: object
x-kubernetes-validations:
- message: Only konnectID type currently supported as controlPlaneRef.
rule: self.type == 'konnectID'
- message: when type is konnectNamespacedRef, konnectNamespacedRef
must be set
rule: 'self.type == ''konnectNamespacedRef'' ? has(self.konnectNamespacedRef)
Expand Down
5 changes: 5 additions & 0 deletions controller/dataplane/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
return ctrl.Result{}, markErr
}

log.Trace(logger, "applying extensions", dataplane)
if err = applyExtensions(ctx, r.Client, dataplane); err != nil {
return ctrl.Result{}, err
}

log.Trace(logger, "exposing DataPlane deployment admin API via headless service", dataplane)
res, dataplaneAdminService, err := ensureAdminServiceForDataPlane(ctx, r.Client, dataplane,
client.MatchingLabels{
Expand Down
8 changes: 8 additions & 0 deletions controller/dataplane/controller_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,11 @@ func isDeploymentReady(deploymentStatus appsv1.DeploymentStatus) (metav1.Conditi
return metav1.ConditionFalse, false
}
}

// -----------------------------------------------------------------------------
// DataPlane - Private Functions - extensions
// -----------------------------------------------------------------------------

func applyExtensions(ctx context.Context, cl client.Client, dataplane *operatorv1beta1.DataPlane) error {
return applyDataPlaneKonnectExtension(ctx, cl, dataplane)
}
117 changes: 117 additions & 0 deletions controller/dataplane/dataplanekonnectextension_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package dataplane

import (
"context"
"reflect"

k8serrors "k8s.io/apimachinery/pkg/api/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

operatorv1alpha1 "github.com/kong/gateway-operator/api/v1alpha1"
operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1"
"github.com/kong/gateway-operator/controller/pkg/ctxinjector"
"github.com/kong/gateway-operator/controller/pkg/log"
operatorerrors "github.com/kong/gateway-operator/internal/errors"
"github.com/kong/gateway-operator/internal/utils/index"
"github.com/kong/gateway-operator/pkg/consts"
)

// -----------------------------------------------------------------------------
// DataKonnectExtensionReconciler
// -----------------------------------------------------------------------------

// DataPlaneKonnectExtensionReconciler reconciles a DataPlaneKonnectExtension object.
type DataPlaneKonnectExtensionReconciler struct {
client.Client
ContextInjector ctxinjector.CtxInjector
// DevelopmentMode indicates if the controller should run in development mode,
// which causes it to e.g. perform less validations.
DevelopmentMode bool
}

// SetupWithManager sets up the controller with the Manager.
func (r *DataPlaneKonnectExtensionReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&operatorv1alpha1.DataPlaneKonnectExtension{}).
Watches(&operatorv1beta1.DataPlane{}, handler.EnqueueRequestsFromMapFunc(r.listDataPlaneExtensionsReferenced)).
Complete(r)
}

// listDataPlaneExtensionsReferenced returns a list of all the DataPlaneKonnectExtensions referenced by the DataPlane object.
// Maximum one reference is expected.
func (r *DataPlaneKonnectExtensionReconciler) listDataPlaneExtensionsReferenced(ctx context.Context, obj client.Object) []reconcile.Request {
logger := ctrllog.FromContext(ctx)
dataPlane, ok := obj.(*operatorv1beta1.DataPlane)
if !ok {
logger.Error(
operatorerrors.ErrUnexpectedObject,
"failed to run map funcs",
"expected", "DataPlane", "found", reflect.TypeOf(obj),
)
return nil
}

if len(dataPlane.Spec.Extensions) == 0 {
return nil
}

recs := []reconcile.Request{}
for _, extension := range dataPlane.Spec.Extensions {
namespace := dataPlane.Namespace
if extension.Group == operatorv1alpha1.SchemeGroupVersion.Group &&
extension.Kind == "DataPlaneKonnectExtension" {
if extension.Namespace != nil {
namespace = *extension.Namespace
}
recs = append(recs, reconcile.Request{
NamespacedName: client.ObjectKey{
Namespace: namespace,
Name: extension.Name,
},
})
}
}
return recs
}

// Reconcile reconciles a DataPlaneKonnectExtension object.
func (r *DataPlaneKonnectExtensionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
ctx = r.ContextInjector.InjectKeyValues(ctx)
var konnectExtension operatorv1alpha1.DataPlaneKonnectExtension
if err := r.Client.Get(ctx, req.NamespacedName, &konnectExtension); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

logger := log.GetLogger(ctx, "dataplaneKonnectExtension", r.DevelopmentMode)
var dataPlaneList operatorv1beta1.DataPlaneList
if err := r.List(ctx, &dataPlaneList, client.MatchingFields{
index.DataPlaneKonnectExtensionIndex: konnectExtension.Namespace + "/" + konnectExtension.Name,
}); err != nil {
return ctrl.Result{}, err
}

var updated bool
switch len(dataPlaneList.Items) {
case 0:
updated = controllerutil.RemoveFinalizer(&konnectExtension, consts.DataPlaneExtensionFinalizer)
default:
updated = controllerutil.AddFinalizer(&konnectExtension, consts.DataPlaneExtensionFinalizer)
}
if updated {
if err := r.Client.Update(ctx, &konnectExtension); err != nil {
if k8serrors.IsConflict(err) {
return ctrl.Result{Requeue: true}, nil
}
return ctrl.Result{}, err
}

log.Info(logger, "DataPlaneKonnectExtension finalizer updated", konnectExtension)
}

return ctrl.Result{}, nil
}
113 changes: 113 additions & 0 deletions controller/dataplane/konnect_extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package dataplane

import (
"context"
"strings"

"github.com/samber/lo"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kong/gateway-operator/api/v1alpha1"
"github.com/kong/gateway-operator/api/v1beta1"
dputils "github.com/kong/gateway-operator/internal/utils/dataplane"
"github.com/kong/gateway-operator/pkg/consts"
k8sresources "github.com/kong/gateway-operator/pkg/utils/kubernetes/resources"
)

// applyDataPlaneKonnectExtension gets the DataPlane as argument, and in case it references a KonnectExtension, it
// fetch the referenced extension and applies the necessary changes to the DataPlane spec.
func applyDataPlaneKonnectExtension(ctx context.Context, cl client.Client, dataplane *v1beta1.DataPlane) error {
if len(dataplane.Spec.Extensions) == 0 {
return nil
}

for _, extensionRef := range dataplane.Spec.Extensions {
if extensionRef.Group != v1alpha1.SchemeGroupVersion.Group || extensionRef.Kind != "DataPlaneKonnectExtension" {
continue
}
namespace := dataplane.Namespace
if extensionRef.Namespace != nil {
namespace = *extensionRef.Namespace
}

konnectExt := v1alpha1.DataPlaneKonnectExtension{}
if err := cl.Get(ctx, client.ObjectKey{
Namespace: namespace,
Name: extensionRef.Name,
}, &konnectExt); err != nil {
return err
}

d := k8sresources.Deployment(appsv1.Deployment{
Spec: appsv1.DeploymentSpec{
Template: *dataplane.Spec.Deployment.PodTemplateSpec,
},
})

d.WithVolume(kongInKonnectClusterCertificateVolume())
d.WithVolumeMount(kongInKonnectClusterCertificateVolumeMount(), consts.DataPlaneProxyContainerName)
d.WithVolume(kongInKonnectClusterCertVolume(konnectExt.Spec.AuthConfiguration.ClusterCertificateSecretName.Name))
d.WithVolumeMount(kongInKonnectClusterVolumeMount(), consts.DataPlaneProxyContainerName)

envSet := customizeKongInKonnectDefaults(
dputils.KongInKonnectDefaults,
*konnectExt.Spec.ControlPlaneRef.KonnectID,
konnectExt.Spec.ControlPlaneRegion,
konnectExt.Spec.ServerHostname)

dputils.FillDataPlaneProxyContainerEnvs(nil, &d.Spec.Template, envSet)
dataplane.Spec.Deployment.PodTemplateSpec = &d.Spec.Template
}
return nil
}

// customizeKongInKonnectDefaults replaces placeholders in the KongInKonnect env list with the actual values.
func customizeKongInKonnectDefaults(envSet map[string]string,
controlPlane,
region,
server string,
) map[string]string {
newEnvSet := make(map[string]string, len(envSet))
for k, v := range envSet {
v = strings.ReplaceAll(v, "<CP-ID>", controlPlane)
v = strings.ReplaceAll(v, "<REGION>", region)
v = strings.ReplaceAll(v, "<SERVER>", server)
newEnvSet[k] = v
}
return newEnvSet
}

func kongInKonnectClusterCertVolume(secretName string) corev1.Volume {
return corev1.Volume{
Name: consts.KongClusterCertVolume,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
DefaultMode: lo.ToPtr(int32(420)),
},
},
}
}

func kongInKonnectClusterVolumeMount() corev1.VolumeMount {
return corev1.VolumeMount{
Name: consts.KongClusterCertVolume,
MountPath: consts.KongClusterCertVolumeMountPath,
}
}

func kongInKonnectClusterCertificateVolume() corev1.Volume {
return corev1.Volume{
Name: consts.ClusterCertificateVolume,
}
}

func kongInKonnectClusterCertificateVolumeMount() corev1.VolumeMount {
return corev1.VolumeMount{
Name: consts.ClusterCertificateVolume,
MountPath: consts.ClusterCertificateVolumeMountPath,
ReadOnly: true,
}
}
5 changes: 3 additions & 2 deletions controller/dataplane/owned_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (d *DeploymentBuilder) BuildAndDeploy(
return nil, op.Noop, err
}
// apply default envvars and restore the hacked-out ones
desiredDeployment = applyEnvForDataPlane(existingEnvVars, desiredDeployment)
desiredDeployment = applyEnvForDataPlane(existingEnvVars, desiredDeployment, dputils.KongDefaults)

// push the complete Deployment to Kubernetes
res, deployment, err := reconcileDataPlaneDeployment(ctx, d.client, d.logger,
Expand Down Expand Up @@ -200,8 +200,9 @@ func applyDeploymentUserPatchesForDataPlane(
func applyEnvForDataPlane(
existing []corev1.EnvVar,
deployment *k8sresources.Deployment,
envSet map[string]string,
) *k8sresources.Deployment {
dputils.FillDataPlaneProxyContainerEnvs(existing, &deployment.Spec.Template)
dputils.FillDataPlaneProxyContainerEnvs(existing, &deployment.Spec.Template, envSet)
return deployment
}

Expand Down
33 changes: 33 additions & 0 deletions controller/dataplane/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"

operatorv1alpha1 "github.com/kong/gateway-operator/api/v1alpha1"
operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1"
"github.com/kong/gateway-operator/internal/utils/index"
"github.com/kong/gateway-operator/pkg/consts"
Expand Down Expand Up @@ -48,6 +49,13 @@ func DataPlaneWatchBuilder(mgr ctrl.Manager) *builder.Builder {
&corev1.ConfigMap{},
handler.TypedEnqueueRequestsFromMapFunc(listDataPlanesReferencingKongPluginInstallation(mgr.GetClient())),
),
).
WatchesRawSource(
source.Kind(
mgr.GetCache(),
&operatorv1alpha1.DataPlaneKonnectExtension{},
handler.TypedEnqueueRequestsFromMapFunc(listDataPlanesReferencingDataPlaneKonnectExtension(mgr.GetClient())),
),
)
}

Expand Down Expand Up @@ -79,3 +87,28 @@ func listDataPlanesReferencingKongPluginInstallation(
})
}
}

func listDataPlanesReferencingDataPlaneKonnectExtension(
c client.Client,
) handler.TypedMapFunc[*operatorv1alpha1.DataPlaneKonnectExtension, reconcile.Request] {
return func(
ctx context.Context, ext *operatorv1alpha1.DataPlaneKonnectExtension,
) []reconcile.Request {
logger := ctrllog.FromContext(ctx)

// Find all DataPlane resources referencing DataPlaneKonnectExtension
// that maps to the DataPlaneKonnectExtension enqueued for reconciliation.
var dataPlaneList operatorv1beta1.DataPlaneList
if err := c.List(ctx, &dataPlaneList, client.MatchingFields{
index.DataPlaneKonnectExtensionIndex: ext.Namespace + "/" + ext.Name,
}); err != nil {
logger.Error(err, "Failed to list DataPlanes in watch", "DataPlaneKonnectExtension")
return nil
}
return lo.Map(dataPlaneList.Items, func(dp operatorv1beta1.DataPlane, _ int) reconcile.Request {
return reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(&dp),
}
})
}
}
Loading

0 comments on commit 369c606

Please sign in to comment.