Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add Konnect methods to KongConsumer #43

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions api/configuration/v1/kongconsumer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import (
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`,description="Age"
// +kubebuilder:printcolumn:name="Programmed",type=string,JSONPath=`.status.conditions[?(@.type=="Programmed")].status`
// +kubebuilder:validation:XValidation:rule="has(self.username) || has(self.custom_id)", message="Need to provide either username or custom_id"
// +kubebuilder:validation:XValidation:rule="!has(oldSelf.spec.controlPlaneRef) || has(self.spec.controlPlaneRef)", message="controlPlaneRef is required once set"
// +kubebuilder:validation:XValidation:rule="(!has(self.status) || !self.status.conditions.exists(c, c.type == 'Programmed' && c.status == 'True')) ? true : oldSelf.spec.controlPlaneRef == self.spec.controlPlaneRef", message="spec.controlPlaneRef is immutable when entity is already Programmed."

// KongConsumer is the Schema for the kongconsumers API.
type KongConsumer struct {
Expand Down Expand Up @@ -62,7 +64,28 @@ type KongConsumer struct {
Status KongConsumerStatus `json:"status,omitempty"`
}

func (c *KongConsumer) InitKonnectStatus() {
c.Status.Konnect = &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{}
}

func (c *KongConsumer) GetControlPlaneID() string {
if c.Status.Konnect == nil {
return ""
}
return c.Status.Konnect.ControlPlaneID
}

func (c *KongConsumer) SetControlPlaneID(id string) {
if c.Status.Konnect == nil {
c.InitKonnectStatus()
}
c.Status.Konnect.ControlPlaneID = id
}

func (c *KongConsumer) GetKonnectStatus() *konnectv1alpha1.KonnectEntityStatus {
if c.Status.Konnect == nil {
return nil
}
return &c.Status.Konnect.KonnectEntityStatus
}

Expand All @@ -73,10 +96,6 @@ func (c KongConsumer) GetTypeName() string {
func (c *KongConsumer) SetKonnectLabels(labels map[string]string) {
}

func (c *KongConsumer) GetKonnectAPIAuthConfigurationRef() konnectv1alpha1.KonnectAPIAuthConfigurationRef {
return c.Spec.KonnectConfiguration.APIAuthConfigurationRef
}

// GetConditions returns the Status Conditions
func (c *KongConsumer) GetConditions() []metav1.Condition {
return c.Status.Conditions
Expand All @@ -91,9 +110,6 @@ type KongConsumerSpec struct {
// ControlPlaneRef is a reference to a ControlPlane this Consumer is associated with.
// +optional
ControlPlaneRef *configurationv1alpha1.ControlPlaneRef `json:"controlPlaneRef,omitempty"`

// KonnectConfiguration holds the Konnect configuration like authentication configuration.
KonnectConfiguration konnectv1alpha1.KonnectConfiguration `json:"konnect,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
1 change: 0 additions & 1 deletion api/configuration/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 6 additions & 19 deletions config/crd/bases/configuration.konghq.com_kongconsumers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,25 +113,6 @@ spec:
: true'
- message: when type is konnectID, konnectID must be set
rule: 'self.type == ''konnectID'' ? has(self.konnectID) : true'
konnect:
description: KonnectConfiguration holds the Konnect configuration
like authentication configuration.
properties:
authRef:
description: |-
APIAuthConfigurationRef is the reference to the API Auth Configuration
that should be used for this Konnect Configuration.
properties:
name:
description: Name is the name of the KonnectAPIAuthConfiguration
resource.
type: string
required:
- name
type: object
required:
- authRef
type: object
type: object
status:
description: Status represents the current status of the KongConsumer
Expand Down Expand Up @@ -253,6 +234,12 @@ spec:
x-kubernetes-validations:
- message: Need to provide either username or custom_id
rule: has(self.username) || has(self.custom_id)
- message: controlPlaneRef is required once set
rule: '!has(oldSelf.spec.controlPlaneRef) || has(self.spec.controlPlaneRef)'
- message: spec.controlPlaneRef is immutable when entity is already Programmed.
rule: '(!has(self.status) || !self.status.conditions.exists(c, c.type ==
''Programmed'' && c.status == ''True'')) ? true : oldSelf.spec.controlPlaneRef
== self.spec.controlPlaneRef'
served: true
storage: true
subresources:
Expand Down
1 change: 0 additions & 1 deletion docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ _Appears in:_
| Field | Description |
| --- | --- |
| `controlPlaneRef` _[ControlPlaneRef](#controlplaneref)_ | ControlPlaneRef is a reference to a ControlPlane this Consumer is associated with. |
| `konnect` _[KonnectConfiguration](#konnectconfiguration)_ | KonnectConfiguration holds the Konnect configuration like authentication configuration. |


_Appears in:_
Expand Down
64 changes: 64 additions & 0 deletions test/crdsvalidation/kongconsumer/kongconsumer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package kongconsumer

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"

configurationv1client "github.com/kong/kubernetes-configuration/pkg/clientset/typed/configuration/v1"
"github.com/kong/kubernetes-configuration/test/crdsvalidation/kongconsumer/testcases"
)

func TestKongConsumer(t *testing.T) {
ctx := context.Background()
cfg, err := config.GetConfig()
require.NoError(t, err, "error loading Kubernetes config")
cl, err := configurationv1client.NewForConfig(cfg)
require.NoError(t, err, "error creating configurationv1 client")

for _, tcsGroup := range testcases.TestCases {
tcsGroup := tcsGroup
t.Run(tcsGroup.Name, func(t *testing.T) {
for _, tc := range tcsGroup.TestCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
cl := cl.KongConsumers(tc.KongConsumer.Namespace)
entity, err := cl.Create(ctx, &tc.KongConsumer, metav1.CreateOptions{})
if err == nil {
t.Cleanup(func() {
assert.NoError(t, client.IgnoreNotFound(cl.Delete(ctx, entity.Name, metav1.DeleteOptions{})))
})
// Create doesn't set the status, so we need to update it explicitly.
entity.Status = tc.KongConsumer.Status
entity, err = cl.UpdateStatus(ctx, entity, metav1.UpdateOptions{})
assert.NoError(t, err)
}

if tc.ExpectedErrorMessage == nil {
assert.NoError(t, err)

// Update the object and check if the update is allowed.
if tc.Update != nil {
tc.Update(entity)
_, err := cl.Update(ctx, entity, metav1.UpdateOptions{})
if tc.ExpectedUpdateErrorMessage != nil {
require.NotNil(t, err)
assert.Contains(t, err.Error(), *tc.ExpectedUpdateErrorMessage)
} else {
assert.NoError(t, err)
}
}
} else {
require.NotNil(t, err)
assert.Contains(t, err.Error(), *tc.ExpectedErrorMessage)
}
})
}
})
}
}
38 changes: 38 additions & 0 deletions test/crdsvalidation/kongconsumer/testcases/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package testcases

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1"
)

