Skip to content

Commit

Permalink
feat(operator): create KeptnAppCreationRequest in pod webhook (#1277)
Browse files Browse the repository at this point in the history
Signed-off-by: Florian Bacher <florian.bacher@dynatrace.com>
  • Loading branch information
bacherfl committed May 2, 2023
1 parent ba7b679 commit da942c2
Show file tree
Hide file tree
Showing 12 changed files with 919 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package v1alpha3

import (
"github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -61,6 +63,16 @@ func init() {
SchemeBuilder.Register(&KeptnAppCreationRequest{}, &KeptnAppCreationRequestList{})
}

func (kacr *KeptnAppCreationRequest) IsSingleService() bool {
func (kacr KeptnAppCreationRequest) IsSingleService() bool {
return kacr.Annotations[common.AppTypeAnnotation] == string(common.AppTypeSingleService)
}

func (kacr KeptnAppCreationRequest) SetSpanAttributes(span trace.Span) {
span.SetAttributes(kacr.GetSpanAttributes()...)
}

func (kacr KeptnAppCreationRequest) GetSpanAttributes() []attribute.KeyValue {
return []attribute.KeyValue{
common.AppName.String(kacr.Name),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/attribute"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -55,3 +57,18 @@ func TestKeptnAppCreationRequest_IsSingleService(t *testing.T) {
})
}
}

