diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index eb97bfab68..fb6daf6add 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -87,7 +87,6 @@ configmapref configmapreference containedctx containerspec -contextcommon contextdata controllercommon controllererrors @@ -294,6 +293,7 @@ keptnappversionstatus keptnconfig keptnconfiglist keptnconfigspec +keptncontext keptncontroller keptndemo keptndemoapp @@ -487,7 +487,6 @@ previousversion printargs printcolumn privs -Process proj promapi promhttp diff --git a/.github/scripts/.helm-tests/default/result.yaml b/.github/scripts/.helm-tests/default/result.yaml index 4d0423f02d..8a18ab1b4c 100644 --- a/.github/scripts/.helm-tests/default/result.yaml +++ b/.github/scripts/.helm-tests/default/result.yaml @@ -7207,6 +7207,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed @@ -7690,6 +7696,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/.github/scripts/.helm-tests/lifecycle-only/result.yaml b/.github/scripts/.helm-tests/lifecycle-only/result.yaml index 0c5c90aa3e..63be58164c 100644 --- a/.github/scripts/.helm-tests/lifecycle-only/result.yaml +++ b/.github/scripts/.helm-tests/lifecycle-only/result.yaml @@ -7153,6 +7153,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed @@ -7636,6 +7642,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/.github/scripts/.helm-tests/lifecycle-with-certs/result.yaml b/.github/scripts/.helm-tests/lifecycle-with-certs/result.yaml index d0594e364f..75b43a0a7a 100644 --- a/.github/scripts/.helm-tests/lifecycle-with-certs/result.yaml +++ b/.github/scripts/.helm-tests/lifecycle-with-certs/result.yaml @@ -7187,6 +7187,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed @@ -7671,6 +7677,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/docs/docs/reference/api-reference/lifecycle/v1beta1/index.md b/docs/docs/reference/api-reference/lifecycle/v1beta1/index.md index 99939d311d..bf4d164f1c 100644 --- a/docs/docs/reference/api-reference/lifecycle/v1beta1/index.md +++ b/docs/docs/reference/api-reference/lifecycle/v1beta1/index.md @@ -800,6 +800,7 @@ _Appears in:_ | `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the Keptn namespace. || ✓ | | `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the Keptn namespace. || ✓ | | `resourceReference` _[ResourceReference](#resourcereference)_ | ResourceReference is a reference to the Kubernetes resource (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. || x | +| `metadata` _object (keys:string, values:string)_ | Refer to Kubernetes API documentation for fields of `metadata`. || ✓ | #### KeptnWorkloadStatus @@ -868,6 +869,7 @@ _Appears in:_ | `preDeploymentEvaluations` _string array_ | PreDeploymentEvaluations is a list of all evaluations to be performed during the pre-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the Keptn namespace. || ✓ | | `postDeploymentEvaluations` _string array_ | PostDeploymentEvaluations is a list of all evaluations to be performed during the post-deployment phase of the KeptnWorkload. The items of this list refer to the names of KeptnEvaluationDefinitions located in the same namespace as the KeptnWorkload, or in the Keptn namespace. || ✓ | | `resourceReference` _[ResourceReference](#resourcereference)_ | ResourceReference is a reference to the Kubernetes resource (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. || x | +| `metadata` _object (keys:string, values:string)_ | Refer to Kubernetes API documentation for fields of `metadata`. || ✓ | | `workloadName` _string_ | WorkloadName is the name of the KeptnWorkload. || x | | `previousVersion` _string_ | PreviousVersion is the version of the KeptnWorkload that has been deployed prior to this version. || ✓ | | `traceId` _object (keys:string, values:string)_ | TraceId contains the OpenTelemetry trace ID. || ✓ | diff --git a/lifecycle-operator/apis/lifecycle/v1beta1/common/common.go b/lifecycle-operator/apis/lifecycle/v1beta1/common/common.go index bebfa1ce9b..d94ca7ce2a 100644 --- a/lifecycle-operator/apis/lifecycle/v1beta1/common/common.go +++ b/lifecycle-operator/apis/lifecycle/v1beta1/common/common.go @@ -33,6 +33,7 @@ const CreateWorkloadEvalSpanName = "create_%s_deployment_evaluation" const AppTypeAnnotation = "keptn.sh/app-type" const KeptnGate = "keptn-prechecks-gate" const ContainerNameAnnotation = "keptn.sh/container" +const MetadataAnnotation = "keptn.sh/metadata" const MinKeptnNameLen = 80 const MaxK8sObjectLength = 253 diff --git a/lifecycle-operator/apis/lifecycle/v1beta1/keptnworkload_types.go b/lifecycle-operator/apis/lifecycle/v1beta1/keptnworkload_types.go index f057fd3c21..560b4fd800 100644 --- a/lifecycle-operator/apis/lifecycle/v1beta1/keptnworkload_types.go +++ b/lifecycle-operator/apis/lifecycle/v1beta1/keptnworkload_types.go @@ -58,6 +58,9 @@ type KeptnWorkloadSpec struct { // ResourceReference is a reference to the Kubernetes resource // (Deployment, DaemonSet, StatefulSet or ReplicaSet) the KeptnWorkload is representing. ResourceReference ResourceReference `json:"resourceReference"` + // +optional + // Metadata contains additional key-value pairs for contextual information. + Metadata map[string]string `json:"metadata,omitempty"` } // KeptnWorkloadStatus defines the observed state of KeptnWorkload diff --git a/lifecycle-operator/apis/lifecycle/v1beta1/zz_generated.deepcopy.go b/lifecycle-operator/apis/lifecycle/v1beta1/zz_generated.deepcopy.go index 1819d8d6b1..a69c4081ba 100644 --- a/lifecycle-operator/apis/lifecycle/v1beta1/zz_generated.deepcopy.go +++ b/lifecycle-operator/apis/lifecycle/v1beta1/zz_generated.deepcopy.go @@ -1155,6 +1155,13 @@ func (in *KeptnWorkloadSpec) DeepCopyInto(out *KeptnWorkloadSpec) { copy(*out, *in) } out.ResourceReference = in.ResourceReference + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeptnWorkloadSpec. @@ -1252,6 +1259,13 @@ func (in *KeptnWorkloadVersionSpec) DeepCopyInto(out *KeptnWorkloadVersionSpec) (*out)[key] = val } } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeptnWorkloadVersionSpec. diff --git a/lifecycle-operator/chart/templates/keptnworkload-crd.yaml b/lifecycle-operator/chart/templates/keptnworkload-crd.yaml index 791bdc9602..6459e37fe3 100644 --- a/lifecycle-operator/chart/templates/keptnworkload-crd.yaml +++ b/lifecycle-operator/chart/templates/keptnworkload-crd.yaml @@ -337,6 +337,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/lifecycle-operator/chart/templates/keptnworkloadversion-crd.yaml b/lifecycle-operator/chart/templates/keptnworkloadversion-crd.yaml index 7d169bb66d..f6fc4d4dce 100644 --- a/lifecycle-operator/chart/templates/keptnworkloadversion-crd.yaml +++ b/lifecycle-operator/chart/templates/keptnworkloadversion-crd.yaml @@ -403,6 +403,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml b/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml index d0df83cb61..650639dad3 100644 --- a/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml +++ b/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloads.yaml @@ -329,6 +329,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadversions.yaml b/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadversions.yaml index 5bb47ad6f7..a0d4cd8cda 100644 --- a/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadversions.yaml +++ b/lifecycle-operator/config/crd/bases/lifecycle.keptn.sh_keptnworkloadversions.yaml @@ -395,6 +395,12 @@ spec: app: description: AppName is the name of the KeptnApp containing the KeptnWorkload. type: string + metadata: + additionalProperties: + type: string + description: Metadata contains additional key-value pairs for contextual + information. + type: object postDeploymentEvaluations: description: |- PostDeploymentEvaluations is a list of all evaluations to be performed diff --git a/lifecycle-operator/controllers/common/context/context.go b/lifecycle-operator/controllers/common/context/context.go new file mode 100644 index 0000000000..986aa558a1 --- /dev/null +++ b/lifecycle-operator/controllers/common/context/context.go @@ -0,0 +1,27 @@ +package context + +import "context" + +type keptnAppContextKeyType string + +var keptnAppContextKey = keptnAppContextKeyType("keptnAppContextMeta") + +func WithAppMetadata(ctx context.Context, appContextMeta ...map[string]string) context.Context { + mergedMap := map[string]string{} + for _, meta := range appContextMeta { + for key, value := range meta { + mergedMap[key] = value + } + } + return context.WithValue(ctx, keptnAppContextKey, mergedMap) +} + +func GetAppMetadataFromContext(ctx context.Context) (map[string]string, bool) { + value := ctx.Value(keptnAppContextKey) + + appContextMeta, ok := value.(map[string]string) + if ok { + return appContextMeta, true + } + return map[string]string{}, false +} diff --git a/lifecycle-operator/controllers/common/context/context_test.go b/lifecycle-operator/controllers/common/context/context_test.go new file mode 100644 index 0000000000..1138870d50 --- /dev/null +++ b/lifecycle-operator/controllers/common/context/context_test.go @@ -0,0 +1,25 @@ +package context + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestContextWithAppMetadata(t *testing.T) { + ctx := context.Background() + + metadata := map[string]string{ + "foo": "bar", + } + + ctx = WithAppMetadata(ctx, metadata) + + require.NotNil(t, ctx) + + metadata, ok := GetAppMetadataFromContext(ctx) + + require.True(t, ok) + require.Equal(t, "bar", metadata["foo"]) +} diff --git a/lifecycle-operator/controllers/common/telemetry/spanhandler.go b/lifecycle-operator/controllers/common/telemetry/spanhandler.go index f6e3d7af7f..9be63c4fd8 100644 --- a/lifecycle-operator/controllers/common/telemetry/spanhandler.go +++ b/lifecycle-operator/controllers/common/telemetry/spanhandler.go @@ -4,8 +4,10 @@ import ( "context" "sync" + keptncontext "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/context" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/lifecycle/interfaces" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/trace" "sigs.k8s.io/controller-runtime/pkg/client" @@ -45,6 +47,16 @@ func (r *Handler) GetSpan(ctx context.Context, tracer ITracer, reconcileObject c childCtx, span := tracer.Start(ctx, spanName, trace.WithSpanKind(trace.SpanKindConsumer)) piWrapper.SetSpanAttributes(span) + // also get attributes from context + if meta, ok := keptncontext.GetAppMetadataFromContext(ctx); ok { + for key, value := range meta { + span.SetAttributes(attribute.KeyValue{ + Key: attribute.Key(key), + Value: attribute.StringValue(value), + }) + } + } + if phase != "" { traceContextCarrier := propagation.MapCarrier{} otel.GetTextMapPropagator().Inject(childCtx, traceContextCarrier) diff --git a/lifecycle-operator/controllers/common/telemetry/spanhandler_test.go b/lifecycle-operator/controllers/common/telemetry/spanhandler_test.go index 70807dee2f..bf466fbbaa 100644 --- a/lifecycle-operator/controllers/common/telemetry/spanhandler_test.go +++ b/lifecycle-operator/controllers/common/telemetry/spanhandler_test.go @@ -6,8 +6,10 @@ import ( "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1" apicommon "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1/common" + keptncontext "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/context" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/trace" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -103,3 +105,35 @@ func TestSpanHandler_GetSpan(t *testing.T) { require.Equal(t, span, span5) } + +func TestSpanHandler_GetSpanWithAttributes(t *testing.T) { + wi := &v1beta1.KeptnWorkloadVersion{} + wi.Spec.TraceId = make(map[string]string, 1) + wi.Spec.TraceId["traceparent"] = "test-parent" + wi.Spec.AppName = "test" + wi.Spec.WorkloadName = "test" + wi.Spec.Version = "test" + + r := Handler{} + phase := apicommon.PhaseAppDeployment.ShortName + + tp := trace.NewTracerProvider() + + tracer := tp.Tracer("keptn") + + ctx := context.TODO() + + ctx = keptncontext.WithAppMetadata(ctx, map[string]string{"foo": "bar"}) + ctx, span, err := r.GetSpan(ctx, tracer, wi, phase) + + require.Nil(t, err) + require.NotNil(t, span) + require.NotNil(t, ctx) + + attributes := span.(trace.ReadOnlySpan).Attributes() + require.NotNil(t, attributes) + + // the total number of attributes should be 5 (i.e. the workload specific ones + the additional one) + require.Len(t, attributes, 5) + require.Equal(t, "bar", attributes[4].Value.AsString()) +} diff --git a/lifecycle-operator/controllers/lifecycle/keptnworkload/controller.go b/lifecycle-operator/controllers/lifecycle/keptnworkload/controller.go index bb53dc86b5..66fb74d98c 100644 --- a/lifecycle-operator/controllers/lifecycle/keptnworkload/controller.go +++ b/lifecycle-operator/controllers/lifecycle/keptnworkload/controller.go @@ -96,6 +96,7 @@ func (r *KeptnWorkloadReconciler) Reconcile(ctx context.Context, req ctrl.Reques if err != nil { return reconcile.Result{}, err } + err = r.Client.Create(ctx, workloadVersion) if err != nil { r.Log.Error(err, "could not create WorkloadVersion") @@ -142,6 +143,7 @@ func (r *KeptnWorkloadReconciler) createWorkloadVersion(ctx context.Context, wor } workloadVersion := generateWorkloadVersion(previousVersion, traceContextCarrier, workload) + err := controllerutil.SetControllerReference(workload, &workloadVersion, r.Scheme) if err != nil { r.Log.Error(err, "could not set controller reference for WorkloadVersion: "+workloadVersion.Name) diff --git a/lifecycle-operator/controllers/lifecycle/keptnworkload/controller_test.go b/lifecycle-operator/controllers/lifecycle/keptnworkload/controller_test.go index d025517d71..912e84eef6 100644 --- a/lifecycle-operator/controllers/lifecycle/keptnworkload/controller_test.go +++ b/lifecycle-operator/controllers/lifecycle/keptnworkload/controller_test.go @@ -17,6 +17,9 @@ func TestKeptnWorkload(t *testing.T) { Spec: klcv1beta1.KeptnWorkloadSpec{ Version: "version", AppName: "app", + Metadata: map[string]string{ + "foo": "bar", + }, }, } @@ -31,6 +34,9 @@ func TestKeptnWorkload(t *testing.T) { KeptnWorkloadSpec: klcv1beta1.KeptnWorkloadSpec{ Version: "version", AppName: "app", + Metadata: map[string]string{ + "foo": "bar", + }, }, WorkloadName: "workload", PreviousVersion: "prev", diff --git a/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller.go b/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller.go index af2da2589d..bf4838bd3e 100644 --- a/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller.go +++ b/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller.go @@ -25,6 +25,7 @@ import ( klcv1beta1 "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1" apicommon "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1/common" controllercommon "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common" + keptncontext "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/context" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/evaluation" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/eventsender" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/phase" @@ -54,7 +55,7 @@ type KeptnWorkloadVersionReconciler struct { EventSender eventsender.IEvent Log logr.Logger Meters apicommon.KeptnMeters - SpanHandler *telemetry.Handler + SpanHandler telemetry.ISpanHandler TracerFactory telemetry.TracerFactory SchedulingGatesHandler schedulinggates.ISchedulingGatesHandler EvaluationHandler evaluation.IEvaluationHandler @@ -105,6 +106,11 @@ func (r *KeptnWorkloadVersionReconciler) Reconcile(ctx context.Context, req ctrl appTraceContextCarrier := propagation.MapCarrier(workloadVersion.Spec.TraceId) ctxAppTrace := otel.GetTextMapPropagator().Extract(context.TODO(), appTraceContextCarrier) + ctxAppTrace = keptncontext.WithAppMetadata( + ctxAppTrace, + workloadVersion.Spec.Metadata, + ) + // this will be the parent span for all phases of the WorkloadVersion ctxWorkloadTrace, spanWorkloadTrace, err := r.SpanHandler.GetSpan(ctxAppTrace, r.getTracer(), workloadVersion, "") if err != nil { diff --git a/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller_test.go b/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller_test.go index 5709addfe5..2afb861374 100644 --- a/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller_test.go +++ b/lifecycle-operator/controllers/lifecycle/keptnworkloadversion/controller_test.go @@ -10,6 +10,7 @@ import ( klcv1beta1 "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1" apicommon "github.com/keptn/lifecycle-toolkit/lifecycle-operator/apis/lifecycle/v1beta1/common" + keptncontext "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/context" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/evaluation" evaluationfake "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/evaluation/fake" "github.com/keptn/lifecycle-toolkit/lifecycle-operator/controllers/common/eventsender" @@ -754,6 +755,9 @@ func TestKeptnWorkloadVersionReconciler_ReconcileReachCompletion(t *testing.T) { KeptnWorkloadSpec: klcv1beta1.KeptnWorkloadSpec{ AppName: "some-app", Version: "1.0.0", + Metadata: map[string]string{ + "foo": "bar", + }, }, WorkloadName: "some-app-some-workload", PreviousVersion: "", @@ -819,6 +823,17 @@ func TestKeptnWorkloadVersionReconciler_ReconcileReachCompletion(t *testing.T) { assert.Equal(t, strings.Contains(event, req.Namespace), true, "wrong namespace") assert.Equal(t, strings.Contains(event, e), true, fmt.Sprintf("no %s found in %s", e, event)) } + + spanHandlerMock := r.SpanHandler.(*telemetryfake.ISpanHandlerMock) + + require.Len(t, spanHandlerMock.GetSpanCalls(), 1) + require.Len(t, spanHandlerMock.UnbindSpanCalls(), 1) + + // verify the propagation of the context attributes to the span handler + metadata, b := keptncontext.GetAppMetadataFromContext(spanHandlerMock.GetSpanCalls()[0].Ctx) + + require.True(t, b) + require.Equal(t, "bar", metadata["foo"]) } func TestKeptnWorkloadVersionReconciler_ReconcileReachCompletion_SchedulingGates(t *testing.T) { @@ -1178,13 +1193,23 @@ func setupReconciler(objs ...client.Object) (*KeptnWorkloadVersionReconciler, ch recorder := record.NewFakeRecorder(100) + spanHandlerMock := &telemetryfake.ISpanHandlerMock{ + GetSpanFunc: func(ctx context.Context, tracer telemetry.ITracer, reconcileObject client.Object, phase string) (context.Context, trace.Span, error) { + ctx, span := tracer.Start(ctx, phase, trace.WithSpanKind(trace.SpanKindConsumer)) + return ctx, span, nil + }, + UnbindSpanFunc: func(_ client.Object, _ string) error { + return nil + }, + } + r := &KeptnWorkloadVersionReconciler{ Client: fakeClient, Scheme: scheme.Scheme, EventSender: eventsender.NewK8sSender(recorder), Log: ctrl.Log.WithName("test-appController"), Meters: testcommon.InitAppMeters(), - SpanHandler: &telemetry.Handler{}, + SpanHandler: spanHandlerMock, TracerFactory: tf, EvaluationHandler: &evaluationfake.MockEvaluationHandler{ ReconcileEvaluationsFunc: func(ctx context.Context, phaseCtx context.Context, reconcileObject client.Object, evaluationCreateAttributes evaluation.CreateEvaluationAttributes) ([]klcv1beta1.ItemStatus, apicommon.StatusSummary, error) { diff --git a/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation.go b/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation.go index 88926aad78..ab8a8814dc 100644 --- a/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation.go +++ b/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation.go @@ -96,6 +96,7 @@ func copyResourceLabelsIfPresent(sourceResource *metav1.ObjectMeta, targetPod *c preEvaluationChecks, _ = GetLabelOrAnnotation(sourceResource, apicommon.PreDeploymentEvaluationAnnotation, "") postEvaluationChecks, _ = GetLabelOrAnnotation(sourceResource, apicommon.PostDeploymentEvaluationAnnotation, "") containerName, _ := GetLabelOrAnnotation(sourceResource, apicommon.ContainerNameAnnotation, "") + metadata, _ := GetLabelOrAnnotation(sourceResource, apicommon.MetadataAnnotation, "") if gotWorkloadName { setMapKey(targetPod.Annotations, apicommon.WorkloadAnnotation, workloadName) @@ -116,6 +117,7 @@ func copyResourceLabelsIfPresent(sourceResource *metav1.ObjectMeta, targetPod *c setMapKey(targetPod.Annotations, apicommon.PostDeploymentTaskAnnotation, postDeploymentChecks) setMapKey(targetPod.Annotations, apicommon.PreDeploymentEvaluationAnnotation, preEvaluationChecks) setMapKey(targetPod.Annotations, apicommon.PostDeploymentEvaluationAnnotation, postEvaluationChecks) + setMapKey(targetPod.Annotations, apicommon.MetadataAnnotation, metadata) return true } diff --git a/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation_test.go b/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation_test.go index 7daedca8e6..bdbcd13361 100644 --- a/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation_test.go +++ b/lifecycle-operator/webhooks/pod_mutator/handlers/pod_annotation_test.go @@ -28,6 +28,7 @@ const preEval = "some-pre-deployment-evaluation" const postEval = "some-post-deployment-evaluation" const version = "v1.0.0" const uid = "this-is-the-pod-uid" +const metadata = "foo=bar" func TestCopyAnnotationsIfParentAnnotated(t *testing.T) { testNamespace := "test-namespace" @@ -440,6 +441,7 @@ func TestCopyResourceLabelsIfPresent(t *testing.T) { apicommon.PostDeploymentTaskAnnotation: postDep, apicommon.PreDeploymentEvaluationAnnotation: preEval, apicommon.PostDeploymentEvaluationAnnotation: postEval, + apicommon.MetadataAnnotation: metadata, }, }, targetPod: &corev1.Pod{ @@ -461,6 +463,7 @@ func TestCopyResourceLabelsIfPresent(t *testing.T) { apicommon.PostDeploymentTaskAnnotation: postDep, apicommon.PreDeploymentEvaluationAnnotation: preEval, apicommon.PostDeploymentEvaluationAnnotation: postEval, + apicommon.MetadataAnnotation: metadata, }, }, }, diff --git a/lifecycle-operator/webhooks/pod_mutator/handlers/workload.go b/lifecycle-operator/webhooks/pod_mutator/handlers/workload.go index 7084312af2..cf2f483dfc 100644 --- a/lifecycle-operator/webhooks/pod_mutator/handlers/workload.go +++ b/lifecycle-operator/webhooks/pod_mutator/handlers/workload.go @@ -107,6 +107,20 @@ func generateWorkload(ctx context.Context, pod *corev1.Pod, namespace string) *k PostDeploymentTasks: postDeploymentTasks, PreDeploymentEvaluations: preDeploymentEvaluation, PostDeploymentEvaluations: postDeploymentEvaluation, + Metadata: parseWorkloadMetadata(getValuesForAnnotations(&pod.ObjectMeta, apicommon.MetadataAnnotation)), }, } } + +func parseWorkloadMetadata(annotations []string) map[string]string { + result := make(map[string]string, len(annotations)) + for _, value := range annotations { + split := strings.Split(value, "=") + + if len(split) != 2 { + continue + } + result[split[0]] = split[1] + } + return result +} diff --git a/lifecycle-operator/webhooks/pod_mutator/handlers/workload_test.go b/lifecycle-operator/webhooks/pod_mutator/handlers/workload_test.go index b6aa729e40..ae5d0b4937 100644 --- a/lifecycle-operator/webhooks/pod_mutator/handlers/workload_test.go +++ b/lifecycle-operator/webhooks/pod_mutator/handlers/workload_test.go @@ -2,6 +2,7 @@ package handlers import ( "context" + "reflect" "testing" "github.com/go-logr/logr/testr" @@ -51,6 +52,10 @@ func TestHandle(t *testing.T) { Spec: klcv1beta1.KeptnWorkloadSpec{ AppName: TestWorkload, Version: "0.1", + Metadata: map[string]string{ + "foo": "bar", + "bar": "foo", + }, }, } @@ -61,6 +66,7 @@ func TestHandle(t *testing.T) { Annotations: map[string]string{ apicommon.WorkloadAnnotation: TestWorkload, apicommon.VersionAnnotation: "0.1", + apicommon.MetadataAnnotation: "foo=bar,bar=foo", }, }} // Define test cases @@ -200,6 +206,7 @@ func TestGenerateWorkload(t *testing.T) { PostDeploymentTasks: []string{"task3", "task4"}, PreDeploymentEvaluations: []string{"eval1", "eval2"}, PostDeploymentEvaluations: []string{"eval3", "eval4"}, + Metadata: map[string]string{}, }, }, }, @@ -222,7 +229,11 @@ func TestGenerateWorkload(t *testing.T) { ResourceReference: klcv1beta1.ResourceReference{ UID: "owner-uid", Kind: "Deployment", - Name: "deployment-1"}}}, + Name: "deployment-1", + }, + Metadata: map[string]string{}, + }, + }, }, } @@ -249,3 +260,46 @@ func TestGenerateWorkload(t *testing.T) { }) } } + +func Test_parseWorkloadMetadata(t *testing.T) { + type args struct { + annotations []string + } + tests := []struct { + name string + args args + want map[string]string + }{ + { + name: "valid input", + args: args{ + annotations: []string{"foo=bar", "bar=foo"}, + }, + want: map[string]string{ + "foo": "bar", + "bar": "foo", + }, + }, + { + name: "invalid input", + args: args{ + annotations: []string{"foobar"}, + }, + want: map[string]string{}, + }, + { + name: "empty input", + args: args{ + annotations: []string{}, + }, + want: map[string]string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseWorkloadMetadata(tt.args.annotations); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseWorkloadMetadata() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/integration/podtato-head-application/00-assert.yaml b/test/integration/podtato-head-application/00-assert.yaml index be00d96664..3f3e556c87 100644 --- a/test/integration/podtato-head-application/00-assert.yaml +++ b/test/integration/podtato-head-application/00-assert.yaml @@ -20,6 +20,10 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: podtato-head-podtato-head-entry-0.1.0 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/podtato-head-application/00-install.yaml b/test/integration/podtato-head-application/00-install.yaml index cc56b3f31d..8cab2ee906 100644 --- a/test/integration/podtato-head-application/00-install.yaml +++ b/test/integration/podtato-head-application/00-install.yaml @@ -34,6 +34,7 @@ spec: keptn.sh/post-deployment-evaluations: available-cpus keptn.sh/pre-deployment-tasks: pre-deployment-hello keptn.sh/post-deployment-tasks: post-deployment-hello + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: terminationGracePeriodSeconds: 5 initContainers: diff --git a/test/integration/simple-daemonset-annotated/00-install.yaml b/test/integration/simple-daemonset-annotated/00-install.yaml index c464a5106e..9f91451b9a 100644 --- a/test/integration/simple-daemonset-annotated/00-install.yaml +++ b/test/integration/simple-daemonset-annotated/00-install.yaml @@ -19,6 +19,7 @@ metadata: keptn.sh/workload: work keptn.sh/version: "0.4" keptn.sh/pre-deployment-tasks: pre-deployment-hello + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: selector: matchLabels: diff --git a/test/integration/simple-daemonset-annotated/01-assert.yaml b/test/integration/simple-daemonset-annotated/01-assert.yaml index 330a72eb76..0b26b3f5ac 100644 --- a/test/integration/simple-daemonset-annotated/01-assert.yaml +++ b/test/integration/simple-daemonset-annotated/01-assert.yaml @@ -2,13 +2,20 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: work-work - +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: work-work-0.4 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/simple-deployment-annotated/00-install.yaml b/test/integration/simple-deployment-annotated/00-install.yaml index cde632aefe..796a1530f3 100644 --- a/test/integration/simple-deployment-annotated/00-install.yaml +++ b/test/integration/simple-deployment-annotated/00-install.yaml @@ -20,6 +20,7 @@ metadata: keptn.sh/version: "0.4" keptn.sh/pre-deployment-tasks: pre-deployment-hello keptn.sh/post-deployment-tasks: pre-deployment-hello + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: replicas: 2 selector: diff --git a/test/integration/simple-deployment-annotated/01-assert.yaml b/test/integration/simple-deployment-annotated/01-assert.yaml index 77aeb5deeb..3f73f1fed3 100644 --- a/test/integration/simple-deployment-annotated/01-assert.yaml +++ b/test/integration/simple-deployment-annotated/01-assert.yaml @@ -2,12 +2,20 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: waiter-waiter +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: waiter-waiter-0.4 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/simple-deployment-container-annotation/00-install.yaml b/test/integration/simple-deployment-container-annotation/00-install.yaml index 2277552378..995f485f05 100644 --- a/test/integration/simple-deployment-container-annotation/00-install.yaml +++ b/test/integration/simple-deployment-container-annotation/00-install.yaml @@ -17,6 +17,7 @@ spec: annotations: keptn.sh/workload: waiter keptn.sh/container: busybox + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: containers: - image: busybox:1.35 diff --git a/test/integration/simple-deployment-container-annotation/01-assert.yaml b/test/integration/simple-deployment-container-annotation/01-assert.yaml index b2e539b6f2..e07e77707f 100644 --- a/test/integration/simple-deployment-container-annotation/01-assert.yaml +++ b/test/integration/simple-deployment-container-annotation/01-assert.yaml @@ -2,11 +2,19 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: waiter-waiter +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: waiter-waiter-1.36 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/simple-deployment-evaluation/00-install.yaml b/test/integration/simple-deployment-evaluation/00-install.yaml index 7d067edff1..84a2070736 100644 --- a/test/integration/simple-deployment-evaluation/00-install.yaml +++ b/test/integration/simple-deployment-evaluation/00-install.yaml @@ -48,7 +48,7 @@ spec: keptn.sh/version: "0.4" keptn.sh/pre-deployment-evaluations: pre-deployment-hello keptn.sh/post-deployment-evaluations: pre-deployment-hello - + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: containers: - image: busybox diff --git a/test/integration/simple-deployment-evaluation/01-assert.yaml b/test/integration/simple-deployment-evaluation/01-assert.yaml index 7eafbd2d83..47e926950c 100644 --- a/test/integration/simple-deployment-evaluation/01-assert.yaml +++ b/test/integration/simple-deployment-evaluation/01-assert.yaml @@ -2,13 +2,20 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: waiter-waiter - +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: waiter-waiter-0.4 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/simple-deployment-k8s-recommended-label/00-install.yaml b/test/integration/simple-deployment-k8s-recommended-label/00-install.yaml index 73b2b71a5b..d5ac7f6b42 100644 --- a/test/integration/simple-deployment-k8s-recommended-label/00-install.yaml +++ b/test/integration/simple-deployment-k8s-recommended-label/00-install.yaml @@ -17,6 +17,7 @@ spec: annotations: app.kubernetes.io/name: "waiter" keptn.sh/version: "0.4" + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: containers: - image: busybox diff --git a/test/integration/simple-deployment-k8s-recommended-label/01-assert.yaml b/test/integration/simple-deployment-k8s-recommended-label/01-assert.yaml index cf26786a9a..23da6c15ed 100644 --- a/test/integration/simple-deployment-k8s-recommended-label/01-assert.yaml +++ b/test/integration/simple-deployment-k8s-recommended-label/01-assert.yaml @@ -2,11 +2,19 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: waiter-waiter +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: waiter-waiter-0.4 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded diff --git a/test/integration/simple-statefulset-annotated/00-install.yaml b/test/integration/simple-statefulset-annotated/00-install.yaml index 389cc88f6a..578c048ef1 100644 --- a/test/integration/simple-statefulset-annotated/00-install.yaml +++ b/test/integration/simple-statefulset-annotated/00-install.yaml @@ -21,6 +21,7 @@ metadata: keptn.sh/workload: work keptn.sh/version: "0.4" keptn.sh/pre-deployment-tasks: pre-deployment-hello + keptn.sh/metadata: "commit-id=1234,stage=dev" spec: serviceName: statefultest replicas: 2 diff --git a/test/integration/simple-statefulset-annotated/01-assert.yaml b/test/integration/simple-statefulset-annotated/01-assert.yaml index 330a72eb76..0b26b3f5ac 100644 --- a/test/integration/simple-statefulset-annotated/01-assert.yaml +++ b/test/integration/simple-statefulset-annotated/01-assert.yaml @@ -2,13 +2,20 @@ apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkload metadata: name: work-work - +spec: + metadata: + commit-id: "1234" + stage: dev --- apiVersion: lifecycle.keptn.sh/v1beta1 kind: KeptnWorkloadVersion metadata: name: work-work-0.4 +spec: + metadata: + commit-id: "1234" + stage: dev status: currentPhase: Completed deploymentStatus: Succeeded