// testCase is a test case related to KongConsumer validation.
type testCase struct {
Name string
KongConsumer configurationv1.KongConsumer
Update func(*configurationv1.KongConsumer)
ExpectedErrorMessage *string
ExpectedUpdateErrorMessage *string
}

// testCasesGroup is a group of test cases related to KongConsumer validation.
// The grouping is done by a common name.
type testCasesGroup struct {
Name string
TestCases []testCase
}

// TestCases is a collection of all test cases groups related to KongConsumer validation.
var TestCases = []testCasesGroup{}

func init() {
TestCases = append(TestCases,
requiredFields,
updatesNotAllowedForStatus,
)
}

var commonObjectMeta = metav1.ObjectMeta{
GenerateName: "test-kongconsumer-",
Namespace: "default",
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package testcases

import (
"github.com/samber/lo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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"
)

// updatesNotAllowedForStatus are test cases checking if updates to konnect.authRef
// are indeed blocked for some status conditions.
var updatesNotAllowedForStatus = testCasesGroup{
Name: "updates not allowed for status conditions",
TestCases: []testCase{
{
Name: "konnect.authRef change is not allowed for Programmed=True",
KongConsumer: configurationv1.KongConsumer{
ObjectMeta: commonObjectMeta,
Spec: configurationv1.KongConsumerSpec{
ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{
Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef,
KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{
Name: "test-konnect-control-plane",
},
},
},
Username: "username-1",
Status: configurationv1.KongConsumerStatus{
Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{},
Conditions: []metav1.Condition{
{
Type: "Programmed",
Status: metav1.ConditionTrue,
Reason: "Valid",
LastTransitionTime: metav1.Now(),
},
},
},
},
Update: func(c *configurationv1.KongConsumer) {
c.Spec.ControlPlaneRef.KonnectNamespacedRef.Name = "new-konnect-control-plane"
},
ExpectedUpdateErrorMessage: lo.ToPtr("spec.controlPlaneRef is immutable when entity is already Programmed"),
},
{
Name: "konnect.authRef change is allowed when cp is not Programmed=True nor APIAuthValid=True",
KongConsumer: configurationv1.KongConsumer{
ObjectMeta: commonObjectMeta,
Spec: configurationv1.KongConsumerSpec{
ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{
Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef,
KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{
Name: "test-konnect-control-plane",
},
},
},
Username: "username-3",
Status: configurationv1.KongConsumerStatus{
Konnect: &konnectv1alpha1.KonnectEntityStatusWithControlPlaneRef{},
Conditions: []metav1.Condition{
{
Type: "Programmed",
Status: metav1.ConditionFalse,
Reason: "NotProgrammed",
LastTransitionTime: metav1.Now(),
},
},
},
},
Update: func(c *configurationv1.KongConsumer) {
c.Spec.ControlPlaneRef.KonnectNamespacedRef.Name = "new-konnect-control-plane"
},
},
},
}
59 changes: 59 additions & 0 deletions test/crdsvalidation/kongconsumer/testcases/required_fields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package testcases

import (
"github.com/samber/lo"

configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1"
configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1"
)

var requiredFields = testCasesGroup{
Name: "consumer required fields",
TestCases: []testCase{
{
Name: "username or custom_id required (username provided)",
KongConsumer: configurationv1.KongConsumer{
ObjectMeta: commonObjectMeta,
Spec: configurationv1.KongConsumerSpec{
ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{
Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef,
KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{
Name: "test-konnect-control-plane",
},
},
},
Username: "username-1",
},
},
{
Name: "username or custom_id required (custom_id provided)",
KongConsumer: configurationv1.KongConsumer{
ObjectMeta: commonObjectMeta,
Spec: configurationv1.KongConsumerSpec{
ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{
Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef,
KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{
Name: "test-konnect-control-plane",
},
},
},
CustomID: "customid-1",
},
},
{
Name: "username or custom_id required (none provided)",
KongConsumer: configurationv1.KongConsumer{
ObjectMeta: commonObjectMeta,
Spec: configurationv1.KongConsumerSpec{
ControlPlaneRef: &configurationv1alpha1.ControlPlaneRef{
Type: configurationv1alpha1.ControlPlaneRefKonnectNamespacedRef,
KonnectNamespacedRef: &configurationv1alpha1.KonnectNamespacedRef{
Name: "test-konnect-control-plane",
},
},
},
},
ExpectedErrorMessage: lo.ToPtr("Need to provide either username or custom_id"),
},
},
}
23 changes: 23 additions & 0 deletions test/unit/kongconsumer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package test

import (
"testing"

"github.com/stretchr/testify/require"

configurationv1 "github.com/kong/kubernetes-configuration/api/configuration/v1"
)

func TestKongConsumer(t *testing.T) {
c := &configurationv1.KongConsumer{}

require.Nil(t, c.GetKonnectStatus())
require.Empty(t, c.GetKonnectStatus().GetKonnectID())
require.Empty(t, c.GetKonnectStatus().GetOrgID())
require.Empty(t, c.GetKonnectStatus().GetServerURL())

require.Equal(t, "", c.GetControlPlaneID())
c.SetControlPlaneID("123")
require.Equal(t, "123", c.GetControlPlaneID())
require.Equal(t, "123", c.Status.Konnect.ControlPlaneID)
}
1 change: 1 addition & 0 deletions test/unit/kongservice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestKongService(t *testing.T) {
require.Empty(t, svc.GetKonnectStatus().GetOrgID())
require.Empty(t, svc.GetKonnectStatus().GetServerURL())

require.Equal(t, "", svc.GetControlPlaneID())
svc.SetControlPlaneID("123")
require.Equal(t, "123", svc.GetControlPlaneID())
require.Equal(t, "123", svc.Status.Konnect.ControlPlaneID)
Expand Down