Skip to content

Commit

Permalink
feat(konnect): add KongKey - KongKeySet binding
Browse files Browse the repository at this point in the history
  • Loading branch information
czeslavo committed Sep 27, 2024
1 parent c3cf78b commit b676014
Show file tree
Hide file tree
Showing 7 changed files with 421 additions and 87 deletions.
14 changes: 14 additions & 0 deletions controller/konnect/conditions/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,17 @@ const (
// condition type indicating that the KongUpstream reference is invalid.
KongUpstreamRefReasonInvalid = "Invalid"
)

const (
// KeySetRefValidConditionType is the type of the condition that indicates
// whether the KeySet reference is valid and points to an existing
// KeySet.
KeySetRefValidConditionType = "KeySetRefValid"

// KeySetRefReasonValid is the reason used with the KeySetRefValid
// condition type indicating that the KeySet reference is valid.
KeySetRefReasonValid = "Valid"
// KeySetRefReasonInvalid is the reason used with the KeySetRefValid
// condition type indicating that the KeySet reference is invalid.
KeySetRefReasonInvalid = "Invalid"
)
8 changes: 8 additions & 0 deletions controller/konnect/ops/ops_kongkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func kongKeyToKeyInput(key *configurationv1alpha1.KongKey) sdkkonnectcomp.KeyInp
specTags = key.Spec.Tags
k8sMetaTags = GenerateKubernetesMetadataTags(key)
)

k := sdkkonnectcomp.KeyInput{
Jwk: key.Spec.JWK,
Kid: key.Spec.KID,
Expand All @@ -151,5 +152,12 @@ func kongKeyToKeyInput(key *configurationv1alpha1.KongKey) sdkkonnectcomp.KeyInp
PublicKey: lo.ToPtr(key.Spec.PEM.PublicKey),
}
}
if konnectStatus := key.Status.Konnect; konnectStatus != nil {
if keySetID := konnectStatus.GetKeySetID(); keySetID != "" {
k.Set = &sdkkonnectcomp.Set{
ID: lo.ToPtr(konnectStatus.GetKeySetID()),
}
}
}
return k
}
166 changes: 125 additions & 41 deletions controller/konnect/ops/ops_kongkey_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package ops

