diff --git a/api/v1alpha1/dataplane_konnect_extension_types.go b/api/v1alpha1/dataplane_konnect_extension_types.go index a27d37d6a..03b98f086 100644 --- a/api/v1alpha1/dataplane_konnect_extension_types.go +++ b/api/v1alpha1/dataplane_konnect_extension_types.go @@ -95,9 +95,9 @@ type DataPlaneKonnectExtensionSpec struct { // KonnectControlPlaneAPIAuthConfiguration contains the configuration to authenticate with Konnect API ControlPlane. // +apireference:kgo:include type KonnectControlPlaneAPIAuthConfiguration struct { - // ClusterCertificateSecretName is a name of the Secret containing the Konnect Control Plane's cluster certificate. + // ClusterCertificateSecretRef is the reference to the Secret containing the Konnect Control Plane's cluster certificate. // +kubebuilder:validation:Required - ClusterCertificateSecretName ClusterCertificateSecretRef `json:"clusterCertificateSecretRef"` + ClusterCertificateSecretRef ClusterCertificateSecretRef `json:"clusterCertificateSecretRef"` } // ClusterCertificateSecretRef contains the reference to the Secret containing the Konnect Control Plane's cluster certificate. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a07ff1c0f..be3754869 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -596,7 +596,7 @@ func (in *KongPluginInstallationStatus) DeepCopy() *KongPluginInstallationStatus // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KonnectControlPlaneAPIAuthConfiguration) DeepCopyInto(out *KonnectControlPlaneAPIAuthConfiguration) { *out = *in - out.ClusterCertificateSecretName = in.ClusterCertificateSecretName + out.ClusterCertificateSecretRef = in.ClusterCertificateSecretRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KonnectControlPlaneAPIAuthConfiguration. diff --git a/config/crd/bases/gateway-operator.konghq.com_dataplanekonnectextensions.yaml b/config/crd/bases/gateway-operator.konghq.com_dataplanekonnectextensions.yaml index 5efd92abd..fa2bd931d 100644 --- a/config/crd/bases/gateway-operator.konghq.com_dataplanekonnectextensions.yaml +++ b/config/crd/bases/gateway-operator.konghq.com_dataplanekonnectextensions.yaml @@ -110,8 +110,8 @@ spec: API authentication. properties: clusterCertificateSecretRef: - description: ClusterCertificateSecretName is a name of the Secret - containing the Konnect Control Plane's cluster certificate. + description: ClusterCertificateSecretRef is the reference to the + Secret containing the Konnect Control Plane's cluster certificate. properties: name: description: Name is the name of the Secret containing the diff --git a/controller/dataplane/konnect_extension.go b/controller/dataplane/konnect_extension.go index a9f946d36..2e723c30c 100644 --- a/controller/dataplane/konnect_extension.go +++ b/controller/dataplane/konnect_extension.go @@ -13,6 +13,7 @@ import ( "github.com/kong/gateway-operator/api/v1beta1" dputils "github.com/kong/gateway-operator/internal/utils/dataplane" "github.com/kong/gateway-operator/pkg/consts" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" k8sresources "github.com/kong/gateway-operator/pkg/utils/kubernetes/resources" ) @@ -40,15 +41,24 @@ func applyDataPlaneKonnectExtension(ctx context.Context, cl client.Client, datap return err } + if dataplane.Spec.Deployment.PodTemplateSpec == nil { + dataplane.Spec.Deployment.PodTemplateSpec = &corev1.PodTemplateSpec{} + } + d := k8sresources.Deployment(appsv1.Deployment{ Spec: appsv1.DeploymentSpec{ Template: *dataplane.Spec.Deployment.PodTemplateSpec, }, }) + if container := k8sutils.GetPodContainerByName(&d.Spec.Template.Spec, consts.DataPlaneProxyContainerName); container == nil { + d.Spec.Template.Spec.Containers = append(d.Spec.Template.Spec.Containers, corev1.Container{ + Name: consts.DataPlaneProxyContainerName, + }) + } d.WithVolume(kongInKonnectClusterCertificateVolume()) d.WithVolumeMount(kongInKonnectClusterCertificateVolumeMount(), consts.DataPlaneProxyContainerName) - d.WithVolume(kongInKonnectClusterCertVolume(konnectExt.Spec.AuthConfiguration.ClusterCertificateSecretName.Name)) + d.WithVolume(kongInKonnectClusterCertVolume(konnectExt.Spec.AuthConfiguration.ClusterCertificateSecretRef.Name)) d.WithVolumeMount(kongInKonnectClusterVolumeMount(), consts.DataPlaneProxyContainerName) envSet := customizeKongInKonnectDefaults( diff --git a/controller/dataplane/konnect_extension_test.go b/controller/dataplane/konnect_extension_test.go new file mode 100644 index 000000000..38245d226 --- /dev/null +++ b/controller/dataplane/konnect_extension_test.go @@ -0,0 +1,229 @@ +package dataplane + +import ( + "context" + "sort" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + operatorv1alpha1 "github.com/kong/gateway-operator/api/v1alpha1" + operatorv1beta1 "github.com/kong/gateway-operator/api/v1beta1" + dputils "github.com/kong/gateway-operator/internal/utils/dataplane" + "github.com/kong/gateway-operator/pkg/consts" + k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes" + + configurationv1alpha1 "github.com/kong/kubernetes-configuration/api/configuration/v1alpha1" +) + +func TestApplyDataPlaneKonnectExtension(t *testing.T) { + s := scheme.Scheme + require.NoError(t, operatorv1alpha1.AddToScheme(s)) + require.NoError(t, operatorv1beta1.AddToScheme(s)) + + tests := []struct { + name string + dataplane *operatorv1beta1.DataPlane + konnectExt *operatorv1alpha1.DataPlaneKonnectExtension + expectedError bool + }{ + { + name: "no extensions", + dataplane: &operatorv1beta1.DataPlane{ + Spec: operatorv1beta1.DataPlaneSpec{ + DataPlaneOptions: operatorv1beta1.DataPlaneOptions{ + Extensions: []operatorv1alpha1.ExtensionRef{}, + }, + }, + }, + expectedError: false, + }, + { + name: "Extension not found", + dataplane: &operatorv1beta1.DataPlane{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: operatorv1beta1.DataPlaneSpec{ + DataPlaneOptions: operatorv1beta1.DataPlaneOptions{ + Extensions: []operatorv1alpha1.ExtensionRef{ + { + Group: operatorv1alpha1.SchemeGroupVersion.Group, + Kind: "DataPlaneKonnectExtension", + NamespacedRef: operatorv1alpha1.NamespacedRef{ + Name: "konnect-ext", + }, + }, + }, + Deployment: operatorv1beta1.DataPlaneDeploymentOptions{ + DeploymentOptions: operatorv1beta1.DeploymentOptions{ + PodTemplateSpec: &corev1.PodTemplateSpec{}, + }, + }, + }, + }, + }, + expectedError: true, + }, + { + name: "Extension properly referenced, no deployment Options set.", + dataplane: &operatorv1beta1.DataPlane{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: operatorv1beta1.DataPlaneSpec{ + DataPlaneOptions: operatorv1beta1.DataPlaneOptions{ + Extensions: []operatorv1alpha1.ExtensionRef{ + { + Group: operatorv1alpha1.SchemeGroupVersion.Group, + Kind: "DataPlaneKonnectExtension", + NamespacedRef: operatorv1alpha1.NamespacedRef{ + Name: "konnect-ext", + }, + }, + }, + Deployment: operatorv1beta1.DataPlaneDeploymentOptions{ + DeploymentOptions: operatorv1beta1.DeploymentOptions{ + PodTemplateSpec: &corev1.PodTemplateSpec{}, + }, + }, + }, + }, + }, + konnectExt: &operatorv1alpha1.DataPlaneKonnectExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "konnect-ext", + Namespace: "default", + }, + Spec: operatorv1alpha1.DataPlaneKonnectExtensionSpec{ + AuthConfiguration: operatorv1alpha1.KonnectControlPlaneAPIAuthConfiguration{ + ClusterCertificateSecretRef: operatorv1alpha1.ClusterCertificateSecretRef{ + Name: "cluster-cert-secret", + }, + }, + ControlPlaneRef: configurationv1alpha1.ControlPlaneRef{ + KonnectID: lo.ToPtr("konnect-id"), + }, + ControlPlaneRegion: "us-west", + ServerHostname: "konnect.example.com", + }, + }, + expectedError: false, + }, + { + name: "Extension properly referenced, with deployment Options set.", + dataplane: &operatorv1beta1.DataPlane{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + }, + Spec: operatorv1beta1.DataPlaneSpec{ + DataPlaneOptions: operatorv1beta1.DataPlaneOptions{ + Extensions: []operatorv1alpha1.ExtensionRef{ + { + Group: operatorv1alpha1.SchemeGroupVersion.Group, + Kind: "DataPlaneKonnectExtension", + NamespacedRef: operatorv1alpha1.NamespacedRef{ + Name: "konnect-ext", + }, + }, + }, + Deployment: operatorv1beta1.DataPlaneDeploymentOptions{ + DeploymentOptions: operatorv1beta1.DeploymentOptions{ + PodTemplateSpec: &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "proxy", + Env: []corev1.EnvVar{ + { + Name: "KONG_TEST", + Value: "test", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + konnectExt: &operatorv1alpha1.DataPlaneKonnectExtension{ + ObjectMeta: metav1.ObjectMeta{ + Name: "konnect-ext", + Namespace: "default", + }, + Spec: operatorv1alpha1.DataPlaneKonnectExtensionSpec{ + AuthConfiguration: operatorv1alpha1.KonnectControlPlaneAPIAuthConfiguration{ + ClusterCertificateSecretRef: operatorv1alpha1.ClusterCertificateSecretRef{ + Name: "cluster-cert-secret", + }, + }, + ControlPlaneRef: configurationv1alpha1.ControlPlaneRef{ + KonnectID: lo.ToPtr("konnect-id"), + }, + ControlPlaneRegion: "us-west", + ServerHostname: "konnect.example.com", + }, + }, + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + objs := []runtime.Object{tt.dataplane} + if tt.konnectExt != nil { + objs = append(objs, tt.konnectExt) + } + cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build() + + dataplane := tt.dataplane.DeepCopy() + err := applyDataPlaneKonnectExtension(context.Background(), cl, dataplane) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + requiredEnv := []corev1.EnvVar{} + if tt.dataplane.Spec.Deployment.PodTemplateSpec != nil { + if container := k8sutils.GetPodContainerByName(&tt.dataplane.Spec.Deployment.PodTemplateSpec.Spec, consts.DataPlaneProxyContainerName); container != nil { + requiredEnv = container.Env + } + } + + if tt.konnectExt != nil { + requiredEnv = append(requiredEnv, getKongInKonnectEnvVars(*tt.konnectExt)...) + sort.Sort(k8sutils.SortableEnvVars(requiredEnv)) + assert.NotNil(t, dataplane.Spec.Deployment.PodTemplateSpec) + assert.Equal(t, requiredEnv, dataplane.Spec.Deployment.PodTemplateSpec.Spec.Containers[0].Env) + } + }) + } +} + +func getKongInKonnectEnvVars(konnectExt operatorv1alpha1.DataPlaneKonnectExtension) []corev1.EnvVar { + envSet := []corev1.EnvVar{} + envMap := customizeKongInKonnectDefaults( + dputils.KongInKonnectDefaults, + *konnectExt.Spec.ControlPlaneRef.KonnectID, + konnectExt.Spec.ControlPlaneRegion, + konnectExt.Spec.ServerHostname, + ) + for k, v := range envMap { + envSet = append(envSet, corev1.EnvVar{ + Name: k, + Value: v, + }) + } + return envSet +}