From d32885332c98dc72bd432bf458dd4d1d2fb1e6eb Mon Sep 17 00:00:00 2001 From: Geoffrey Israel Date: Wed, 7 Jun 2023 08:51:31 +0100 Subject: [PATCH] feat: add validating webhook for KeptnTaskDefinition (#1514) Signed-off-by: odubajDT Signed-off-by: geoffrey1330 Signed-off-by: Geoffrey Israel Co-authored-by: odubajDT <93584209+odubajDT@users.noreply.github.com> Co-authored-by: Rakshit Gondwal <98955085+rakshitgondwal@users.noreply.github.com> Co-authored-by: Florian Bacher --- operator/PROJECT | 3 + .../v1alpha3/keptntaskdefinition_webhook.go | 98 +++++++ .../keptntaskdefinition_webhook_test.go | 121 +++++++++ .../v1alpha3/zz_generated.deepcopy.go | 2 +- .../config/default/manager_webhook_patch.yaml | 23 ++ operator/config/webhook/manifests.yaml | 29 ++ .../interfaces/fake/phaseitem_mock.go | 248 ++++++++++-------- operator/main.go | 4 + .../00-teststep-install.yaml | 7 + .../01-teststep-assert.yaml | 6 + .../badtaskdefinition.yaml | 27 ++ .../goodtaskdefinition.yaml | 29 ++ 12 files changed, 485 insertions(+), 112 deletions(-) create mode 100644 operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook.go create mode 100644 operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook_test.go create mode 100644 operator/config/default/manager_webhook_patch.yaml create mode 100644 test/integration/validate-taskdefinition/00-teststep-install.yaml create mode 100644 test/integration/validate-taskdefinition/01-teststep-assert.yaml create mode 100644 test/integration/validate-taskdefinition/badtaskdefinition.yaml create mode 100644 test/integration/validate-taskdefinition/goodtaskdefinition.yaml diff --git a/operator/PROJECT b/operator/PROJECT index 5e2a1e5003..8c88afe3d8 100644 --- a/operator/PROJECT +++ b/operator/PROJECT @@ -234,6 +234,9 @@ resources: kind: KeptnTaskDefinition path: github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3 version: v1alpha3 + webhooks: + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook.go b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook.go new file mode 100644 index 0000000000..d06601747c --- /dev/null +++ b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook.go @@ -0,0 +1,98 @@ +/* +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 v1alpha3 + +import ( + "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var keptntaskdefinitionlog = logf.Log.WithName("keptntaskdefinition-resource") + +func (r *KeptnTaskDefinition) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-lifecycle-keptn-sh-v1alpha3-keptntaskdefinition,mutating=false,failurePolicy=fail,sideEffects=None,groups=lifecycle.keptn.sh,resources=keptntaskdefinitions,verbs=create;update,versions=v1alpha3,name=vkeptntaskdefinition.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &KeptnTaskDefinition{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *KeptnTaskDefinition) ValidateCreate() error { + keptntaskdefinitionlog.Info("validate create", "name", r.Name) + + return r.validateKeptnTaskDefinition() +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *KeptnTaskDefinition) ValidateUpdate(old runtime.Object) error { + keptntaskdefinitionlog.Info("validate update", "name", r.Name) + + return r.validateKeptnTaskDefinition() +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *KeptnTaskDefinition) ValidateDelete() error { + keptntaskdefinitionlog.Info("validate delete", "name", r.Name) + + return nil +} + +func (r *KeptnTaskDefinition) validateKeptnTaskDefinition() error { + var allErrs field.ErrorList //defined as a list to allow returning multiple validation errors + var err *field.Error + if err = r.validateFields(); err != nil { + allErrs = append(allErrs, err) + } + if len(allErrs) == 0 { + return nil + } + + return apierrors.NewInvalid( + schema.GroupKind{Group: "lifecycle.keptn.sh", Kind: "KeptnTaskDefinition"}, + r.Name, + allErrs) +} +func (r *KeptnTaskDefinition) validateFields() *field.Error { + + if r.Spec.Function == nil && r.Spec.Container == nil { + return field.Invalid( + field.NewPath("spec"), + r.Spec, + errors.New("Forbidden! Either Function or Container field must be defined").Error(), + ) + } + + if r.Spec.Function != nil && r.Spec.Container != nil { + return field.Invalid( + field.NewPath("spec"), + r.Spec, + errors.New("Forbidden! Both Function and Container fields cannot be defined simultaneously").Error(), + ) + } + + return nil +} diff --git a/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook_test.go b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook_test.go new file mode 100644 index 0000000000..efd372c307 --- /dev/null +++ b/operator/apis/lifecycle/v1alpha3/keptntaskdefinition_webhook_test.go @@ -0,0 +1,121 @@ +package v1alpha3 + +import ( + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestKeptnTaskDefinition_ValidateFields(t *testing.T) { + + specWithFunctionAndContainer := KeptnTaskDefinitionSpec{ + Function: &FunctionSpec{}, + Container: &ContainerSpec{}, + } + + emptySpec := KeptnTaskDefinitionSpec{} + + tests := []struct { + name string + spec KeptnTaskDefinitionSpec + want error + verb string + oldSpec runtime.Object + }{ + { + name: "with-no-function-or-container", + spec: emptySpec, + want: apierrors.NewInvalid( + schema.GroupKind{Group: "lifecycle.keptn.sh", Kind: "KeptnTaskDefinition"}, + "with-no-function-or-container", + []*field.Error{field.Invalid( + field.NewPath("spec"), + emptySpec, + errors.New("Forbidden! Either Function or Container field must be defined").Error(), + )}, + ), + verb: "create", + }, + { + name: "with-both-function-and-container", + spec: specWithFunctionAndContainer, + verb: "create", + want: apierrors.NewInvalid( + schema.GroupKind{Group: "lifecycle.keptn.sh", Kind: "KeptnTaskDefinition"}, + "with-both-function-and-container", + []*field.Error{field.Invalid( + field.NewPath("spec"), + specWithFunctionAndContainer, + errors.New("Forbidden! Both Function and Container fields cannot be defined simultaneously").Error(), + )}, + ), + }, + { + name: "with-function-only", + spec: KeptnTaskDefinitionSpec{ + Function: &FunctionSpec{}, + }, + verb: "create", + }, + { + name: "with-container-only", + spec: KeptnTaskDefinitionSpec{ + Container: &ContainerSpec{}, + }, + verb: "create", + }, + { + name: "update-with-both-function-and-container", + spec: specWithFunctionAndContainer, + want: apierrors.NewInvalid( + schema.GroupKind{Group: "lifecycle.keptn.sh", Kind: "KeptnTaskDefinition"}, + "update-with-both-function-and-container", + []*field.Error{field.Invalid( + field.NewPath("spec"), + specWithFunctionAndContainer, + errors.New("Forbidden! Both Function and Container fields cannot be defined simultaneously").Error(), + )}, + ), + oldSpec: &KeptnTaskDefinition{ + Spec: KeptnTaskDefinitionSpec{}, + }, + verb: "update", + }, + { + name: "delete", + verb: "delete", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ktd := &KeptnTaskDefinition{ + ObjectMeta: metav1.ObjectMeta{Name: tt.name}, + Spec: tt.spec, + } + + var got error + switch tt.verb { + case "create": + got = ktd.ValidateCreate() + case "update": + got = ktd.ValidateUpdate(tt.oldSpec) + case "delete": + got = ktd.ValidateDelete() + } + + if tt.want != nil { + require.NotNil(t, got) + require.EqualValues(t, tt.want, got) + } else { + require.Nil(t, got) + } + }) + } +} diff --git a/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go b/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go index cc68f44e91..e7bd2204bc 100644 --- a/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go +++ b/operator/apis/lifecycle/v1alpha3/zz_generated.deepcopy.go @@ -25,7 +25,7 @@ import ( "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" "go.opentelemetry.io/otel/propagation" "k8s.io/api/core/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/operator/config/default/manager_webhook_patch.yaml b/operator/config/default/manager_webhook_patch.yaml new file mode 100644 index 0000000000..8a96063a23 --- /dev/null +++ b/operator/config/default/manager_webhook_patch.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert diff --git a/operator/config/webhook/manifests.yaml b/operator/config/webhook/manifests.yaml index 08a2df38c8..b53d4e7942 100644 --- a/operator/config/webhook/manifests.yaml +++ b/operator/config/webhook/manifests.yaml @@ -27,3 +27,32 @@ webhooks: resources: - pods sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: lifecycle-validating-webhook-configuration + labels: + keptn.sh/inject-cert: "true" +webhooks: + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: lifecycle-webhook-service + namespace: system + path: /validate-lifecycle-keptn-sh-v1alpha3-keptntaskdefinition + failurePolicy: Fail + name: vkeptntaskdefinition.kb.io + rules: + - apiGroups: + - lifecycle.keptn.sh + apiVersions: + - v1alpha3 + operations: + - CREATE + - UPDATE + resources: + - keptntaskdefinitions + sideEffects: None diff --git a/operator/controllers/lifecycle/interfaces/fake/phaseitem_mock.go b/operator/controllers/lifecycle/interfaces/fake/phaseitem_mock.go index ba2d753f70..55cb514a1b 100644 --- a/operator/controllers/lifecycle/interfaces/fake/phaseitem_mock.go +++ b/operator/controllers/lifecycle/interfaces/fake/phaseitem_mock.go @@ -14,94 +14,94 @@ import ( // PhaseItemMock is a mock implementation of interfaces.PhaseItem. // -// func TestSomethingThatUsesPhaseItem(t *testing.T) { +// func TestSomethingThatUsesPhaseItem(t *testing.T) { // -// // make and configure a mocked interfaces.PhaseItem -// mockedPhaseItem := &PhaseItemMock{ -// CompleteFunc: func() { -// panic("mock out the Complete method") -// }, -// DeprecateRemainingPhasesFunc: func(phase apicommon.KeptnPhaseType) { -// panic("mock out the DeprecateRemainingPhases method") -// }, -// GenerateEvaluationFunc: func(evaluationDefinition klcv1alpha3.KeptnEvaluationDefinition, checkType apicommon.CheckType) klcv1alpha3.KeptnEvaluation { -// panic("mock out the GenerateEvaluation method") -// }, -// GenerateTaskFunc: func(taskDefinition klcv1alpha3.KeptnTaskDefinition, checkType apicommon.CheckType) klcv1alpha3.KeptnTask { -// panic("mock out the GenerateTask method") -// }, -// GetAppNameFunc: func() string { -// panic("mock out the GetAppName method") -// }, -// GetCurrentPhaseFunc: func() string { -// panic("mock out the GetCurrentPhase method") -// }, -// GetEndTimeFunc: func() time.Time { -// panic("mock out the GetEndTime method") -// }, -// GetNamespaceFunc: func() string { -// panic("mock out the GetNamespace method") -// }, -// GetParentNameFunc: func() string { -// panic("mock out the GetParentName method") -// }, -// GetPostDeploymentEvaluationTaskStatusFunc: func() []klcv1alpha3.ItemStatus { -// panic("mock out the GetPostDeploymentEvaluationTaskStatus method") -// }, -// GetPostDeploymentEvaluationsFunc: func() []string { -// panic("mock out the GetPostDeploymentEvaluations method") -// }, -// GetPostDeploymentTaskStatusFunc: func() []klcv1alpha3.ItemStatus { -// panic("mock out the GetPostDeploymentTaskStatus method") -// }, -// GetPostDeploymentTasksFunc: func() []string { -// panic("mock out the GetPostDeploymentTasks method") -// }, -// GetPreDeploymentEvaluationTaskStatusFunc: func() []klcv1alpha3.ItemStatus { -// panic("mock out the GetPreDeploymentEvaluationTaskStatus method") -// }, -// GetPreDeploymentEvaluationsFunc: func() []string { -// panic("mock out the GetPreDeploymentEvaluations method") -// }, -// GetPreDeploymentTaskStatusFunc: func() []klcv1alpha3.ItemStatus { -// panic("mock out the GetPreDeploymentTaskStatus method") -// }, -// GetPreDeploymentTasksFunc: func() []string { -// panic("mock out the GetPreDeploymentTasks method") -// }, -// GetPreviousVersionFunc: func() string { -// panic("mock out the GetPreviousVersion method") -// }, -// GetSpanAttributesFunc: func() []attribute.KeyValue { -// panic("mock out the GetSpanAttributes method") -// }, -// GetStartTimeFunc: func() time.Time { -// panic("mock out the GetStartTime method") -// }, -// GetStateFunc: func() apicommon.KeptnState { -// panic("mock out the GetState method") -// }, -// GetVersionFunc: func() string { -// panic("mock out the GetVersion method") -// }, -// IsEndTimeSetFunc: func() bool { -// panic("mock out the IsEndTimeSet method") -// }, -// SetCurrentPhaseFunc: func(s string) { -// panic("mock out the SetCurrentPhase method") -// }, -// SetSpanAttributesFunc: func(span trace.Span) { -// panic("mock out the SetSpanAttributes method") -// }, -// SetStateFunc: func(keptnState apicommon.KeptnState) { -// panic("mock out the SetState method") -// }, -// } +// // make and configure a mocked interfaces.PhaseItem +// mockedPhaseItem := &PhaseItemMock{ +// CompleteFunc: func() { +// panic("mock out the Complete method") +// }, +// DeprecateRemainingPhasesFunc: func(phase apicommon.KeptnPhaseType) { +// panic("mock out the DeprecateRemainingPhases method") +// }, +// GenerateEvaluationFunc: func(evaluationDefinition klcv1alpha3.KeptnEvaluationDefinition, checkType apicommon.CheckType) klcv1alpha3.KeptnEvaluation { +// panic("mock out the GenerateEvaluation method") +// }, +// GenerateTaskFunc: func(taskDefinition klcv1alpha3.KeptnTaskDefinition, checkType apicommon.CheckType) klcv1alpha3.KeptnTask { +// panic("mock out the GenerateTask method") +// }, +// GetAppNameFunc: func() string { +// panic("mock out the GetAppName method") +// }, +// GetCurrentPhaseFunc: func() string { +// panic("mock out the GetCurrentPhase method") +// }, +// GetEndTimeFunc: func() time.Time { +// panic("mock out the GetEndTime method") +// }, +// GetNamespaceFunc: func() string { +// panic("mock out the GetNamespace method") +// }, +// GetParentNameFunc: func() string { +// panic("mock out the GetParentName method") +// }, +// GetPostDeploymentEvaluationTaskStatusFunc: func() []klcv1alpha3.ItemStatus { +// panic("mock out the GetPostDeploymentEvaluationTaskStatus method") +// }, +// GetPostDeploymentEvaluationsFunc: func() []string { +// panic("mock out the GetPostDeploymentEvaluations method") +// }, +// GetPostDeploymentTaskStatusFunc: func() []klcv1alpha3.ItemStatus { +// panic("mock out the GetPostDeploymentTaskStatus method") +// }, +// GetPostDeploymentTasksFunc: func() []string { +// panic("mock out the GetPostDeploymentTasks method") +// }, +// GetPreDeploymentEvaluationTaskStatusFunc: func() []klcv1alpha3.ItemStatus { +// panic("mock out the GetPreDeploymentEvaluationTaskStatus method") +// }, +// GetPreDeploymentEvaluationsFunc: func() []string { +// panic("mock out the GetPreDeploymentEvaluations method") +// }, +// GetPreDeploymentTaskStatusFunc: func() []klcv1alpha3.ItemStatus { +// panic("mock out the GetPreDeploymentTaskStatus method") +// }, +// GetPreDeploymentTasksFunc: func() []string { +// panic("mock out the GetPreDeploymentTasks method") +// }, +// GetPreviousVersionFunc: func() string { +// panic("mock out the GetPreviousVersion method") +// }, +// GetSpanAttributesFunc: func() []attribute.KeyValue { +// panic("mock out the GetSpanAttributes method") +// }, +// GetStartTimeFunc: func() time.Time { +// panic("mock out the GetStartTime method") +// }, +// GetStateFunc: func() apicommon.KeptnState { +// panic("mock out the GetState method") +// }, +// GetVersionFunc: func() string { +// panic("mock out the GetVersion method") +// }, +// IsEndTimeSetFunc: func() bool { +// panic("mock out the IsEndTimeSet method") +// }, +// SetCurrentPhaseFunc: func(s string) { +// panic("mock out the SetCurrentPhase method") +// }, +// SetSpanAttributesFunc: func(span trace.Span) { +// panic("mock out the SetSpanAttributes method") +// }, +// SetStateFunc: func(keptnState apicommon.KeptnState) { +// panic("mock out the SetState method") +// }, +// } // -// // use mockedPhaseItem in code that requires interfaces.PhaseItem -// // and then make assertions. +// // use mockedPhaseItem in code that requires interfaces.PhaseItem +// // and then make assertions. // -// } +// } type PhaseItemMock struct { // CompleteFunc mocks the Complete method. CompleteFunc func() @@ -321,7 +321,8 @@ func (mock *PhaseItemMock) Complete() { // CompleteCalls gets all the calls that were made to Complete. // Check the length with: -// len(mockedPhaseItem.CompleteCalls()) +// +// len(mockedPhaseItem.CompleteCalls()) func (mock *PhaseItemMock) CompleteCalls() []struct { } { var calls []struct { @@ -350,7 +351,8 @@ func (mock *PhaseItemMock) DeprecateRemainingPhases(phase apicommon.KeptnPhaseTy // DeprecateRemainingPhasesCalls gets all the calls that were made to DeprecateRemainingPhases. // Check the length with: -// len(mockedPhaseItem.DeprecateRemainingPhasesCalls()) +// +// len(mockedPhaseItem.DeprecateRemainingPhasesCalls()) func (mock *PhaseItemMock) DeprecateRemainingPhasesCalls() []struct { Phase apicommon.KeptnPhaseType } { @@ -383,7 +385,8 @@ func (mock *PhaseItemMock) GenerateEvaluation(evaluationDefinition klcv1alpha3.K // GenerateEvaluationCalls gets all the calls that were made to GenerateEvaluation. // Check the length with: -// len(mockedPhaseItem.GenerateEvaluationCalls()) +// +// len(mockedPhaseItem.GenerateEvaluationCalls()) func (mock *PhaseItemMock) GenerateEvaluationCalls() []struct { EvaluationDefinition klcv1alpha3.KeptnEvaluationDefinition CheckType apicommon.CheckType @@ -418,7 +421,8 @@ func (mock *PhaseItemMock) GenerateTask(taskDefinition klcv1alpha3.KeptnTaskDefi // GenerateTaskCalls gets all the calls that were made to GenerateTask. // Check the length with: -// len(mockedPhaseItem.GenerateTaskCalls()) +// +// len(mockedPhaseItem.GenerateTaskCalls()) func (mock *PhaseItemMock) GenerateTaskCalls() []struct { TaskDefinition klcv1alpha3.KeptnTaskDefinition CheckType apicommon.CheckType @@ -448,7 +452,8 @@ func (mock *PhaseItemMock) GetAppName() string { // GetAppNameCalls gets all the calls that were made to GetAppName. // Check the length with: -// len(mockedPhaseItem.GetAppNameCalls()) +// +// len(mockedPhaseItem.GetAppNameCalls()) func (mock *PhaseItemMock) GetAppNameCalls() []struct { } { var calls []struct { @@ -474,7 +479,8 @@ func (mock *PhaseItemMock) GetCurrentPhase() string { // GetCurrentPhaseCalls gets all the calls that were made to GetCurrentPhase. // Check the length with: -// len(mockedPhaseItem.GetCurrentPhaseCalls()) +// +// len(mockedPhaseItem.GetCurrentPhaseCalls()) func (mock *PhaseItemMock) GetCurrentPhaseCalls() []struct { } { var calls []struct { @@ -500,7 +506,8 @@ func (mock *PhaseItemMock) GetEndTime() time.Time { // GetEndTimeCalls gets all the calls that were made to GetEndTime. // Check the length with: -// len(mockedPhaseItem.GetEndTimeCalls()) +// +// len(mockedPhaseItem.GetEndTimeCalls()) func (mock *PhaseItemMock) GetEndTimeCalls() []struct { } { var calls []struct { @@ -526,7 +533,8 @@ func (mock *PhaseItemMock) GetNamespace() string { // GetNamespaceCalls gets all the calls that were made to GetNamespace. // Check the length with: -// len(mockedPhaseItem.GetNamespaceCalls()) +// +// len(mockedPhaseItem.GetNamespaceCalls()) func (mock *PhaseItemMock) GetNamespaceCalls() []struct { } { var calls []struct { @@ -552,7 +560,8 @@ func (mock *PhaseItemMock) GetParentName() string { // GetParentNameCalls gets all the calls that were made to GetParentName. // Check the length with: -// len(mockedPhaseItem.GetParentNameCalls()) +// +// len(mockedPhaseItem.GetParentNameCalls()) func (mock *PhaseItemMock) GetParentNameCalls() []struct { } { var calls []struct { @@ -578,7 +587,8 @@ func (mock *PhaseItemMock) GetPostDeploymentEvaluationTaskStatus() []klcv1alpha3 // GetPostDeploymentEvaluationTaskStatusCalls gets all the calls that were made to GetPostDeploymentEvaluationTaskStatus. // Check the length with: -// len(mockedPhaseItem.GetPostDeploymentEvaluationTaskStatusCalls()) +// +// len(mockedPhaseItem.GetPostDeploymentEvaluationTaskStatusCalls()) func (mock *PhaseItemMock) GetPostDeploymentEvaluationTaskStatusCalls() []struct { } { var calls []struct { @@ -604,7 +614,8 @@ func (mock *PhaseItemMock) GetPostDeploymentEvaluations() []string { // GetPostDeploymentEvaluationsCalls gets all the calls that were made to GetPostDeploymentEvaluations. // Check the length with: -// len(mockedPhaseItem.GetPostDeploymentEvaluationsCalls()) +// +// len(mockedPhaseItem.GetPostDeploymentEvaluationsCalls()) func (mock *PhaseItemMock) GetPostDeploymentEvaluationsCalls() []struct { } { var calls []struct { @@ -630,7 +641,8 @@ func (mock *PhaseItemMock) GetPostDeploymentTaskStatus() []klcv1alpha3.ItemStatu // GetPostDeploymentTaskStatusCalls gets all the calls that were made to GetPostDeploymentTaskStatus. // Check the length with: -// len(mockedPhaseItem.GetPostDeploymentTaskStatusCalls()) +// +// len(mockedPhaseItem.GetPostDeploymentTaskStatusCalls()) func (mock *PhaseItemMock) GetPostDeploymentTaskStatusCalls() []struct { } { var calls []struct { @@ -656,7 +668,8 @@ func (mock *PhaseItemMock) GetPostDeploymentTasks() []string { // GetPostDeploymentTasksCalls gets all the calls that were made to GetPostDeploymentTasks. // Check the length with: -// len(mockedPhaseItem.GetPostDeploymentTasksCalls()) +// +// len(mockedPhaseItem.GetPostDeploymentTasksCalls()) func (mock *PhaseItemMock) GetPostDeploymentTasksCalls() []struct { } { var calls []struct { @@ -682,7 +695,8 @@ func (mock *PhaseItemMock) GetPreDeploymentEvaluationTaskStatus() []klcv1alpha3. // GetPreDeploymentEvaluationTaskStatusCalls gets all the calls that were made to GetPreDeploymentEvaluationTaskStatus. // Check the length with: -// len(mockedPhaseItem.GetPreDeploymentEvaluationTaskStatusCalls()) +// +// len(mockedPhaseItem.GetPreDeploymentEvaluationTaskStatusCalls()) func (mock *PhaseItemMock) GetPreDeploymentEvaluationTaskStatusCalls() []struct { } { var calls []struct { @@ -708,7 +722,8 @@ func (mock *PhaseItemMock) GetPreDeploymentEvaluations() []string { // GetPreDeploymentEvaluationsCalls gets all the calls that were made to GetPreDeploymentEvaluations. // Check the length with: -// len(mockedPhaseItem.GetPreDeploymentEvaluationsCalls()) +// +// len(mockedPhaseItem.GetPreDeploymentEvaluationsCalls()) func (mock *PhaseItemMock) GetPreDeploymentEvaluationsCalls() []struct { } { var calls []struct { @@ -734,7 +749,8 @@ func (mock *PhaseItemMock) GetPreDeploymentTaskStatus() []klcv1alpha3.ItemStatus // GetPreDeploymentTaskStatusCalls gets all the calls that were made to GetPreDeploymentTaskStatus. // Check the length with: -// len(mockedPhaseItem.GetPreDeploymentTaskStatusCalls()) +// +// len(mockedPhaseItem.GetPreDeploymentTaskStatusCalls()) func (mock *PhaseItemMock) GetPreDeploymentTaskStatusCalls() []struct { } { var calls []struct { @@ -760,7 +776,8 @@ func (mock *PhaseItemMock) GetPreDeploymentTasks() []string { // GetPreDeploymentTasksCalls gets all the calls that were made to GetPreDeploymentTasks. // Check the length with: -// len(mockedPhaseItem.GetPreDeploymentTasksCalls()) +// +// len(mockedPhaseItem.GetPreDeploymentTasksCalls()) func (mock *PhaseItemMock) GetPreDeploymentTasksCalls() []struct { } { var calls []struct { @@ -786,7 +803,8 @@ func (mock *PhaseItemMock) GetPreviousVersion() string { // GetPreviousVersionCalls gets all the calls that were made to GetPreviousVersion. // Check the length with: -// len(mockedPhaseItem.GetPreviousVersionCalls()) +// +// len(mockedPhaseItem.GetPreviousVersionCalls()) func (mock *PhaseItemMock) GetPreviousVersionCalls() []struct { } { var calls []struct { @@ -812,7 +830,8 @@ func (mock *PhaseItemMock) GetSpanAttributes() []attribute.KeyValue { // GetSpanAttributesCalls gets all the calls that were made to GetSpanAttributes. // Check the length with: -// len(mockedPhaseItem.GetSpanAttributesCalls()) +// +// len(mockedPhaseItem.GetSpanAttributesCalls()) func (mock *PhaseItemMock) GetSpanAttributesCalls() []struct { } { var calls []struct { @@ -838,7 +857,8 @@ func (mock *PhaseItemMock) GetStartTime() time.Time { // GetStartTimeCalls gets all the calls that were made to GetStartTime. // Check the length with: -// len(mockedPhaseItem.GetStartTimeCalls()) +// +// len(mockedPhaseItem.GetStartTimeCalls()) func (mock *PhaseItemMock) GetStartTimeCalls() []struct { } { var calls []struct { @@ -864,7 +884,8 @@ func (mock *PhaseItemMock) GetState() apicommon.KeptnState { // GetStateCalls gets all the calls that were made to GetState. // Check the length with: -// len(mockedPhaseItem.GetStateCalls()) +// +// len(mockedPhaseItem.GetStateCalls()) func (mock *PhaseItemMock) GetStateCalls() []struct { } { var calls []struct { @@ -890,7 +911,8 @@ func (mock *PhaseItemMock) GetVersion() string { // GetVersionCalls gets all the calls that were made to GetVersion. // Check the length with: -// len(mockedPhaseItem.GetVersionCalls()) +// +// len(mockedPhaseItem.GetVersionCalls()) func (mock *PhaseItemMock) GetVersionCalls() []struct { } { var calls []struct { @@ -916,7 +938,8 @@ func (mock *PhaseItemMock) IsEndTimeSet() bool { // IsEndTimeSetCalls gets all the calls that were made to IsEndTimeSet. // Check the length with: -// len(mockedPhaseItem.IsEndTimeSetCalls()) +// +// len(mockedPhaseItem.IsEndTimeSetCalls()) func (mock *PhaseItemMock) IsEndTimeSetCalls() []struct { } { var calls []struct { @@ -945,7 +968,8 @@ func (mock *PhaseItemMock) SetCurrentPhase(s string) { // SetCurrentPhaseCalls gets all the calls that were made to SetCurrentPhase. // Check the length with: -// len(mockedPhaseItem.SetCurrentPhaseCalls()) +// +// len(mockedPhaseItem.SetCurrentPhaseCalls()) func (mock *PhaseItemMock) SetCurrentPhaseCalls() []struct { S string } { @@ -976,7 +1000,8 @@ func (mock *PhaseItemMock) SetSpanAttributes(span trace.Span) { // SetSpanAttributesCalls gets all the calls that were made to SetSpanAttributes. // Check the length with: -// len(mockedPhaseItem.SetSpanAttributesCalls()) +// +// len(mockedPhaseItem.SetSpanAttributesCalls()) func (mock *PhaseItemMock) SetSpanAttributesCalls() []struct { Span trace.Span } { @@ -1007,7 +1032,8 @@ func (mock *PhaseItemMock) SetState(keptnState apicommon.KeptnState) { // SetStateCalls gets all the calls that were made to SetState. // Check the length with: -// len(mockedPhaseItem.SetStateCalls()) +// +// len(mockedPhaseItem.SetStateCalls()) func (mock *PhaseItemMock) SetStateCalls() []struct { KeptnState apicommon.KeptnState } { diff --git a/operator/main.go b/operator/main.go index 3c1b5f2378..809f844a86 100644 --- a/operator/main.go +++ b/operator/main.go @@ -306,6 +306,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "KeptnWorkloadInstance") os.Exit(1) } + if err = (&lifecyclev1alpha3.KeptnTaskDefinition{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "KeptnTaskDefinition") + os.Exit(1) + } // +kubebuilder:scaffold:builder controllercommon.SetUpKeptnMeters(meter, mgr.GetClient()) diff --git a/test/integration/validate-taskdefinition/00-teststep-install.yaml b/test/integration/validate-taskdefinition/00-teststep-install.yaml new file mode 100644 index 0000000000..a65f372148 --- /dev/null +++ b/test/integration/validate-taskdefinition/00-teststep-install.yaml @@ -0,0 +1,7 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +apply: + - goodtaskdefinition.yaml +commands: + - command: kubectl apply -f badtaskdefinition.yaml + ignoreFailure: true # we must install ignoring the validating webhook error to proceed with the test diff --git a/test/integration/validate-taskdefinition/01-teststep-assert.yaml b/test/integration/validate-taskdefinition/01-teststep-assert.yaml new file mode 100644 index 0000000000..92a0b614a1 --- /dev/null +++ b/test/integration/validate-taskdefinition/01-teststep-assert.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +error: # this checks that kubectl get resource fails, AKA bad CRD not added + - badtaskdefinition.yaml +assert: # this checks that kubectl get resource succeeds + - goodtaskdefinition.yaml diff --git a/test/integration/validate-taskdefinition/badtaskdefinition.yaml b/test/integration/validate-taskdefinition/badtaskdefinition.yaml new file mode 100644 index 0000000000..3ddb285e7a --- /dev/null +++ b/test/integration/validate-taskdefinition/badtaskdefinition.yaml @@ -0,0 +1,27 @@ +# This TaskDefinition will not be accepted by the validation webhook as it contains both containerSpec and functionSpec +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: badtaskdefinition1 +spec: + container: + name: keptntaskdefinition + image: busybox:1.36.0 + resources: + limits: + memory: "200Mi" + command: + - 'echo' + - 'Hello World!' + - '>' + - '/cache/log.txt' + volumeMounts: + - mountPath: /cache + name: logger +--- +# This TaskDefinition will not be accepted by the validation webhook as it doesn't contain either containerSpec or functionSpec +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: badtaskdefinition2 +spec: diff --git a/test/integration/validate-taskdefinition/goodtaskdefinition.yaml b/test/integration/validate-taskdefinition/goodtaskdefinition.yaml new file mode 100644 index 0000000000..519f5dcbee --- /dev/null +++ b/test/integration/validate-taskdefinition/goodtaskdefinition.yaml @@ -0,0 +1,29 @@ +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: goodtaskdefinition1 +spec: + container: + name: keptntaskdefinition1 + image: busybox:1.36.0 + resources: + limits: + memory: "200Mi" + command: + - 'echo' + - 'Hello World!' + - '>' + - '/cache/log.txt' + volumeMounts: + - mountPath: /cache + name: logger +--- +apiVersion: lifecycle.keptn.sh/v1alpha3 +kind: KeptnTaskDefinition +metadata: + name: goodtaskdefinition2 +spec: + function: + inline: + code: | + console.log('hello');