import (
"sort"
"testing"

"github.com/google/uuid"
sdkkonnectcomp "github.com/Kong/sdk-konnect-go/models/components"
"github.com/samber/lo"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -12,55 +13,138 @@ import (
konnectconsts "github.com/kong/gateway-operator/controller/konnect/consts"

configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1"
konnectv1alpha1 "github.com/kong/kubernetes-configuration/api/konnect/v1alpha1"
)

func TestKongKeyToKeyInput(t *testing.T) {
key := &configurationv1alpha1.KongKey{
TypeMeta: metav1.TypeMeta{
Kind: "KongKey",
APIVersion: "configuration.konghq.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "key-1",
Namespace: "default",
Generation: 2,
UID: k8stypes.UID(uuid.NewString()),
Annotations: map[string]string{
konnectconsts.AnnotationTags: "tag1,tag2,duplicate",
testCases := []struct {
name string
key *configurationv1alpha1.KongKey
expectedOutput sdkkonnectcomp.KeyInput
}{
{
name: "kong key with all fields set without key set",
key: &configurationv1alpha1.KongKey{
TypeMeta: metav1.TypeMeta{
Kind: "KongKey",
APIVersion: "configuration.konghq.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "key-1",
Namespace: "default",
Generation: 2,
UID: k8stypes.UID("key-uid"),
Annotations: map[string]string{
konnectconsts.AnnotationTags: "tag1,tag2,duplicate",
},
},
Spec: configurationv1alpha1.KongKeySpec{
KongKeyAPISpec: configurationv1alpha1.KongKeyAPISpec{
KID: "kid",
Name: lo.ToPtr("name"),
JWK: lo.ToPtr("jwk"),
PEM: &configurationv1alpha1.PEMKeyPair{
PublicKey: "public",
PrivateKey: "private",
},
Tags: []string{"tag3", "tag4", "duplicate"},
},
},
},
expectedOutput: sdkkonnectcomp.KeyInput{
Kid: "kid",
Name: lo.ToPtr("name"),
Jwk: lo.ToPtr("jwk"),
Pem: &sdkkonnectcomp.Pem{
PublicKey: lo.ToPtr("public"),
PrivateKey: lo.ToPtr("private"),
},
Tags: []string{
"duplicate",
"k8s-generation:2",
"k8s-group:configuration.konghq.com",
"k8s-kind:KongKey",
"k8s-name:key-1",
"k8s-namespace:default",
"k8s-uid:key-uid",
"k8s-version:v1alpha1",
"tag1",
"tag2",
"tag3",
"tag4",
},
},
},
Spec: configurationv1alpha1.KongKeySpec{
KongKeyAPISpec: configurationv1alpha1.KongKeyAPISpec{
KID: "kid",
{
name: "kong key with all fields set with key set",
key: &configurationv1alpha1.KongKey{
TypeMeta: metav1.TypeMeta{
Kind: "KongKey",
APIVersion: "configuration.konghq.com/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "key-1",
Namespace: "default",
Generation: 2,
UID: k8stypes.UID("key-uid"),
Annotations: map[string]string{
konnectconsts.AnnotationTags: "tag1,tag2,duplicate",
},
},
Spec: configurationv1alpha1.KongKeySpec{
KongKeyAPISpec: configurationv1alpha1.KongKeyAPISpec{
KID: "kid",
Name: lo.ToPtr("name"),
JWK: lo.ToPtr("jwk"),
PEM: &configurationv1alpha1.PEMKeyPair{
PublicKey: "public",
PrivateKey: "private",
},
Tags: []string{"tag3", "tag4", "duplicate"},
},
},
Status: configurationv1alpha1.KongKeyStatus{
Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneAndKeySetRef{
KeySetID: "key-set-id",
},
},
},
expectedOutput: sdkkonnectcomp.KeyInput{
Kid: "kid",
Name: lo.ToPtr("name"),
JWK: lo.ToPtr("jwk"),
PEM: &configurationv1alpha1.PEMKeyPair{
PublicKey: "public",
PrivateKey: "private",
Jwk: lo.ToPtr("jwk"),
Pem: &sdkkonnectcomp.Pem{
PublicKey: lo.ToPtr("public"),
PrivateKey: lo.ToPtr("private"),
},
Set: &sdkkonnectcomp.Set{
ID: lo.ToPtr("key-set-id"),
},
Tags: []string{
"duplicate",
"k8s-generation:2",
"k8s-group:configuration.konghq.com",
"k8s-kind:KongKey",
"k8s-name:key-1",
"k8s-namespace:default",
"k8s-uid:key-uid",
"k8s-version:v1alpha1",
"tag1",
"tag2",
"tag3",
"tag4",
},
Tags: []string{"tag3", "tag4", "duplicate"},
},
},
}
output := kongKeyToKeyInput(key)
expectedTags := []string{
"k8s-generation:2",
"k8s-kind:KongKey",
"k8s-name:key-1",
"k8s-uid:" + string(key.GetUID()),
"k8s-version:v1alpha1",
"k8s-group:configuration.konghq.com",
"k8s-namespace:default",
"tag1",
"tag2",
"tag3",
"tag4",
"duplicate",

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output := kongKeyToKeyInput(tc.key)

// Tags order is not guaranteed, so we need to sort them before comparing.
sort.Strings(output.Tags)
require.Equal(t, tc.expectedOutput, output)
})
}
require.ElementsMatch(t, expectedTags, output.Tags)
require.Equal(t, "kid", output.Kid)
require.Equal(t, "name", *output.Name)
require.Equal(t, "jwk", *output.Jwk)
require.Equal(t, "public", *output.Pem.PublicKey)
require.Equal(t, "private", *output.Pem.PrivateKey)
}
119 changes: 119 additions & 0 deletions controller/konnect/reconciler_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,12 @@ func (r *KonnectEntityReconciler[T, TEnt]) Reconcile(
return res, nil
}

// If a type has a KongKeySet ref, handle it.
res, err = handleKongKeySetRef(ctx, r.Client, ent)
if err != nil || !res.IsZero() {
return res, err
}

apiAuthRef, err := getAPIAuthRefNN(ctx, r.Client, ent)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get APIAuth ref for %s: %w", client.ObjectKeyFromObject(ent), err)
Expand Down Expand Up @@ -679,6 +685,20 @@ func getServiceRef[T constraints.SupportedKonnectEntityType, TEnt constraints.En
}
}

func getKeySetRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]](
e TEnt,
) mo.Option[configurationv1alpha1.KeySetRef] {
switch e := any(e).(type) {
case *configurationv1alpha1.KongKey:
if e.Spec.KeySetRef == nil {
return mo.None[configurationv1alpha1.KeySetRef]()
}
return mo.Some(*e.Spec.KeySetRef)
default:
return mo.None[configurationv1alpha1.KeySetRef]()
}
}

// 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.
Expand Down Expand Up @@ -1147,6 +1167,105 @@ func handleControlPlaneRef[T constraints.SupportedKonnectEntityType, TEnt constr
}
}

// handleKongKeySetRef handles the KeySetRef for the given entity.
func handleKongKeySetRef[T constraints.SupportedKonnectEntityType, TEnt constraints.EntityType[T]](
ctx context.Context,
cl client.Client,
ent TEnt,
) (ctrl.Result, error) {
keySetRef, ok := getKeySetRef(ent).Get()
if !ok {
if key, ok := any(ent).(*configurationv1alpha1.KongKey); ok {
// If the entity has a resolved reference, but the spec has changed, we need to adjust the status.
if key.Status.Konnect != nil && key.Status.Konnect.GetKeySetID() != "" {
key.Status.Konnect.KeySetID = ""
if res, err := updateStatusWithCondition(ctx, cl, key,
conditions.KeySetRefValidConditionType,
metav1.ConditionTrue,
conditions.KeySetRefReasonValid,
"KeySetRef is nil",
); err != nil || !res.IsZero() {
return res, err
}
}
}
return ctrl.Result{}, nil
}

if keySetRef.Type != configurationv1alpha1.KeySetRefNamespacedRef {
return ctrl.Result{}, fmt.Errorf("unsupported KeySet ref type %q", keySetRef.Type)
}

keySet := configurationv1alpha1.KongKeySet{}
nn := types.NamespacedName{
Name: keySetRef.NamespacedRef.Name,
Namespace: ent.GetNamespace(),
}

if err := cl.Get(ctx, nn, &keySet); err != nil {
if res, errStatus := updateStatusWithCondition(
ctx, cl, ent,
conditions.KeySetRefValidConditionType,
metav1.ConditionFalse,
conditions.KeySetRefReasonInvalid,
err.Error(),
); errStatus != nil || res.Requeue {
return res, errStatus
}

// If it was not found, let's requeue.
if k8serrors.IsNotFound(err) {
return ctrl.Result{Requeue: true}, nil
}

return ctrl.Result{}, fmt.Errorf("failed getting KongKeySet %s: %w", nn, err)
}

// If referenced KongKeySet is being deleted, requeue.
if delTimestamp := keySet.GetDeletionTimestamp(); !delTimestamp.IsZero() {
return ctrl.Result{
RequeueAfter: time.Until(delTimestamp.Time),
}, nil
}

// Verify that the KongKeySet is programmed.
cond, ok := k8sutils.GetCondition(conditions.KonnectEntityProgrammedConditionType, &keySet)
if !ok || cond.Status != metav1.ConditionTrue {
if res, err := updateStatusWithCondition(
ctx, cl, ent,
conditions.KeySetRefValidConditionType,
metav1.ConditionFalse,
conditions.KeySetRefReasonInvalid,
fmt.Sprintf("Referenced KongKeySet %s is not programmed yet", nn),
); err != nil || res.Requeue {
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}

// TODO: make this generic.
// KongKeySet ID is not stored in KonnectEntityStatus because not all entities
// have a KeySetRef, hence the type constraints in the reconciler can't be used.
if key, ok := any(ent).(*configurationv1alpha1.KongKey); ok {
if key.Status.Konnect == nil {
key.Status.Konnect = &konnectv1alpha1.KonnectEntityStatusWithControlPlaneAndKeySetRef{}
}
key.Status.Konnect.KeySetID = keySet.Status.Konnect.GetKonnectID()
}

if res, errStatus := updateStatusWithCondition(
ctx, cl, ent,
conditions.KeySetRefValidConditionType,
metav1.ConditionTrue,
conditions.KeySetRefReasonValid,
fmt.Sprintf("Referenced KongKeySet %s programmed", nn),
); errStatus != nil || res.Requeue {
return res, errStatus
}

return ctrl.Result{}, nil
}

func conditionMessageReferenceKonnectAPIAuthConfigurationInvalid(apiAuthRef types.NamespacedName) string {
return fmt.Sprintf("referenced KonnectAPIAuthConfiguration %s is invalid", apiAuthRef)
}
Expand Down
Loading

0 comments on commit b676014

Please sign in to comment.