diff --git a/.gitignore b/.gitignore index 38310eeae5..d845e2c8e9 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ manifests/ /examples/observability/assets/backups/ /dashboards/grafana/output/ + +## Kubebuilder +**/kubebuilder diff --git a/operator/PROJECT b/operator/PROJECT index cfbaff34ce..ff0084804b 100644 --- a/operator/PROJECT +++ b/operator/PROJECT @@ -159,6 +159,9 @@ resources: kind: KeptnEvaluationProvider path: github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2 version: v1alpha2 + webhooks: + conversion: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/operator/apis/lifecycle/v1alpha1/common/common.go b/operator/apis/lifecycle/v1alpha1/common/common.go index a2173b6078..5c2e69d21a 100644 --- a/operator/apis/lifecycle/v1alpha1/common/common.go +++ b/operator/apis/lifecycle/v1alpha1/common/common.go @@ -1,6 +1,7 @@ package common import ( + "errors" "fmt" "math/rand" @@ -31,6 +32,8 @@ const MaxWorkloadNameLength = 25 const MaxTaskNameLength = 25 const MaxVersionLength = 12 +var ErrCannotCastKeptnEvaluationProvider = errors.New("cannot be cast KeptnEvaluationProvider to v1alpha2") + type KeptnState string const ( diff --git a/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion.go b/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion.go new file mode 100644 index 0000000000..43b066b383 --- /dev/null +++ b/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion.go @@ -0,0 +1,53 @@ +package v1alpha1 + +import ( + "fmt" + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha1/common" + corev1 "k8s.io/api/core/v1" + + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// ConvertTo converts the src v1alpha1.KeptnEvaluationProvider to the hub version (v1alpha2.KeptnEvaluationProvider) +func (src *KeptnEvaluationProvider) ConvertTo(dstRaw conversion.Hub) error { + dst, ok := dstRaw.(*v1alpha2.KeptnEvaluationProvider) + + if !ok { + return fmt.Errorf("type %T %w", dstRaw, common.ErrCannotCastKeptnEvaluationProvider) + } + + // Copy equal stuff to new object + // DO NOT COPY TypeMeta + dst.ObjectMeta = src.ObjectMeta + + dst.Spec.TargetServer = src.Spec.TargetServer + + // Set sensible defaults for new fields + dst.Spec.SecretKeyRef = corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: src.Spec.SecretName, + }, + Key: "apiToken", + } + + return nil +} + +// ConvertFrom converts from the hub version (v1alpha2.KeptnEvaluationProvider) to this version (v1alpha1.KeptnEvaluationProvider) +func (dst *KeptnEvaluationProvider) ConvertFrom(srcRaw conversion.Hub) error { + src, ok := srcRaw.(*v1alpha2.KeptnEvaluationProvider) + + if !ok { + return fmt.Errorf("type %T %w", srcRaw, common.ErrCannotCastKeptnEvaluationProvider) + } + + // Copy equal stuff to new object + // DO NOT COPY TypeMeta + dst.ObjectMeta = src.ObjectMeta + + dst.Spec.TargetServer = src.Spec.TargetServer + dst.Spec.SecretName = src.Spec.SecretKeyRef.Name + + return nil +} diff --git a/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion_test.go b/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion_test.go new file mode 100644 index 0000000000..673ba1e60a --- /dev/null +++ b/operator/apis/lifecycle/v1alpha1/keptnevaluationprovider_conversion_test.go @@ -0,0 +1,188 @@ +package v1alpha1 + +import ( + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha1/common" + corev1 "k8s.io/api/core/v1" + v2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v2" + "testing" + + "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestKeptnEvalProvider_ConvertFrom(t *testing.T) { + tests := []struct { + name string + srcObj *v1alpha2.KeptnEvaluationProvider + wantErr bool + wantObj *KeptnEvaluationProvider + }{ + { + name: "Test that conversion from v1alpha2 to v1alpha1 works", + srcObj: &v1alpha2.KeptnEvaluationProvider{ + TypeMeta: v1.TypeMeta{ + Kind: "KeptnEvaluationProvider", + APIVersion: "lifecycle.keptn.sh/v1alpha2", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "some-keptn-app-name", + Namespace: "", + Labels: map[string]string{ + "some-label": "some-label-value", + }, + Annotations: map[string]string{ + "some-annotation": "some-annotation-value", + }, + }, + Spec: v1alpha2.KeptnEvaluationProviderSpec{ + TargetServer: "my-server", + SecretKeyRef: corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret-name", + }, + Key: "my-secret-key", + }, + }, + Status: v1alpha2.KeptnEvaluationProviderStatus{}, + }, + wantErr: false, + wantObj: &KeptnEvaluationProvider{ + ObjectMeta: v1.ObjectMeta{ + Name: "some-keptn-app-name", + Namespace: "", + Labels: map[string]string{ + "some-label": "some-label-value", + }, + Annotations: map[string]string{ + "some-annotation": "some-annotation-value", + }, + }, + Spec: KeptnEvaluationProviderSpec{ + TargetServer: "my-server", + SecretName: "my-secret-name", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := &KeptnEvaluationProvider{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: KeptnEvaluationProviderSpec{}, + Status: KeptnEvaluationProviderStatus{}, + } + if err := dst.ConvertFrom(tt.srcObj); (err != nil) != tt.wantErr { + t.Errorf("ConvertFrom() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantObj != nil { + require.Equal(t, tt.wantObj, dst, "Object was not converted correctly") + } + }) + } +} + +func TestKeptnEvalProvider_ConvertTo(t *testing.T) { + tests := []struct { + name string + src *KeptnEvaluationProvider + wantErr bool + wantObj *v1alpha2.KeptnEvaluationProvider + }{ + { + name: "Test that conversion from v1alpha1 to v1alpha2 works", + src: &KeptnEvaluationProvider{ + TypeMeta: v1.TypeMeta{ + Kind: "KeptnEvaluationProvider", + APIVersion: "lifecycle.keptn.sh/v1alpha1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "some-keptn-app-name", + Namespace: "", + Labels: map[string]string{ + "some-label": "some-label-value", + }, + Annotations: map[string]string{ + "some-annotation": "some-annotation-value", + }, + }, + Spec: KeptnEvaluationProviderSpec{ + TargetServer: "my-server", + SecretName: "my-secret-name", + }, + Status: KeptnEvaluationProviderStatus{}, + }, + wantErr: false, + wantObj: &v1alpha2.KeptnEvaluationProvider{ + ObjectMeta: v1.ObjectMeta{ + Name: "some-keptn-app-name", + Namespace: "", + Labels: map[string]string{ + "some-label": "some-label-value", + }, + Annotations: map[string]string{ + "some-annotation": "some-annotation-value", + }, + }, + Spec: v1alpha2.KeptnEvaluationProviderSpec{ + TargetServer: "my-server", + SecretKeyRef: corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret-name", + }, + Key: "apiToken", + }, + }, + Status: v1alpha2.KeptnEvaluationProviderStatus{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := v1alpha2.KeptnEvaluationProvider{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: v1alpha2.KeptnEvaluationProviderSpec{}, + Status: v1alpha2.KeptnEvaluationProviderStatus{}, + } + if err := tt.src.ConvertTo(&dst); (err != nil) != tt.wantErr { + t.Errorf("ConvertTo() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantObj != nil { + require.Equal(t, tt.wantObj, &dst, "Object was not converted correctly") + } + }) + } +} + +func TestKeptnEvalProvider_ConvertFrom_Errorcase(t *testing.T) { + // A random different object is used here to simulate a different API version + testObj := v2.ExternalJob{} + + dst := &KeptnEvaluationProvider{ + TypeMeta: v1.TypeMeta{}, + ObjectMeta: v1.ObjectMeta{}, + Spec: KeptnEvaluationProviderSpec{}, + Status: KeptnEvaluationProviderStatus{}, + } + + if err := dst.ConvertFrom(&testObj); err == nil { + t.Errorf("ConvertFrom() error = %v", err) + } else { + require.ErrorIs(t, err, common.ErrCannotCastKeptnEvaluationProvider) + } +} + +func TestKeptnEvalProvider_ConvertTo_Errorcase(t *testing.T) { + testObj := KeptnEvaluationProvider{} + + // A random different object is used here to simulate a different API version + dst := v2.ExternalJob{} + + if err := testObj.ConvertTo(&dst); err == nil { + t.Errorf("ConvertTo() error = %v", err) + } else { + require.ErrorIs(t, err, common.ErrCannotCastKeptnEvaluationProvider) + } +} diff --git a/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_conversion.go b/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_conversion.go new file mode 100644 index 0000000000..35573e8f3f --- /dev/null +++ b/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_conversion.go @@ -0,0 +1,6 @@ +package v1alpha2 + +// Hub is the stub function to make the API conversion pattern with hub and spokes complete +func (*KeptnEvaluationProvider) Hub() { + // Hub() needed to implement interface +} diff --git a/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_webhook.go b/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_webhook.go new file mode 100644 index 0000000000..77eaada810 --- /dev/null +++ b/operator/apis/lifecycle/v1alpha2/keptnevaluationprovider_webhook.go @@ -0,0 +1,27 @@ +/* +Copyright 2022. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *KeptnEvaluationProvider) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} diff --git a/operator/config/crd/kustomization.yaml b/operator/config/crd/kustomization.yaml index 04469e4b9a..f700168cc1 100644 --- a/operator/config/crd/kustomization.yaml +++ b/operator/config/crd/kustomization.yaml @@ -23,7 +23,7 @@ patchesStrategicMerge: #- patches/webhook_in_keptnworkloadinstances.yaml #- patches/webhook_in_keptnappversions.yaml #- patches/webhook_in_keptnevaluationdefinitions.yaml -#- patches/webhook_in_keptnevaluationproviders.yaml +- patches/webhook_in_keptnevaluationproviders.yaml #- patches/webhook_in_keptnevaluations.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch @@ -36,7 +36,7 @@ patchesStrategicMerge: #- patches/cainjection_in_keptnworkloadinstances.yaml #- patches/cainjection_in_keptnappversions.yaml #- patches/cainjection_in_keptnevaluationdefinitions.yaml -#- patches/cainjection_in_keptnevaluationproviders.yaml +- patches/cainjection_in_keptnevaluationproviders.yaml #- patches/cainjection_in_keptnevaluations.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/operator/kubebuilder b/operator/kubebuilder deleted file mode 100644 index 8537307691..0000000000 --- a/operator/kubebuilder +++ /dev/null @@ -1 +0,0 @@ -Not Found \ No newline at end of file diff --git a/operator/main.go b/operator/main.go index c9eabe0f6b..118a515942 100644 --- a/operator/main.go +++ b/operator/main.go @@ -55,6 +55,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -348,6 +349,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "KeptnApp") os.Exit(1) } + if err = (&lifecyclev1alpha2.KeptnEvaluationProvider{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "KeptnEvaluationProvider") + os.Exit(1) + } //+kubebuilder:scaffold:builder err = meter.RegisterCallback(