func TestKeptnAppCreationRequest_GetSpanAttributes(t *testing.T) {
kacr := KeptnAppCreationRequest{
ObjectMeta: v1.ObjectMeta{
Name: "my-app",
},
Spec: KeptnAppCreationRequestSpec{},
}

spanAttrs := kacr.GetSpanAttributes()

require.Equal(t, []attribute.KeyValue{
common.AppName.String(kacr.Name),
}, spanAttrs)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"crypto/sha256"
"fmt"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -114,14 +115,11 @@ func (r *KeptnAppCreationRequestReconciler) Reconcile(ctx context.Context, req c
}

// look up all the KeptnWorkloads referencing the KeptnApp
workloads := &lifecycle.KeptnWorkloadList{}
if err := r.Client.List(ctx, workloads, client.InNamespace(creationRequest.Namespace), client.MatchingFields{
"spec.app": creationRequest.Spec.AppName,
}); err != nil {
workloads, err := r.getWorkloads(ctx, creationRequest)
if err != nil {
return ctrl.Result{}, fmt.Errorf("could not retrieve KeptnWorkloads: %w", err)
}

var err error
if !appFound {
err = r.createKeptnApp(ctx, creationRequest, workloads)
} else {
Expand All @@ -137,6 +135,19 @@ func (r *KeptnAppCreationRequestReconciler) Reconcile(ctx context.Context, req c
return ctrl.Result{}, nil
}

func (r *KeptnAppCreationRequestReconciler) getWorkloads(ctx context.Context, creationRequest *lifecycle.KeptnAppCreationRequest) ([]lifecycle.KeptnWorkload, error) {
workloads := &lifecycle.KeptnWorkloadList{}
if err := r.Client.List(ctx, workloads, client.InNamespace(creationRequest.Namespace), client.MatchingFields{
"spec.app": creationRequest.Spec.AppName,
}); err != nil {
return nil, err
}
sort.Slice(workloads.Items, func(i, j int) bool {
return workloads.Items[i].Name < workloads.Items[j].Name
})
return workloads.Items, nil
}

func (r *KeptnAppCreationRequestReconciler) getCreationRequestExpirationDuration(cr *lifecycle.KeptnAppCreationRequest) time.Duration {
creationRequestTimeout := r.config.GetCreationRequestTimeout()
deadline := cr.CreationTimestamp.Add(creationRequestTimeout)
Expand All @@ -162,7 +173,7 @@ func (r *KeptnAppCreationRequestReconciler) SetupWithManager(mgr ctrl.Manager) e
Complete(r)
}

func (r *KeptnAppCreationRequestReconciler) updateKeptnApp(ctx context.Context, keptnApp *lifecycle.KeptnApp, workloads *lifecycle.KeptnWorkloadList) error {
func (r *KeptnAppCreationRequestReconciler) updateKeptnApp(ctx context.Context, keptnApp *lifecycle.KeptnApp, workloads []lifecycle.KeptnWorkload) error {

addOrUpdatedWorkload := r.addOrUpdateWorkloads(workloads, keptnApp)
removedWorkload := r.cleanupWorkloads(workloads, keptnApp)
Expand All @@ -171,14 +182,14 @@ func (r *KeptnAppCreationRequestReconciler) updateKeptnApp(ctx context.Context,
return nil
}

keptnApp.Spec.Version = computeVersionFromWorkloads(workloads.Items)
keptnApp.Spec.Version = computeVersionFromWorkloads(workloads)

return r.Update(ctx, keptnApp)
}

func (r *KeptnAppCreationRequestReconciler) addOrUpdateWorkloads(workloads *lifecycle.KeptnWorkloadList, keptnApp *lifecycle.KeptnApp) bool {
func (r *KeptnAppCreationRequestReconciler) addOrUpdateWorkloads(workloads []lifecycle.KeptnWorkload, keptnApp *lifecycle.KeptnApp) bool {
updated := false
for _, workload := range workloads.Items {
for _, workload := range workloads {
foundWorkload := false
workloadName := workload.GetNameWithoutAppPrefix()
for index, appWorkload := range keptnApp.Spec.Workloads {
Expand All @@ -205,12 +216,12 @@ func (r *KeptnAppCreationRequestReconciler) addOrUpdateWorkloads(workloads *life
return updated
}

func (r *KeptnAppCreationRequestReconciler) cleanupWorkloads(workloads *lifecycle.KeptnWorkloadList, keptnApp *lifecycle.KeptnApp) bool {
func (r *KeptnAppCreationRequestReconciler) cleanupWorkloads(workloads []lifecycle.KeptnWorkload, keptnApp *lifecycle.KeptnApp) bool {
updated := false
updatedWorkloads := []lifecycle.KeptnWorkloadRef{}
for index, appWorkload := range keptnApp.Spec.Workloads {
foundWorkload := false
for _, workload := range workloads.Items {
for _, workload := range workloads {
if appWorkload.Name == workload.GetNameWithoutAppPrefix() {
updatedWorkloads = append(updatedWorkloads, keptnApp.Spec.Workloads[index])
break
Expand All @@ -225,17 +236,19 @@ func (r *KeptnAppCreationRequestReconciler) cleanupWorkloads(workloads *lifecycl
return updated
}

func (r *KeptnAppCreationRequestReconciler) createKeptnApp(ctx context.Context, creationRequest *lifecycle.KeptnAppCreationRequest, workloads *lifecycle.KeptnWorkloadList) error {
func (r *KeptnAppCreationRequestReconciler) createKeptnApp(ctx context.Context, creationRequest *lifecycle.KeptnAppCreationRequest, workloads []lifecycle.KeptnWorkload) error {
keptnApp := &lifecycle.KeptnApp{
ObjectMeta: metav1.ObjectMeta{
Name: creationRequest.Spec.AppName,
Namespace: creationRequest.Namespace,
Labels: map[string]string{
common.K8sRecommendedManagedByAnnotations: managedByKLT,
},
// pass through the annotations since those contain the trace context
Annotations: creationRequest.Annotations,
},
Spec: lifecycle.KeptnAppSpec{
Version: computeVersionFromWorkloads(workloads.Items),
Version: computeVersionFromWorkloads(workloads),
PreDeploymentTasks: []string{},
PostDeploymentTasks: []string{},
PreDeploymentEvaluations: []string{},
Expand All @@ -244,7 +257,7 @@ func (r *KeptnAppCreationRequestReconciler) createKeptnApp(ctx context.Context,
},
}

for _, workload := range workloads.Items {
for _, workload := range workloads {
keptnApp.Spec.Workloads = append(keptnApp.Spec.Workloads, lifecycle.KeptnWorkloadRef{
Name: strings.TrimPrefix(workload.Name, fmt.Sprintf("%s-", creationRequest.Spec.AppName)),
Version: workload.Spec.Version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,3 +600,93 @@ func TestKeptnAppCreationRequestReconciler_cleanupWorkloads(t *testing.T) {
fmt.Println(cap(mySlice))
fmt.Println(cap(res))
}

func TestKeptnAppCreationRequestReconciler_getWorkloads(t *testing.T) {
namespace := "my-namespace"

appName := "my-app"

workload1 := &klcv1alpha3.KeptnWorkload{
ObjectMeta: metav1.ObjectMeta{
Name: "workloadA",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnWorkloadSpec{
AppName: appName,
Version: "1.0",
},
}

workload2 := &klcv1alpha3.KeptnWorkload{
ObjectMeta: metav1.ObjectMeta{
Name: "workloadB",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnWorkloadSpec{
AppName: appName,
Version: "1.0",
},
}

type args struct {
ctx context.Context
creationRequest *klcv1alpha3.KeptnAppCreationRequest
}
tests := []struct {
name string
args args
workloadsInCluster []klcv1alpha3.KeptnWorkload
want []klcv1alpha3.KeptnWorkload
wantErr bool
}{
{
name: "get workloads in alphabetical order - already sorted",
args: args{
ctx: context.Background(),
creationRequest: &klcv1alpha3.KeptnAppCreationRequest{
Spec: klcv1alpha3.KeptnAppCreationRequestSpec{
AppName: appName,
},
},
},
workloadsInCluster: []klcv1alpha3.KeptnWorkload{*workload1, *workload2},
want: []klcv1alpha3.KeptnWorkload{*workload1, *workload2},
wantErr: false,
},
{
name: "get workloads in alphabetical order - not sorted",
args: args{
ctx: context.Background(),
creationRequest: &klcv1alpha3.KeptnAppCreationRequest{
Spec: klcv1alpha3.KeptnAppCreationRequestSpec{
AppName: appName,
},
},
},
workloadsInCluster: []klcv1alpha3.KeptnWorkload{*workload2, *workload1},
want: []klcv1alpha3.KeptnWorkload{*workload1, *workload2},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, fakeClient, _ := setupReconcilerAndClient(t)

for _, workload := range tt.workloadsInCluster {
err := fakeClient.Create(context.Background(), &workload)
require.Nil(t, err)
}
got, err := c.getWorkloads(tt.args.ctx, tt.args.creationRequest)
if !tt.wantErr {
require.Nil(t, err)
} else {
require.NotNil(t, err)
}
require.Equal(t, len(tt.want), len(got))

for i, wantWorkload := range tt.want {
require.Equal(t, wantWorkload.Name, got[i].Name)
}
})
}
}
Loading

0 comments on commit da942c2

Please sign in to comment.