From bb8af4782077d22d617692c7eea770a8b1ee867e Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 5 Aug 2025 09:19:28 +0200 Subject: [PATCH 001/119] #119 add standard labels to dogus and coponents --- .../componentcr/componentInstallationRepo.go | 2 - .../componentInstallationRepo_test.go | 48 ++++++++++++------- .../componentcr/componentSerializer.go | 28 +++++++---- .../componentcr/componentSerializer_test.go | 12 ++++- .../dogucr/doguInstallationRepo_test.go | 10 +++- .../kubernetes/dogucr/doguSerializer.go | 10 +++- .../kubernetes/dogucr/doguSerializer_test.go | 30 +++++++++--- 7 files changed, 101 insertions(+), 39 deletions(-) diff --git a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go index ff3ffbdf..badd3e6c 100644 --- a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go +++ b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go @@ -16,8 +16,6 @@ import ( ) const ( - ComponentNameLabelKey = "k8s.cloudogu.com/component.name" - ComponentVersionLabelKey = "k8s.cloudogu.com/component.version" componentInstallationRepoContextKey = "componentInstallationRepoContext" ) diff --git a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go index 7bfe53df..c5f5ef9d 100644 --- a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go @@ -333,29 +333,37 @@ func Test_componentInstallationRepo_Create(t *testing.T) { sut := componentInstallationRepo{ componentClient: componentClientMock, } - componentInstallation := &ecosystem.ComponentInstallation{ + component := &ecosystem.ComponentInstallation{ Name: testComponentName, ExpectedVersion: testVersion1, } expectedCR := &compV1.Component{ ObjectMeta: metav1.ObjectMeta{ - Name: string(componentInstallation.Name.SimpleName), + Name: string(component.Name.SimpleName), Labels: map[string]string{ - ComponentNameLabelKey: string(componentInstallation.Name.SimpleName), - ComponentVersionLabelKey: componentInstallation.ExpectedVersion.String(), + ComponentNameLabelKey: string(component.Name.SimpleName), + ComponentVersionLabelKey: component.ExpectedVersion.String(), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(component.Name.SimpleName), + "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "app.kubernetes.io/name": string(component.Name.SimpleName), + "app.kubernetes.io/version": component.ExpectedVersion.String(), + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: compV1.ComponentSpec{ - Namespace: string(componentInstallation.Name.Namespace), - Name: string(componentInstallation.Name.SimpleName), - Version: componentInstallation.ExpectedVersion.String(), + Namespace: string(component.Name.Namespace), + Name: string(component.Name.SimpleName), + Version: component.ExpectedVersion.String(), }, } componentClientMock.EXPECT().Create(testCtx, expectedCR, metav1.CreateOptions{}).Return(nil, nil) // when - err := sut.Create(testCtx, componentInstallation) + err := sut.Create(testCtx, component) // then require.NoError(t, err) @@ -390,29 +398,37 @@ func Test_componentInstallationRepo_Create(t *testing.T) { sut := componentInstallationRepo{ componentClient: componentClientMock, } - componentInstallation := &ecosystem.ComponentInstallation{ + component := &ecosystem.ComponentInstallation{ Name: testComponentName, ExpectedVersion: testVersion1, } expectedCR := &compV1.Component{ ObjectMeta: metav1.ObjectMeta{ - Name: string(componentInstallation.Name.SimpleName), + Name: string(component.Name.SimpleName), Labels: map[string]string{ - ComponentNameLabelKey: string(componentInstallation.Name.SimpleName), - ComponentVersionLabelKey: componentInstallation.ExpectedVersion.String(), + ComponentNameLabelKey: string(component.Name.SimpleName), + ComponentVersionLabelKey: component.ExpectedVersion.String(), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(component.Name.SimpleName), + "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "app.kubernetes.io/name": string(component.Name.SimpleName), + "app.kubernetes.io/version": component.ExpectedVersion.String(), + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: compV1.ComponentSpec{ - Namespace: string(componentInstallation.Name.Namespace), - Name: string(componentInstallation.Name.SimpleName), - Version: componentInstallation.ExpectedVersion.String(), + Namespace: string(component.Name.Namespace), + Name: string(component.Name.SimpleName), + Version: component.ExpectedVersion.String(), }, } componentClientMock.EXPECT().Create(testCtx, expectedCR, metav1.CreateOptions{}).Return(nil, assert.AnError) // when - err := sut.Create(testCtx, componentInstallation) + err := sut.Create(testCtx, component) // then require.Error(t, err) diff --git a/pkg/adapter/kubernetes/componentcr/componentSerializer.go b/pkg/adapter/kubernetes/componentcr/componentSerializer.go index af14aaea..426352eb 100644 --- a/pkg/adapter/kubernetes/componentcr/componentSerializer.go +++ b/pkg/adapter/kubernetes/componentcr/componentSerializer.go @@ -16,6 +16,8 @@ import ( const ( deployConfigKeyDeployNamespace = "deployNamespace" deployConfigKeyOverwriteConfig = "overwriteConfig" + ComponentNameLabelKey = "k8s.cloudogu.com/component.name" + ComponentVersionLabelKey = "k8s.cloudogu.com/component.version" ) func parseComponentCR(cr *compV1.Component) (*ecosystem.ComponentInstallation, error) { @@ -76,21 +78,21 @@ func parseDeployConfig(cr *compV1.Component) (ecosystem.DeployConfig, error) { return componentConfig, nil } -func toComponentCR(componentInstallation *ecosystem.ComponentInstallation) (*compV1.Component, error) { - deployNamespace, err := toDeployNamespace(componentInstallation.DeployConfig) +func toComponentCR(component *ecosystem.ComponentInstallation) (*compV1.Component, error) { + deployNamespace, err := toDeployNamespace(component.DeployConfig) if err != nil { return nil, err } - valuesYamlOverwrite, err := toValuesYamlOverwrite(componentInstallation.DeployConfig) + valuesYamlOverwrite, err := toValuesYamlOverwrite(component.DeployConfig) if err != nil { return nil, err } spec := compV1.ComponentSpec{ - Namespace: string(componentInstallation.Name.Namespace), - Name: string(componentInstallation.Name.SimpleName), - Version: componentInstallation.ExpectedVersion.String(), + Namespace: string(component.Name.Namespace), + Name: string(component.Name.SimpleName), + Version: component.ExpectedVersion.String(), } if deployNamespace != "" { spec.DeployNamespace = deployNamespace @@ -101,10 +103,18 @@ func toComponentCR(componentInstallation *ecosystem.ComponentInstallation) (*com return &compV1.Component{ ObjectMeta: metav1.ObjectMeta{ - Name: string(componentInstallation.Name.SimpleName), + Name: string(component.Name.SimpleName), Labels: map[string]string{ - ComponentNameLabelKey: string(componentInstallation.Name.SimpleName), - ComponentVersionLabelKey: componentInstallation.ExpectedVersion.String(), + ComponentNameLabelKey: string(component.Name.SimpleName), + ComponentVersionLabelKey: component.ExpectedVersion.String(), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(component.Name.SimpleName), + "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "app.kubernetes.io/name": string(component.Name.SimpleName), + "app.kubernetes.io/version": component.ExpectedVersion.String(), + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: spec, diff --git a/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go b/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go index 0c523536..55152059 100644 --- a/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go +++ b/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go @@ -207,8 +207,16 @@ func Test_toComponentCR(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: testComponentNameRaw, Labels: map[string]string{ - ComponentNameLabelKey: testComponentNameRaw, - ComponentVersionLabelKey: testVersion1.String(), + ComponentNameLabelKey: testComponentNameRaw, + ComponentVersionLabelKey: testVersion1.String(), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(testComponentName.SimpleName), + "k8s.cloudogu.com/dogu.name": string(testComponentName.SimpleName), + "app.kubernetes.io/name": string(testComponentName.SimpleName), + "app.kubernetes.io/version": testVersion1.String(), + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: compV1.ComponentSpec{ diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index 554be5bc..c1af9829 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -275,8 +275,14 @@ func Test_doguInstallationRepo_Create(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: string(postgresDoguName.SimpleName), Labels: map[string]string{ - "app": "ces", - "dogu.name": string(postgresDoguName.SimpleName), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(postgresDoguName.SimpleName), + "k8s.cloudogu.com/dogu.name": string(postgresDoguName.SimpleName), + "app.kubernetes.io/name": string(postgresDoguName.SimpleName), + "app.kubernetes.io/version": version3214.Raw, + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: v2.DoguSpec{ diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index b9a5879d..746a5aa1 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -99,8 +99,14 @@ func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { ObjectMeta: metav1.ObjectMeta{ Name: string(dogu.Name.SimpleName), Labels: map[string]string{ - "app": "ces", - "dogu.name": string(dogu.Name.SimpleName), + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": string(dogu.Name.SimpleName), + "k8s.cloudogu.com/dogu.name": string(dogu.Name.SimpleName), + "app.kubernetes.io/name": string(dogu.Name.SimpleName), + "app.kubernetes.io/version": dogu.Version.String(), + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: v2.DoguSpec{ diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 20472b3b..63e284a4 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -257,8 +257,14 @@ func Test_toDoguCR(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "postgresql", Labels: map[string]string{ - "app": "ces", - "dogu.name": "postgresql", + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": "postgresql", + "k8s.cloudogu.com/dogu.name": "postgresql", + "app.kubernetes.io/name": "postgresql", + "app.kubernetes.io/version": version3214.Raw, + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: v2.DoguSpec{ @@ -301,8 +307,14 @@ func Test_toDoguCR(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "postgresql", Labels: map[string]string{ - "app": "ces", - "dogu.name": "postgresql", + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": "postgresql", + "k8s.cloudogu.com/dogu.name": "postgresql", + "app.kubernetes.io/name": "postgresql", + "app.kubernetes.io/version": version3214.Raw, + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: v2.DoguSpec{ @@ -343,8 +355,14 @@ func Test_toDoguCR(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "postgresql", Labels: map[string]string{ - "app": "ces", - "dogu.name": "postgresql", + "app": "ces", + "k8s.cloudogu.com/app": "ces", + "dogu.name": "postgresql", + "k8s.cloudogu.com/dogu.name": "postgresql", + "app.kubernetes.io/name": "postgresql", + "app.kubernetes.io/version": version3214.Raw, + "app.kubernetes.io/part-of": "ces", + "app.kubernetes.io/managed-by": "k8s-blueprint-operator", }, }, Spec: v2.DoguSpec{ From aa7bea44bc3f59f60b1b05f99a5c24380b5d66a0 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 5 Aug 2025 15:18:28 +0200 Subject: [PATCH 002/119] #121 create state diff without loading the blueprint again --- go.mod | 2 +- go.sum | 8 +- pkg/application/blueprintSpecChangeUseCase.go | 84 +++++++------------ .../blueprintSpecValidationUseCase.go | 50 +++-------- pkg/application/effectiveBlueprintUseCase.go | 19 ++--- pkg/application/interfaces.go | 8 +- pkg/application/stateDiffUseCase.go | 30 +++---- 7 files changed, 64 insertions(+), 137 deletions(-) diff --git a/go.mod b/go.mod index fa52b419..d1cd738a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250731163609-f388bd8c571c + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073 github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index ac6c1a5b..921a657b 100644 --- a/go.sum +++ b/go.sum @@ -48,12 +48,8 @@ github.com/cloudogu/ces-commons-lib v0.2.0 h1:yOEZWFl4W9N3J/6fok4svE3UufK5GQQtyx github.com/cloudogu/ces-commons-lib v0.2.0/go.mod h1:4rvR2RTDDaz5a6OZ1fW27G0MOnl5I3ackeiHxt4gn3o= github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSAaYi7U= github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250723132542-b0472a456ff0 h1:b3vbIIV1J8YtRCMkJC0EKB10DcTPDqVNda/FNX11E80= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250723132542-b0472a456ff0/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250731094124-310a388d0c7f h1:05BZKhsUJrv4oHkmK4CbNd2/0egXqXup/sfHOOCWC/0= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250731094124-310a388d0c7f/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250731163609-f388bd8c571c h1:o2hARW5ifab56Yjzxedc95wCBnjsJuF1k0bOfqUNXB0= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250731163609-f388bd8c571c/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073 h1:4E+BOxsDuidq2uwWyQI2VsPPvD34rUXaKDSm2O50ffU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 6e916e6b..ce418425 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -49,38 +49,49 @@ func NewBlueprintSpecChangeUseCase( // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write or // a domain.InvalidBlueprintError if the blueprint is invalid. -func (useCase *BlueprintSpecChangeUseCase) HandleChange(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx). +func (useCase *BlueprintSpecChangeUseCase) HandleChange(givenCtx context.Context, blueprintId string) error { + logger := log.FromContext(givenCtx). WithName("BlueprintSpecChangeUseCase.HandleChange"). WithValues("blueprintId", blueprintId) + // set the logger in the context to make use of structured logging + // we will give this ctx in every use case, therefore all of them will include the values given here + ctx := log.IntoContext(givenCtx, logger) logger.Info("getting changed blueprint") // log with id - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) + blueprint, err := useCase.repo.GetById(ctx, blueprintId) if err != nil { errMsg := "cannot load blueprint spec" logger.Error(err, errMsg) return fmt.Errorf("%s: %w", errMsg, err) } - logger = logger.WithValues("blueprintStatus", blueprintSpec.Status) - logger.Info("handle blueprint") // log with id and status values. + logger.Info("handle blueprint") // log with id + + err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) + if err != nil { + return err + } + err = useCase.effectiveBlueprint.CalculateEffectiveBlueprint(ctx, blueprint) + if err != nil { + return err + } + err = useCase.validation.ValidateBlueprintSpecDynamically(ctx, blueprint) + if err != nil { + return err + } + err = useCase.stateDiff.DetermineStateDiff(ctx, blueprint) + if err != nil { + // error could be either a technical error from a repository or an InvalidBlueprintError from the domain + // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. + return err + } // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. - switch blueprintSpec.Status { - case domain.StatusPhaseNew: - return useCase.validateStatically(ctx, blueprintId) - case domain.StatusPhaseInvalid: - return nil - case domain.StatusPhaseStaticallyValidated: - return useCase.calculateEffectiveBlueprint(ctx, blueprintId) - case domain.StatusPhaseEffectiveBlueprintGenerated: - return useCase.validateDynamically(ctx, blueprintId) - case domain.StatusPhaseValidated: - return useCase.determineStateDiff(ctx, blueprintId) + switch blueprint.Status { case domain.StatusPhaseStateDiffDetermined: return useCase.checkEcosystemHealthUpfront(ctx, blueprintId) case domain.StatusPhaseEcosystemHealthyUpfront: - return useCase.preProcessBlueprintApplication(ctx, blueprintSpec) + return useCase.preProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyUpfront: return nil case domain.StatusPhaseBlueprintApplicationPreProcessed: @@ -117,45 +128,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleChange(ctx context.Context, blu } } -func (useCase *BlueprintSpecChangeUseCase) validateStatically(ctx context.Context, blueprintId string) error { - err := useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) calculateEffectiveBlueprint(ctx context.Context, blueprintId string) error { - err := useCase.effectiveBlueprint.CalculateEffectiveBlueprint(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) validateDynamically(ctx context.Context, blueprintId string) error { - err := useCase.validation.ValidateBlueprintSpecDynamically(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) determineStateDiff(ctx context.Context, blueprintId string) error { - err := useCase.stateDiff.DetermineStateDiff(ctx, blueprintId) - - // error could be either a technical error from a repository or an InvalidBlueprintError from the domain - // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - func (useCase *BlueprintSpecChangeUseCase) checkEcosystemHealthUpfront(ctx context.Context, blueprintId string) error { err := useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprintId) if err != nil { diff --git a/pkg/application/blueprintSpecValidationUseCase.go b/pkg/application/blueprintSpecValidationUseCase.go index 44a6ee4d..4780002c 100644 --- a/pkg/application/blueprintSpecValidationUseCase.go +++ b/pkg/application/blueprintSpecValidationUseCase.go @@ -29,34 +29,16 @@ func NewBlueprintSpecValidationUseCase( // ValidateBlueprintSpecStatically checks the blueprintSpec for semantic errors and persists it. // returns a domain.InvalidBlueprintError if blueprint is invalid or -// a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write. -func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(ctx context.Context, blueprintId string) error { +func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx). - WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecStatically"). - WithValues("blueprintId", blueprintId) - - logger.Info("getting blueprint spec for static validation") - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - var invalidError *domain.InvalidBlueprintError - if errors.As(err, &invalidError) { - blueprintSpec.MarkInvalid(err) - updateErr := useCase.repo.Update(ctx, blueprintSpec) - if updateErr != nil { - return updateErr - } - return fmt.Errorf("blueprint spec syntax is invalid: %w", err) - } else { - return fmt.Errorf("cannot load blueprint spec to validate it: %w", err) - } - } + WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecStatically") - logger.Info("statically validate blueprint spec", "blueprintStatus", blueprintSpec.Status) + logger.Info("statically validate blueprint spec") - invalidBlueprintError := blueprintSpec.ValidateStatically() - err = useCase.repo.Update(ctx, blueprintSpec) + invalidBlueprintError := blueprint.ValidateStatically() + err := useCase.repo.Update(ctx, blueprint) if err != nil { // InternalError or ConflictError, both should be handled by the caller return fmt.Errorf("cannot update blueprint spec after static validation: %w", err) @@ -70,22 +52,14 @@ func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(c // a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write. -func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically(ctx context.Context, blueprintId string) error { +func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx). - WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecDynamically"). - WithValues("blueprintId", blueprintId) - - logger.Info("getting blueprint spec for dynamic validation") - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec to validate it: %w", err) - } - - logger.Info("dynamically validate blueprint spec", "blueprintStatus", blueprintSpec.Status) + WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecDynamically") + logger.Info("dynamically validate blueprint spec") validationError := errors.Join( - useCase.validateDependenciesUseCase.ValidateDependenciesForAllDogus(ctx, blueprintSpec.EffectiveBlueprint), - useCase.ValidateMountsUseCase.ValidateAdditionalMounts(ctx, blueprintSpec.EffectiveBlueprint), + useCase.validateDependenciesUseCase.ValidateDependenciesForAllDogus(ctx, blueprint.EffectiveBlueprint), + useCase.ValidateMountsUseCase.ValidateAdditionalMounts(ctx, blueprint.EffectiveBlueprint), ) if validationError != nil { @@ -95,8 +69,8 @@ func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically( } } - blueprintSpec.ValidateDynamically(validationError) - err = useCase.repo.Update(ctx, blueprintSpec) + blueprint.ValidateDynamically(validationError) + err := useCase.repo.Update(ctx, blueprint) if err != nil { return fmt.Errorf("cannot update blueprint spec after dynamic validation: %w", err) } diff --git a/pkg/application/effectiveBlueprintUseCase.go b/pkg/application/effectiveBlueprintUseCase.go index b2ddf691..3b96508b 100644 --- a/pkg/application/effectiveBlueprintUseCase.go +++ b/pkg/application/effectiveBlueprintUseCase.go @@ -3,6 +3,7 @@ package application import ( "context" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "sigs.k8s.io/controller-runtime/pkg/log" @@ -20,19 +21,11 @@ func NewEffectiveBlueprintUseCase(blueprintSpecRepo domainservice.BlueprintSpecR // returns a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write. -func (useCase *EffectiveBlueprintUseCase) CalculateEffectiveBlueprint(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("EffectiveBlueprintUseCase.CalculateEffectiveBlueprint"). - WithValues("blueprintId", blueprintId) - - logger.Info("getting blueprint spec for effective blueprint calculation") - blueprintSpec, err := useCase.blueprintSpecRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec to calculate effective blueprint: %w", err) - } - - logger.Info("calculate effective blueprint", "blueprintStatus", blueprintSpec.Status) - calcError := blueprintSpec.CalculateEffectiveBlueprint() - err = useCase.blueprintSpecRepo.Update(ctx, blueprintSpec) +func (useCase *EffectiveBlueprintUseCase) CalculateEffectiveBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("EffectiveBlueprintUseCase.CalculateEffectiveBlueprint") + logger.Info("calculate effective blueprint", "blueprintStatus") + calcError := blueprint.CalculateEffectiveBlueprint() + err := useCase.blueprintSpecRepo.Update(ctx, blueprint) if err != nil { return fmt.Errorf("cannot save blueprint spec after calculating the effective blueprint: %w", err) } diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 87d96244..37c51081 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -8,16 +8,16 @@ import ( ) type blueprintSpecValidationUseCase interface { - ValidateBlueprintSpecStatically(ctx context.Context, blueprintId string) error - ValidateBlueprintSpecDynamically(ctx context.Context, blueprintId string) error + ValidateBlueprintSpecStatically(ctx context.Context, blueprint *domain.BlueprintSpec) error + ValidateBlueprintSpecDynamically(ctx context.Context, blueprint *domain.BlueprintSpec) error } type effectiveBlueprintUseCase interface { - CalculateEffectiveBlueprint(ctx context.Context, blueprintId string) error + CalculateEffectiveBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error } type stateDiffUseCase interface { - DetermineStateDiff(ctx context.Context, blueprintId string) error + DetermineStateDiff(ctx context.Context, blueprint *domain.BlueprintSpec) error } type doguInstallationUseCase interface { diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index 7e705ce8..66965d14 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -44,31 +44,23 @@ func NewStateDiffUseCase( // DetermineStateDiff loads the state of the ecosystem and compares it to the blueprint. It creates a declarative diff. // returns: -// - a domainservice.NotFoundError if the blueprint was not found or could not found dogu decryption keys or // - a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or while collecting the ecosystem state or // - a domainservice.ConflictError if there was a concurrent write to the blueprint or // - a domain.InvalidBlueprintError if there are any forbidden actions in the stateDiff. // - any error if there is any other error. -func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("StateDiffUseCase.DetermineStateDiff"). - WithValues("blueprintId", blueprintId) - - logger.Info("getting blueprint spec for determining state diff") - blueprintSpec, err := useCase.blueprintSpecRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to determine state diff: %w", blueprintId, err) - } +func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("StateDiffUseCase.DetermineStateDiff") logger.Info("load referenced sensitive config") // load referenced config before collecting ecosystem state // if an error happens here, we save a lot of heavy work referencedSensitiveConfig, err := useCase.sensitiveConfigRefReader.GetValues( - ctx, blueprintSpec.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), + ctx, blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), ) if err != nil { err = fmt.Errorf("%s: %w", REFERENCED_CONFIG_NOT_FOUND, err) - blueprintSpec.MissingConfigReferences(err) - updateError := useCase.blueprintSpecRepo.Update(ctx, blueprintSpec) + blueprint.MissingConfigReferences(err) + updateError := useCase.blueprintSpecRepo.Update(ctx, blueprint) if updateError != nil { return errors.Join(updateError, err) } @@ -76,22 +68,22 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri } logger.Info("collect ecosystem state for state diff") - ecosystemState, err := useCase.collectEcosystemState(ctx, blueprintSpec.EffectiveBlueprint) + ecosystemState, err := useCase.collectEcosystemState(ctx, blueprint.EffectiveBlueprint) if err != nil { return fmt.Errorf("could not determine state diff: %w", err) } - logger.Info("determine state diff to the cloudogu ecosystem", "blueprintStatus", blueprintSpec.Status) - stateDiffError := blueprintSpec.DetermineStateDiff(ecosystemState, referencedSensitiveConfig) + logger.Info("determine state diff to the cloudogu ecosystem", "blueprintStatus", blueprint.Status) + stateDiffError := blueprint.DetermineStateDiff(ecosystemState, referencedSensitiveConfig) var invalidError *domain.InvalidBlueprintError if errors.As(stateDiffError, &invalidError) { // do not return here as with this error the blueprint status and events should be persisted as normal. } else if stateDiffError != nil { - return fmt.Errorf("failed to determine state diff for blueprint %q: %w", blueprintId, stateDiffError) + return fmt.Errorf("failed to determine state diff for blueprint %q: %w", blueprint.Id, stateDiffError) } - err = useCase.blueprintSpecRepo.Update(ctx, blueprintSpec) + err = useCase.blueprintSpecRepo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after determining the state diff to the ecosystem: %w", blueprintId, err) + return fmt.Errorf("cannot save blueprint spec %q after determining the state diff to the ecosystem: %w", blueprint.Id, err) } // return this error back here to persist the blueprint status and events first. From a5de5726f7bfff0604a3b239337b615f5fbc4d86 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 5 Aug 2025 15:47:54 +0200 Subject: [PATCH 003/119] #119 update changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3614ca7..0ab3a87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +*Breaking Change ahead!* + +### Changed +- [#119] *breaking* sensitive dogu config can now only be referenced with secrets + - it was not safe to have these values in clear text in the blueprint +- [#119] we now support blueprint v2 CRs + +### Removed +- [#119] *breaking* no support for v1 blueprint CRs anymore + - make sure to persist your blueprints before upgrading + - you need to transform your blueprints to the new v2 format yourself + ## [v2.7.0] - 2025-07-17 ### Fixed - [#117] configuring operator log-level From ae794692dc252bc591ac5a3b84e5257f56bf041d Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 5 Aug 2025 15:53:48 +0200 Subject: [PATCH 004/119] #119 fix duplicate error if config key is present and absent --- pkg/domain/config.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/domain/config.go b/pkg/domain/config.go index e4a2fc3e..1ac6c110 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -167,17 +167,12 @@ func validateDoguConfigKeys(keys []common.DoguConfigKey, referencedDoguName cesc func validateNoDuplicates(presentKeys []common.DoguConfigKey, absentKeys []common.DoguConfigKey) error { var errs []error // no present keys in absent + // a duplicate needs to be in the present and the absent list, therefore we only need to check one of the lists. for _, presentKey := range presentKeys { if slices.Contains(absentKeys, presentKey) { errs = append(errs, fmt.Errorf("key %q of dogu %q cannot be present and absent at the same time", presentKey.Key, presentKey.DoguName)) } } - // no absent keys in present - for _, absentKey := range absentKeys { - if slices.Contains(presentKeys, absentKey) { - errs = append(errs, fmt.Errorf("key %q of dogu %q cannot be present and absent at the same time", absentKey.Key, absentKey.DoguName)) - } - } // no absent duplicates absentDuplicates := util.GetDuplicates(absentKeys) From 2085982a7352d9fccbfdb74aea47c79a1d8ff8fe Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 5 Aug 2025 16:59:54 +0200 Subject: [PATCH 005/119] #121 WIP: do not load blueprint in every use case --- .../reconciler/blueprint_controller.go | 2 +- pkg/adapter/reconciler/interfaces.go | 2 +- .../mock_BlueprintChangeHandler_test.go | 8 +- pkg/application/blueprintSpecChangeUseCase.go | 101 ++++++------------ .../blueprintSpecChangeUseCase_test.go | 42 ++++---- pkg/application/interfaces.go | 18 ++-- pkg/domain/blueprintSpec.go | 15 +++ 7 files changed, 81 insertions(+), 107 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 88c2f10e..a74a3047 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -44,7 +44,7 @@ func (r *BlueprintReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( WithName("BlueprintReconciler.Reconcile"). WithValues("resourceName", req.Name) - err := r.blueprintChangeHandler.HandleChange(ctx, req.Name) + err := r.blueprintChangeHandler.HandleUntilApplied(ctx, req.Name) if err != nil { return decideRequeueForError(logger, err) diff --git a/pkg/adapter/reconciler/interfaces.go b/pkg/adapter/reconciler/interfaces.go index a684cd1e..3092005d 100644 --- a/pkg/adapter/reconciler/interfaces.go +++ b/pkg/adapter/reconciler/interfaces.go @@ -14,5 +14,5 @@ type controllerManager interface { } type BlueprintChangeHandler interface { - HandleChange(ctx context.Context, blueprintId string) error + HandleUntilApplied(ctx context.Context, blueprintId string) error } diff --git a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go index 7aedce6c..bd16237b 100644 --- a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go +++ b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go @@ -22,11 +22,11 @@ func (_m *MockBlueprintChangeHandler) EXPECT() *MockBlueprintChangeHandler_Expec } // HandleChange provides a mock function with given fields: ctx, blueprintId -func (_m *MockBlueprintChangeHandler) HandleChange(ctx context.Context, blueprintId string) error { +func (_m *MockBlueprintChangeHandler) HandleUntilApplied(ctx context.Context, blueprintId string) error { ret := _m.Called(ctx, blueprintId) if len(ret) == 0 { - panic("no return value specified for HandleChange") + panic("no return value specified for HandleUntilApplied") } var r0 error @@ -39,7 +39,7 @@ func (_m *MockBlueprintChangeHandler) HandleChange(ctx context.Context, blueprin return r0 } -// MockBlueprintChangeHandler_HandleChange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleChange' +// MockBlueprintChangeHandler_HandleChange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleUntilApplied' type MockBlueprintChangeHandler_HandleChange_Call struct { *mock.Call } @@ -48,7 +48,7 @@ type MockBlueprintChangeHandler_HandleChange_Call struct { // - ctx context.Context // - blueprintId string func (_e *MockBlueprintChangeHandler_Expecter) HandleChange(ctx interface{}, blueprintId interface{}) *MockBlueprintChangeHandler_HandleChange_Call { - return &MockBlueprintChangeHandler_HandleChange_Call{Call: _e.mock.On("HandleChange", ctx, blueprintId)} + return &MockBlueprintChangeHandler_HandleChange_Call{Call: _e.mock.On("HandleUntilApplied", ctx, blueprintId)} } func (_c *MockBlueprintChangeHandler_HandleChange_Call) Run(run func(ctx context.Context, blueprintId string)) *MockBlueprintChangeHandler_HandleChange_Call { diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index ce418425..9fb029b7 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -44,14 +44,14 @@ func NewBlueprintSpecChangeUseCase( } } -// HandleChange further executes a blueprint spec given by the blueprintId until it is fully applied or an error occurred. +// HandleUntilApplied further executes a blueprint spec given by the blueprintId until it is fully applied or an error occurred. // Returns a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write or // a domain.InvalidBlueprintError if the blueprint is invalid. -func (useCase *BlueprintSpecChangeUseCase) HandleChange(givenCtx context.Context, blueprintId string) error { +func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.Context, blueprintId string) error { logger := log.FromContext(givenCtx). - WithName("BlueprintSpecChangeUseCase.HandleChange"). + WithName("BlueprintSpecChangeUseCase.HandleUntilApplied"). WithValues("blueprintId", blueprintId) // set the logger in the context to make use of structured logging // we will give this ctx in every use case, therefore all of them will include the values given here @@ -65,7 +65,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleChange(givenCtx context.Context return fmt.Errorf("%s: %w", errMsg, err) } - logger.Info("handle blueprint") // log with id + logger.Info("handle blueprint") err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) if err != nil { @@ -87,38 +87,50 @@ func (useCase *BlueprintSpecChangeUseCase) HandleChange(givenCtx context.Context } // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. + for blueprint.Status != domain.StatusPhaseCompleted { + err := useCase.handleChange(ctx, blueprint) + if err != nil { + return err + } + } + + logger.Info("blueprint successfully applied") + return nil +} + +func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { case domain.StatusPhaseStateDiffDetermined: - return useCase.checkEcosystemHealthUpfront(ctx, blueprintId) + return useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprint) case domain.StatusPhaseEcosystemHealthyUpfront: return useCase.preProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyUpfront: return nil case domain.StatusPhaseBlueprintApplicationPreProcessed: - return useCase.handleSelfUpgrade(ctx, blueprintId) + return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseAwaitSelfUpgrade: - return useCase.handleSelfUpgrade(ctx, blueprintId) + return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseSelfUpgradeCompleted: - return useCase.applyEcosystemConfig(ctx, blueprintId) + return useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) case domain.StatusPhaseEcosystemConfigApplied: - return useCase.applyBlueprintSpec(ctx, blueprintId) + return useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) case domain.StatusPhaseApplyEcosystemConfigFailed: - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseInProgress: // should only happen if the system was interrupted, normally this state will be updated to blueprintApplied or BlueprintApplicationFailed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseBlueprintApplied: - return useCase.triggerDoguRestarts(ctx, blueprintId) + return useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprint) case domain.StatusPhaseRestartsTriggered: - return useCase.checkEcosystemHealthAfterwards(ctx, blueprintId) + return useCase.applyUseCase.CheckEcosystemHealthAfterwards(ctx, blueprint) case domain.StatusPhaseBlueprintApplicationFailed: - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemHealthyAfterwards: // deactivate maintenance mode and set status to completed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyAfterwards: // deactivate maintenance mode and set status to failed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseCompleted: return nil case domain.StatusPhaseFailed: @@ -128,17 +140,8 @@ func (useCase *BlueprintSpecChangeUseCase) HandleChange(givenCtx context.Context } } -func (useCase *BlueprintSpecChangeUseCase) checkEcosystemHealthUpfront(ctx context.Context, blueprintId string) error { - err := useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - func (useCase *BlueprintSpecChangeUseCase) preProcessBlueprintApplication(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - err := useCase.applyUseCase.PreProcessBlueprintApplication(ctx, blueprintSpec.Id) + err := useCase.applyUseCase.PreProcessBlueprintApplication(ctx, blueprintSpec) if err != nil { return err } @@ -147,49 +150,5 @@ func (useCase *BlueprintSpecChangeUseCase) preProcessBlueprintApplication(ctx co // just stop the loop here on dry run or early exit return nil } - return useCase.HandleChange(ctx, blueprintSpec.Id) -} - -func (useCase *BlueprintSpecChangeUseCase) handleSelfUpgrade(ctx context.Context, blueprintId string) error { - err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprintId) - if err != nil { - return err - } - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) applyBlueprintSpec(ctx context.Context, blueprintId string) error { - err := useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) checkEcosystemHealthAfterwards(ctx context.Context, blueprintId string) error { - err := useCase.applyUseCase.CheckEcosystemHealthAfterwards(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) applyEcosystemConfig(ctx context.Context, blueprintId string) error { - err := useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) -} - -func (useCase *BlueprintSpecChangeUseCase) triggerDoguRestarts(ctx context.Context, blueprintId string) error { - err := useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprintId) - if err != nil { - return err - } - - return useCase.HandleChange(ctx, blueprintId) + return useCase.HandleUntilApplied(ctx, blueprintSpec.Id) } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 425bdd41..55c50e5b 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -83,7 +83,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) // when - err := useCase.HandleChange(testCtx, testBlueprintId) + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) // then require.NoError(t, err) assert.Equal(t, domain.StatusPhaseCompleted, blueprintSpec.Status) @@ -108,7 +108,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, expectedError) // when - err := useCase.HandleChange(testCtx, blueprintId) + err := useCase.HandleUntilApplied(testCtx, blueprintId) // then require.Error(t, err) @@ -137,7 +137,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(assert.AnError) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) @@ -169,7 +169,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(assert.AnError) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) @@ -205,7 +205,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(assert.AnError) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) @@ -244,7 +244,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(assert.AnError) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) @@ -288,7 +288,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock.EXPECT().CheckEcosystemHealthUpfront(testCtx, "testBlueprint1").Return(assert.AnError) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) @@ -312,7 +312,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseInvalid, }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -334,7 +334,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseEcosystemUnhealthyUpfront, }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -356,7 +356,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseEcosystemUnhealthyUpfront, }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -380,7 +380,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(testCtx, blueprintSpec.Id).Return(assert.AnError) // when - actualErr := useCase.HandleChange(testCtx, "testBlueprint1") + actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.ErrorIs(t, actualErr, assert.AnError) }) @@ -404,7 +404,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintSpec.Id).Return(nil) // when - actualErr := useCase.HandleChange(testCtx, "testBlueprint1") + actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, actualErr) }) @@ -428,7 +428,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintSpec.Id).Return(assert.AnError) // when - actualErr := useCase.HandleChange(testCtx, "testBlueprint1") + actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.ErrorIs(t, actualErr, assert.AnError) }) @@ -457,7 +457,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) // when - err := useCase.HandleChange(testCtx, testBlueprintId) + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) // then require.ErrorIs(t, err, assert.AnError) }) @@ -483,7 +483,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { blueprintSpec.Status = domain.StatusPhaseCompleted }) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -509,7 +509,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { blueprintSpec.Status = domain.StatusPhaseCompleted }) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -531,7 +531,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseCompleted, }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -554,7 +554,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }, nil) applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintId).Return(nil) // when - err := useCase.HandleChange(testCtx, blueprintId) + err := useCase.HandleUntilApplied(testCtx, blueprintId) // then require.NoError(t, err) }) @@ -576,7 +576,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseFailed, }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.NoError(t, err) }) @@ -598,7 +598,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: "unknown", }, nil) // when - err := useCase.HandleChange(testCtx, "testBlueprint1") + err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") // then require.Error(t, err) require.ErrorContains(t, err, "could not handle unknown status of blueprint") @@ -689,7 +689,7 @@ func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(errors.New("testerror")) // when - err := useCase.HandleChange(testCtx, testBlueprintId) + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) // then require.Error(t, err) assert.Equal(t, "testerror", err.Error()) diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 37c51081..24419da8 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -27,22 +27,22 @@ type doguInstallationUseCase interface { } type doguRestartUseCase interface { - TriggerDoguRestarts(ctx context.Context, blueprintid string) error + TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error } type componentInstallationUseCase interface { - ApplyComponentStates(ctx context.Context, blueprintId string) error + ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) WaitForHealthyComponents(ctx context.Context) (ecosystem.ComponentHealthResult, error) applyComponentState(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error } type applyBlueprintSpecUseCase interface { - CheckEcosystemHealthUpfront(ctx context.Context, blueprintId string) error - CheckEcosystemHealthAfterwards(ctx context.Context, blueprintId string) error - PreProcessBlueprintApplication(ctx context.Context, blueprintId string) error - PostProcessBlueprintApplication(ctx context.Context, blueprintId string) error - ApplyBlueprintSpec(ctx context.Context, blueprintId string) error + CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error + CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error + PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error + PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error + ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error } type ecosystemHealthUseCase interface { @@ -51,11 +51,11 @@ type ecosystemHealthUseCase interface { } type selfUpgradeUseCase interface { - HandleSelfUpgrade(ctx context.Context, blueprintId string) error + HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error } type ecosystemConfigUseCase interface { - ApplyConfig(ctx context.Context, blueprintId string) error + ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error } type doguInstallationRepository interface { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 03db8e3e..2c7149ea 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -8,6 +8,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "maps" "slices" ) @@ -20,6 +21,7 @@ type BlueprintSpec struct { StateDiff StateDiff Config BlueprintConfiguration Status StatusPhase + Conditions []Condition // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. // This field should be ignored in the whole domain. @@ -27,6 +29,19 @@ type BlueprintSpec struct { Events []Event } +type Condition = metav1.Condition + +var ( + conditionTypeValid = "Valid" + conditionEcosystemHealthy = "EcosystemHealthy" + conditionSelfUpgradeCompleted = "SelfUpgradeCompleted" + conditionConfigApplied = "ConfigApplied" + conditionDogusApplied = "DogusApplied" + conditionComponentsApplied = "ComponentsApplied" + // how do we watch restarts? + conditionBlueprintApplied = "BlueprintApplied" +) + type StatusPhase string const ( From cee514fb7b2ab81c3c7be8a6538048445d5e1a18 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 7 Aug 2025 12:19:55 +0200 Subject: [PATCH 006/119] #121 WIP: use bluerpint instead of the id to call use cases this way, whe use cases don't need to load the blueprint everytime. If there is an concurrent update, we handle the conflicts when we try to update the blueprint ourselves. If we use the same blueprint the whole time we don't need to recalculate the state diff. We only have to do this, if we need to reconcile the CR again, e.g. for non-blocking health checks. --- .../mock_BlueprintChangeHandler_test.go | 18 +- pkg/application/applyBlueprintSpecUseCase.go | 103 +++----- .../applyBlueprintSpecUseCase_test.go | 236 ++++++------------ pkg/application/blueprintSpecChangeUseCase.go | 4 +- .../blueprintSpecChangeUseCase_test.go | 154 +++++------- .../blueprintSpecValidationUseCase_test.go | 161 +++--------- .../componentInstallationUseCase.go | 15 +- .../componentInstallationUseCase_test.go | 47 +--- pkg/application/doguInstallationUseCase.go | 17 +- .../doguInstallationUseCase_test.go | 40 +-- pkg/application/doguRestartUseCase.go | 17 +- pkg/application/doguRestartUseCase_test.go | 92 ++----- pkg/application/ecosystemConfigUseCase.go | 50 ++-- .../ecosystemConfigUseCase_test.go | 89 +++---- .../effectiveBlueprintUseCase_test.go | 59 +---- pkg/application/interfaces.go | 2 +- .../mock_applyBlueprintSpecUseCase_test.go | 111 ++++---- ...ock_blueprintSpecValidationUseCase_test.go | 45 ++-- .../mock_componentInstallationUseCase_test.go | 22 +- .../mock_doguInstallationUseCase_test.go | 24 +- .../mock_doguRestartUseCase_test.go | 23 +- .../mock_ecosystemConfigUseCase_test.go | 23 +- .../mock_effectiveBlueprintUseCase_test.go | 23 +- .../mock_selfUpgradeUseCase_test.go | 23 +- pkg/application/mock_stateDiffUseCase_test.go | 23 +- pkg/application/selfUpgradeUseCase.go | 25 +- pkg/application/selfUpgradeUseCase_test.go | 75 +----- pkg/application/stateDiffUseCase_test.go | 75 ++---- 28 files changed, 548 insertions(+), 1048 deletions(-) diff --git a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go index bd16237b..9005793d 100644 --- a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go +++ b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go @@ -21,7 +21,7 @@ func (_m *MockBlueprintChangeHandler) EXPECT() *MockBlueprintChangeHandler_Expec return &MockBlueprintChangeHandler_Expecter{mock: &_m.Mock} } -// HandleChange provides a mock function with given fields: ctx, blueprintId +// HandleUntilApplied provides a mock function with given fields: ctx, blueprintId func (_m *MockBlueprintChangeHandler) HandleUntilApplied(ctx context.Context, blueprintId string) error { ret := _m.Called(ctx, blueprintId) @@ -39,31 +39,31 @@ func (_m *MockBlueprintChangeHandler) HandleUntilApplied(ctx context.Context, bl return r0 } -// MockBlueprintChangeHandler_HandleChange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleUntilApplied' -type MockBlueprintChangeHandler_HandleChange_Call struct { +// MockBlueprintChangeHandler_HandleUntilApplied_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleUntilApplied' +type MockBlueprintChangeHandler_HandleUntilApplied_Call struct { *mock.Call } -// HandleChange is a helper method to define mock.On call +// HandleUntilApplied is a helper method to define mock.On call // - ctx context.Context // - blueprintId string -func (_e *MockBlueprintChangeHandler_Expecter) HandleChange(ctx interface{}, blueprintId interface{}) *MockBlueprintChangeHandler_HandleChange_Call { - return &MockBlueprintChangeHandler_HandleChange_Call{Call: _e.mock.On("HandleUntilApplied", ctx, blueprintId)} +func (_e *MockBlueprintChangeHandler_Expecter) HandleUntilApplied(ctx interface{}, blueprintId interface{}) *MockBlueprintChangeHandler_HandleUntilApplied_Call { + return &MockBlueprintChangeHandler_HandleUntilApplied_Call{Call: _e.mock.On("HandleUntilApplied", ctx, blueprintId)} } -func (_c *MockBlueprintChangeHandler_HandleChange_Call) Run(run func(ctx context.Context, blueprintId string)) *MockBlueprintChangeHandler_HandleChange_Call { +func (_c *MockBlueprintChangeHandler_HandleUntilApplied_Call) Run(run func(ctx context.Context, blueprintId string)) *MockBlueprintChangeHandler_HandleUntilApplied_Call { _c.Call.Run(func(args mock.Arguments) { run(args[0].(context.Context), args[1].(string)) }) return _c } -func (_c *MockBlueprintChangeHandler_HandleChange_Call) Return(_a0 error) *MockBlueprintChangeHandler_HandleChange_Call { +func (_c *MockBlueprintChangeHandler_HandleUntilApplied_Call) Return(_a0 error) *MockBlueprintChangeHandler_HandleUntilApplied_Call { _c.Call.Return(_a0) return _c } -func (_c *MockBlueprintChangeHandler_HandleChange_Call) RunAndReturn(run func(context.Context, string) error) *MockBlueprintChangeHandler_HandleChange_Call { +func (_c *MockBlueprintChangeHandler_HandleUntilApplied_Call) RunAndReturn(run func(context.Context, string) error) *MockBlueprintChangeHandler_HandleUntilApplied_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index 712f9cb8..b9df715b 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -36,27 +36,21 @@ func NewApplyBlueprintSpecUseCase( // returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state or // There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthUpfront"). - WithValues("blueprintId", blueprintId) +func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthUpfront") - logger.Info("getting blueprint spec for checking ecosystem health") - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) + logger.Info("check ecosystem health") + healthResult, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint.Config.IgnoreDoguHealth, blueprint.Config.IgnoreComponentHealth) if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to check ecosystem health: %w", blueprintId, err) + return fmt.Errorf("cannot check ecosystem health upfront of applying the blueprint %q: %w", blueprint.Id, err) } - - healthResult, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprintSpec.Config.IgnoreDoguHealth, blueprintSpec.Config.IgnoreComponentHealth) - if err != nil { - return fmt.Errorf("cannot check ecosystem health upfront of applying the blueprint %q: %w", blueprintId, err) - } - healthErr := blueprintSpec.CheckEcosystemHealthUpfront(healthResult) + healthErr := blueprint.CheckEcosystemHealthUpfront(healthResult) // persist blueprint even with error, because it will set conditions - err = useCase.repo.Update(ctx, blueprintSpec) + err = useCase.repo.Update(ctx, blueprint) if err != nil { // healthErr can be ignored here. We have a more serious problem if we cannot persist the blueprint // the health check will be repeated anyway - return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprintId, err) + return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprint.Id, err) } return healthErr @@ -67,29 +61,24 @@ func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx contex // returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. // There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthAfterwards"). - WithValues("blueprintId", blueprintId) +func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthAfterwards") - logger.Info("getting blueprint spec for checking ecosystem health afterwards") - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to check ecosystem health: %w", blueprintId, err) - } + logger.Info("check ecosystem health") // do not ignore the health states of dogus and components here, as we want to set the blueprint status according to the result. // The blueprint is already executed here. healthResult, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, false, false) if err != nil { - return fmt.Errorf("cannot check ecosystem health after applying the blueprint %q: %w", blueprintId, err) + return fmt.Errorf("cannot check ecosystem health after applying the blueprint %q: %w", blueprint.Id, err) } - healthErr := blueprintSpec.CheckEcosystemHealthAfterwards(healthResult) + healthErr := blueprint.CheckEcosystemHealthAfterwards(healthResult) - err = useCase.repo.Update(ctx, blueprintSpec) + err = useCase.repo.Update(ctx, blueprint) if err != nil { // healthErr can be ignored here. We have a more serious problem if we cannot persist the blueprint // the health check will be repeated anyway - return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprintId, err) + return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprint.Id, err) } return healthErr @@ -97,24 +86,18 @@ func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx con // PreProcessBlueprintApplication prepares the environment for applying the blueprint. // returns a domainservice.InternalError on any error. -func (useCase *ApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PreProcessBlueprintApplication"). - WithValues("blueprintId", blueprintId) - - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q for preprocessing: %w", blueprintId, err) - } +func (useCase *ApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PreProcessBlueprintApplication") - if !blueprintSpec.ShouldBeApplied() { + if !blueprint.ShouldBeApplied() { logger.Info("stop as blueprint should not be applied") } - blueprintSpec.CompletePreProcessing() + blueprint.CompletePreProcessing() - err = useCase.repo.Update(ctx, blueprintSpec) + err := useCase.repo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after preprocessing: %w", blueprintId, err) + return fmt.Errorf("cannot save blueprint spec %q after preprocessing: %w", blueprint.Id, err) } return nil @@ -122,23 +105,17 @@ func (useCase *ApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx con // PostProcessBlueprintApplication makes changes to the environment after applying the blueprint, e.g. censoring the state diff. // returns a domainservice.InternalError on any error. -func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PostProcessBlueprintApplication"). - WithValues("blueprintId", blueprintId) - - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q while post-processing blueprint application: %w", blueprintId, err) - } +func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PostProcessBlueprintApplication") logger.Info("censor sensitive data") - blueprintSpec.CensorSensitiveData() + blueprint.CensorSensitiveData() - blueprintSpec.CompletePostProcessing() + blueprint.CompletePostProcessing() - err = useCase.repo.Update(ctx, blueprintSpec) + err := useCase.repo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot update blueprint spec %q while post-processing blueprint application: %w", blueprintId, err) + return fmt.Errorf("cannot update blueprint spec %q while post-processing blueprint application: %w", blueprint.Id, err) } return nil @@ -148,34 +125,28 @@ func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx co // Returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or other resources or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. // There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.ApplyBlueprintSpec"). - WithValues("blueprintId", blueprintId) - - blueprintSpec, err := useCase.repo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint to apply blueprint spec: %w", err) - } +func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.ApplyBlueprintSpec") logger.Info("start applying blueprint to the cluster") - err = useCase.startApplying(ctx, blueprintSpec) + err := useCase.startApplying(ctx, blueprint) if err != nil { return err } - applyError := useCase.componentInstallUseCase.ApplyComponentStates(ctx, blueprintId) + applyError := useCase.componentInstallUseCase.ApplyComponentStates(ctx, blueprint) if applyError != nil { - return useCase.handleApplyFailedError(ctx, blueprintSpec, applyError) + return useCase.handleApplyFailedError(ctx, blueprint, applyError) } _, err = useCase.componentInstallUseCase.WaitForHealthyComponents(ctx) if err != nil { - return useCase.handleApplyFailedError(ctx, blueprintSpec, err) + return useCase.handleApplyFailedError(ctx, blueprint, err) } - applyError = useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprintId) + applyError = useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) if applyError != nil { - return useCase.handleApplyFailedError(ctx, blueprintSpec, applyError) + return useCase.handleApplyFailedError(ctx, blueprint, applyError) } // FIXME: this health check is blocking. I think we need to split the apply logic into multiple steps to @@ -183,11 +154,11 @@ func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context // otherwise service account creation might fail because dogus are restarted right after this step _, err = useCase.doguInstallUseCase.WaitForHealthyDogus(ctx) if err != nil { - return useCase.handleApplyFailedError(ctx, blueprintSpec, err) + return useCase.handleApplyFailedError(ctx, blueprint, err) } logger.Info("blueprint successfully applied to the cluster") - return useCase.markBlueprintApplied(ctx, blueprintSpec) + return useCase.markBlueprintApplied(ctx, blueprint) } func (useCase *ApplyBlueprintSpecUseCase) handleApplyFailedError(ctx context.Context, blueprintSpec *domain.BlueprintSpec, applyError error) error { diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index a99c1297..92e542ee 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -31,35 +31,24 @@ func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(spec, nil) repoMock.EXPECT().Update(testCtx, spec).Return(nil) useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - err := useCase.PreProcessBlueprintApplication(testCtx, blueprintId) + err := useCase.PreProcessBlueprintApplication(testCtx, spec) require.NoError(t, err) assert.Equal(t, domain.StatusPhaseBlueprintApplicationPreProcessed, spec.Status) }) - t.Run("repo error while loading", func(t *testing.T) { - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - err := useCase.PreProcessBlueprintApplication(testCtx, blueprintId) - - require.ErrorIs(t, err, assert.AnError) - }) t.Run("repo error while saving", func(t *testing.T) { spec := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(spec, nil) repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - err := useCase.PreProcessBlueprintApplication(testCtx, blueprintId) + err := useCase.PreProcessBlueprintApplication(testCtx, spec) require.ErrorIs(t, err, assert.AnError) }) @@ -70,11 +59,10 @@ func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(spec, nil) repoMock.EXPECT().Update(testCtx, spec).Return(nil) useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - err := useCase.PreProcessBlueprintApplication(testCtx, blueprintId) + err := useCase.PreProcessBlueprintApplication(testCtx, spec) require.NoError(t, err) require.Equal(t, 1, len(spec.Events)) @@ -174,149 +162,130 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { 2: domain.StatusPhaseBlueprintApplied, } t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) var counter = 0 - repoMock.EXPECT().Update(testCtx, spec).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { + repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { counter++ assert.Equal(t, statusTransitions[counter], spec.Status) return nil }).Times(2) installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, "blueprintId").Return(nil) + installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, nil) componentInstallUseCase := newMockComponentInstallationUseCase(t) - componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(nil) + componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) componentInstallUseCase.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCase} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseBlueprintApplied, spec.Status) + assert.Equal(t, domain.StatusPhaseBlueprintApplied, blueprint.Status) }) t.Run("error waiting for dogu health", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, "blueprintId").Return(nil) + installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, assert.AnError) componentInstallUseCase := newMockComponentInstallationUseCase(t) - componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(nil) + componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) componentInstallUseCase.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCase} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) - }) - - t.Run("cannot load spec", func(t *testing.T) { - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(nil, assert.AnError) - - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil} - - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint to apply blueprint spec") + assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) t.Run("fail to mark in progress", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseInProgress, spec.Status) + assert.Equal(t, domain.StatusPhaseInProgress, blueprint.Status) }) t.Run("fail to apply component state", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(assert.AnError) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil, componentInstallUseCase: componentInstallUseCaseMock} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) + assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) t.Run("fail to wait for component health", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(nil) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, assert.AnError) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil, componentInstallUseCase: componentInstallUseCaseMock} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) + assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) t.Run("fail to apply dogu state", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(nil) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, "blueprintId").Return(assert.AnError) + installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCaseMock} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) + assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) t.Run("fail to apply state and fail to mark execution failed", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyUpfront, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, "blueprintId").Return(spec, nil) counter := 0 - repoMock.EXPECT().Update(testCtx, spec).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { + repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { counter++ if counter == 1 { return nil @@ -326,47 +295,33 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, "blueprintId").Return(nil) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, "blueprintId").Return(assert.AnError) + installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCaseMock} - err := useCase.ApplyBlueprintSpec(testCtx, "blueprintId") + err := useCase.ApplyBlueprintSpec(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) + assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) } func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { - t.Run("should fail to get blueprint spec", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprintId) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint spec \"blueprint1\" to check ecosystem health") - }) t.Run("should fail to get health result", func(t *testing.T) { // given - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{}, nil) + blueprint := &domain.BlueprintSpec{ + Id: blueprintId, + } healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, assert.AnError) - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) + sut := NewApplyBlueprintSpecUseCase(nil, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprintId) + err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) // then require.Error(t, err) @@ -375,10 +330,11 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { }) t.Run("should fail to update blueprint spec", func(t *testing.T) { // given - blueprintSpec := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{ + Id: blueprintId, + } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(blueprintSpec, nil) - repoMock.EXPECT().Update(testCtx, blueprintSpec).Return(assert.AnError) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, false, false).Return(ecosystem.HealthResult{}, nil) @@ -386,7 +342,7 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprintId) + err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) // then require.Error(t, err) @@ -395,13 +351,12 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { }) t.Run("should succeed, ignoring dogu and component health", func(t *testing.T) { // given - blueprintSpec := &domain.BlueprintSpec{Config: domain.BlueprintConfiguration{ + blueprint := &domain.BlueprintSpec{Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: true, IgnoreComponentHealth: true, }} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(blueprintSpec, nil) - repoMock.EXPECT().Update(testCtx, blueprintSpec).Return(nil) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, true, true).Return(ecosystem.HealthResult{}, nil) @@ -409,17 +364,16 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprintId) + err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) // then require.NoError(t, err) }) t.Run("should succeed", func(t *testing.T) { // given - blueprintSpec := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(blueprintSpec, nil) - repoMock.EXPECT().Update(testCtx, blueprintSpec).Return(nil) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, false, false).Return(ecosystem.HealthResult{}, nil) @@ -427,7 +381,7 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprintId) + err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) // then require.NoError(t, err) @@ -435,34 +389,19 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { } func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) { - t.Run("should fail to get blueprint spec", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprintId) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint spec \"blueprint1\" to check ecosystem health") - }) - t.Run("should fail to get health result", func(t *testing.T) { // given - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{}, nil) + blueprint := &domain.BlueprintSpec{ + Id: blueprintId, + } healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, assert.AnError) - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) + sut := NewApplyBlueprintSpecUseCase(nil, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprintId) + err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) // then require.Error(t, err) @@ -472,10 +411,11 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) t.Run("should fail to update blueprint spec", func(t *testing.T) { // given - blueprintSpec := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{ + Id: blueprintId, + } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(blueprintSpec, nil) - repoMock.EXPECT().Update(testCtx, blueprintSpec).Return(assert.AnError) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, nil) @@ -483,7 +423,7 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprintId) + err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) // then require.Error(t, err) @@ -493,10 +433,9 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) t.Run("should succeed", func(t *testing.T) { // given - blueprintSpec := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(blueprintSpec, nil) - repoMock.EXPECT().Update(testCtx, blueprintSpec).Return(nil) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) healthMock := newMockEcosystemHealthUseCase(t) healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, nil) @@ -504,56 +443,43 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprintId) + err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseEcosystemHealthyAfterwards, blueprintSpec.Status) + assert.Equal(t, domain.StatusPhaseEcosystemHealthyAfterwards, blueprint.Status) }) } func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) { t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyAfterwards, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - err := useCase.PostProcessBlueprintApplication(testCtx, blueprintId) + err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseCompleted, spec.Status) - assert.Len(t, spec.Events, 2) - assert.Contains(t, spec.Events, domain.SensitiveConfigDataCensoredEvent{}, spec.Events) - assert.Contains(t, spec.Events, domain.CompletedEvent{}, spec.Events) + assert.Equal(t, domain.StatusPhaseCompleted, blueprint.Status) + assert.Len(t, blueprint.Events, 2) + assert.Contains(t, blueprint.Events, domain.SensitiveConfigDataCensoredEvent{}, blueprint.Events) + assert.Contains(t, blueprint.Events, domain.CompletedEvent{}, blueprint.Events) }) - - t.Run("repo error while loading", func(t *testing.T) { - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - err := useCase.PostProcessBlueprintApplication(testCtx, blueprintId) - - require.ErrorIs(t, err, assert.AnError) - }) - t.Run("repo error while saving", func(t *testing.T) { - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Status: domain.StatusPhaseEcosystemHealthyAfterwards, } repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(spec, nil) - repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - err := useCase.PostProcessBlueprintApplication(testCtx, blueprintId) + err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) }) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 97d327bf..e3c603f4 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -127,10 +127,10 @@ func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blu return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemHealthyAfterwards: // censor and set status to completed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyAfterwards: // censor and set status to failed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprintId) + return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseCompleted: return nil case domain.StatusPhaseFailed: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 55c50e5b..d7ffd7eb 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -4,6 +4,8 @@ import ( "context" "errors" cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/stretchr/testify/mock" + "sigs.k8s.io/controller-runtime/pkg/log" "testing" "github.com/stretchr/testify/assert" @@ -20,6 +22,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { t.Run("do all steps with blueprint", func(t *testing.T) { // given + logger := log.FromContext(testCtx). + WithName("BlueprintSpecChangeUseCase.HandleUntilApplied"). + WithValues("blueprintId", blueprintId) + log.SetLogger(logger) + ctxWithLogger := log.IntoContext(testCtx, logger) + repoMock := newMockBlueprintSpecRepository(t) validationMock := newMockBlueprintSpecValidationUseCase(t) effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) @@ -34,56 +42,56 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Id: testBlueprintId, Status: domain.StatusPhaseNew, } - repoMock.EXPECT().GetById(testCtx, testBlueprintId).Return(blueprintSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseStaticallyValidated + repoMock.EXPECT().GetById(mock.Anything, testBlueprintId).Return(blueprintSpec, nil) + validationMock.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseStaticallyValidated }) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated + effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseEffectiveBlueprintGenerated }) - validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseValidated + validationMock.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseValidated }) - stateDiffMock.EXPECT().DetermineStateDiff(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseStateDiffDetermined + stateDiffMock.EXPECT().DetermineStateDiff(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseStateDiffDetermined }) - applyMock.EXPECT().CheckEcosystemHealthUpfront(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseEcosystemHealthyUpfront + applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseEcosystemHealthyUpfront }) - applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseBlueprintApplicationPreProcessed + applyMock.EXPECT().PreProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseBlueprintApplicationPreProcessed }) - ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(testCtx, testBlueprintId).Return(nil).Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseEcosystemConfigApplied + ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseEcosystemConfigApplied }) - selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseSelfUpgradeCompleted + selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseSelfUpgradeCompleted }) - applyMock.EXPECT().ApplyBlueprintSpec(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseBlueprintApplied + applyMock.EXPECT().ApplyBlueprintSpec(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseBlueprintApplied }) - applyMock.EXPECT().CheckEcosystemHealthAfterwards(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseEcosystemHealthyAfterwards + applyMock.EXPECT().CheckEcosystemHealthAfterwards(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseEcosystemHealthyAfterwards }) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseCompleted + applyMock.EXPECT().PostProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseCompleted }) - doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseRestartsTriggered + doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(mock.Anything, blueprintSpec).Return(nil). + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseRestartsTriggered }) // when - err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + err := useCase.HandleUntilApplied(ctxWithLogger, testBlueprintId) // then require.NoError(t, err) assert.Equal(t, domain.StatusPhaseCompleted, blueprintSpec.Status) @@ -163,7 +171,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseStaticallyValidated }) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(assert.AnError) @@ -195,11 +203,11 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseStaticallyValidated }) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(assert.AnError) @@ -231,15 +239,15 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseStaticallyValidated }) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseValidated }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(assert.AnError) @@ -270,19 +278,19 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseStaticallyValidated }) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseValidated }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprintId string) { + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseStateDiffDetermined }) applyMock.EXPECT().CheckEcosystemHealthUpfront(testCtx, "testBlueprint1").Return(assert.AnError) @@ -452,8 +460,8 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) applyMock.EXPECT().CheckEcosystemHealthAfterwards(testCtx, testBlueprintId).Return(assert.AnError) doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseRestartsTriggered + Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseRestartsTriggered }) // when @@ -479,8 +487,8 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseEcosystemHealthyAfterwards, } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseCompleted + applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseCompleted }) // when err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") @@ -505,8 +513,8 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: domain.StatusPhaseEcosystemUnhealthyAfterwards, } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprintId string) { - blueprintSpec.Status = domain.StatusPhaseCompleted + applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + blueprint.Status = domain.StatusPhaseCompleted }) // when err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") @@ -608,21 +616,21 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { func TestBlueprintSpecChangeUseCase_preProcessBlueprintApplication(t *testing.T) { t.Run("stop on dry run", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: blueprintId, Config: domain.BlueprintConfiguration{DryRun: true}, } applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprintId).Return(nil) + applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprint).Return(nil) useCase := NewBlueprintSpecChangeUseCase(nil, nil, nil, nil, applyMock, nil, nil, nil) // when - err := useCase.preProcessBlueprintApplication(testCtx, spec) + err := useCase.preProcessBlueprintApplication(testCtx, blueprint) // then require.NoError(t, err) }) t.Run("error", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: blueprintId, } repoMock := newMockBlueprintSpecRepository(t) @@ -630,39 +638,13 @@ func TestBlueprintSpecChangeUseCase_preProcessBlueprintApplication(t *testing.T) effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprintId).Return(assert.AnError) + applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprint).Return(assert.AnError) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) // when - err := useCase.preProcessBlueprintApplication(testCtx, spec) - // then - require.ErrorIs(t, err, assert.AnError) - }) -} - -func TestBlueprintSpecChangeUseCase_applyBlueprintSpec(t *testing.T) { - t.Run("error", func(t *testing.T) { - // given - applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().ApplyBlueprintSpec(testCtx, blueprintId).Return(assert.AnError) - useCase := NewBlueprintSpecChangeUseCase(nil, nil, nil, nil, applyMock, nil, nil, nil) - // when - err := useCase.applyBlueprintSpec(testCtx, blueprintId) - // then - require.ErrorIs(t, err, assert.AnError) - }) -} - -func TestBlueprintSpecChangeUseCase_checkEcosystemHealthAfterwards(t *testing.T) { - t.Run("error", func(t *testing.T) { - // given - applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().CheckEcosystemHealthAfterwards(testCtx, blueprintId).Return(assert.AnError) - useCase := NewBlueprintSpecChangeUseCase(nil, nil, nil, nil, applyMock, nil, nil, nil) - // when - err := useCase.checkEcosystemHealthAfterwards(testCtx, blueprintId) + err := useCase.preProcessBlueprintApplication(testCtx, blueprint) // then require.ErrorIs(t, err, assert.AnError) }) @@ -685,8 +667,8 @@ func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { Id: testBlueprintId, Status: domain.StatusPhaseBlueprintApplied, } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(errors.New("testerror")) + repoMock.EXPECT().GetById(mock.Anything, "testBlueprint1").Return(blueprintSpec, nil) + doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(mock.Anything, testBlueprintId).Return(errors.New("testerror")) // when err := useCase.HandleUntilApplied(testCtx, testBlueprintId) diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index ed79ade1..f5ca51da 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -20,16 +20,17 @@ var redmineQualifiedDoguName = cescommons.QualifiedName{ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { //given + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Status: domain.StatusPhaseNew, + } + repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - }, nil) repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ Id: "testBlueprint1", Status: domain.StatusPhaseStaticallyValidated, @@ -37,7 +38,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { }).Return(nil) //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") + err := useCase.ValidateBlueprintSpecStatically(ctx, blueprint) //then require.NoError(t, err) @@ -45,23 +46,24 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing.T) { //given + blueprint := &domain.BlueprintSpec{ + //missing ID + Status: domain.StatusPhaseNew, + } + repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "", - Status: domain.StatusPhaseNew, - }, nil) repoMock.EXPECT().Update(ctx, mock.MatchedBy(func(i interface{}) bool { spec := i.(*domain.BlueprintSpec) return spec.Status == domain.StatusPhaseInvalid })).Return(nil) //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") + err := useCase.ValidateBlueprintSpecStatically(ctx, blueprint) //then require.Error(t, err) @@ -71,83 +73,22 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing } func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_repoError(t *testing.T) { - - t.Run("blueprint spec not found while loading", func(t *testing.T) { - //given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) - MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) - useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.NotFoundError{Message: "test-error"}) - //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") - - //then - require.Error(t, err) - var invalidError *domainservice.NotFoundError - assert.ErrorAs(t, err, &invalidError) - assert.ErrorContains(t, err, "cannot load blueprint spec to validate it: test-error") - }) - - t.Run("cannot parse blueprint in repository", func(t *testing.T) { - //given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) - MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) - useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - invalidError := domain.InvalidBlueprintError{Message: "test-error"} - var events []domain.Event - events = append(events, domain.BlueprintSpecInvalidEvent{ValidationError: &invalidError}) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{Id: "testBlueprint1"}, &invalidError) - repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{Id: "testBlueprint1", Status: domain.StatusPhaseInvalid, Events: events}).Return(nil) - //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") - - //then - require.Error(t, err) - var expectedErrorType *domain.InvalidBlueprintError - assert.ErrorAs(t, err, &expectedErrorType) - assert.ErrorContains(t, err, "blueprint spec syntax is invalid: test-error") - }) - - t.Run("internal error while loading blueprint spec", func(t *testing.T) { - //given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) - MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) - useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.InternalError{Message: "test-error"}) - //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") - - //then - require.Error(t, err) - var invalidError *domainservice.InternalError - assert.ErrorAs(t, err, &invalidError) - assert.ErrorContains(t, err, "cannot load blueprint spec to validate it: test-error") - }) - t.Run("error while saving blueprint spec", func(t *testing.T) { //given + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Status: domain.StatusPhaseNew, + } repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - }, nil) repoMock.EXPECT().Update(ctx, mock.Anything).Return(&domainservice.InternalError{Message: "test-error"}) //when - err := useCase.ValidateBlueprintSpecStatically(ctx, "testBlueprint1") + err := useCase.ValidateBlueprintSpecStatically(ctx, blueprint) //then require.Error(t, err) @@ -160,6 +101,10 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_repoError(t *testi func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) { // given + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Status: domain.StatusPhaseValidated, + } repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) @@ -169,10 +114,6 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) DependencyUseCase.EXPECT().ValidateDependenciesForAllDogus(ctx, mock.Anything).Return(nil) MountsUseCase.EXPECT().ValidateAdditionalMounts(ctx, mock.Anything).Return(nil) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, - }, nil) repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ Id: "testBlueprint1", Blueprint: domain.Blueprint{}, @@ -184,7 +125,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) }).Return(nil) // when - err := useCase.ValidateBlueprintSpecDynamically(ctx, "testBlueprint1") + err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then require.NoError(t, err) @@ -199,7 +140,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) version, _ := core.ParseVersion("1.0.0-1") - blueprintSpec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{{ Name: redmineQualifiedDoguName, @@ -208,15 +149,14 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin }}}, Status: domain.StatusPhaseValidated, } - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(blueprintSpec, nil) invalidDependencyError := errors.New("invalid dependencies") invalidMountsError := errors.New("invalid mounts") DependencyUseCase.EXPECT().ValidateDependenciesForAllDogus(ctx, mock.Anything).Return(invalidDependencyError) MountsUseCase.EXPECT().ValidateAdditionalMounts(ctx, mock.Anything).Return(invalidMountsError) - repoMock.EXPECT().Update(ctx, mock.Anything).Return(nil) + repoMock.EXPECT().Update(ctx, blueprint).Return(nil) // when - err := useCase.ValidateBlueprintSpecDynamically(ctx, "testBlueprint1") + err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then require.Error(t, err) @@ -226,50 +166,9 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin assert.ErrorIs(t, err, invalidMountsError) assert.ErrorContains(t, err, "blueprint spec is invalid") - assert.Equal(t, "testBlueprint1", blueprintSpec.Id) - assert.Equal(t, domain.StatusPhaseInvalid, blueprintSpec.Status) - require.Equal(t, 1, len(blueprintSpec.Events)) - assert.IsType(t, domain.BlueprintSpecInvalidEvent{}, blueprintSpec.Events[0]) - assert.ErrorContains(t, blueprintSpec.Events[0].(domain.BlueprintSpecInvalidEvent).ValidationError, "blueprint spec is invalid: ") -} - -func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_repoError(t *testing.T) { - t.Run("internal error", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) - MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) - useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.InternalError{Message: "test-error"}) - - // when - err := useCase.ValidateBlueprintSpecDynamically(ctx, "testBlueprint1") - - // then - require.Error(t, err) - var invalidError *domainservice.InternalError - assert.ErrorAs(t, err, &invalidError) - assert.ErrorContains(t, err, "cannot load blueprint spec to validate it: test-error") - }) - t.Run("not found error", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - DependencyUseCase := newMockValidateDependenciesDomainUseCase(t) - MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) - useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.NotFoundError{Message: "test-error"}) - - // when - err := useCase.ValidateBlueprintSpecDynamically(ctx, "testBlueprint1") - - // then - require.Error(t, err) - var invalidError *domainservice.NotFoundError - assert.ErrorAs(t, err, &invalidError) - assert.ErrorContains(t, err, "cannot load blueprint spec to validate it: test-error") - }) + assert.Equal(t, "testBlueprint1", blueprint.Id) + assert.Equal(t, domain.StatusPhaseInvalid, blueprint.Status) + require.Equal(t, 1, len(blueprint.Events)) + assert.IsType(t, domain.BlueprintSpecInvalidEvent{}, blueprint.Events[0]) + assert.ErrorContains(t, blueprint.Events[0].(domain.BlueprintSpecInvalidEvent).ValidationError, "blueprint spec is invalid: ") } diff --git a/pkg/application/componentInstallationUseCase.go b/pkg/application/componentInstallationUseCase.go index 87d35095..9da61e81 100644 --- a/pkg/application/componentInstallationUseCase.go +++ b/pkg/application/componentInstallationUseCase.go @@ -100,17 +100,10 @@ func (useCase *ComponentInstallationUseCase) checkComponentHealthStatesRetryable // ApplyComponentStates applies the expected component state from the Blueprint to the ecosystem. // Fail-fast here, so that the possible damage is as small as possible. -func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplyComponentStates"). - WithValues("blueprintId", blueprintId) - log.IntoContext(ctx, logger) +func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplyComponentStates") - blueprintSpec, err := useCase.blueprintSpecRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to apply components: %w", blueprintId, err) - } - - if len(blueprintSpec.StateDiff.ComponentDiffs) == 0 { + if len(blueprint.StateDiff.ComponentDiffs) == 0 { logger.Info("apply no components because blueprint has no component state differences") return nil } @@ -121,7 +114,7 @@ func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Co return fmt.Errorf("cannot load component installations to apply component state: %w", err) } - for _, componentDiff := range blueprintSpec.StateDiff.ComponentDiffs { + for _, componentDiff := range blueprint.StateDiff.ComponentDiffs { err = useCase.applyComponentState(ctx, componentDiff, components[componentDiff.Name]) if err != nil { return fmt.Errorf("an error occurred while applying component state to the ecosystem: %w", err) diff --git a/pkg/application/componentInstallationUseCase_test.go b/pkg/application/componentInstallationUseCase_test.go index e196490a..9b206fd6 100644 --- a/pkg/application/componentInstallationUseCase_test.go +++ b/pkg/application/componentInstallationUseCase_test.go @@ -46,7 +46,7 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) componentRepoMock := newMockComponentInstallationRepository(t) - expectedBlueprintSpec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ ComponentDiffs: []domain.ComponentDiff{ { @@ -67,7 +67,6 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { componentName1: nil, } - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(expectedBlueprintSpec, nil) componentRepoMock.EXPECT().GetAll(testCtx).Return(allComponents, nil) sut := &ComponentInstallationUseCase{ @@ -76,45 +75,19 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { } // when - err := sut.ApplyComponentStates(testCtx, blueprintId) + err := sut.ApplyComponentStates(testCtx, blueprint) // then require.NoError(t, err) }) - t.Run("should return error getting blueprint spec", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - } - - // when - err := sut.ApplyComponentStates(testCtx, blueprintId) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint spec \"blueprint1\" to apply components") - }) - t.Run("should return nil and do nothing with no component diffs", func(t *testing.T) { // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - - expectedBlueprintSpec := &domain.BlueprintSpec{} - - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(expectedBlueprintSpec, nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - } + blueprint := &domain.BlueprintSpec{} + sut := &ComponentInstallationUseCase{} // when - err := sut.ApplyComponentStates(testCtx, blueprintId) + err := sut.ApplyComponentStates(testCtx, blueprint) // then require.NoError(t, err) @@ -125,7 +98,7 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) componentRepoMock := newMockComponentInstallationRepository(t) - expectedBlueprintSpec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ ComponentDiffs: []domain.ComponentDiff{ {}, @@ -133,7 +106,6 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { }, } - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(expectedBlueprintSpec, nil) componentRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) sut := &ComponentInstallationUseCase{ @@ -142,7 +114,7 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { } // when - err := sut.ApplyComponentStates(testCtx, blueprintId) + err := sut.ApplyComponentStates(testCtx, blueprint) // then require.Error(t, err) @@ -155,7 +127,7 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) componentRepoMock := newMockComponentInstallationRepository(t) - expectedBlueprintSpec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ ComponentDiffs: []domain.ComponentDiff{ { @@ -170,7 +142,6 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { componentName1: nil, } - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(expectedBlueprintSpec, nil) componentRepoMock.EXPECT().GetAll(testCtx).Return(allComponents, nil) sut := &ComponentInstallationUseCase{ @@ -179,7 +150,7 @@ func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { } // when - err := sut.ApplyComponentStates(testCtx, blueprintId) + err := sut.ApplyComponentStates(testCtx, blueprint) // then require.Error(t, err) diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index aa0b97bc..c1e7aaf6 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -86,24 +86,17 @@ func (useCase *DoguInstallationUseCase) checkDoguHealthStatesRetryable(ctx conte // ApplyDoguStates applies the expected dogu state from the Blueprint to the ecosystem. // Fail-fast here, so that the possible damage is as small as possible. -func (useCase *DoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.ApplyDoguChanges"). - WithValues("blueprintId", blueprintId) - log.IntoContext(ctx, logger) - - blueprintSpec, err := useCase.blueprintSpecRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to install dogus: %w", blueprintId, err) - } - +func (useCase *DoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.ApplyDoguChanges") + logger.Info("apply dogu states") // DoguDiff contains all installed dogus anyway (but some with action none) so we can load them all at once dogus, err := useCase.doguRepo.GetAll(ctx) if err != nil { return fmt.Errorf("cannot load dogu installations to apply dogu state: %w", err) } - for _, doguDiff := range blueprintSpec.StateDiff.DoguDiffs { - err = useCase.applyDoguState(ctx, doguDiff, dogus[doguDiff.DoguName], blueprintSpec.Config) + for _, doguDiff := range blueprint.StateDiff.DoguDiffs { + err = useCase.applyDoguState(ctx, doguDiff, dogus[doguDiff.DoguName], blueprint.Config) if err != nil { return fmt.Errorf("an error occurred while applying dogu state to the ecosystem: %w", err) } diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 8e8cd135..50009aa2 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -600,34 +600,15 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { } func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { - t.Run("cannot load blueprintSpec", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, assert.AnError) - - doguRepoMock := newMockDoguInstallationRepository(t) - - sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil) - - // when - err := sut.ApplyDoguStates(testCtx, blueprintId) - - // then - require.ErrorIs(t, err, assert.AnError) - }) - t.Run("cannot load doguInstallations", func(t *testing.T) { // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, nil) - doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) - sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) // when - err := sut.ApplyDoguStates(testCtx, blueprintId) + err := sut.ApplyDoguStates(testCtx, &domain.BlueprintSpec{}) // then require.ErrorIs(t, err, assert.AnError) @@ -636,8 +617,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { t.Run("success", func(t *testing.T) { // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguDiffs: []domain.DoguDiff{ { @@ -647,7 +627,8 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { }, }, Config: domain.BlueprintConfiguration{}, - }, nil) + } + blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) @@ -655,7 +636,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil) // when - err := sut.ApplyDoguStates(testCtx, blueprintId) + err := sut.ApplyDoguStates(testCtx, blueprint) // then require.NoError(t, err) @@ -663,8 +644,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { t.Run("action error", func(t *testing.T) { // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - blueprintSpecRepoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguDiffs: []domain.DoguDiff{ { @@ -674,7 +654,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { }, }, Config: domain.BlueprintConfiguration{}, - }, nil) + } doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ @@ -685,10 +665,10 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { }, }, nil) - sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) // when - err := sut.ApplyDoguStates(testCtx, blueprintId) + err := sut.ApplyDoguStates(testCtx, blueprint) // then require.ErrorContains(t, err, fmt.Sprintf(noDowngradesExplanationTextFmt, "dogu", "dogus")) diff --git a/pkg/application/doguRestartUseCase.go b/pkg/application/doguRestartUseCase.go index 0f8a9e4b..8e23513c 100644 --- a/pkg/application/doguRestartUseCase.go +++ b/pkg/application/doguRestartUseCase.go @@ -2,7 +2,6 @@ package application import ( "context" - "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" @@ -19,24 +18,20 @@ func NewDoguRestartUseCase(doguInstallationRepository doguInstallationRepository return &DoguRestartUseCase{doguInstallationRepository: doguInstallationRepository, blueprintSpecRepo: blueprintSpecRepo, restartRepository: restartRepository} } -func (useCase *DoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprintId string) error { +func (useCase *DoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("DoguRestartUseCase.TriggerDoguRestarts") - blueprintSpec, err := useCase.blueprintSpecRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("could not get blueprint spec by id: %q", err) - } logger.Info("searching for Dogus that need a restart...") var dogusThatNeedARestart []cescommons.SimpleName - - if blueprintSpec.StateDiff.GlobalConfigDiffs.HasChanges() { + var err error + if blueprint.StateDiff.GlobalConfigDiffs.HasChanges() { logger.Info("restarting all installed Dogus...") err = useCase.restartAllInstalledDogus(ctx) if err != nil { return domainservice.NewInternalError(err, "could not restart all installed Dogus") } } else { - dogusThatNeedARestart = blueprintSpec.GetDogusThatNeedARestart() + dogusThatNeedARestart = blueprint.GetDogusThatNeedARestart() if len(dogusThatNeedARestart) > 0 { logger.Info("restarting Dogus...") restartError := useCase.restartRepository.RestartAll(ctx, dogusThatNeedARestart) @@ -48,8 +43,8 @@ func (useCase *DoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blue } } - blueprintSpec.Status = domain.StatusPhaseRestartsTriggered - err = useCase.blueprintSpecRepo.Update(ctx, blueprintSpec) + blueprint.Status = domain.StatusPhaseRestartsTriggered + err = useCase.blueprintSpecRepo.Update(ctx, blueprint) if err != nil { return domainservice.NewInternalError(err, "could not update blueprint spec") } diff --git a/pkg/application/doguRestartUseCase_test.go b/pkg/application/doguRestartUseCase_test.go index 4213afe6..5e7e726a 100644 --- a/pkg/application/doguRestartUseCase_test.go +++ b/pkg/application/doguRestartUseCase_test.go @@ -27,7 +27,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, GlobalConfigDiffs: domain.GlobalConfigDiffs{}, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -41,13 +41,12 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) - blueprintSpecRepo.EXPECT().Update(testContext, &testBlueprint).Return(nil) + blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.NoError(t, err) @@ -68,7 +67,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { NeededAction: domain.ConfigActionSet, }}, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -94,15 +93,14 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) installationRepository.EXPECT().GetAll(testContext).Return(installedDogus, nil) restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) - blueprintSpecRepo.EXPECT().Update(testContext, &testBlueprint).Return(nil) + blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.NoError(t, err) @@ -123,7 +121,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { NeededAction: domain.ConfigActionSet, }}, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -137,13 +135,12 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) installationRepository.EXPECT().GetAll(testContext).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, assert.AnError) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.Error(t, err) @@ -165,7 +162,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { NeededAction: domain.ConfigActionSet, }}, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -191,13 +188,12 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) installationRepository.EXPECT().GetAll(testContext).Return(installedDogus, nil) restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(assert.AnError) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.Error(t, err) @@ -226,7 +222,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, TargetState: 0, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -241,13 +237,12 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - blueprintSpecRepo.EXPECT().Update(testContext, &testBlueprint).Return(nil) + blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.NoError(t, err) @@ -274,7 +269,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, TargetState: 0, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -287,14 +282,12 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { } dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(assert.AnError) - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) + restartUseCase := NewDoguRestartUseCase(installationRepository, nil, restartRepository) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.Error(t, err) @@ -322,7 +315,7 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, TargetState: 0, } - testBlueprint := domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ Id: testBlueprintId, Blueprint: domain.Blueprint{}, BlueprintMask: domain.BlueprintMask{}, @@ -337,62 +330,15 @@ func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { installationRepository := newMockDoguInstallationRepository(t) blueprintSpecRepo := newMockBlueprintSpecRepository(t) restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, nil) restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - blueprintSpecRepo.EXPECT().Update(testContext, &testBlueprint).Return(assert.AnError) + blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(assert.AnError) // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) + err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) // then require.Error(t, err) assert.ErrorContains(t, err, "could not update blueprint spec") }) - - t.Run("fail on error when getting blueprint", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ - testDoguSimpleName: {{ - Key: common.DoguConfigKey{DoguName: testDoguSimpleName, Key: "testKey"}, - Actual: domain.DoguConfigValueState{Value: "changed", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet}}, - }, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - } - testDogu := domain.Dogu{ - Name: cescommons.QualifiedName{SimpleName: testDoguSimpleName, Namespace: "testing"}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - TargetState: 0, - } - testBlueprint := domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{testDogu}}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().GetById(testContext, testBlueprintId).Return(&testBlueprint, assert.AnError) - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, testBlueprintId) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "could not get blueprint spec by id") - }) } diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index ceaf0e2b..73998352 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -35,57 +35,51 @@ func NewEcosystemConfigUseCase( } // ApplyConfig fetches the dogu and global config stateDiff of the blueprint and applies these keys to the repositories. -func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprintId string) error { - logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig"). - WithValues("blueprintId", blueprintId) +func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig") - blueprintSpec, err := useCase.blueprintRepository.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint to apply config: %w", err) - } - - doguConfigDiffs := blueprintSpec.StateDiff.DoguConfigDiffs + doguConfigDiffs := blueprint.StateDiff.DoguConfigDiffs isEmptyDoguDiff := len(doguConfigDiffs) == 0 if isEmptyDoguDiff { logger.Info("dogu config diffs are empty...") } - sensitiveDoguConfigDiffs := blueprintSpec.StateDiff.SensitiveDoguConfigDiffs + sensitiveDoguConfigDiffs := blueprint.StateDiff.SensitiveDoguConfigDiffs isEmptySensitiveDiff := len(sensitiveDoguConfigDiffs) == 0 if isEmptySensitiveDiff { logger.Info("sensitive dogu config diffs are empty...") } - globalConfigDiffs := blueprintSpec.StateDiff.GlobalConfigDiffs + globalConfigDiffs := blueprint.StateDiff.GlobalConfigDiffs isEmptyGlobalDiff := len(globalConfigDiffs) == 0 if isEmptyGlobalDiff { logger.Info("global config diffs are empty...") } if isEmptyDoguDiff && isEmptyGlobalDiff && isEmptySensitiveDiff { - return useCase.markConfigApplied(ctx, blueprintSpec) + return useCase.markConfigApplied(ctx, blueprint) } - err = useCase.markApplyConfigStart(ctx, blueprintSpec) + err := useCase.markApplyConfigStart(ctx, blueprint) if err != nil { - return useCase.handleFailedApplyEcosystemConfig(ctx, blueprintSpec, err) + return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, err) } // do not apply further configs if error happens, we don't want to corrupt the system more than needed. - err = applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprintSpec.StateDiff.DoguConfigDiffs) + err = applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) if err != nil { - return useCase.handleFailedApplyEcosystemConfig(ctx, blueprintSpec, fmt.Errorf("could not apply normal dogu config: %w", err)) + return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply normal dogu config: %w", err)) } - err = applyDoguConfigDiffs(ctx, useCase.sensitiveDoguConfigRepository, blueprintSpec.StateDiff.SensitiveDoguConfigDiffs) + err = applyDoguConfigDiffs(ctx, useCase.sensitiveDoguConfigRepository, blueprint.StateDiff.SensitiveDoguConfigDiffs) if err != nil { - return useCase.handleFailedApplyEcosystemConfig(ctx, blueprintSpec, fmt.Errorf("could not apply sensitive dogu config: %w", err)) + return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply sensitive dogu config: %w", err)) } err = useCase.applyGlobalConfigDiffs(ctx, globalConfigDiffs.GetGlobalConfigDiffsByAction()) if err != nil { - return useCase.handleFailedApplyEcosystemConfig(ctx, blueprintSpec, fmt.Errorf("could not apply global config: %w", err)) + return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply global config: %w", err)) } - return useCase.markConfigApplied(ctx, blueprintSpec) + return useCase.markConfigApplied(ctx, blueprint) } func (useCase *EcosystemConfigUseCase) applyGlobalConfigDiffs(ctx context.Context, globalConfigDiffsByAction map[domain.ConfigAction][]domain.GlobalConfigEntryDiff) error { @@ -165,27 +159,27 @@ func saveDoguConfigs( return nil } -func (useCase *EcosystemConfigUseCase) markApplyConfigStart(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - blueprintSpec.StartApplyEcosystemConfig() - err := useCase.blueprintRepository.Update(ctx, blueprintSpec) +func (useCase *EcosystemConfigUseCase) markApplyConfigStart(ctx context.Context, blueprint *domain.BlueprintSpec) error { + blueprint.StartApplyEcosystemConfig() + err := useCase.blueprintRepository.Update(ctx, blueprint) if err != nil { return fmt.Errorf("cannot mark blueprint as applying config: %w", err) } return nil } -func (useCase *EcosystemConfigUseCase) handleFailedApplyEcosystemConfig(ctx context.Context, blueprintSpec *domain.BlueprintSpec, err error) error { +func (useCase *EcosystemConfigUseCase) handleFailedApplyEcosystemConfig(ctx context.Context, blueprint *domain.BlueprintSpec, err error) error { logger := log.FromContext(ctx). WithName("EcosystemConfigUseCase.handleFailedApplyEcosystemConfig"). - WithValues("blueprintId", blueprintSpec.Id) + WithValues("blueprintId", blueprint.Id) - blueprintSpec.MarkApplyEcosystemConfigFailed(err) - repoErr := useCase.blueprintRepository.Update(ctx, blueprintSpec) + blueprint.MarkApplyEcosystemConfigFailed(err) + repoErr := useCase.blueprintRepository.Update(ctx, blueprint) if repoErr != nil { repoErr = errors.Join(repoErr, err) logger.Error(repoErr, "cannot mark blueprint config apply as failed") - return fmt.Errorf("cannot mark blueprint config apply as failed while handling %q status: %w", blueprintSpec.Status, repoErr) + return fmt.Errorf("cannot mark blueprint config apply as failed while handling %q status: %w", blueprint.Status, repoErr) } return nil } diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index a2bc27a3..6d00e086 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -31,7 +31,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { sensitiveRedmineDiff := getSensitiveDoguConfigEntryDiffForAction("key", "value", redmine, domain.ConfigActionSet) sensitiveCasDiff := getSensitiveDoguConfigEntryDiffForAction("key", "value", cas, domain.ConfigActionSet) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguDiffs: []domain.DoguDiff{ { @@ -92,40 +92,22 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) globalConfigRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, nil) - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigRepoMock) // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) }) - t.Run("should return error on fetch blueprint error", func(t *testing.T) { - // given - blueprintRepoMock := newMockBlueprintSpecRepository(t) - - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(nil, assert.AnError) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.ApplyConfig(testCtx, testBlueprintID) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "cannot load blueprint to apply config") - assert.ErrorIs(t, err, assert.AnError) - }) - t.Run("mark applied if diffs are empty", func(t *testing.T) { // given blueprintRepoMock := newMockBlueprintSpecRepository(t) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -133,24 +115,23 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, } - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(1) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseEcosystemConfigApplied, spec.Status) + assert.Equal(t, domain.StatusPhaseEcosystemConfigApplied, blueprint.Status) }) t.Run("should return on mark apply config start error", func(t *testing.T) { // given blueprintRepoMock := newMockBlueprintSpecRepository(t) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -160,18 +141,17 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, } - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(assert.AnError).Times(1) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(1) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, spec.Status, domain.StatusPhaseApplyEcosystemConfigFailed) + assert.Equal(t, blueprint.Status, domain.StatusPhaseApplyEcosystemConfigFailed) }) t.Run("error applying dogu config", func(t *testing.T) { @@ -181,7 +161,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) globalConfigMock := newMockGlobalConfigRepository(t) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -202,24 +182,22 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { cas: config.CreateDoguConfig(cas, map[config.Key]config.Value{}), }, nil) doguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) - - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, spec.Status) - require.Len(t, spec.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, spec.Events[0]) - assert.Contains(t, spec.Events[1].Message(), "could not apply normal dogu config") + assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Len(t, blueprint.Events, 2) + assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) + assert.Contains(t, blueprint.Events[1].Message(), "could not apply normal dogu config") // cannot check for dogu name here as the order of the events is not fixed. It could be either redmine or cas - assert.Contains(t, spec.Events[1].Message(), "could not persist config for dogu") - assert.Contains(t, spec.Events[1].Message(), "assert.AnError general error for testing") + assert.Contains(t, blueprint.Events[1].Message(), "could not persist config for dogu") + assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") }) t.Run("error applying sensitive config", func(t *testing.T) { // given @@ -228,7 +206,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) globalConfigMock := newMockGlobalConfigRepository(t) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -252,24 +230,22 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { cas: config.CreateDoguConfig(cas, map[config.Key]config.Value{}), }, nil) sensitiveDoguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) - - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, spec.Status) - require.Len(t, spec.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, spec.Events[0]) - assert.Contains(t, spec.Events[1].Message(), "could not apply sensitive dogu config") + assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Len(t, blueprint.Events, 2) + assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) + assert.Contains(t, blueprint.Events[1].Message(), "could not apply sensitive dogu config") // cannot check for dogu name here as the order of the events is not fixed. It could be either redmine or cas - assert.Contains(t, spec.Events[1].Message(), "could not persist config for dogu") - assert.Contains(t, spec.Events[1].Message(), "assert.AnError general error for testing") + assert.Contains(t, blueprint.Events[1].Message(), "could not persist config for dogu") + assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") }) t.Run("error applying global config", func(t *testing.T) { // given @@ -278,7 +254,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) globalConfigMock := newMockGlobalConfigRepository(t) - spec := &domain.BlueprintSpec{ + blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -302,21 +278,20 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock.EXPECT().Get(testCtx).Return(globalConfig, nil) globalConfigMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, assert.AnError) - blueprintRepoMock.EXPECT().GetById(testCtx, testBlueprintID).Return(spec, nil) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) // when - err := sut.ApplyConfig(testCtx, testBlueprintID) + err := sut.ApplyConfig(testCtx, blueprint) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, spec.Status) - require.Len(t, spec.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, spec.Events[0]) - assert.Contains(t, spec.Events[1].Message(), "could not apply global config") - assert.Contains(t, spec.Events[1].Message(), "assert.AnError general error for testing") + assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Len(t, blueprint.Events, 2) + assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) + assert.Contains(t, blueprint.Events[1].Message(), "could not apply global config") + assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") }) } diff --git a/pkg/application/effectiveBlueprintUseCase_test.go b/pkg/application/effectiveBlueprintUseCase_test.go index 34e51547..ee042d24 100644 --- a/pkg/application/effectiveBlueprintUseCase_test.go +++ b/pkg/application/effectiveBlueprintUseCase_test.go @@ -13,14 +13,15 @@ import ( func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_ok(t *testing.T) { // given + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Status: domain.StatusPhaseValidated, + } + repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() useCase := NewEffectiveBlueprintUseCase(repoMock) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, - }, nil) repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ Id: "testBlueprint1", Blueprint: domain.Blueprint{}, @@ -32,60 +33,24 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_ok(t *testing.T) { }).Return(nil) // when - err := useCase.CalculateEffectiveBlueprint(ctx, "testBlueprint1") + err := useCase.CalculateEffectiveBlueprint(ctx, blueprint) // then require.NoError(t, err) } func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T) { - t.Run("blueprint spec not found", func(t *testing.T) { - //given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - useCase := NewEffectiveBlueprintUseCase(repoMock) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.NotFoundError{Message: "test-error"}) - - //when - err := useCase.CalculateEffectiveBlueprint(ctx, "testBlueprint1") - - //then - require.Error(t, err) - var errorToCheck *domainservice.NotFoundError - assert.ErrorAs(t, err, &errorToCheck) - assert.ErrorContains(t, err, "cannot load blueprint spec to calculate effective blueprint: test-error") - }) - - t.Run("internal error while loading", func(t *testing.T) { - //given - repoMock := newMockBlueprintSpecRepository(t) - ctx := context.Background() - useCase := NewEffectiveBlueprintUseCase(repoMock) - - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(nil, &domainservice.InternalError{Message: "test-error"}) - - //when - err := useCase.CalculateEffectiveBlueprint(ctx, "testBlueprint1") - - //then - require.Error(t, err) - var errorToCheck *domainservice.InternalError - assert.ErrorAs(t, err, &errorToCheck) - assert.ErrorContains(t, err, "cannot load blueprint spec to calculate effective blueprint: test-error") - }) - t.Run("cannot save", func(t *testing.T) { //given + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Status: domain.StatusPhaseValidated, + } + repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() useCase := NewEffectiveBlueprintUseCase(repoMock) - repoMock.EXPECT().GetById(ctx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, - }, nil) - repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ Id: "testBlueprint1", Blueprint: domain.Blueprint{}, @@ -97,7 +62,7 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T }).Return(&domainservice.InternalError{Message: "test-error"}) //when - err := useCase.CalculateEffectiveBlueprint(ctx, "testBlueprint1") + err := useCase.CalculateEffectiveBlueprint(ctx, blueprint) //then require.Error(t, err) diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 70cccf62..592b2b49 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -23,7 +23,7 @@ type stateDiffUseCase interface { type doguInstallationUseCase interface { CheckDoguHealth(ctx context.Context) (ecosystem.DoguHealthResult, error) WaitForHealthyDogus(ctx context.Context) (ecosystem.DoguHealthResult, error) - ApplyDoguStates(ctx context.Context, blueprintId string) error + ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error } type doguRestartUseCase interface { diff --git a/pkg/application/mock_applyBlueprintSpecUseCase_test.go b/pkg/application/mock_applyBlueprintSpecUseCase_test.go index 70d780c8..3b5c7838 100644 --- a/pkg/application/mock_applyBlueprintSpecUseCase_test.go +++ b/pkg/application/mock_applyBlueprintSpecUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockApplyBlueprintSpecUseCase) EXPECT() *mockApplyBlueprintSpecUseCase return &mockApplyBlueprintSpecUseCase_Expecter{mock: &_m.Mock} } -// ApplyBlueprintSpec provides a mock function with given fields: ctx, blueprintId -func (_m *mockApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ApplyBlueprintSpec provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyBlueprintSpec") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call struct { // ApplyBlueprintSpec is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockApplyBlueprintSpecUseCase_Expecter) ApplyBlueprintSpec(ctx interface{}, blueprintId interface{}) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { - return &mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call{Call: _e.mock.On("ApplyBlueprintSpec", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyBlueprintSpecUseCase_Expecter) ApplyBlueprintSpec(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { + return &mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call{Call: _e.mock.On("ApplyBlueprintSpec", ctx, blueprint)} } -func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) Run(run func(ctx context.Context, blueprintId string)) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { +func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,22 +64,22 @@ func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) Return(_a0 erro return _c } -func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) RunAndReturn(run func(context.Context, string) error) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { +func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { _c.Call.Return(run) return _c } -// CheckEcosystemHealthAfterwards provides a mock function with given fields: ctx, blueprintId -func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// CheckEcosystemHealthAfterwards provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for CheckEcosystemHealthAfterwards") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -93,14 +94,14 @@ type mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call struct { // CheckEcosystemHealthAfterwards is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthAfterwards(ctx interface{}, blueprintId interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { - return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call{Call: _e.mock.On("CheckEcosystemHealthAfterwards", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthAfterwards(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { + return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call{Call: _e.mock.On("CheckEcosystemHealthAfterwards", ctx, blueprint)} } -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) Run(run func(ctx context.Context, blueprintId string)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { +func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -110,22 +111,22 @@ func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) Ret return _c } -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) RunAndReturn(run func(context.Context, string) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { +func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { _c.Call.Return(run) return _c } -// CheckEcosystemHealthUpfront provides a mock function with given fields: ctx, blueprintId -func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// CheckEcosystemHealthUpfront provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for CheckEcosystemHealthUpfront") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -140,14 +141,14 @@ type mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call struct { // CheckEcosystemHealthUpfront is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthUpfront(ctx interface{}, blueprintId interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { - return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call{Call: _e.mock.On("CheckEcosystemHealthUpfront", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthUpfront(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { + return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call{Call: _e.mock.On("CheckEcosystemHealthUpfront", ctx, blueprint)} } -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) Run(run func(ctx context.Context, blueprintId string)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { +func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -157,22 +158,22 @@ func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) Return return _c } -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) RunAndReturn(run func(context.Context, string) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { +func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { _c.Call.Return(run) return _c } -// PostProcessBlueprintApplication provides a mock function with given fields: ctx, blueprintId -func (_m *mockApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// PostProcessBlueprintApplication provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for PostProcessBlueprintApplication") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -187,14 +188,14 @@ type mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call struct { // PostProcessBlueprintApplication is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockApplyBlueprintSpecUseCase_Expecter) PostProcessBlueprintApplication(ctx interface{}, blueprintId interface{}) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { - return &mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call{Call: _e.mock.On("PostProcessBlueprintApplication", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyBlueprintSpecUseCase_Expecter) PostProcessBlueprintApplication(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { + return &mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call{Call: _e.mock.On("PostProcessBlueprintApplication", ctx, blueprint)} } -func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprintId string)) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { +func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -204,22 +205,22 @@ func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Re return _c } -func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, string) error) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { +func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { _c.Call.Return(run) return _c } -// PreProcessBlueprintApplication provides a mock function with given fields: ctx, blueprintId -func (_m *mockApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// PreProcessBlueprintApplication provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for PreProcessBlueprintApplication") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -234,14 +235,14 @@ type mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call struct { // PreProcessBlueprintApplication is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockApplyBlueprintSpecUseCase_Expecter) PreProcessBlueprintApplication(ctx interface{}, blueprintId interface{}) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { - return &mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call{Call: _e.mock.On("PreProcessBlueprintApplication", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyBlueprintSpecUseCase_Expecter) PreProcessBlueprintApplication(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { + return &mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call{Call: _e.mock.On("PreProcessBlueprintApplication", ctx, blueprint)} } -func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprintId string)) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { +func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -251,7 +252,7 @@ func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) Ret return _c } -func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, string) error) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { +func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_blueprintSpecValidationUseCase_test.go b/pkg/application/mock_blueprintSpecValidationUseCase_test.go index 9f317f9d..e8f12294 100644 --- a/pkg/application/mock_blueprintSpecValidationUseCase_test.go +++ b/pkg/application/mock_blueprintSpecValidationUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockBlueprintSpecValidationUseCase) EXPECT() *mockBlueprintSpecValidat return &mockBlueprintSpecValidationUseCase_Expecter{mock: &_m.Mock} } -// ValidateBlueprintSpecDynamically provides a mock function with given fields: ctx, blueprintId -func (_m *mockBlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ValidateBlueprintSpecDynamically provides a mock function with given fields: ctx, blueprint +func (_m *mockBlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ValidateBlueprintSpecDynamically") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call st // ValidateBlueprintSpecDynamically is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockBlueprintSpecValidationUseCase_Expecter) ValidateBlueprintSpecDynamically(ctx interface{}, blueprintId interface{}) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { - return &mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call{Call: _e.mock.On("ValidateBlueprintSpecDynamically", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockBlueprintSpecValidationUseCase_Expecter) ValidateBlueprintSpecDynamically(ctx interface{}, blueprint interface{}) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { + return &mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call{Call: _e.mock.On("ValidateBlueprintSpecDynamically", ctx, blueprint)} } -func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call) Run(run func(ctx context.Context, blueprintId string)) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { +func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,22 +64,22 @@ func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Ca return _c } -func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call) RunAndReturn(run func(context.Context, string) error) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { +func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecDynamically_Call { _c.Call.Return(run) return _c } -// ValidateBlueprintSpecStatically provides a mock function with given fields: ctx, blueprintId -func (_m *mockBlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ValidateBlueprintSpecStatically provides a mock function with given fields: ctx, blueprint +func (_m *mockBlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ValidateBlueprintSpecStatically") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -93,14 +94,14 @@ type mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call str // ValidateBlueprintSpecStatically is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockBlueprintSpecValidationUseCase_Expecter) ValidateBlueprintSpecStatically(ctx interface{}, blueprintId interface{}) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { - return &mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call{Call: _e.mock.On("ValidateBlueprintSpecStatically", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockBlueprintSpecValidationUseCase_Expecter) ValidateBlueprintSpecStatically(ctx interface{}, blueprint interface{}) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { + return &mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call{Call: _e.mock.On("ValidateBlueprintSpecStatically", ctx, blueprint)} } -func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call) Run(run func(ctx context.Context, blueprintId string)) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { +func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -110,7 +111,7 @@ func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Cal return _c } -func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call) RunAndReturn(run func(context.Context, string) error) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { +func (_c *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockBlueprintSpecValidationUseCase_ValidateBlueprintSpecStatically_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_componentInstallationUseCase_test.go b/pkg/application/mock_componentInstallationUseCase_test.go index 36452ead..41cd7c9d 100644 --- a/pkg/application/mock_componentInstallationUseCase_test.go +++ b/pkg/application/mock_componentInstallationUseCase_test.go @@ -24,17 +24,17 @@ func (_m *mockComponentInstallationUseCase) EXPECT() *mockComponentInstallationU return &mockComponentInstallationUseCase_Expecter{mock: &_m.Mock} } -// ApplyComponentStates provides a mock function with given fields: ctx, blueprintId -func (_m *mockComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ApplyComponentStates provides a mock function with given fields: ctx, blueprint +func (_m *mockComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyComponentStates") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -49,14 +49,14 @@ type mockComponentInstallationUseCase_ApplyComponentStates_Call struct { // ApplyComponentStates is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockComponentInstallationUseCase_Expecter) ApplyComponentStates(ctx interface{}, blueprintId interface{}) *mockComponentInstallationUseCase_ApplyComponentStates_Call { - return &mockComponentInstallationUseCase_ApplyComponentStates_Call{Call: _e.mock.On("ApplyComponentStates", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockComponentInstallationUseCase_Expecter) ApplyComponentStates(ctx interface{}, blueprint interface{}) *mockComponentInstallationUseCase_ApplyComponentStates_Call { + return &mockComponentInstallationUseCase_ApplyComponentStates_Call{Call: _e.mock.On("ApplyComponentStates", ctx, blueprint)} } -func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) Run(run func(ctx context.Context, blueprintId string)) *mockComponentInstallationUseCase_ApplyComponentStates_Call { +func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockComponentInstallationUseCase_ApplyComponentStates_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -66,7 +66,7 @@ func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) Return(_a0 return _c } -func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) RunAndReturn(run func(context.Context, string) error) *mockComponentInstallationUseCase_ApplyComponentStates_Call { +func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockComponentInstallationUseCase_ApplyComponentStates_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_doguInstallationUseCase_test.go b/pkg/application/mock_doguInstallationUseCase_test.go index 2ef1213e..34583e49 100644 --- a/pkg/application/mock_doguInstallationUseCase_test.go +++ b/pkg/application/mock_doguInstallationUseCase_test.go @@ -5,7 +5,9 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + mock "github.com/stretchr/testify/mock" ) @@ -22,17 +24,17 @@ func (_m *mockDoguInstallationUseCase) EXPECT() *mockDoguInstallationUseCase_Exp return &mockDoguInstallationUseCase_Expecter{mock: &_m.Mock} } -// ApplyDoguStates provides a mock function with given fields: ctx, blueprintId -func (_m *mockDoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ApplyDoguStates provides a mock function with given fields: ctx, blueprint +func (_m *mockDoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyDoguStates") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -47,14 +49,14 @@ type mockDoguInstallationUseCase_ApplyDoguStates_Call struct { // ApplyDoguStates is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockDoguInstallationUseCase_Expecter) ApplyDoguStates(ctx interface{}, blueprintId interface{}) *mockDoguInstallationUseCase_ApplyDoguStates_Call { - return &mockDoguInstallationUseCase_ApplyDoguStates_Call{Call: _e.mock.On("ApplyDoguStates", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockDoguInstallationUseCase_Expecter) ApplyDoguStates(ctx interface{}, blueprint interface{}) *mockDoguInstallationUseCase_ApplyDoguStates_Call { + return &mockDoguInstallationUseCase_ApplyDoguStates_Call{Call: _e.mock.On("ApplyDoguStates", ctx, blueprint)} } -func (_c *mockDoguInstallationUseCase_ApplyDoguStates_Call) Run(run func(ctx context.Context, blueprintId string)) *mockDoguInstallationUseCase_ApplyDoguStates_Call { +func (_c *mockDoguInstallationUseCase_ApplyDoguStates_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockDoguInstallationUseCase_ApplyDoguStates_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -64,7 +66,7 @@ func (_c *mockDoguInstallationUseCase_ApplyDoguStates_Call) Return(_a0 error) *m return _c } -func (_c *mockDoguInstallationUseCase_ApplyDoguStates_Call) RunAndReturn(run func(context.Context, string) error) *mockDoguInstallationUseCase_ApplyDoguStates_Call { +func (_c *mockDoguInstallationUseCase_ApplyDoguStates_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockDoguInstallationUseCase_ApplyDoguStates_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_doguRestartUseCase_test.go b/pkg/application/mock_doguRestartUseCase_test.go index acdc6cd0..7a9a7393 100644 --- a/pkg/application/mock_doguRestartUseCase_test.go +++ b/pkg/application/mock_doguRestartUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockDoguRestartUseCase) EXPECT() *mockDoguRestartUseCase_Expecter { return &mockDoguRestartUseCase_Expecter{mock: &_m.Mock} } -// TriggerDoguRestarts provides a mock function with given fields: ctx, blueprintid -func (_m *mockDoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprintid string) error { - ret := _m.Called(ctx, blueprintid) +// TriggerDoguRestarts provides a mock function with given fields: ctx, blueprint +func (_m *mockDoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for TriggerDoguRestarts") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintid) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockDoguRestartUseCase_TriggerDoguRestarts_Call struct { // TriggerDoguRestarts is a helper method to define mock.On call // - ctx context.Context -// - blueprintid string -func (_e *mockDoguRestartUseCase_Expecter) TriggerDoguRestarts(ctx interface{}, blueprintid interface{}) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { - return &mockDoguRestartUseCase_TriggerDoguRestarts_Call{Call: _e.mock.On("TriggerDoguRestarts", ctx, blueprintid)} +// - blueprint *domain.BlueprintSpec +func (_e *mockDoguRestartUseCase_Expecter) TriggerDoguRestarts(ctx interface{}, blueprint interface{}) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { + return &mockDoguRestartUseCase_TriggerDoguRestarts_Call{Call: _e.mock.On("TriggerDoguRestarts", ctx, blueprint)} } -func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) Run(run func(ctx context.Context, blueprintid string)) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { +func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,7 +64,7 @@ func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) Return(_a0 error) *mo return _c } -func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) RunAndReturn(run func(context.Context, string) error) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { +func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_ecosystemConfigUseCase_test.go b/pkg/application/mock_ecosystemConfigUseCase_test.go index b36d1cb0..93e678ce 100644 --- a/pkg/application/mock_ecosystemConfigUseCase_test.go +++ b/pkg/application/mock_ecosystemConfigUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockEcosystemConfigUseCase) EXPECT() *mockEcosystemConfigUseCase_Expec return &mockEcosystemConfigUseCase_Expecter{mock: &_m.Mock} } -// ApplyConfig provides a mock function with given fields: ctx, blueprintId -func (_m *mockEcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// ApplyConfig provides a mock function with given fields: ctx, blueprint +func (_m *mockEcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyConfig") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockEcosystemConfigUseCase_ApplyConfig_Call struct { // ApplyConfig is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockEcosystemConfigUseCase_Expecter) ApplyConfig(ctx interface{}, blueprintId interface{}) *mockEcosystemConfigUseCase_ApplyConfig_Call { - return &mockEcosystemConfigUseCase_ApplyConfig_Call{Call: _e.mock.On("ApplyConfig", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockEcosystemConfigUseCase_Expecter) ApplyConfig(ctx interface{}, blueprint interface{}) *mockEcosystemConfigUseCase_ApplyConfig_Call { + return &mockEcosystemConfigUseCase_ApplyConfig_Call{Call: _e.mock.On("ApplyConfig", ctx, blueprint)} } -func (_c *mockEcosystemConfigUseCase_ApplyConfig_Call) Run(run func(ctx context.Context, blueprintId string)) *mockEcosystemConfigUseCase_ApplyConfig_Call { +func (_c *mockEcosystemConfigUseCase_ApplyConfig_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockEcosystemConfigUseCase_ApplyConfig_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,7 +64,7 @@ func (_c *mockEcosystemConfigUseCase_ApplyConfig_Call) Return(_a0 error) *mockEc return _c } -func (_c *mockEcosystemConfigUseCase_ApplyConfig_Call) RunAndReturn(run func(context.Context, string) error) *mockEcosystemConfigUseCase_ApplyConfig_Call { +func (_c *mockEcosystemConfigUseCase_ApplyConfig_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockEcosystemConfigUseCase_ApplyConfig_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_effectiveBlueprintUseCase_test.go b/pkg/application/mock_effectiveBlueprintUseCase_test.go index 84762fca..73554c4a 100644 --- a/pkg/application/mock_effectiveBlueprintUseCase_test.go +++ b/pkg/application/mock_effectiveBlueprintUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockEffectiveBlueprintUseCase) EXPECT() *mockEffectiveBlueprintUseCase return &mockEffectiveBlueprintUseCase_Expecter{mock: &_m.Mock} } -// CalculateEffectiveBlueprint provides a mock function with given fields: ctx, blueprintId -func (_m *mockEffectiveBlueprintUseCase) CalculateEffectiveBlueprint(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// CalculateEffectiveBlueprint provides a mock function with given fields: ctx, blueprint +func (_m *mockEffectiveBlueprintUseCase) CalculateEffectiveBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for CalculateEffectiveBlueprint") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call struct { // CalculateEffectiveBlueprint is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockEffectiveBlueprintUseCase_Expecter) CalculateEffectiveBlueprint(ctx interface{}, blueprintId interface{}) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { - return &mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call{Call: _e.mock.On("CalculateEffectiveBlueprint", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockEffectiveBlueprintUseCase_Expecter) CalculateEffectiveBlueprint(ctx interface{}, blueprint interface{}) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { + return &mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call{Call: _e.mock.On("CalculateEffectiveBlueprint", ctx, blueprint)} } -func (_c *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call) Run(run func(ctx context.Context, blueprintId string)) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { +func (_c *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,7 +64,7 @@ func (_c *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call) Return return _c } -func (_c *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call) RunAndReturn(run func(context.Context, string) error) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { +func (_c *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockEffectiveBlueprintUseCase_CalculateEffectiveBlueprint_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_selfUpgradeUseCase_test.go b/pkg/application/mock_selfUpgradeUseCase_test.go index 04639aa1..453ca93a 100644 --- a/pkg/application/mock_selfUpgradeUseCase_test.go +++ b/pkg/application/mock_selfUpgradeUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockSelfUpgradeUseCase) EXPECT() *mockSelfUpgradeUseCase_Expecter { return &mockSelfUpgradeUseCase_Expecter{mock: &_m.Mock} } -// HandleSelfUpgrade provides a mock function with given fields: ctx, blueprintId -func (_m *mockSelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// HandleSelfUpgrade provides a mock function with given fields: ctx, blueprint +func (_m *mockSelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for HandleSelfUpgrade") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockSelfUpgradeUseCase_HandleSelfUpgrade_Call struct { // HandleSelfUpgrade is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockSelfUpgradeUseCase_Expecter) HandleSelfUpgrade(ctx interface{}, blueprintId interface{}) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { - return &mockSelfUpgradeUseCase_HandleSelfUpgrade_Call{Call: _e.mock.On("HandleSelfUpgrade", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockSelfUpgradeUseCase_Expecter) HandleSelfUpgrade(ctx interface{}, blueprint interface{}) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { + return &mockSelfUpgradeUseCase_HandleSelfUpgrade_Call{Call: _e.mock.On("HandleSelfUpgrade", ctx, blueprint)} } -func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) Run(run func(ctx context.Context, blueprintId string)) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { +func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,7 +64,7 @@ func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) Return(_a0 error) *mock return _c } -func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) RunAndReturn(run func(context.Context, string) error) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { +func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_stateDiffUseCase_test.go b/pkg/application/mock_stateDiffUseCase_test.go index 3759daf6..2375a0eb 100644 --- a/pkg/application/mock_stateDiffUseCase_test.go +++ b/pkg/application/mock_stateDiffUseCase_test.go @@ -5,6 +5,7 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" mock "github.com/stretchr/testify/mock" ) @@ -21,17 +22,17 @@ func (_m *mockStateDiffUseCase) EXPECT() *mockStateDiffUseCase_Expecter { return &mockStateDiffUseCase_Expecter{mock: &_m.Mock} } -// DetermineStateDiff provides a mock function with given fields: ctx, blueprintId -func (_m *mockStateDiffUseCase) DetermineStateDiff(ctx context.Context, blueprintId string) error { - ret := _m.Called(ctx, blueprintId) +// DetermineStateDiff provides a mock function with given fields: ctx, blueprint +func (_m *mockStateDiffUseCase) DetermineStateDiff(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for DetermineStateDiff") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, blueprintId) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) } else { r0 = ret.Error(0) } @@ -46,14 +47,14 @@ type mockStateDiffUseCase_DetermineStateDiff_Call struct { // DetermineStateDiff is a helper method to define mock.On call // - ctx context.Context -// - blueprintId string -func (_e *mockStateDiffUseCase_Expecter) DetermineStateDiff(ctx interface{}, blueprintId interface{}) *mockStateDiffUseCase_DetermineStateDiff_Call { - return &mockStateDiffUseCase_DetermineStateDiff_Call{Call: _e.mock.On("DetermineStateDiff", ctx, blueprintId)} +// - blueprint *domain.BlueprintSpec +func (_e *mockStateDiffUseCase_Expecter) DetermineStateDiff(ctx interface{}, blueprint interface{}) *mockStateDiffUseCase_DetermineStateDiff_Call { + return &mockStateDiffUseCase_DetermineStateDiff_Call{Call: _e.mock.On("DetermineStateDiff", ctx, blueprint)} } -func (_c *mockStateDiffUseCase_DetermineStateDiff_Call) Run(run func(ctx context.Context, blueprintId string)) *mockStateDiffUseCase_DetermineStateDiff_Call { +func (_c *mockStateDiffUseCase_DetermineStateDiff_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockStateDiffUseCase_DetermineStateDiff_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -63,7 +64,7 @@ func (_c *mockStateDiffUseCase_DetermineStateDiff_Call) Return(_a0 error) *mockS return _c } -func (_c *mockStateDiffUseCase_DetermineStateDiff_Call) RunAndReturn(run func(context.Context, string) error) *mockStateDiffUseCase_DetermineStateDiff_Call { +func (_c *mockStateDiffUseCase_DetermineStateDiff_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockStateDiffUseCase_DetermineStateDiff_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/selfUpgradeUseCase.go b/pkg/application/selfUpgradeUseCase.go index fd637108..54aa9f81 100644 --- a/pkg/application/selfUpgradeUseCase.go +++ b/pkg/application/selfUpgradeUseCase.go @@ -40,22 +40,17 @@ func NewSelfUpgradeUseCase( // HandleSelfUpgrade checks if a self upgrade is necessary, executes all needed steps and // can check if the self upgrade was successful after a restart. // It always sets the fitting status in the blueprint spec. -func (useCase *SelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprintId string) error { +func (useCase *SelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplySelfUpgrade") - blueprintSpec, err := useCase.blueprintRepo.GetById(ctx, blueprintId) - if err != nil { - return fmt.Errorf("cannot load blueprint spec %q to possibly self upgrade the operator: %w", blueprintId, err) - } - - ownDiff := blueprintSpec.StateDiff.ComponentDiffs.GetComponentDiffByName(useCase.blueprintOperatorName) + ownDiff := blueprint.StateDiff.ComponentDiffs.GetComponentDiffByName(useCase.blueprintOperatorName) if !ownDiff.HasChanges() { logger.Info("self upgrade not needed") - blueprintSpec.MarkSelfUpgradeCompleted() - err = useCase.blueprintRepo.Update(ctx, blueprintSpec) + blueprint.MarkSelfUpgradeCompleted() + err := useCase.blueprintRepo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot save blueprint spec %q to skip self upgrade: %w", blueprintSpec.Id, err) + return fmt.Errorf("cannot save blueprint spec %q to skip self upgrade: %w", blueprint.Id, err) } return nil } @@ -75,21 +70,21 @@ func (useCase *SelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, bluepr } if !ownDiff.IsExpectedVersion(expectedVersion) { - return useCase.doSelfUpgrade(ctx, blueprintSpec, ownDiff, ownComponent) + return useCase.doSelfUpgrade(ctx, blueprint, ownDiff, ownComponent) // the operator waits for termination, unless there was an error, so we can return here } if !ownDiff.IsExpectedVersion(actualVersion) { - err = useCase.awaitInstallationConfirmation(ctx, blueprintSpec) + err = useCase.awaitInstallationConfirmation(ctx, blueprint) if err != nil { return err } } - blueprintSpec.MarkSelfUpgradeCompleted() - err = useCase.blueprintRepo.Update(ctx, blueprintSpec) + blueprint.MarkSelfUpgradeCompleted() + err = useCase.blueprintRepo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after self upgrading the operator: %w", blueprintSpec.Id, err) + return fmt.Errorf("cannot save blueprint spec %q after self upgrading the operator: %w", blueprint.Id, err) } logger.Info("self upgrade successful") diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go index de103956..fd8edc1a 100644 --- a/pkg/application/selfUpgradeUseCase_test.go +++ b/pkg/application/selfUpgradeUseCase_test.go @@ -39,28 +39,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { }, } - t.Run("nothing to do", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) - - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{}, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, - } - - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil).Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { - require.Equal(t, domain.StatusPhaseSelfUpgradeCompleted, blueprint.Status) - }) - - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) - - assert.NoError(t, err) - }) - t.Run("apply upgrade until termination", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) @@ -81,7 +59,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // but usually cancel defer cancelCtx() - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) blueprintRepo.EXPECT().Update(mock.Anything, blueprint). Return(nil). Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { @@ -96,7 +73,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { cancelCtx() }) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprintId) + err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) assert.NoError(t, err) }) @@ -118,13 +95,12 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { ActualVersion: version2, } - blueprintRepo.EXPECT().GetById(testCtx, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() // no reload for actual version check blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { require.Equal(t, domain.StatusPhaseSelfUpgradeCompleted, blueprint.Status) }) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.NoError(t, err) }) @@ -152,7 +128,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as defer cancelCtx() - blueprintRepo.EXPECT().GetById(timeoutCtx, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component1, nil).Once() componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component2, nil).Once() configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, nil) @@ -160,7 +135,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { require.Equal(t, domain.StatusPhaseSelfUpgradeCompleted, blueprint.Status) }) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprintId) + err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) assert.NoError(t, err) }) @@ -180,7 +155,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // but usually cancel defer cancelCtx() - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(nil, domainservice.NewNotFoundError(assert.AnError, "test-error")) blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil) var nilComponent *ecosystem.ComponentInstallation @@ -190,31 +164,11 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { }, ) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprintId) + err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) assert.NoError(t, err) }) - t.Run("could not load blueprint", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, - } - - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, assert.AnError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint spec \""+blueprintId+"\" to possibly self upgrade the operator") - }) - t.Run("cannot load component", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) @@ -227,10 +181,9 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(nil, internalTestError) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.ErrorIs(t, err, internalTestError) assert.ErrorContains(t, err, "cannot load component installation for \""+string(blueprintOperatorName)+"\" from ecosystem") @@ -253,11 +206,10 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { ExpectedVersion: version1, } - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(component, nil) blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(internalTestError) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.ErrorIs(t, err, internalTestError) assert.ErrorContains(t, err, "cannot persist blueprint spec \""+blueprintId+"\" to mark it waiting for self upgrade") @@ -279,12 +231,11 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { ExpectedVersion: version1, } - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(component, nil) blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil) componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, component).Return(internalTestError) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.ErrorIs(t, err, internalTestError) assert.ErrorContains(t, err, "an error occurred while applying the self-upgrade to the ecosystem") @@ -303,10 +254,9 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } - blueprintRepo.EXPECT().GetById(mock.Anything, blueprintId).Return(blueprint, nil) blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(assert.AnError) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "cannot save blueprint spec \""+blueprintId+"\" to skip self upgrade") @@ -331,12 +281,11 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as defer cancelCtx() - blueprintRepo.EXPECT().GetById(timeoutCtx, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component, nil).Once() componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(nil, assert.AnError).Once() configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, nil) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprintId) + err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "error while waiting for version confirmation") @@ -362,11 +311,10 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as defer cancelCtx() - blueprintRepo.EXPECT().GetById(timeoutCtx, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component, nil).Once() configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, assert.AnError) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprintId) + err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not retrieve wait interval config for self upgrade") @@ -390,11 +338,10 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { ActualVersion: version2, } - blueprintRepo.EXPECT().GetById(testCtx, blueprintId).Return(blueprint, nil) componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() // no reload for actual version check blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - err := useCase.HandleSelfUpgrade(testCtx, blueprintId) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "cannot save blueprint spec \"myBlueprint\" after self upgrading the operator") diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 53c4cbaf..bd808de8 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -51,31 +51,10 @@ var ( ) func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { - t.Run("should fail to load blueprint spec", func(t *testing.T) { - // given - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(nil, assert.AnError) - - doguInstallRepoMock := newMockDoguInstallationRepository(t) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, nil, nil, nil, nil) - - // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load blueprint spec \"testBlueprint1\" to determine state diff") - }) t.Run("should fail to get installed dogus", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, internalTestError) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -94,10 +73,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -109,9 +88,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -131,10 +107,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -146,9 +122,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -168,10 +141,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -185,9 +158,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -209,10 +179,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -226,9 +196,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -250,10 +217,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -267,9 +234,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) - doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -289,10 +253,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -303,7 +267,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprint := &domain.BlueprintSpec{Id: "testBlueprint1", Status: domain.StatusPhaseValidated} blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) doguInstallRepoMock := newMockDoguInstallationRepository(t) @@ -328,7 +291,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.Error(t, err) @@ -367,7 +330,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) @@ -397,7 +359,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.NoError(t, err) @@ -476,7 +438,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) @@ -504,7 +465,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.NoError(t, err) @@ -551,7 +512,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) @@ -591,7 +551,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.NoError(t, err) @@ -642,7 +602,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprint, nil) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) @@ -687,7 +646,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when - err := sut.DetermineStateDiff(testCtx, "testBlueprint1") + err := sut.DetermineStateDiff(testCtx, blueprint) // then require.NoError(t, err) From 7fe4543109893fd7a80e61057b0e3fecc793dc96 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 10:12:56 +0200 Subject: [PATCH 007/119] #121 fix reconciler tests --- pkg/adapter/reconciler/blueprint_controller_test.go | 4 ++-- pkg/application/blueprintSpecChangeUseCase.go | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index 3b7d2c97..228fc435 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -84,7 +84,7 @@ func TestBlueprintReconciler_Reconcile(t *testing.T) { changeHandlerMock := NewMockBlueprintChangeHandler(t) sut := &BlueprintReconciler{blueprintChangeHandler: changeHandlerMock} - changeHandlerMock.EXPECT().HandleChange(testCtx, testBlueprint).Return(nil) + changeHandlerMock.EXPECT().HandleUntilApplied(testCtx, testBlueprint).Return(nil) // when actual, err := sut.Reconcile(testCtx, request) @@ -98,7 +98,7 @@ func TestBlueprintReconciler_Reconcile(t *testing.T) { changeHandlerMock := NewMockBlueprintChangeHandler(t) sut := &BlueprintReconciler{blueprintChangeHandler: changeHandlerMock} - changeHandlerMock.EXPECT().HandleChange(testCtx, testBlueprint).Return(errors.New("test")) + changeHandlerMock.EXPECT().HandleUntilApplied(testCtx, testBlueprint).Return(errors.New("test")) // when _, err := sut.Reconcile(testCtx, request) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index e3c603f4..3f6df54d 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -44,7 +44,9 @@ func NewBlueprintSpecChangeUseCase( } } -// HandleUntilApplied further executes a blueprint spec given by the blueprintId until it is fully applied or an error occurred. +// HandleUntilApplied further executes a blueprint given by the blueprintId until it is as far applied as possible or an error occurred. +// If the process needs to wait for something, this function will return. +// Another call of this function is necessary to proceed. // Returns a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write or From 4e320762b2e4b43a12e3fa3d3dd47ad5df306dbe Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 11:22:03 +0200 Subject: [PATCH 008/119] #121 remove StatusPhaseStaticallyValidated --- .../blueprintSpecChangeUseCase_test.go | 27 +++---- .../blueprintSpecValidationUseCase_test.go | 1 - pkg/domain/blueprintSpec.go | 61 ++++++++-------- pkg/domain/blueprintSpec_test.go | 73 +------------------ 4 files changed, 41 insertions(+), 121 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index d7ffd7eb..1cd32339 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -5,6 +5,8 @@ import ( "errors" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/log" "testing" @@ -45,7 +47,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(mock.Anything, testBlueprintId).Return(blueprintSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseStaticallyValidated + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeValid, + Status: metav1.ConditionTrue, + }) }) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { @@ -170,10 +175,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseStaticallyValidated - }) + validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(assert.AnError) // when @@ -202,10 +204,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseStaticallyValidated - }) + validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated @@ -238,10 +237,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseStaticallyValidated - }) + validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated @@ -277,10 +273,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseStaticallyValidated - }) + validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index f5ca51da..96d492db 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -33,7 +33,6 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ Id: "testBlueprint1", - Status: domain.StatusPhaseStaticallyValidated, Events: []domain.Event{domain.BlueprintSpecStaticallyValidatedEvent{}}, }).Return(nil) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index b0308d01..501cd64d 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -8,6 +8,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "maps" "slices" @@ -21,7 +22,7 @@ type BlueprintSpec struct { StateDiff StateDiff Config BlueprintConfiguration Status StatusPhase - Conditions []Condition + Conditions *[]Condition // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. // This field should be ignored in the whole domain. @@ -32,14 +33,14 @@ type BlueprintSpec struct { type Condition = metav1.Condition var ( - conditionTypeValid = "Valid" - conditionEcosystemHealthy = "EcosystemHealthy" - conditionSelfUpgradeCompleted = "SelfUpgradeCompleted" - conditionConfigApplied = "ConfigApplied" - conditionDogusApplied = "DogusApplied" - conditionComponentsApplied = "ComponentsApplied" + ConditionTypeValid = "Valid" + ConditionEcosystemHealthy = "EcosystemHealthy" + ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" + ConditionConfigApplied = "ConfigApplied" + ConditionDogusApplied = "DogusApplied" + ConditionComponentsApplied = "ComponentsApplied" // how do we watch restarts? - conditionBlueprintApplied = "BlueprintApplied" + ConditionBlueprintApplied = "BlueprintApplied" ) type StatusPhase string @@ -47,8 +48,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseStaticallyValidated marks the given blueprint spec as validated. - StatusPhaseStaticallyValidated StatusPhase = "staticallyValidated" // StatusPhaseValidated marks the given blueprint spec as validated. StatusPhaseValidated StatusPhase = "validated" // StatusPhaseEffectiveBlueprintGenerated marks that the effective blueprint was generated out of the blueprint and the mask. @@ -112,13 +111,6 @@ type BlueprintConfiguration struct { // returns a domain.InvalidBlueprintError if blueprint is invalid // or nil otherwise. func (spec *BlueprintSpec) ValidateStatically() error { - switch spec.Status { - case StatusPhaseNew: // continue - case StatusPhaseInvalid: // do not validate again - return &InvalidBlueprintError{Message: "blueprint spec was marked invalid before: do not revalidate"} - default: // do not validate again. for all other status it must be either status validated or a status beyond that - return nil - } var errorList []error if spec.Id == "" { @@ -133,10 +125,19 @@ func (spec *BlueprintSpec) ValidateStatically() error { WrappedError: err, Message: "blueprint spec is invalid", } - spec.Status = StatusPhaseInvalid spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) + meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeValid, + Status: metav1.ConditionFalse, + Reason: "blueprint invalid", + Message: err.Error(), + }) } else { - spec.Status = StatusPhaseStaticallyValidated + // Do not set condition to true here. + // We reuse the condition for the dynamic validation and + // if the blueprint is completely consistent and valid can only be decided there + //TODO: Is it really clever to set this event here? If we validate the blueprint at every reconcile + // this could lead to hundreds of events. spec.Events = append(spec.Events, BlueprintSpecStaticallyValidatedEvent{}) } return err @@ -178,9 +179,19 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError } spec.Status = StatusPhaseInvalid spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) + meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeValid, + Status: metav1.ConditionFalse, + Reason: "inconsistent blueprint", + Message: err.Error(), + }) } else { spec.Status = StatusPhaseValidated spec.Events = append(spec.Events, BlueprintSpecValidatedEvent{}) + meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeValid, + Status: metav1.ConditionTrue, + }) } } @@ -296,18 +307,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( ecosystemState ecosystem.EcosystemState, referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, ) error { - switch spec.Status { - case StatusPhaseNew: - fallthrough - case StatusPhaseStaticallyValidated: - fallthrough - case StatusPhaseEffectiveBlueprintGenerated: - return fmt.Errorf("cannot determine state diff in status phase %q", spec.Status) - case StatusPhaseValidated: // this is the state, the blueprint spec should be - default: - return nil // do not re-determine the state diff from status StatusPhaseStateDiffDetermined and above - } - doguDiffs := determineDoguDiffs(spec.EffectiveBlueprint.Dogus, ecosystemState.InstalledDogus) compDiffs, err := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) if err != nil { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 6328e5be..9c71ab8b 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -42,42 +42,11 @@ func Test_BlueprintSpec_Validate_allOk(t *testing.T) { err := spec.ValidateStatically() require.Nil(t, err) - assert.Equal(t, StatusPhaseStaticallyValidated, spec.Status) + assert.Equal(t, StatusPhaseNew, spec.Status) require.Equal(t, 1, len(spec.Events)) assert.Equal(t, BlueprintSpecStaticallyValidatedEvent{}, spec.Events[0]) } -func Test_BlueprintSpec_Validate_inStatusValidated(t *testing.T) { - spec := BlueprintSpec{Id: "29.11.2023", Status: StatusPhaseStaticallyValidated} - - err := spec.ValidateStatically() - - require.Nil(t, err) - assert.Equal(t, StatusPhaseStaticallyValidated, spec.Status) - require.Equal(t, 0, len(spec.Events), "there should be no additional Events generated") -} - -func Test_BlueprintSpec_Validate_inStatusInProgress(t *testing.T) { - spec := BlueprintSpec{Id: "29.11.2023", Status: StatusPhaseInProgress} - - err := spec.ValidateStatically() - - require.Nil(t, err) - assert.Equal(t, StatusPhaseInProgress, spec.Status, "should stay in the old status") - require.Equal(t, 0, len(spec.Events), "there should be no additional Events generated") -} - -func Test_BlueprintSpec_Validate_inStatusInvalid(t *testing.T) { - spec := BlueprintSpec{Id: "29.11.2023", Status: StatusPhaseInvalid} - - err := spec.ValidateStatically() - - require.NotNil(t, err, "should not evaluate again and should stop with an error") - var invalidError *InvalidBlueprintError - assert.ErrorAs(t, err, &invalidError) - assert.ErrorContains(t, err, "blueprint spec was marked invalid before: do not revalidate") -} - func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { spec := BlueprintSpec{} @@ -486,46 +455,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") }) - notAllowedStatus := []StatusPhase{StatusPhaseNew, StatusPhaseStaticallyValidated, StatusPhaseEffectiveBlueprintGenerated} - for _, initialStatus := range notAllowedStatus { - t.Run(fmt.Sprintf("cannot determine state diff in status %q", initialStatus), func(t *testing.T) { - // given - spec := BlueprintSpec{ - Status: initialStatus, - } - clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, - } - // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) - - // then - assert.Error(t, err) - assert.Equal(t, spec.Status, initialStatus) - require.Equal(t, 0, len(spec.Events)) - assert.ErrorContains(t, err, fmt.Sprintf("cannot determine state diff in status phase %q", initialStatus)) - }) - } - t.Run("do not re-determine state diff", func(t *testing.T) { - initialStatus := StatusPhaseCompleted - // given - spec := BlueprintSpec{ - Status: initialStatus, - } - clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, - } - // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) - - // then - assert.NoError(t, err) - assert.Equal(t, spec.Status, initialStatus) - require.Equal(t, 0, len(spec.Events)) - }) - t.Run("should return error with not allowed component namespace switch action", func(t *testing.T) { // given spec := BlueprintSpec{ From 82ea6083139b611297ff72257c72785f3f6f5a74 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 12:12:19 +0200 Subject: [PATCH 009/119] #121 fix tests --- .../blueprintSpecChangeUseCase_test.go | 2 ++ .../blueprintSpecValidationUseCase_test.go | 9 +++++---- pkg/application/stateDiffUseCase.go | 1 + pkg/application/stateDiffUseCase_test.go | 19 ++++++++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 1cd32339..b304e121 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -21,6 +21,8 @@ var testCtx = context.Background() var testBlueprintId = "testBlueprint1" func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { + //FIXME: this test runs endless at the moment. Refactoring the changeUseCase is the last step + require.True(t, false, "tests deactivated until the refactoring is done.") t.Run("do all steps with blueprint", func(t *testing.T) { // given diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index 96d492db..f80dbab5 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -56,15 +56,16 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing MountsUseCase := newMockValidateAdditionalMountsDomainUseCase(t) useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) - repoMock.EXPECT().Update(ctx, mock.MatchedBy(func(i interface{}) bool { - spec := i.(*domain.BlueprintSpec) - return spec.Status == domain.StatusPhaseInvalid - })).Return(nil) + repoMock.EXPECT(). + Update(ctx, blueprint). + Return(nil) //when err := useCase.ValidateBlueprintSpecStatically(ctx, blueprint) //then + assert.Nil(t, blueprint.Conditions, "should not set conditions") + require.Error(t, err) var invalidError *domain.InvalidBlueprintError assert.ErrorAs(t, err, &invalidError, "error should be an InvalidBlueprintError") diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index 66965d14..6fbd778f 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -81,6 +81,7 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri } else if stateDiffError != nil { return fmt.Errorf("failed to determine state diff for blueprint %q: %w", blueprint.Id, stateDiffError) } + err = useCase.blueprintSpecRepo.Update(ctx, blueprint) if err != nil { return fmt.Errorf("cannot save blueprint spec %q after determining the state diff to the ecosystem: %w", blueprint.Id, err) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index bd808de8..032a69b6 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -232,7 +232,24 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should fail to determine state diff for blueprint", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + EffectiveBlueprint: domain.EffectiveBlueprint{ + Components: []domain.Component{ + { + Name: common.QualifiedComponentName{ + Namespace: "k8s", + SimpleName: "k8s-dogu-operator", + }, + Version: semVer3212, + // invalid TargetState to provoke special error + // delete this test, if we replace the target states with the absent flag + // Instead we should have a test with a forbidden diff action + TargetState: domain.TargetState(-10), + }, + }, + }, + } doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) From ad52028099c2876cb9f01909360ee713d687aeaf Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 13:34:34 +0200 Subject: [PATCH 010/119] #121 remove BlueprintSpecStaticallyValidated event Otherwise, we would publish dozens of this events because we will validate the blueprint as every reconciliation now --- .../blueprintcr/v2/blueprintSpecCRRepository_test.go | 2 -- pkg/application/blueprintSpecValidationUseCase_test.go | 3 +-- pkg/domain/blueprintSpec.go | 10 +++------- pkg/domain/blueprintSpec_test.go | 3 +-- pkg/domain/events.go | 10 ---------- pkg/domain/events_test.go | 6 ------ 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index 3d41a6b4..d9838e9c 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -320,7 +320,6 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { var events []domain.Event events = append(events, - domain.BlueprintSpecStaticallyValidatedEvent{}, domain.BlueprintSpecValidatedEvent{}, domain.EffectiveBlueprintCalculatedEvent{}, domain.StateDiffDoguDeterminedEvent{}, @@ -329,7 +328,6 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { domain.EcosystemUnhealthyUpfrontEvent{HealthResult: ecosystem.HealthResult{}}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecStaticallyValidated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecValidated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EffectiveBlueprintCalculated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index f80dbab5..c73e0d27 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -32,8 +32,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { useCase := NewBlueprintSpecValidationUseCase(repoMock, DependencyUseCase, MountsUseCase) repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ - Id: "testBlueprint1", - Events: []domain.Event{domain.BlueprintSpecStaticallyValidatedEvent{}}, + Id: "testBlueprint1", }).Return(nil) //when diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 501cd64d..8337d1d6 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -132,14 +132,10 @@ func (spec *BlueprintSpec) ValidateStatically() error { Reason: "blueprint invalid", Message: err.Error(), }) - } else { - // Do not set condition to true here. - // We reuse the condition for the dynamic validation and - // if the blueprint is completely consistent and valid can only be decided there - //TODO: Is it really clever to set this event here? If we validate the blueprint at every reconcile - // this could lead to hundreds of events. - spec.Events = append(spec.Events, BlueprintSpecStaticallyValidatedEvent{}) } + // Do not set condition to true here. + // We reuse the condition for the dynamic validation. + // If the blueprint is completely consistent and valid can only be decided there return err } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 9c71ab8b..fec63b56 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -43,8 +43,7 @@ func Test_BlueprintSpec_Validate_allOk(t *testing.T) { require.Nil(t, err) assert.Equal(t, StatusPhaseNew, spec.Status) - require.Equal(t, 1, len(spec.Events)) - assert.Equal(t, BlueprintSpecStaticallyValidatedEvent{}, spec.Events[0]) + require.Equal(t, 0, len(spec.Events)) } func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { diff --git a/pkg/domain/events.go b/pkg/domain/events.go index baf018ad..62f7952b 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -25,16 +25,6 @@ func (b BlueprintSpecInvalidEvent) Message() string { return b.ValidationError.Error() } -type BlueprintSpecStaticallyValidatedEvent struct{} - -func (b BlueprintSpecStaticallyValidatedEvent) Name() string { - return "BlueprintSpecStaticallyValidated" -} - -func (b BlueprintSpecStaticallyValidatedEvent) Message() string { - return "" -} - type BlueprintSpecValidatedEvent struct{} func (b BlueprintSpecValidatedEvent) Name() string { diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 01eaec5f..83c7ece1 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -27,12 +27,6 @@ func TestEvents(t *testing.T) { expectedName: "BlueprintSpecInvalid", expectedMessage: assert.AnError.Error(), }, - { - name: "blueprint spec statically validated", - event: BlueprintSpecStaticallyValidatedEvent{}, - expectedName: "BlueprintSpecStaticallyValidated", - expectedMessage: "", - }, { name: "blueprint spec validated", event: BlueprintSpecValidatedEvent{}, From 1e42af1ddd3ee12723d8c4bc01ba1807f05d15f9 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 15:48:16 +0200 Subject: [PATCH 011/119] #121 remove StatusPhaseValidated status --- .../v2/blueprintSpecCRRepository_test.go | 9 +- .../blueprintSpecChangeUseCase_test.go | 15 +- .../blueprintSpecValidationUseCase_test.go | 20 ++- .../effectiveBlueprintUseCase_test.go | 6 +- pkg/application/stateDiffUseCase_test.go | 6 +- pkg/domain/blueprintSpec.go | 16 +-- pkg/domain/blueprintSpec_test.go | 132 ++++-------------- 7 files changed, 52 insertions(+), 152 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index d9838e9c..9879caad 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -151,8 +151,8 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { restClientMock := newMockBlueprintInterface(t) eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + //FIXME: I removed the status field in this test. Do not forget to add a condition to this test instead expectedStatus := bpv2.BlueprintStatus{ - Phase: bpv2.StatusPhaseValidated, EffectiveBlueprint: bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, @@ -172,7 +172,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} err := repo.Update(ctx, &domain.BlueprintSpec{ Id: blueprintId, - Status: domain.StatusPhaseValidated, Events: nil, PersistenceContext: persistenceContext, }) @@ -190,7 +189,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { // when err := repo.Update(ctx, &domain.BlueprintSpec{ Id: blueprintId, - Status: domain.StatusPhaseValidated, Events: nil, }) @@ -210,7 +208,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { persistenceContext[blueprintSpecRepoContextKey] = 1 err := repo.Update(ctx, &domain.BlueprintSpec{ Id: blueprintId, - Status: domain.StatusPhaseValidated, Events: nil, PersistenceContext: persistenceContext, }) @@ -226,7 +223,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := bpv2.BlueprintStatus{ - Phase: bpv2.StatusPhaseValidated, EffectiveBlueprint: bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, @@ -251,7 +247,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} err := repo.Update(ctx, &domain.BlueprintSpec{ Id: blueprintId, - Status: domain.StatusPhaseValidated, Events: nil, PersistenceContext: persistenceContext, }) @@ -269,7 +264,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := bpv2.BlueprintStatus{ - Phase: bpv2.StatusPhaseValidated, EffectiveBlueprint: bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, @@ -290,7 +284,6 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} err := repo.Update(ctx, &domain.BlueprintSpec{ Id: blueprintId, - Status: domain.StatusPhaseValidated, Events: nil, PersistenceContext: persistenceContext, }) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index b304e121..ea104d28 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -60,7 +60,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseValidated + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeValid, + Status: metav1.ConditionTrue, + }) }) stateDiffMock.EXPECT().DetermineStateDiff(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { @@ -246,7 +249,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseValidated + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeValid, + Status: metav1.ConditionTrue, + }) }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(assert.AnError) // when @@ -282,7 +288,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseValidated + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeValid, + Status: metav1.ConditionTrue, + }) }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index c73e0d27..a7aaa41c 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" "testing" ) @@ -101,8 +102,8 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_repoError(t *testi func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() @@ -113,21 +114,14 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) DependencyUseCase.EXPECT().ValidateDependenciesForAllDogus(ctx, mock.Anything).Return(nil) MountsUseCase.EXPECT().ValidateAdditionalMounts(ctx, mock.Anything).Return(nil) - repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ - Id: "testBlueprint1", - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: domain.StateDiff{}, - Status: domain.StatusPhaseValidated, - Events: []domain.Event{domain.BlueprintSpecValidatedEvent{}}, - }).Return(nil) + repoMock.EXPECT().Update(ctx, blueprint).Return(nil) // when err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionTypeValid)) } func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testing.T) { @@ -146,7 +140,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin Version: version, TargetState: domain.TargetStatePresent, }}}, - Status: domain.StatusPhaseValidated, + Conditions: &[]domain.Condition{}, } invalidDependencyError := errors.New("invalid dependencies") invalidMountsError := errors.New("invalid mounts") @@ -158,6 +152,8 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionTypeValid)) + require.Error(t, err) var invalidError *domain.InvalidBlueprintError assert.ErrorAs(t, err, &invalidError) diff --git a/pkg/application/effectiveBlueprintUseCase_test.go b/pkg/application/effectiveBlueprintUseCase_test.go index ee042d24..2f7c8c74 100644 --- a/pkg/application/effectiveBlueprintUseCase_test.go +++ b/pkg/application/effectiveBlueprintUseCase_test.go @@ -14,8 +14,7 @@ import ( func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_ok(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, + Id: "testBlueprint1", } repoMock := newMockBlueprintSpecRepository(t) @@ -43,8 +42,7 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T t.Run("cannot save", func(t *testing.T) { //given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseValidated, + Id: "testBlueprint1", } repoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 032a69b6..6ce6a804 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -281,7 +281,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should fail to update blueprint", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1", Status: domain.StatusPhaseValidated} + blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) @@ -342,7 +342,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, }, }, - Status: domain.StatusPhaseValidated, // TODO: add config to test } @@ -451,7 +450,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, }, }, - Status: domain.StatusPhaseValidated, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -525,7 +523,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, }, }, - Status: domain.StatusPhaseValidated, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -615,7 +612,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, }, }, - Status: domain.StatusPhaseValidated, } blueprintRepoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 8337d1d6..73e93f1e 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -48,8 +48,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseValidated marks the given blueprint spec as validated. - StatusPhaseValidated StatusPhase = "validated" // StatusPhaseEffectiveBlueprintGenerated marks that the effective blueprint was generated out of the blueprint and the mask. StatusPhaseEffectiveBlueprintGenerated StatusPhase = "effectiveBlueprintGenerated" // StatusPhaseStateDiffDetermined marks that the diff to the ecosystem state was successfully determined. @@ -165,8 +163,7 @@ func (spec *BlueprintSpec) validateMaskAgainstBlueprint() error { // ValidateDynamically sets the Status either to StatusPhaseInvalid or StatusPhaseValidated // depending on if the dependencies or versions of the elements in the blueprint are invalid. -// returns a domain.InvalidBlueprintError if blueprint is invalid -// or nil otherwise. +// This function decides completely on the given error, therefore no error will be returned explicitly again. func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError error) { if possibleInvalidDependenciesError != nil { err := &InvalidBlueprintError{ @@ -182,7 +179,6 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError Message: err.Error(), }) } else { - spec.Status = StatusPhaseValidated spec.Events = append(spec.Events, BlueprintSpecValidatedEvent{}) meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionTypeValid, @@ -192,16 +188,6 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError } func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { - switch spec.Status { - case StatusPhaseEffectiveBlueprintGenerated: - return nil // do not regenerate effective blueprint - case StatusPhaseNew: // stop - return fmt.Errorf("cannot calculate effective blueprint before the blueprint spec is validated") - case StatusPhaseInvalid: // stop - return fmt.Errorf("cannot calculate effective blueprint on invalid blueprint spec") - default: // continue: StatusPhaseValidated, StatusPhaseInProgress, StatusPhaseFailed, StatusPhaseCompleted - } - effectiveDogus, err := spec.calculateEffectiveDogus() if err != nil { return err diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index fec63b56..114e5621 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -6,6 +6,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "golang.org/x/exp/maps" + "k8s.io/apimachinery/pkg/api/meta" "testing" "github.com/stretchr/testify/assert" @@ -133,50 +134,13 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { spec := BlueprintSpec{ Blueprint: Blueprint{Dogus: dogus}, BlueprintMask: BlueprintMask{Dogus: []MaskDogu{}}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() require.Nil(t, err) }) - t.Run("status new", func(t *testing.T) { - spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: []Dogu{}}, - BlueprintMask: BlueprintMask{Dogus: []MaskDogu{}}, - Status: StatusPhaseNew, - } - - err := spec.CalculateEffectiveBlueprint() - - require.NotNil(t, err) - assert.ErrorContains(t, err, "cannot calculate effective blueprint before the blueprint spec is validated") - }) - t.Run("status effective blueprint generated", func(t *testing.T) { - spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: []Dogu{}}, - BlueprintMask: BlueprintMask{Dogus: []MaskDogu{}}, - Status: StatusPhaseEffectiveBlueprintGenerated, - } - expectedSpec := spec - err := spec.CalculateEffectiveBlueprint() - - require.Nil(t, err) - assert.Equal(t, expectedSpec, spec) - }) - t.Run("status invalid", func(t *testing.T) { - spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: []Dogu{}}, - BlueprintMask: BlueprintMask{Dogus: []MaskDogu{}}, - Status: StatusPhaseInvalid, - } - - err := spec.CalculateEffectiveBlueprint() - - require.NotNil(t, err) - assert.ErrorContains(t, err, "cannot calculate effective blueprint on invalid blueprint spec") - }) t.Run("change version", func(t *testing.T) { dogus := []Dogu{ {Name: officialDogu1, Version: version3211, TargetState: TargetStatePresent}, @@ -191,7 +155,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { spec := BlueprintSpec{ Blueprint: Blueprint{Dogus: dogus}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() @@ -200,6 +163,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { assert.Equal(t, Dogu{Name: officialDogu1, Version: version3212, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[0]) assert.Equal(t, Dogu{Name: officialDogu2, Version: version3211, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[1]) }) + t.Run("make dogu absent", func(t *testing.T) { dogus := []Dogu{ {Name: officialDogu1, Version: version3211, TargetState: TargetStatePresent}, @@ -220,7 +184,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { spec := BlueprintSpec{ Blueprint: Blueprint{Dogus: dogus, Config: config}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() @@ -230,6 +193,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { assert.Equal(t, Dogu{Name: officialDogu2, Version: version3212, TargetState: TargetStateAbsent}, spec.EffectiveBlueprint.Dogus[1]) assert.NotContains(t, spec.EffectiveBlueprint.Config.Dogus, officialDogu1.SimpleName) }) + t.Run("change dogu namespace", func(t *testing.T) { dogus := []Dogu{ {Name: officialNexus, Version: version3211, TargetState: TargetStatePresent}, @@ -243,13 +207,13 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { Blueprint: Blueprint{Dogus: dogus}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, Config: BlueprintConfiguration{AllowDoguNamespaceSwitch: false}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() require.Error(t, err, "without the feature flag, namespace changes are not allowed") require.ErrorContains(t, err, "changing the dogu namespace is forbidden by default and can be allowed by a flag: \"official/nexus\" -> \"premium/nexus\"") }) + t.Run("change dogu namespace with flag", func(t *testing.T) { dogus := []Dogu{ {Name: officialNexus, Version: version3211, TargetState: TargetStatePresent}, @@ -263,7 +227,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { Blueprint: Blueprint{Dogus: dogus}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, Config: BlueprintConfiguration{AllowDoguNamespaceSwitch: true}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() @@ -271,6 +234,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { require.Equal(t, 1, len(spec.EffectiveBlueprint.Dogus), "effective blueprint should contain the elements from the mask") assert.Equal(t, Dogu{Name: premiumNexus, Version: version3211, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[0]) }) + t.Run("validate only config for dogus in blueprint", func(t *testing.T) { config := Config{ Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ @@ -280,7 +244,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { spec := BlueprintSpec{ Blueprint: Blueprint{Config: config}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() @@ -303,7 +266,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { spec := BlueprintSpec{ Blueprint: Blueprint{Dogus: dogus}, - Status: StatusPhaseValidated, } err := spec.CalculateEffectiveBlueprint() @@ -315,7 +277,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { func TestBlueprintSpec_MarkInvalid(t *testing.T) { spec := BlueprintSpec{ Config: BlueprintConfiguration{AllowDoguNamespaceSwitch: true}, - Status: StatusPhaseValidated, } expectedErr := &InvalidBlueprintError{ WrappedError: nil, @@ -346,7 +307,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Dogus: []Dogu{}, Components: []Component{}, }, - Status: StatusPhaseValidated, } clusterState := ecosystem.EcosystemState{ @@ -395,7 +355,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: true, }, - Status: StatusPhaseValidated, } clusterState := ecosystem.EcosystemState{ @@ -432,7 +391,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: false, }, - Status: StatusPhaseValidated, } clusterState := ecosystem.EcosystemState{ @@ -468,7 +426,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, - Status: StatusPhaseValidated, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -502,7 +459,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, - Status: StatusPhaseValidated, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -802,63 +758,29 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { } func TestBlueprintSpec_ValidateDynamically(t *testing.T) { - type fields struct { - Id string - Blueprint Blueprint - BlueprintMask BlueprintMask - EffectiveBlueprint EffectiveBlueprint - StateDiff StateDiff - Config BlueprintConfiguration - Status StatusPhase - PersistenceContext map[string]interface{} - Events []Event - } - type args struct { - possibleInvalidDependenciesError error - } - tests := []struct { - name string - fields fields - args args - expectedPhase StatusPhase - expectedEvents []Event - }{ - { - name: "statusphase invalid on error", - fields: fields{}, - args: args{possibleInvalidDependenciesError: assert.AnError}, - expectedPhase: "invalid", - expectedEvents: []Event{BlueprintSpecInvalidEvent{ - ValidationError: &InvalidBlueprintError{WrappedError: assert.AnError, Message: "blueprint spec is invalid"}}, - }, - }, - { - name: "statusphase valid on nil", - fields: fields{}, - args: args{possibleInvalidDependenciesError: nil}, - expectedPhase: "validated", - expectedEvents: []Event{BlueprintSpecValidatedEvent{}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - spec := &BlueprintSpec{ - Id: tt.fields.Id, - Blueprint: tt.fields.Blueprint, - BlueprintMask: tt.fields.BlueprintMask, - EffectiveBlueprint: tt.fields.EffectiveBlueprint, - StateDiff: tt.fields.StateDiff, - Config: tt.fields.Config, - Status: tt.fields.Status, - PersistenceContext: tt.fields.PersistenceContext, - Events: tt.fields.Events, - } - spec.ValidateDynamically(tt.args.possibleInvalidDependenciesError) + t.Run("all ok, no errors", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } - assert.Equal(t, tt.expectedPhase, spec.Status) - assert.Equal(t, tt.expectedEvents, spec.Events) - }) - } + blueprint.ValidateDynamically(nil) + + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionTypeValid)) + require.Equal(t, 1, len(blueprint.Events)) + + }) + + t.Run("given dependency error", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } + givenErr := assert.AnError + + blueprint.ValidateDynamically(givenErr) + + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionTypeValid)) + require.Equal(t, 1, len(blueprint.Events)) + }) } func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { From dcd96180a2fb05a80a18686b4365233b32ad2d7f Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 12 Aug 2025 15:59:05 +0200 Subject: [PATCH 012/119] #121 remove BlueprintSpecValidated event We have a condition instead. We also want to prevent event duplication over multiple reconciles. --- .../blueprintcr/v2/blueprintSpecCRRepository_test.go | 2 -- pkg/domain/blueprintSpec.go | 4 ++-- pkg/domain/blueprintSpec_test.go | 2 +- pkg/domain/events.go | 10 ---------- pkg/domain/events_test.go | 6 ------ 5 files changed, 3 insertions(+), 21 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index 9879caad..cfeda4d4 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -313,7 +313,6 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { var events []domain.Event events = append(events, - domain.BlueprintSpecValidatedEvent{}, domain.EffectiveBlueprintCalculatedEvent{}, domain.StateDiffDoguDeterminedEvent{}, domain.StateDiffComponentDeterminedEvent{}, @@ -321,7 +320,6 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { domain.EcosystemUnhealthyUpfrontEvent{HealthResult: ecosystem.HealthResult{}}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecValidated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EffectiveBlueprintCalculated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffComponentDetermined", "component state diff determined: 0 actions ()") diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 73e93f1e..3cc1e481 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -179,7 +179,6 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError Message: err.Error(), }) } else { - spec.Events = append(spec.Events, BlueprintSpecValidatedEvent{}) meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionTypeValid, Status: metav1.ConditionTrue, @@ -293,7 +292,8 @@ func (spec *BlueprintSpec) DetermineStateDiff( compDiffs, err := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) if err != nil { // FIXME: a proper state and event should be set, so that this error don't lead to an endless retry. - // we need to analyze first, what kind of error this is. Why do we need one? + // The error here occurs, if a targetState is not properly set in components. We can remove this case + // when we introduce the absent flag in the domain or we just ignore this error like for dogu targetState return err } doguConfigDiffs, sensitiveDoguConfigDiffs, globalConfigDiffs := determineConfigDiffs( diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 114e5621..1782e12b 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -766,7 +766,7 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { blueprint.ValidateDynamically(nil) assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionTypeValid)) - require.Equal(t, 1, len(blueprint.Events)) + require.Equal(t, 0, len(blueprint.Events)) }) diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 62f7952b..487db7b3 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -25,16 +25,6 @@ func (b BlueprintSpecInvalidEvent) Message() string { return b.ValidationError.Error() } -type BlueprintSpecValidatedEvent struct{} - -func (b BlueprintSpecValidatedEvent) Name() string { - return "BlueprintSpecValidated" -} - -func (b BlueprintSpecValidatedEvent) Message() string { - return "" -} - type EffectiveBlueprintCalculatedEvent struct { Result EffectiveBlueprint } diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 83c7ece1..0103c2a3 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -27,12 +27,6 @@ func TestEvents(t *testing.T) { expectedName: "BlueprintSpecInvalid", expectedMessage: assert.AnError.Error(), }, - { - name: "blueprint spec validated", - event: BlueprintSpecValidatedEvent{}, - expectedName: "BlueprintSpecValidated", - expectedMessage: "", - }, { name: "ecosystem healthy", event: EcosystemHealthyUpfrontEvent{}, From 19a8262500ee8ce2bf3283608c75fc056a2e4b89 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 08:02:07 +0200 Subject: [PATCH 013/119] #121 remove StatusPhaseInvalid --- .../blueprintSpecChangeUseCase_test.go | 3 +- .../blueprintSpecValidationUseCase_test.go | 1 - pkg/domain/blueprintSpec.go | 50 ++++++++++++------- pkg/domain/blueprintSpec_test.go | 36 ++++++------- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index ea104d28..2e581a58 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -320,8 +320,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseInvalid, + Id: "testBlueprint1", }, nil) // when err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index a7aaa41c..df0548ad 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -162,7 +162,6 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin assert.ErrorContains(t, err, "blueprint spec is invalid") assert.Equal(t, "testBlueprint1", blueprint.Id) - assert.Equal(t, domain.StatusPhaseInvalid, blueprint.Status) require.Equal(t, 1, len(blueprint.Events)) assert.IsType(t, domain.BlueprintSpecInvalidEvent{}, blueprint.Events[0]) assert.ErrorContains(t, blueprint.Events[0].(domain.BlueprintSpecInvalidEvent).ValidationError, "blueprint spec is invalid: ") diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 3cc1e481..941df312 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -32,8 +32,9 @@ type BlueprintSpec struct { type Condition = metav1.Condition -var ( +const ( ConditionTypeValid = "Valid" + ConditionTypeExecutable = "Executable" ConditionEcosystemHealthy = "EcosystemHealthy" ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" ConditionConfigApplied = "ConfigApplied" @@ -52,8 +53,6 @@ const ( StatusPhaseEffectiveBlueprintGenerated StatusPhase = "effectiveBlueprintGenerated" // StatusPhaseStateDiffDetermined marks that the diff to the ecosystem state was successfully determined. StatusPhaseStateDiffDetermined StatusPhase = "stateDiffDetermined" - // StatusPhaseInvalid marks the given blueprint spec is semantically incorrect. - StatusPhaseInvalid StatusPhase = "invalid" // StatusPhaseEcosystemHealthyUpfront marks that all currently installed dogus are healthy. StatusPhaseEcosystemHealthyUpfront StatusPhase = "ecosystemHealthyUpfront" // StatusPhaseEcosystemUnhealthyUpfront marks that some currently installed dogus are unhealthy. @@ -161,7 +160,7 @@ func (spec *BlueprintSpec) validateMaskAgainstBlueprint() error { return err } -// ValidateDynamically sets the Status either to StatusPhaseInvalid or StatusPhaseValidated +// ValidateDynamically sets the ConditionTypeValid // depending on if the dependencies or versions of the elements in the blueprint are invalid. // This function decides completely on the given error, therefore no error will be returned explicitly again. func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError error) { @@ -170,14 +169,15 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError WrappedError: possibleInvalidDependenciesError, Message: "blueprint spec is invalid", } - spec.Status = StatusPhaseInvalid - spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) - meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionTypeValid, Status: metav1.ConditionFalse, Reason: "inconsistent blueprint", Message: err.Error(), }) + if conditionChanged { + spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) + } } else { meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionTypeValid, @@ -201,8 +201,15 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { } validationError := spec.EffectiveBlueprint.validateOnlyConfigForDogusInBlueprint() if validationError != nil { - spec.Status = StatusPhaseInvalid - spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: validationError}) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeValid, + Status: metav1.ConditionFalse, + Reason: "inconsistent blueprint", + Message: validationError.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: validationError}) + } return validationError } spec.Status = StatusPhaseEffectiveBlueprintGenerated @@ -267,12 +274,6 @@ func (spec *BlueprintSpec) removeConfigForMaskedDogus() Config { } } -// MarkInvalid is used to mark the blueprint as invalid after dynamically validating it. -func (spec *BlueprintSpec) MarkInvalid(err error) { - spec.Status = StatusPhaseInvalid - spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) -} - // MissingConfigReferences adds a given error, which was caused during preparations for determining the state diff func (spec *BlueprintSpec) MissingConfigReferences(error error) { spec.Events = append(spec.Events, NewMissingConfigReferencesEvent(error)) @@ -320,11 +321,26 @@ func (spec *BlueprintSpec) DetermineStateDiff( invalidBlueprintError := spec.validateStateDiff() if invalidBlueprintError != nil { - spec.Status = StatusPhaseInvalid - spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: invalidBlueprintError}) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeExecutable, + Status: metav1.ConditionFalse, + Reason: "forbidden operations needed", + Message: invalidBlueprintError.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: invalidBlueprintError}) + } return invalidBlueprintError } + meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionTypeExecutable, + Status: metav1.ConditionTrue, + }) + //TODO: we cannot just deduplicate the events here by detecting a condition change, + // because the blueprint could be executable even after a change of the blueprint. + // Therefore, a check with "conditionChanged" is not enough to prevent, that we regenerate all events on every reconcile. + spec.Status = StatusPhaseStateDiffDetermined return nil diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 1782e12b..19cbcb14 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -48,10 +48,13 @@ func Test_BlueprintSpec_Validate_allOk(t *testing.T) { } func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { - spec := BlueprintSpec{} + spec := BlueprintSpec{ + Conditions: &[]Condition{}, + } err := spec.ValidateStatically() + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeValid)) require.NotNil(t, err, "No ID definition should lead to an error") var invalidError *InvalidBlueprintError assert.ErrorAs(t, err, &invalidError) @@ -249,8 +252,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { err := spec.CalculateEffectiveBlueprint() assert.ErrorContains(t, err, "setting config for dogu \"my-dogu\" is not allowed as it will not be installed with the blueprint") - assert.Equal(t, spec.Status, StatusPhaseInvalid) - assert.Equal(t, spec.Events, []Event{BlueprintSpecInvalidEvent{err}}) + assert.Equal(t, 0, len(spec.Events)) }) t.Run("add additionalMounts", func(t *testing.T) { dogus := []Dogu{ @@ -274,21 +276,6 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { }) } -func TestBlueprintSpec_MarkInvalid(t *testing.T) { - spec := BlueprintSpec{ - Config: BlueprintConfiguration{AllowDoguNamespaceSwitch: true}, - } - expectedErr := &InvalidBlueprintError{ - WrappedError: nil, - Message: "test-error", - } - spec.MarkInvalid(expectedErr) - - assert.Equal(t, StatusPhaseInvalid, spec.Status) - require.Equal(t, 1, len(spec.Events)) - assert.Equal(t, BlueprintSpecInvalidEvent{ValidationError: expectedErr}, spec.Events[0]) -} - func TestBlueprintSpec_MissingConfigReferences(t *testing.T) { blueprint := BlueprintSpec{} blueprint.MissingConfigReferences(assert.AnError) @@ -307,6 +294,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Dogus: []Dogu{}, Components: []Component{}, }, + Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -324,6 +312,8 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, } + + assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionTypeExecutable)) require.NoError(t, err) assert.Equal(t, StatusPhaseStateDiffDetermined, spec.Status) require.Equal(t, 5, len(spec.Events)) @@ -391,6 +381,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: false, }, + Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -407,8 +398,8 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) require.Error(t, err) - assert.Equal(t, StatusPhaseInvalid, spec.Status) assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") }) @@ -426,6 +417,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, + Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -441,10 +433,11 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) require.Error(t, err) - assert.Equal(t, StatusPhaseInvalid, spec.Status) assert.ErrorContains(t, err, "action \"component namespace switch\" is not allowed") }) + t.Run("should return error with not allowed component downgrade action", func(t *testing.T) { // given spec := BlueprintSpec{ @@ -459,6 +452,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, + Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -474,8 +468,8 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) require.Error(t, err) - assert.Equal(t, StatusPhaseInvalid, spec.Status) assert.ErrorContains(t, err, "action \"downgrade\" is not allowed") }) } From b37cfbf9f03dd995a8eb996cfed43638ae601e9e Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 08:06:18 +0200 Subject: [PATCH 014/119] #121 remove StatusPhaseEffectiveBlueprintGenerated --- .../blueprintSpecChangeUseCase_test.go | 20 ++++--------------- .../effectiveBlueprintUseCase_test.go | 2 -- pkg/domain/blueprintSpec.go | 3 --- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 2e581a58..e1d5fb9a 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -54,10 +54,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: metav1.ConditionTrue, }) }) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseEffectiveBlueprintGenerated - }) + effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(mock.Anything, blueprintSpec).Return(nil) validationMock.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ @@ -210,10 +207,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated - }) + effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(assert.AnError) // when @@ -243,10 +237,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated - }) + effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ @@ -282,10 +273,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseEffectiveBlueprintGenerated - }) + effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ diff --git a/pkg/application/effectiveBlueprintUseCase_test.go b/pkg/application/effectiveBlueprintUseCase_test.go index 2f7c8c74..28cdcdc9 100644 --- a/pkg/application/effectiveBlueprintUseCase_test.go +++ b/pkg/application/effectiveBlueprintUseCase_test.go @@ -27,7 +27,6 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_ok(t *testing.T) { BlueprintMask: domain.BlueprintMask{}, EffectiveBlueprint: domain.EffectiveBlueprint{}, StateDiff: domain.StateDiff{}, - Status: domain.StatusPhaseEffectiveBlueprintGenerated, Events: []domain.Event{domain.EffectiveBlueprintCalculatedEvent{}}, }).Return(nil) @@ -55,7 +54,6 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T BlueprintMask: domain.BlueprintMask{}, EffectiveBlueprint: domain.EffectiveBlueprint{}, StateDiff: domain.StateDiff{}, - Status: domain.StatusPhaseEffectiveBlueprintGenerated, Events: []domain.Event{domain.EffectiveBlueprintCalculatedEvent{}}, }).Return(&domainservice.InternalError{Message: "test-error"}) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 941df312..9a5df1d5 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -49,8 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseEffectiveBlueprintGenerated marks that the effective blueprint was generated out of the blueprint and the mask. - StatusPhaseEffectiveBlueprintGenerated StatusPhase = "effectiveBlueprintGenerated" // StatusPhaseStateDiffDetermined marks that the diff to the ecosystem state was successfully determined. StatusPhaseStateDiffDetermined StatusPhase = "stateDiffDetermined" // StatusPhaseEcosystemHealthyUpfront marks that all currently installed dogus are healthy. @@ -212,7 +210,6 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { } return validationError } - spec.Status = StatusPhaseEffectiveBlueprintGenerated spec.Events = append(spec.Events, EffectiveBlueprintCalculatedEvent{}) return nil } From b97f726f9d1dceff4b16869c0017fb0b1a245286 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 08:13:54 +0200 Subject: [PATCH 015/119] #121 remove EffectiveBlueprintCalculatedEvent We show the effective blueprint in status. There could be thousands of this event if we recalculate the effective blueprint on every reconciliation. --- .../v2/blueprintSpecCRRepository_test.go | 2 -- .../effectiveBlueprintUseCase_test.go | 20 ++++--------------- pkg/domain/blueprintSpec.go | 3 +-- pkg/domain/events.go | 12 ----------- pkg/domain/events_test.go | 6 ------ 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index cfeda4d4..b350b133 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -313,14 +313,12 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { var events []domain.Event events = append(events, - domain.EffectiveBlueprintCalculatedEvent{}, domain.StateDiffDoguDeterminedEvent{}, domain.StateDiffComponentDeterminedEvent{}, domain.EcosystemHealthyUpfrontEvent{}, domain.EcosystemUnhealthyUpfrontEvent{HealthResult: ecosystem.HealthResult{}}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EffectiveBlueprintCalculated", "") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffComponentDetermined", "component state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EcosystemHealthyUpfront", "dogu health ignored: false; component health ignored: false") diff --git a/pkg/application/effectiveBlueprintUseCase_test.go b/pkg/application/effectiveBlueprintUseCase_test.go index 28cdcdc9..acae7cd0 100644 --- a/pkg/application/effectiveBlueprintUseCase_test.go +++ b/pkg/application/effectiveBlueprintUseCase_test.go @@ -21,20 +21,15 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_ok(t *testing.T) { ctx := context.Background() useCase := NewEffectiveBlueprintUseCase(repoMock) - repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ - Id: "testBlueprint1", - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: domain.StateDiff{}, - Events: []domain.Event{domain.EffectiveBlueprintCalculatedEvent{}}, - }).Return(nil) + repoMock.EXPECT().Update(ctx, blueprint).Return(nil) // when err := useCase.CalculateEffectiveBlueprint(ctx, blueprint) // then require.NoError(t, err) + assert.Equal(t, 0, len(blueprint.Events)) + assert.Equal(t, blueprint.EffectiveBlueprint, domain.EffectiveBlueprint{}) } func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T) { @@ -48,14 +43,7 @@ func TestBlueprintSpecUseCase_CalculateEffectiveBlueprint_repoError(t *testing.T ctx := context.Background() useCase := NewEffectiveBlueprintUseCase(repoMock) - repoMock.EXPECT().Update(ctx, &domain.BlueprintSpec{ - Id: "testBlueprint1", - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: domain.StateDiff{}, - Events: []domain.Event{domain.EffectiveBlueprintCalculatedEvent{}}, - }).Return(&domainservice.InternalError{Message: "test-error"}) + repoMock.EXPECT().Update(ctx, blueprint).Return(&domainservice.InternalError{Message: "test-error"}) //when err := useCase.CalculateEffectiveBlueprint(ctx, blueprint) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 9a5df1d5..0c744602 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -210,7 +210,6 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { } return validationError } - spec.Events = append(spec.Events, EffectiveBlueprintCalculatedEvent{}) return nil } @@ -243,7 +242,7 @@ func (spec *BlueprintSpec) calculateEffectiveDogu(dogu Dogu) (Dogu, error) { } if maskDogu.Name.Namespace != dogu.Name.Namespace { if spec.Config.AllowDoguNamespaceSwitch { - effectiveDogu.Name.Namespace = cescommons.Namespace(maskDogu.Name.Namespace) + effectiveDogu.Name.Namespace = maskDogu.Name.Namespace } else { return Dogu{}, fmt.Errorf( "changing the dogu namespace is forbidden by default and can be allowed by a flag: %q -> %q", dogu.Name, maskDogu.Name) diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 487db7b3..bd247663 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -25,18 +25,6 @@ func (b BlueprintSpecInvalidEvent) Message() string { return b.ValidationError.Error() } -type EffectiveBlueprintCalculatedEvent struct { - Result EffectiveBlueprint -} - -func (e EffectiveBlueprintCalculatedEvent) Name() string { - return "EffectiveBlueprintCalculated" -} - -func (e EffectiveBlueprintCalculatedEvent) Message() string { - return "" -} - type GlobalConfigDiffDeterminedEvent struct { GlobalConfigDiffs GlobalConfigDiffs } diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 0103c2a3..5ea6dccb 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -61,12 +61,6 @@ func TestEvents(t *testing.T) { expectedName: "EcosystemUnhealthyUpfront", expectedMessage: "ecosystem health:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", }, - { - name: "effective blueprint calculated", - event: EffectiveBlueprintCalculatedEvent{}, - expectedName: "EffectiveBlueprintCalculated", - expectedMessage: "", - }, { name: "dogu state diff determined", event: newStateDiffDoguEvent( From bce1e0cf4d1cd42f1718d3b67776fd59a2bc8e54 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 08:22:02 +0200 Subject: [PATCH 016/119] #121 remove StatusPhaseStateDiffDetermined --- pkg/application/blueprintSpecChangeUseCase.go | 6 ++++-- pkg/application/blueprintSpecChangeUseCase_test.go | 10 ++++++++-- pkg/domain/blueprintSpec.go | 4 ---- pkg/domain/blueprintSpec_test.go | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 3f6df54d..7b4b17a2 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -87,6 +87,10 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. return err } + err = useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprint) + if err != nil { + return err + } // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. for blueprint.Status != domain.StatusPhaseCompleted { @@ -102,8 +106,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseStateDiffDetermined: - return useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprint) case domain.StatusPhaseEcosystemHealthyUpfront: return useCase.preProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyUpfront: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index e1d5fb9a..5ee31cb3 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -64,7 +64,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) stateDiffMock.EXPECT().DetermineStateDiff(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseStateDiffDetermined + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeExecutable, + Status: metav1.ConditionTrue, + }) }) applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { @@ -283,7 +286,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - updatedSpec.Status = domain.StatusPhaseStateDiffDetermined + meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ + Type: domain.ConditionTypeExecutable, + Status: metav1.ConditionTrue, + }) }) applyMock.EXPECT().CheckEcosystemHealthUpfront(testCtx, "testBlueprint1").Return(assert.AnError) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 0c744602..ada9ebef 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -49,8 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseStateDiffDetermined marks that the diff to the ecosystem state was successfully determined. - StatusPhaseStateDiffDetermined StatusPhase = "stateDiffDetermined" // StatusPhaseEcosystemHealthyUpfront marks that all currently installed dogus are healthy. StatusPhaseEcosystemHealthyUpfront StatusPhase = "ecosystemHealthyUpfront" // StatusPhaseEcosystemUnhealthyUpfront marks that some currently installed dogus are unhealthy. @@ -337,8 +335,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( // because the blueprint could be executable even after a change of the blueprint. // Therefore, a check with "conditionChanged" is not enough to prevent, that we regenerate all events on every reconcile. - spec.Status = StatusPhaseStateDiffDetermined - return nil } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 19cbcb14..5bbc105b 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -315,7 +315,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionTypeExecutable)) require.NoError(t, err) - assert.Equal(t, StatusPhaseStateDiffDetermined, spec.Status) require.Equal(t, 5, len(spec.Events)) assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) assert.Equal(t, newStateDiffComponentEvent(stateDiff.ComponentDiffs), spec.Events[1]) @@ -345,6 +344,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: true, }, + Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -362,7 +362,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) - assert.Equal(t, StatusPhaseStateDiffDetermined, spec.Status) + assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionTypeExecutable)) }) t.Run("invalid blueprint state with not allowed dogu namespace switch", func(t *testing.T) { From 1aed68a1bdfbbebe923808fa8d7383e2d88b6d4f Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 15:41:07 +0200 Subject: [PATCH 017/119] #121 remove StatusPhaseEcosystemHealthyUpfront add condition for healthy ecosystem --- .../applyBlueprintSpecUseCase_test.go | 41 +--- pkg/application/blueprintSpecChangeUseCase.go | 12 +- .../blueprintSpecChangeUseCase_test.go | 17 +- .../blueprintSpecValidationUseCase_test.go | 4 +- pkg/domain/blueprintSpec.go | 47 +++-- pkg/domain/blueprintSpec_test.go | 179 ++++++++++-------- 6 files changed, 157 insertions(+), 143 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index 92e542ee..6645ac5c 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -26,9 +26,7 @@ func TestNewApplyBlueprintSpecUseCase(t *testing.T) { func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) { t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(nil) @@ -40,9 +38,7 @@ func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) assert.Equal(t, domain.StatusPhaseBlueprintApplicationPreProcessed, spec.Status) }) t.Run("repo error while saving", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) @@ -54,7 +50,6 @@ func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) }) t.Run("do nothing on dry run", func(t *testing.T) { spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, Config: domain.BlueprintConfiguration{DryRun: true}, } @@ -72,9 +67,7 @@ func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) func TestApplyBlueprintSpecUseCase_markInProgress(t *testing.T) { t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(nil) @@ -162,9 +155,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { 2: domain.StatusPhaseBlueprintApplied, } t.Run("ok", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) var counter = 0 repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { @@ -188,9 +179,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { assert.Equal(t, domain.StatusPhaseBlueprintApplied, blueprint.Status) }) t.Run("error waiting for dogu health", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) @@ -210,9 +199,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) t.Run("fail to mark in progress", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) @@ -225,9 +212,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) t.Run("fail to apply component state", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) @@ -242,9 +227,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) t.Run("fail to wait for component health", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) @@ -260,9 +243,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) t.Run("fail to apply dogu state", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) @@ -280,9 +261,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) t.Run("fail to apply state and fail to mark execution failed", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyUpfront, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) counter := 0 repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 7b4b17a2..b095b5ed 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -92,6 +92,16 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } + err = useCase.applyUseCase.PreProcessBlueprintApplication(ctx, blueprint) + if err != nil { + return err + } + if !blueprint.ShouldBeApplied() { + // event recording and so on happen in PreProcessBlueprintApplication + // just stop the loop here on dry run or early exit + return nil + } + // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. for blueprint.Status != domain.StatusPhaseCompleted { err := useCase.handleChange(ctx, blueprint) @@ -106,8 +116,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseEcosystemHealthyUpfront: - return useCase.preProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyUpfront: return nil case domain.StatusPhaseBlueprintApplicationPreProcessed: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 5ee31cb3..7ed50708 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -50,7 +50,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeValid, + Type: domain.ConditionValid, Status: metav1.ConditionTrue, }) }) @@ -58,21 +58,18 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeValid, + Type: domain.ConditionValid, Status: metav1.ConditionTrue, }) }) stateDiffMock.EXPECT().DetermineStateDiff(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeExecutable, + Type: domain.ConditionExecutable, Status: metav1.ConditionTrue, }) }) - applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseEcosystemHealthyUpfront - }) + applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil) applyMock.EXPECT().PreProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseBlueprintApplicationPreProcessed @@ -244,7 +241,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeValid, + Type: domain.ConditionValid, Status: metav1.ConditionTrue, }) }) @@ -280,14 +277,14 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeValid, + Type: domain.ConditionValid, Status: metav1.ConditionTrue, }) }) stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionTypeExecutable, + Type: domain.ConditionExecutable, Status: metav1.ConditionTrue, }) }) diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index df0548ad..e662c521 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -121,7 +121,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionTypeValid)) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionValid)) } func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testing.T) { @@ -152,7 +152,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionTypeValid)) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionValid)) require.Error(t, err) var invalidError *domain.InvalidBlueprintError diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index ada9ebef..c99ee608 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -33,8 +33,8 @@ type BlueprintSpec struct { type Condition = metav1.Condition const ( - ConditionTypeValid = "Valid" - ConditionTypeExecutable = "Executable" + ConditionValid = "Valid" + ConditionExecutable = "Executable" ConditionEcosystemHealthy = "EcosystemHealthy" ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" ConditionConfigApplied = "ConfigApplied" @@ -49,8 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseEcosystemHealthyUpfront marks that all currently installed dogus are healthy. - StatusPhaseEcosystemHealthyUpfront StatusPhase = "ecosystemHealthyUpfront" // StatusPhaseEcosystemUnhealthyUpfront marks that some currently installed dogus are unhealthy. StatusPhaseEcosystemUnhealthyUpfront StatusPhase = "ecosystemUnhealthyUpfront" // StatusPhaseBlueprintApplicationPreProcessed shows that all pre-processing steps for the blueprint application @@ -120,7 +118,7 @@ func (spec *BlueprintSpec) ValidateStatically() error { } spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeValid, + Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "blueprint invalid", Message: err.Error(), @@ -156,7 +154,7 @@ func (spec *BlueprintSpec) validateMaskAgainstBlueprint() error { return err } -// ValidateDynamically sets the ConditionTypeValid +// ValidateDynamically sets the ConditionValid // depending on if the dependencies or versions of the elements in the blueprint are invalid. // This function decides completely on the given error, therefore no error will be returned explicitly again. func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError error) { @@ -166,7 +164,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError Message: "blueprint spec is invalid", } conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeValid, + Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "inconsistent blueprint", Message: err.Error(), @@ -176,7 +174,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError } } else { meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeValid, + Type: ConditionValid, Status: metav1.ConditionTrue, }) } @@ -198,7 +196,7 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { validationError := spec.EffectiveBlueprint.validateOnlyConfigForDogusInBlueprint() if validationError != nil { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeValid, + Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "inconsistent blueprint", Message: validationError.Error(), @@ -316,7 +314,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( invalidBlueprintError := spec.validateStateDiff() if invalidBlueprintError != nil { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeExecutable, + Type: ConditionExecutable, Status: metav1.ConditionFalse, Reason: "forbidden operations needed", Message: invalidBlueprintError.Error(), @@ -328,7 +326,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( } meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionTypeExecutable, + Type: ConditionExecutable, Status: metav1.ConditionTrue, }) //TODO: we cannot just deduplicate the events here by detecting a condition change, @@ -343,16 +341,31 @@ func (spec *BlueprintSpec) CheckEcosystemHealthUpfront(healthResult ecosystem.He // healthResult does not contain dogu info if IgnoreDoguHealth flag is set. (no need to load all doguInstallations then) // Therefore we don't need to exclude dogus while checking with AllHealthy() if healthResult.AllHealthy() { - spec.Status = StatusPhaseEcosystemHealthyUpfront - spec.Events = append(spec.Events, EcosystemHealthyUpfrontEvent{doguHealthIgnored: spec.Config.IgnoreDoguHealth, - componentHealthIgnored: spec.Config.IgnoreComponentHealth}) + event := EcosystemHealthyUpfrontEvent{ + doguHealthIgnored: spec.Config.IgnoreDoguHealth, + componentHealthIgnored: spec.Config.IgnoreComponentHealth, + } + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionTrue, + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } return nil } else { - //TODO: set health condition here in the future - spec.Events = append(spec.Events, EcosystemUnhealthyUpfrontEvent{HealthResult: healthResult}) + event := EcosystemUnhealthyUpfrontEvent{HealthResult: healthResult} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionFalse, + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } return NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy before applying the blueprint", healthResult) } - } // ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 5bbc105b..e6b7feef 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -54,7 +54,7 @@ func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { err := spec.ValidateStatically() - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeValid)) + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionValid)) require.NotNil(t, err, "No ID definition should lead to an error") var invalidError *InvalidBlueprintError assert.ErrorAs(t, err, &invalidError) @@ -313,7 +313,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, } - assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionTypeExecutable)) + assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionExecutable)) require.NoError(t, err) require.Equal(t, 5, len(spec.Events)) assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) @@ -362,7 +362,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionTypeExecutable)) + assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionExecutable)) }) t.Run("invalid blueprint state with not allowed dogu namespace switch", func(t *testing.T) { @@ -398,7 +398,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") }) @@ -433,7 +433,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"component namespace switch\" is not allowed") }) @@ -468,85 +468,106 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionTypeExecutable)) + assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"downgrade\" is not allowed") }) } func TestBlueprintSpec_CheckEcosystemHealthUpfront(t *testing.T) { - tests := []struct { - name string - inputSpec *BlueprintSpec - healthResult ecosystem.HealthResult - expectedStatus StatusPhase - expectedEventNames []string - expectedEventMsgs []string - }{ - { - name: "should return early if health result is empty", - inputSpec: &BlueprintSpec{Config: BlueprintConfiguration{}}, - healthResult: ecosystem.HealthResult{}, - expectedStatus: StatusPhaseEcosystemHealthyUpfront, - expectedEventNames: []string{"EcosystemHealthyUpfront"}, - expectedEventMsgs: []string{"dogu health ignored: false; component health ignored: false"}, - }, - { - name: "should post ignored dogu health in event", - inputSpec: &BlueprintSpec{Config: BlueprintConfiguration{IgnoreDoguHealth: true}}, - healthResult: ecosystem.HealthResult{}, - expectedStatus: StatusPhaseEcosystemHealthyUpfront, - expectedEventNames: []string{"EcosystemHealthyUpfront"}, - expectedEventMsgs: []string{"dogu health ignored: true; component health ignored: false"}, - }, - { - name: "should post ignored component health in event", - inputSpec: &BlueprintSpec{Config: BlueprintConfiguration{IgnoreComponentHealth: true}}, - healthResult: ecosystem.HealthResult{}, - expectedStatus: StatusPhaseEcosystemHealthyUpfront, - expectedEventNames: []string{"EcosystemHealthyUpfront"}, - expectedEventMsgs: []string{"dogu health ignored: false; component health ignored: true"}, - }, - { - name: "should write unhealthy dogus in event", - inputSpec: &BlueprintSpec{}, - healthResult: ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix"}, - ecosystem.UnavailableHealthStatus: {"ldap"}, - ecosystem.PendingHealthStatus: {"postgresql"}, - }, + t.Run("all healthy", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{} + err := blueprint.CheckEcosystemHealthUpfront(health) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionEcosystemHealthy)) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + require.NotNil(t, condition) + assert.Equal(t, "dogu health ignored: false; component health ignored: false", condition.Message) + + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, EcosystemHealthyUpfrontEvent{ + doguHealthIgnored: false, + componentHealthIgnored: false, + }, blueprint.Events[0]) + }) + + t.Run("no healthy event if condition status stays the same", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{} + + err := blueprint.CheckEcosystemHealthUpfront(health) + require.NoError(t, err) + + //reset events + blueprint.Events = []Event{} + require.Equal(t, 0, len(blueprint.Events)) + // call again and check if another event was created + err = blueprint.CheckEcosystemHealthUpfront(health) + require.NoError(t, err) + require.Equal(t, 0, len(blueprint.Events)) + }) + + t.Run("some unhealthy", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{ + DoguHealth: ecosystem.DoguHealthResult{ + DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.AvailableHealthStatus: {"postfix"}, + ecosystem.UnavailableHealthStatus: {"ldap"}, + ecosystem.PendingHealthStatus: {"postgresql"}, }, }, - expectedStatus: StatusPhaseEcosystemUnhealthyUpfront, - expectedEventNames: []string{"EcosystemUnhealthyUpfront"}, - expectedEventMsgs: []string{"ecosystem health:\n 2 dogu(s) are unhealthy: ldap, postgresql\n 0 component(s) are unhealthy: "}, - }, - { - name: "all dogus healthy", - inputSpec: &BlueprintSpec{}, - healthResult: ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix", "ldap", "postgresql"}, - }, + ComponentHealth: ecosystem.ComponentHealthResult{ + ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ + ecosystem.AvailableHealthStatus: {"dogu-operator"}, + ecosystem.UnavailableHealthStatus: {"component-operator"}, + ecosystem.PendingHealthStatus: {"service-discovery"}, }, }, - expectedStatus: StatusPhaseEcosystemHealthyUpfront, - expectedEventNames: []string{"EcosystemHealthyUpfront"}, - expectedEventMsgs: []string{"dogu health ignored: false; component health ignored: false"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.inputSpec.CheckEcosystemHealthUpfront(tt.healthResult) - eventNames := util.Map(tt.inputSpec.Events, Event.Name) - eventMsgs := util.Map(tt.inputSpec.Events, Event.Message) - assert.ElementsMatch(t, tt.expectedEventNames, eventNames) - assert.ElementsMatch(t, tt.expectedEventMsgs, eventMsgs) - }) - } + } + err := blueprint.CheckEcosystemHealthUpfront(health) + require.Error(t, err) + assert.ErrorContains(t, err, "ecosystem is unhealthy before applying the blueprint") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionEcosystemHealthy)) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + require.NotNil(t, condition) + assert.Contains(t, condition.Message, "2 dogu(s) are unhealthy: ldap, postgresql") + assert.Contains(t, condition.Message, "2 component(s) are unhealthy: component-operator, service-discovery") + + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, EcosystemUnhealthyUpfrontEvent{HealthResult: health}, blueprint.Events[0]) + }) + + t.Run("no unhealthy event if condition status stays the same", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{ + DoguHealth: ecosystem.DoguHealthResult{ + DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.UnavailableHealthStatus: {"ldap"}, + }, + }, + } + + err := blueprint.CheckEcosystemHealthUpfront(health) + require.Error(t, err) + + //reset events + blueprint.Events = []Event{} + require.Equal(t, 0, len(blueprint.Events)) + // call again and check if another event was created + err = blueprint.CheckEcosystemHealthUpfront(health) + require.Error(t, err) + require.Equal(t, 0, len(blueprint.Events)) + }) } func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { @@ -603,9 +624,7 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { func TestBlueprintSpec_CompletePreProcessing(t *testing.T) { t.Run("ok", func(t *testing.T) { // given - spec := &BlueprintSpec{ - Status: StatusPhaseEcosystemHealthyUpfront, - } + spec := &BlueprintSpec{} // when spec.CompletePreProcessing() // then @@ -617,14 +636,12 @@ func TestBlueprintSpec_CompletePreProcessing(t *testing.T) { t.Run("dry run", func(t *testing.T) { // given spec := &BlueprintSpec{ - Status: StatusPhaseEcosystemHealthyUpfront, Config: BlueprintConfiguration{DryRun: true}, } // when spec.CompletePreProcessing() // then assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseEcosystemHealthyUpfront, Config: BlueprintConfiguration{DryRun: true}, Events: []Event{BlueprintDryRunEvent{}}, }) @@ -759,7 +776,7 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { blueprint.ValidateDynamically(nil) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionTypeValid)) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionValid)) require.Equal(t, 0, len(blueprint.Events)) }) @@ -772,7 +789,7 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { blueprint.ValidateDynamically(givenErr) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionTypeValid)) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionValid)) require.Equal(t, 1, len(blueprint.Events)) }) } From 7ba9da7335cbb87cd69d81972055f2ff1e9ff654 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 15:52:36 +0200 Subject: [PATCH 018/119] #121 remove StatusPhaseEcosystemUnhealthyUpfront --- pkg/application/blueprintSpecChangeUseCase.go | 2 - .../blueprintSpecChangeUseCase_test.go | 44 ------------------- pkg/domain/blueprintSpec.go | 2 - pkg/domain/blueprintSpec_test.go | 3 -- 4 files changed, 51 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index b095b5ed..ad009791 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -116,8 +116,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseEcosystemUnhealthyUpfront: - return nil case domain.StatusPhaseBlueprintApplicationPreProcessed: return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseAwaitSelfUpgrade: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 7ed50708..cd621d87 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -319,50 +319,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { require.NoError(t, err) }) - t.Run("handle unhealthy ecosystem upfront", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseEcosystemUnhealthyUpfront, - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - - t.Run("handle unhealthy ecosystem upfront", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseEcosystemUnhealthyUpfront, - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - t.Run("handle error apply registry config", func(t *testing.T) { // given repoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index c99ee608..aaec8fdf 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -49,8 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseEcosystemUnhealthyUpfront marks that some currently installed dogus are unhealthy. - StatusPhaseEcosystemUnhealthyUpfront StatusPhase = "ecosystemUnhealthyUpfront" // StatusPhaseBlueprintApplicationPreProcessed shows that all pre-processing steps for the blueprint application // were successful. StatusPhaseBlueprintApplicationPreProcessed StatusPhase = "blueprintApplicationPreProcessed" diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index e6b7feef..2059051f 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -575,7 +575,6 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { name string inputSpec *BlueprintSpec healthResult ecosystem.HealthResult - expectedStatus StatusPhase expectedEventNames []string expectedEventMsgs []string }{ @@ -591,7 +590,6 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { }, }, }, - expectedStatus: StatusPhaseEcosystemUnhealthyUpfront, expectedEventNames: []string{"EcosystemUnhealthyAfterwards"}, expectedEventMsgs: []string{"ecosystem health:\n 2 dogu(s) are unhealthy: ldap, postgresql\n 0 component(s) are unhealthy: "}, }, @@ -605,7 +603,6 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { }, }, }, - expectedStatus: StatusPhaseEcosystemHealthyAfterwards, expectedEventNames: []string{"EcosystemHealthyAfterwards"}, expectedEventMsgs: []string{""}, }, From 211cee19d28423bd8f7d7d29a1920f674abb0516 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 16:12:38 +0200 Subject: [PATCH 019/119] #121 remove blueprint pre-processing We removed the maintenance mode. Therefore, the only thing this use case still did, was to check the dry run flag. --- pkg/application/applyBlueprintSpecUseCase.go | 19 -------- .../applyBlueprintSpecUseCase_test.go | 41 ---------------- pkg/application/blueprintSpecChangeUseCase.go | 25 ++-------- .../blueprintSpecChangeUseCase_test.go | 41 ---------------- pkg/application/interfaces.go | 1 - .../mock_applyBlueprintSpecUseCase_test.go | 47 ------------------- pkg/application/selfUpgradeUseCase_test.go | 6 --- pkg/domain/blueprintSpec.go | 13 ----- pkg/domain/blueprintSpec_test.go | 31 +----------- 9 files changed, 6 insertions(+), 218 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index b9df715b..596de47c 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -84,25 +84,6 @@ func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx con return healthErr } -// PreProcessBlueprintApplication prepares the environment for applying the blueprint. -// returns a domainservice.InternalError on any error. -func (useCase *ApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PreProcessBlueprintApplication") - - if !blueprint.ShouldBeApplied() { - logger.Info("stop as blueprint should not be applied") - } - - blueprint.CompletePreProcessing() - - err := useCase.repo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after preprocessing: %w", blueprint.Id, err) - } - - return nil -} - // PostProcessBlueprintApplication makes changes to the environment after applying the blueprint, e.g. censoring the state diff. // returns a domainservice.InternalError on any error. func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index 6645ac5c..e91e2858 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -24,47 +24,6 @@ func TestNewApplyBlueprintSpecUseCase(t *testing.T) { assert.Equal(t, healthMock, sut.healthUseCase) } -func TestApplyBlueprintSpecUseCase_PreProcessBlueprintApplication(t *testing.T) { - t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - err := useCase.PreProcessBlueprintApplication(testCtx, spec) - - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationPreProcessed, spec.Status) - }) - t.Run("repo error while saving", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - err := useCase.PreProcessBlueprintApplication(testCtx, spec) - - require.ErrorIs(t, err, assert.AnError) - }) - t.Run("do nothing on dry run", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Config: domain.BlueprintConfiguration{DryRun: true}, - } - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) - - err := useCase.PreProcessBlueprintApplication(testCtx, spec) - - require.NoError(t, err) - require.Equal(t, 1, len(spec.Events)) - assert.Equal(t, domain.BlueprintDryRunEvent{}, spec.Events[0]) - }) -} - func TestApplyBlueprintSpecUseCase_markInProgress(t *testing.T) { t.Run("ok", func(t *testing.T) { spec := &domain.BlueprintSpec{} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index ad009791..bdebb5ad 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -92,16 +92,16 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } - err = useCase.applyUseCase.PreProcessBlueprintApplication(ctx, blueprint) - if err != nil { - return err - } if !blueprint.ShouldBeApplied() { - // event recording and so on happen in PreProcessBlueprintApplication // just stop the loop here on dry run or early exit return nil } + err = useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) + if err != nil { + return err + } + // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. for blueprint.Status != domain.StatusPhaseCompleted { err := useCase.handleChange(ctx, blueprint) @@ -116,8 +116,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseBlueprintApplicationPreProcessed: - return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseAwaitSelfUpgrade: return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseSelfUpgradeCompleted: @@ -149,16 +147,3 @@ func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blu return fmt.Errorf("could not handle unknown status of blueprint") } } - -func (useCase *BlueprintSpecChangeUseCase) preProcessBlueprintApplication(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - err := useCase.applyUseCase.PreProcessBlueprintApplication(ctx, blueprintSpec) - if err != nil { - return err - } - if !blueprintSpec.ShouldBeApplied() { - // event recording and so on happen in PreProcessBlueprintApplication - // just stop the loop here on dry run or early exit - return nil - } - return useCase.HandleUntilApplied(ctx, blueprintSpec.Id) -} diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index cd621d87..9c7c0ff1 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -70,10 +70,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) }) applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil) - applyMock.EXPECT().PreProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseBlueprintApplicationPreProcessed - }) ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseEcosystemConfigApplied }) @@ -563,43 +559,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) } -func TestBlueprintSpecChangeUseCase_preProcessBlueprintApplication(t *testing.T) { - t.Run("stop on dry run", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - Config: domain.BlueprintConfiguration{DryRun: true}, - } - applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprint).Return(nil) - useCase := NewBlueprintSpecChangeUseCase(nil, nil, nil, nil, applyMock, nil, nil, nil) - // when - err := useCase.preProcessBlueprintApplication(testCtx, blueprint) - // then - require.NoError(t, err) - }) - t.Run("error", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - } - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - applyMock.EXPECT().PreProcessBlueprintApplication(testCtx, blueprint).Return(assert.AnError) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - // when - err := useCase.preProcessBlueprintApplication(testCtx, blueprint) - // then - require.ErrorIs(t, err, assert.AnError) - }) -} - func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { t.Run("handle error in TriggerDoguRestarts", func(t *testing.T) { // given diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 592b2b49..5ee6c4d0 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -40,7 +40,6 @@ type componentInstallationUseCase interface { type applyBlueprintSpecUseCase interface { CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error - PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error } diff --git a/pkg/application/mock_applyBlueprintSpecUseCase_test.go b/pkg/application/mock_applyBlueprintSpecUseCase_test.go index 3b5c7838..703631e2 100644 --- a/pkg/application/mock_applyBlueprintSpecUseCase_test.go +++ b/pkg/application/mock_applyBlueprintSpecUseCase_test.go @@ -210,53 +210,6 @@ func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Ru return _c } -// PreProcessBlueprintApplication provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyBlueprintSpecUseCase) PreProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for PreProcessBlueprintApplication") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PreProcessBlueprintApplication' -type mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call struct { - *mock.Call -} - -// PreProcessBlueprintApplication is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyBlueprintSpecUseCase_Expecter) PreProcessBlueprintApplication(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { - return &mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call{Call: _e.mock.On("PreProcessBlueprintApplication", ctx, blueprint)} -} - -func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) Return(_a0 error) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_PreProcessBlueprintApplication_Call { - _c.Call.Return(run) - return _c -} - // newMockApplyBlueprintSpecUseCase creates a new instance of mockApplyBlueprintSpecUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func newMockApplyBlueprintSpecUseCase(t interface { diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go index fd8edc1a..1a55cdf9 100644 --- a/pkg/application/selfUpgradeUseCase_test.go +++ b/pkg/application/selfUpgradeUseCase_test.go @@ -48,7 +48,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } component := &ecosystem.ComponentInstallation{ @@ -149,7 +148,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // but usually cancel @@ -178,7 +176,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(nil, internalTestError) @@ -199,7 +196,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ Id: blueprintId, StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } component := &ecosystem.ComponentInstallation{ @@ -224,7 +220,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } component := &ecosystem.ComponentInstallation{ @@ -251,7 +246,6 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ Id: blueprintId, StateDiff: domain.StateDiff{}, - Status: domain.StatusPhaseBlueprintApplicationPreProcessed, } blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(assert.AnError) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index aaec8fdf..9fa9ced4 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -49,9 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseBlueprintApplicationPreProcessed shows that all pre-processing steps for the blueprint application - // were successful. - StatusPhaseBlueprintApplicationPreProcessed StatusPhase = "blueprintApplicationPreProcessed" // StatusPhaseAwaitSelfUpgrade marks that the blueprint operator waits for termination for a self upgrade. StatusPhaseAwaitSelfUpgrade StatusPhase = "awaitSelfUpgrade" // StatusPhaseSelfUpgradeCompleted marks that the blueprint operator itself got successfully upgraded. @@ -372,16 +369,6 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { return !spec.Config.DryRun } -// CompletePreProcessing decides if the blueprint is ready to be applied or not by setting the fitting next status phase. -func (spec *BlueprintSpec) CompletePreProcessing() { - if spec.Config.DryRun { - spec.Events = append(spec.Events, BlueprintDryRunEvent{}) - } else { - spec.Status = StatusPhaseBlueprintApplicationPreProcessed - spec.Events = append(spec.Events, BlueprintApplicationPreProcessedEvent{}) - } -} - func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { if spec.Status != StatusPhaseAwaitSelfUpgrade { spec.Status = StatusPhaseAwaitSelfUpgrade diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 2059051f..feaac8e9 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -618,33 +618,6 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { } } -func TestBlueprintSpec_CompletePreProcessing(t *testing.T) { - t.Run("ok", func(t *testing.T) { - // given - spec := &BlueprintSpec{} - // when - spec.CompletePreProcessing() - // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseBlueprintApplicationPreProcessed, - Events: []Event{BlueprintApplicationPreProcessedEvent{}}, - }) - }) - t.Run("dry run", func(t *testing.T) { - // given - spec := &BlueprintSpec{ - Config: BlueprintConfiguration{DryRun: true}, - } - // when - spec.CompletePreProcessing() - // then - assert.Equal(t, spec, &BlueprintSpec{ - Config: BlueprintConfiguration{DryRun: true}, - Events: []Event{BlueprintDryRunEvent{}}, - }) - }) -} - func TestBlueprintSpec_StartApplying(t *testing.T) { t.Run("ok", func(t *testing.T) { // given @@ -813,9 +786,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { func TestBlueprintSpec_MarkWaitingForSelfUpgrade(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{ - Status: StatusPhaseBlueprintApplicationPreProcessed, - } + blueprint := BlueprintSpec{} blueprint.MarkWaitingForSelfUpgrade() assert.Equal(t, StatusPhaseAwaitSelfUpgrade, blueprint.Status) From ba285aefa94d39ee08bc1f37e1737273adbeb218 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 13 Aug 2025 16:51:51 +0200 Subject: [PATCH 020/119] #121 remove StatusPhaseEcosystemHealthyAfterwards --- .../applyBlueprintSpecUseCase_test.go | 18 ++- pkg/application/blueprintSpecChangeUseCase.go | 9 +- .../blueprintSpecChangeUseCase_test.go | 8 +- pkg/domain/blueprintSpec.go | 33 ++-- pkg/domain/blueprintSpec_test.go | 143 ++++++++++++------ 5 files changed, 135 insertions(+), 76 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index e91e2858..5737bc76 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" ) @@ -371,7 +373,9 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) t.Run("should succeed", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) @@ -385,15 +389,15 @@ func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) // then require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseEcosystemHealthyAfterwards, blueprint.Status) + condition := meta.FindStatusCondition(*blueprint.Conditions, domain.ConditionEcosystemHealthy) + require.NotNil(t, condition) + assert.Equal(t, metav1.ConditionTrue, condition.Status) }) } func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) { t.Run("ok", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyAfterwards, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) @@ -409,9 +413,7 @@ func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) assert.Contains(t, blueprint.Events, domain.CompletedEvent{}, blueprint.Events) }) t.Run("repo error while saving", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Status: domain.StatusPhaseEcosystemHealthyAfterwards, - } + blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index bdebb5ad..a0990a3b 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -130,11 +130,12 @@ func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blu case domain.StatusPhaseBlueprintApplied: return useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprint) case domain.StatusPhaseRestartsTriggered: - return useCase.applyUseCase.CheckEcosystemHealthAfterwards(ctx, blueprint) - case domain.StatusPhaseBlueprintApplicationFailed: + err := useCase.applyUseCase.CheckEcosystemHealthAfterwards(ctx, blueprint) + if err != nil { + return err + } return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) - case domain.StatusPhaseEcosystemHealthyAfterwards: - // censor and set status to completed + case domain.StatusPhaseBlueprintApplicationFailed: return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseEcosystemUnhealthyAfterwards: // censor and set status to failed diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 9c7c0ff1..83c78049 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -80,10 +80,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseBlueprintApplied }) - applyMock.EXPECT().CheckEcosystemHealthAfterwards(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseEcosystemHealthyAfterwards - }) + applyMock.EXPECT().CheckEcosystemHealthAfterwards(mock.Anything, blueprintSpec).Return(nil) applyMock.EXPECT().PostProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseCompleted @@ -429,8 +426,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseEcosystemHealthyAfterwards, + Id: "testBlueprint1", } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 9fa9ced4..428facab 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -59,8 +59,6 @@ const ( StatusPhaseBlueprintApplicationFailed StatusPhase = "blueprintApplicationFailed" // StatusPhaseBlueprintApplied indicates that the blueprint was applied but the ecosystem is not healthy yet. StatusPhaseBlueprintApplied StatusPhase = "blueprintApplied" - // StatusPhaseEcosystemHealthyAfterwards shows that the ecosystem got healthy again after applying the blueprint. - StatusPhaseEcosystemHealthyAfterwards StatusPhase = "ecosystemHealthyAfterwards" // StatusPhaseEcosystemUnhealthyAfterwards shows that the ecosystem got not healthy again after applying the blueprint. StatusPhaseEcosystemUnhealthyAfterwards StatusPhase = "ecosystemUnhealthyAfterwards" // StatusPhaseFailed marks that an error occurred during processing of the blueprint. @@ -386,13 +384,28 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { // CheckEcosystemHealthAfterwards checks with the given health result if the ecosystem is healthy and the blueprint was therefore successful. func (spec *BlueprintSpec) CheckEcosystemHealthAfterwards(healthResult ecosystem.HealthResult) error { if healthResult.AllHealthy() { - spec.Status = StatusPhaseEcosystemHealthyAfterwards - spec.Events = append(spec.Events, EcosystemHealthyAfterwardsEvent{}) + event := EcosystemHealthyAfterwardsEvent{} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionTrue, + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } + return nil } else { - //TODO write condition here in the future + event := EcosystemUnhealthyAfterwardsEvent{HealthResult: healthResult} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionFalse, + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } spec.Status = StatusPhaseEcosystemUnhealthyAfterwards - spec.Events = append(spec.Events, EcosystemUnhealthyAfterwardsEvent{HealthResult: healthResult}) return NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy after applying the blueprint", healthResult) } } @@ -428,9 +441,6 @@ func (spec *BlueprintSpec) CensorSensitiveData() { // CompletePostProcessing is used to mark the blueprint as completed or failed , depending on the blueprint application result. func (spec *BlueprintSpec) CompletePostProcessing() { switch spec.Status { - case StatusPhaseEcosystemHealthyAfterwards: - spec.Status = StatusPhaseCompleted - spec.Events = append(spec.Events, CompletedEvent{}) case StatusPhaseApplyEcosystemConfigFailed: fallthrough case StatusPhaseEcosystemUnhealthyAfterwards: @@ -444,7 +454,12 @@ func (spec *BlueprintSpec) CompletePostProcessing() { case StatusPhaseBlueprintApplicationFailed: spec.Status = StatusPhaseFailed spec.Events = append(spec.Events, ExecutionFailedEvent{err: errors.New("could not apply blueprint")}) + default: + //if healthy + spec.Status = StatusPhaseCompleted + spec.Events = append(spec.Events, CompletedEvent{}) } + } var notAllowedComponentActions = []Action{ActionDowngrade, ActionSwitchComponentNamespace} diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index feaac8e9..ffe01866 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -14,7 +14,6 @@ import ( "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" ) var version3211, _ = core.ParseVersion("3.2.1-1") @@ -571,51 +570,100 @@ func TestBlueprintSpec_CheckEcosystemHealthUpfront(t *testing.T) { } func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { - tests := []struct { - name string - inputSpec *BlueprintSpec - healthResult ecosystem.HealthResult - expectedEventNames []string - expectedEventMsgs []string - }{ - { - name: "should write unhealthy dogus in event", - inputSpec: &BlueprintSpec{}, - healthResult: ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix"}, - ecosystem.UnavailableHealthStatus: {"ldap"}, - ecosystem.PendingHealthStatus: {"postgresql"}, - }, + t.Run("all healthy", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{} + + err := blueprint.CheckEcosystemHealthAfterwards(health) + + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionEcosystemHealthy)) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + require.NotNil(t, condition) + + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, EcosystemHealthyAfterwardsEvent{}, blueprint.Events[0]) + }) + + t.Run("no healthy event if condition status stays the same", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{} + + err := blueprint.CheckEcosystemHealthAfterwards(health) + require.NoError(t, err) + + //reset events + blueprint.Events = []Event{} + require.Equal(t, 0, len(blueprint.Events)) + + // call again and check if another event was created + err = blueprint.CheckEcosystemHealthAfterwards(health) + require.NoError(t, err) + require.Equal(t, 0, len(blueprint.Events)) + }) + + t.Run("some unhealthy", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{ + DoguHealth: ecosystem.DoguHealthResult{ + DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.AvailableHealthStatus: {"postfix"}, + ecosystem.UnavailableHealthStatus: {"ldap"}, + ecosystem.PendingHealthStatus: {"postgresql"}, }, }, - expectedEventNames: []string{"EcosystemUnhealthyAfterwards"}, - expectedEventMsgs: []string{"ecosystem health:\n 2 dogu(s) are unhealthy: ldap, postgresql\n 0 component(s) are unhealthy: "}, - }, - { - name: "ecosystem healthy", - inputSpec: &BlueprintSpec{}, - healthResult: ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix", "ldap", "postgresql"}, - }, + ComponentHealth: ecosystem.ComponentHealthResult{ + ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ + ecosystem.AvailableHealthStatus: {"dogu-operator"}, + ecosystem.UnavailableHealthStatus: {"component-operator"}, + ecosystem.PendingHealthStatus: {"service-discovery"}, }, }, - expectedEventNames: []string{"EcosystemHealthyAfterwards"}, - expectedEventMsgs: []string{""}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.inputSpec.CheckEcosystemHealthAfterwards(tt.healthResult) - eventNames := util.Map(tt.inputSpec.Events, Event.Name) - eventMsgs := util.Map(tt.inputSpec.Events, Event.Message) - assert.ElementsMatch(t, tt.expectedEventNames, eventNames) - assert.ElementsMatch(t, tt.expectedEventMsgs, eventMsgs) - }) - } + } + + err := blueprint.CheckEcosystemHealthAfterwards(health) + + require.Error(t, err) + assert.ErrorContains(t, err, "ecosystem is unhealthy after applying the blueprint") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionEcosystemHealthy)) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + require.NotNil(t, condition) + assert.Contains(t, condition.Message, "2 dogu(s) are unhealthy: ldap, postgresql") + assert.Contains(t, condition.Message, "2 component(s) are unhealthy: component-operator, service-discovery") + + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, EcosystemUnhealthyAfterwardsEvent{HealthResult: health}, blueprint.Events[0]) + }) + + t.Run("no unhealthy event if condition status stays the same", func(t *testing.T) { + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{ + DoguHealth: ecosystem.DoguHealthResult{ + DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.UnavailableHealthStatus: {"ldap"}, + }, + }, + } + + err := blueprint.CheckEcosystemHealthAfterwards(health) + require.Error(t, err) + + //reset events + blueprint.Events = []Event{} + require.Equal(t, 0, len(blueprint.Events)) + // call again and check if another event was created + err = blueprint.CheckEcosystemHealthAfterwards(health) + require.Error(t, err) + require.Equal(t, 0, len(blueprint.Events)) + }) } func TestBlueprintSpec_StartApplying(t *testing.T) { @@ -683,9 +731,7 @@ func TestBlueprintSpec_CensorSensitiveData(t *testing.T) { func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { t.Run("status change on success EcosystemHealthyAfterwards -> Completed", func(t *testing.T) { // given - spec := &BlueprintSpec{ - Status: StatusPhaseEcosystemHealthyAfterwards, - } + spec := &BlueprintSpec{} // when spec.CompletePostProcessing() // then @@ -703,10 +749,9 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { // when spec.CompletePostProcessing() // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseFailed, - Events: []Event{ExecutionFailedEvent{errors.New(handleInProgressMsg)}}, - }) + assert.Equal(t, StatusPhaseFailed, spec.Status) + require.Equal(t, 1, len(spec.Events)) + assert.Equal(t, ExecutionFailedEvent{errors.New(handleInProgressMsg)}, spec.Events[0]) }) t.Run("status change on failure EcosystemUnhealthyAfterwards -> Failed", func(t *testing.T) { From 1cfd2feb0d8b5efdf6fb476862da9d92b0593ab1 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 09:19:55 +0200 Subject: [PATCH 021/119] #121 remove censoring in post-processing In previous versions, the state diff was calculated once and then got persisted in the blueprint CR. Therefore, we had to write all fields there. Now we calculate the state diff at every reconciliation. Because of that we can now decide which fields we want to publish in the blueprint CR. --- pkg/application/applyBlueprintSpecUseCase.go | 5 -- .../applyBlueprintSpecUseCase_test.go | 3 +- pkg/domain/blueprintSpec.go | 8 --- pkg/domain/blueprintSpec_test.go | 24 -------- pkg/domain/events.go | 10 ---- pkg/domain/stateDiffConfig.go | 10 ---- pkg/domain/stateDiffConfig_test.go | 59 ------------------- 7 files changed, 1 insertion(+), 118 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index 596de47c..98cf4d93 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -87,11 +87,6 @@ func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx con // PostProcessBlueprintApplication makes changes to the environment after applying the blueprint, e.g. censoring the state diff. // returns a domainservice.InternalError on any error. func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.PostProcessBlueprintApplication") - - logger.Info("censor sensitive data") - blueprint.CensorSensitiveData() - blueprint.CompletePostProcessing() err := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index 5737bc76..bea3cb29 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -408,8 +408,7 @@ func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) require.NoError(t, err) assert.Equal(t, domain.StatusPhaseCompleted, blueprint.Status) - assert.Len(t, blueprint.Events, 2) - assert.Contains(t, blueprint.Events, domain.SensitiveConfigDataCensoredEvent{}, blueprint.Events) + assert.Len(t, blueprint.Events, 1) assert.Contains(t, blueprint.Events, domain.CompletedEvent{}, blueprint.Events) }) t.Run("repo error while saving", func(t *testing.T) { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 428facab..55c5bf59 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -430,14 +430,6 @@ func (spec *BlueprintSpec) MarkBlueprintApplied() { spec.Events = append(spec.Events, BlueprintAppliedEvent{}) } -// CensorSensitiveData censors all sensitive configuration data of the blueprint, effective blueprint and the statediff, -// to make the values unrecognisable. -func (spec *BlueprintSpec) CensorSensitiveData() { - spec.StateDiff.SensitiveDoguConfigDiffs = censorValues(spec.StateDiff.SensitiveDoguConfigDiffs) - - spec.Events = append(spec.Events, SensitiveConfigDataCensoredEvent{}) -} - // CompletePostProcessing is used to mark the blueprint as completed or failed , depending on the blueprint application result. func (spec *BlueprintSpec) CompletePostProcessing() { switch spec.Status { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index ffe01866..5250da02 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -5,7 +5,6 @@ import ( "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "golang.org/x/exp/maps" "k8s.io/apimachinery/pkg/api/meta" "testing" @@ -705,29 +704,6 @@ func TestBlueprintSpec_MarkBlueprintApplied(t *testing.T) { }) } -func TestBlueprintSpec_CensorSensitiveData(t *testing.T) { - // given - spec := &BlueprintSpec{ - StateDiff: StateDiff{ - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - "ldapDiff": []SensitiveDoguConfigEntryDiff{{ - Actual: DoguConfigValueState{Value: "Test1"}, - Expected: DoguConfigValueState{Value: "Test2"}, - }}, - }, - }, - } - // when - spec.CensorSensitiveData() - - // then - require.Len(t, spec.StateDiff.SensitiveDoguConfigDiffs, 1) - assert.Contains(t, maps.Keys(spec.StateDiff.SensitiveDoguConfigDiffs), cescommons.SimpleName("ldapDiff")) - require.Len(t, spec.StateDiff.SensitiveDoguConfigDiffs["ldapDiff"], 1) - assert.Equal(t, censorValue, spec.StateDiff.SensitiveDoguConfigDiffs["ldapDiff"][0].Actual.Value) - assert.Equal(t, censorValue, spec.StateDiff.SensitiveDoguConfigDiffs["ldapDiff"][0].Expected.Value) -} - func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { t.Run("status change on success EcosystemHealthyAfterwards -> Completed", func(t *testing.T) { // given diff --git a/pkg/domain/events.go b/pkg/domain/events.go index bd247663..bfdf438a 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -282,16 +282,6 @@ func (e EcosystemUnhealthyAfterwardsEvent) Message() string { return e.HealthResult.String() } -type SensitiveConfigDataCensoredEvent struct{} - -func (e SensitiveConfigDataCensoredEvent) Name() string { - return "sensitiveConfigDataCensored" -} - -func (e SensitiveConfigDataCensoredEvent) Message() string { - return "" -} - type ExecutionFailedEvent struct { err error } diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index 8ce1083c..f7b5ca3d 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -4,7 +4,6 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-registry-lib/config" - "maps" ) type ConfigAction string @@ -18,15 +17,6 @@ const ( ConfigActionRemove ConfigAction = "remove" ) -// censorValues censors all sensitive configuration data to make them unrecognisable. -func censorValues(sensitiveConfigByDogu map[cescommons.SimpleName]SensitiveDoguConfigDiffs) map[cescommons.SimpleName]SensitiveDoguConfigDiffs { - censoredByDogu := maps.Clone(sensitiveConfigByDogu) - for dogu, entryDiffs := range sensitiveConfigByDogu { - censoredByDogu[dogu] = entryDiffs.CensorValues() - } - return censoredByDogu -} - func countByAction(diffsByDogu map[cescommons.SimpleName]DoguConfigDiffs) map[ConfigAction]int { countByAction := map[ConfigAction]int{} for _, doguDiffs := range diffsByDogu { diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index 547e9458..4d2875eb 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -438,62 +438,3 @@ func Test_getNeededConfigAction(t *testing.T) { }) } } - -func Test_censorValues(t *testing.T) { - tests := []struct { - name string - configByDogu map[cescommons.SimpleName]SensitiveDoguConfigDiffs - want map[cescommons.SimpleName]SensitiveDoguConfigDiffs - }{ - { - name: "no diff at all", - configByDogu: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, - want: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, - }, - { - name: "no diff for dogu", - configByDogu: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - dogu1: nil, - }, - want: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - dogu1: nil, - }, - }, - { - name: "censored actual and expected values", - configByDogu: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - dogu1: {DoguConfigEntryDiff{ - Key: dogu1Key1, - Actual: DoguConfigValueState{ - Value: "123", - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: "1234", - Exists: true, - }, - NeededAction: ConfigActionSet, - }}, - }, - want: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - dogu1: {DoguConfigEntryDiff{ - Key: dogu1Key1, - Actual: DoguConfigValueState{ - Value: censorValue, - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: censorValue, - Exists: true, - }, - NeededAction: ConfigActionSet, - }}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, censorValues(tt.configByDogu), "censorValues(%v)", tt.configByDogu) - }) - } -} From 9d46832306306d3e4b4043cfd20e6b3037fd17de Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 09:36:35 +0200 Subject: [PATCH 022/119] #121 do not write sensitive data in blueprint CR --- .../v2/serializer/doguConfigDiff.go | 28 +++--- .../v2/serializer/doguConfigDiff_test.go | 3 +- .../blueprintcr/v2/serializer/stateDiff.go | 4 +- .../v2/serializer/stateDiff_test.go | 85 +++++++++++++++++-- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go index f994ffc3..80663772 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go @@ -34,25 +34,29 @@ func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.DoguConfigEntry } } -func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs) []crd.DoguConfigEntryDiff { +func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) []crd.DoguConfigEntryDiff { var dtoDiffs []crd.DoguConfigEntryDiff for _, domainDiff := range domainDiffs { - dtoDiffs = append(dtoDiffs, convertToDoguConfigEntryDiffDTO(domainDiff)) + dtoDiffs = append(dtoDiffs, convertToDoguConfigEntryDiffDTO(domainDiff, isSensitive)) } return dtoDiffs } -func convertToDoguConfigEntryDiffDTO(domainModel domain.DoguConfigEntryDiff) crd.DoguConfigEntryDiff { +func convertToDoguConfigEntryDiffDTO(domainModel domain.DoguConfigEntryDiff, isSensitive bool) crd.DoguConfigEntryDiff { + actual := crd.DoguConfigValueState{ + Exists: domainModel.Actual.Exists, + } + expected := crd.DoguConfigValueState{ + Exists: domainModel.Expected.Exists, + } + if !isSensitive { + actual.Value = domainModel.Actual.Value + expected.Value = domainModel.Expected.Value + } return crd.DoguConfigEntryDiff{ - Key: string(domainModel.Key.Key), - Actual: crd.DoguConfigValueState{ - Value: domainModel.Actual.Value, - Exists: domainModel.Actual.Exists, - }, - Expected: crd.DoguConfigValueState{ - Value: domainModel.Expected.Value, - Exists: domainModel.Expected.Exists, - }, + Key: string(domainModel.Key.Key), + Actual: actual, + Expected: expected, NeededAction: crd.ConfigAction(domainModel.NeededAction), } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go index ffbccd6e..a41b1d7b 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go @@ -13,6 +13,7 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { name string domainModel domain.DoguConfigDiffs want []crd.DoguConfigEntryDiff + isSensitive bool }{ { name: "should exit early if slices are empty", @@ -81,7 +82,7 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertToDoguConfigEntryDiffsDTO(tt.domainModel), "convertToDoguConfigEntryDiffsDTO(%v)", tt.domainModel) + assert.Equalf(t, tt.want, convertToDoguConfigEntryDiffsDTO(tt.domainModel, tt.isSensitive), "convertToDoguConfigEntryDiffsDTO(%v)", tt.domainModel) }) } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go index ce1f14d2..7c78b947 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go @@ -29,12 +29,12 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) crd.StateDiff { combinedConfigDiffs = make(map[string]crd.CombinedDoguConfigDiff) doguConfigDiffByDogu = make(map[cescommons.SimpleName]crd.DoguConfigDiff) for doguName, doguConfigDiff := range domainModel.DoguConfigDiffs { - doguConfigDiffByDogu[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff) + doguConfigDiffByDogu[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, false) dogus = append(dogus, doguName) } sensitiveDoguConfigDiff = make(map[cescommons.SimpleName]crd.SensitiveDoguConfigDiff) for doguName, doguConfigDiff := range domainModel.SensitiveDoguConfigDiffs { - sensitiveDoguConfigDiff[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff) + sensitiveDoguConfigDiff[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, true) dogus = append(dogus, doguName) } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index 52fa9520..0a7f0c2e 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -794,7 +794,86 @@ func TestConvertToStateDiffDTO(t *testing.T) { want crd.StateDiff }{ { - name: "ok", + name: "normal dogu config", + model: domain.StateDiff{ + DoguDiffs: nil, + ComponentDiffs: nil, + DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ + testDogu: { + { + Key: testDoguKey1, + Actual: domain.DoguConfigValueState{ + Value: "1", + Exists: true, + }, + Expected: domain.DoguConfigValueState{ + Value: "123", + Exists: true, + }, + NeededAction: domain.ConfigActionSet, + }, + }, + testDogu2: { + { + Key: testDoguKey2, + Actual: domain.DoguConfigValueState{ + Value: "", + Exists: false, + }, + Expected: domain.DoguConfigValueState{ + Value: "123", + Exists: true, + }, + NeededAction: domain.ConfigActionSet, + }, + }, + }, + GlobalConfigDiffs: nil, + }, + want: crd.StateDiff{ + DoguDiffs: map[string]crd.DoguDiff{}, + ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ + testDogu.String(): { + SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff(nil), + DoguConfigDiff: crd.DoguConfigDiff{ + crd.DoguConfigEntryDiff{ + Key: testDoguKey1.Key.String(), + Actual: crd.DoguConfigValueState{ + Value: "1", + Exists: true, + }, + Expected: crd.DoguConfigValueState{ + Value: "123", + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), + }, + }, + }, + testDogu2.String(): { + SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff(nil), + DoguConfigDiff: crd.DoguConfigDiff{ + crd.DoguConfigEntryDiff{ + Key: testDoguKey2.Key.String(), + Actual: crd.DoguConfigValueState{ + Value: "", + Exists: false, + }, + Expected: crd.DoguConfigValueState{ + Value: "123", + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), + }, + }, + }, + }, + GlobalConfigDiff: nil, + }, + }, + { + name: "censor sensitive config", model: domain.StateDiff{ DoguDiffs: nil, ComponentDiffs: nil, @@ -841,11 +920,9 @@ func TestConvertToStateDiffDTO(t *testing.T) { crd.DoguConfigEntryDiff{ Key: testDoguKey1.Key.String(), Actual: crd.DoguConfigValueState{ - Value: "1", Exists: true, }, Expected: crd.DoguConfigValueState{ - Value: "123", Exists: true, }, NeededAction: crd.ConfigAction("set"), @@ -857,11 +934,9 @@ func TestConvertToStateDiffDTO(t *testing.T) { crd.DoguConfigEntryDiff{ Key: testDoguKey2.Key.String(), Actual: crd.DoguConfigValueState{ - Value: "", Exists: false, }, Expected: crd.DoguConfigValueState{ - Value: "123", Exists: true, }, NeededAction: crd.ConfigAction("set"), From 0aeff2a769ac18989ec28f668e951705ee85d402 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 09:43:11 +0200 Subject: [PATCH 023/119] #121 remove unused censoring functions --- pkg/application/applyBlueprintSpecUseCase.go | 2 +- pkg/domain/blueprintSpec.go | 3 --- pkg/domain/stateDiffDoguConfig.go | 21 -------------------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index 98cf4d93..f281edf9 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -84,7 +84,7 @@ func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx con return healthErr } -// PostProcessBlueprintApplication makes changes to the environment after applying the blueprint, e.g. censoring the state diff. +// PostProcessBlueprintApplication makes changes to the environment after applying the blueprint. // returns a domainservice.InternalError on any error. func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { blueprint.CompletePostProcessing() diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 55c5bf59..77b56615 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -76,9 +76,6 @@ const ( StatusPhaseRestartsTriggered StatusPhase = "restartsTriggered" ) -// censorValue is the value for censoring sensitive blueprint configuration data. -const censorValue = "*****" - type BlueprintConfiguration struct { // IgnoreDoguHealth forces blueprint upgrades even if dogus are unhealthy IgnoreDoguHealth bool diff --git a/pkg/domain/stateDiffDoguConfig.go b/pkg/domain/stateDiffDoguConfig.go index 3088df47..e788fbde 100644 --- a/pkg/domain/stateDiffDoguConfig.go +++ b/pkg/domain/stateDiffDoguConfig.go @@ -18,27 +18,6 @@ func (diffs DoguConfigDiffs) HasChanges() bool { return false } -func (diffs SensitiveDoguConfigDiffs) CensorValues() SensitiveDoguConfigDiffs { - var censoredEntries []DoguConfigEntryDiff - for _, entry := range diffs { - actual := entry.Actual - expected := entry.Expected - if len(entry.Actual.Value) > 0 { - actual.Value = censorValue - } - if len(entry.Expected.Value) > 0 { - expected.Value = censorValue - } - censoredEntries = append(censoredEntries, DoguConfigEntryDiff{ - Key: entry.Key, - Actual: actual, - Expected: expected, - NeededAction: entry.NeededAction, - }) - } - return censoredEntries -} - type DoguConfigValueState ConfigValueState type ConfigValueState struct { From 7eb48619fc8d12763256f6629630f69a66556dec Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 09:54:20 +0200 Subject: [PATCH 024/119] #121 remove StatusPhaseEcosystemUnhealthyAfterwards If the ecosytem is unhealthy, an error is thrown and another reconciliation gets triggered. This way, we repeat the health check until everything is healthy. If the ecosystem is healthy afterward, the post-processing will always run. --- pkg/application/blueprintSpecChangeUseCase.go | 3 --- .../blueprintSpecChangeUseCase_test.go | 26 ------------------- pkg/domain/blueprintSpec.go | 7 +---- pkg/domain/blueprintSpec_test.go | 14 ---------- 4 files changed, 1 insertion(+), 49 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index a0990a3b..e3cdf0f5 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -137,9 +137,6 @@ func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blu return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseBlueprintApplicationFailed: return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) - case domain.StatusPhaseEcosystemUnhealthyAfterwards: - // censor and set status to failed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseCompleted: return nil case domain.StatusPhaseFailed: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 83c78049..4da9b773 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -438,32 +438,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { require.NoError(t, err) }) - t.Run("handle ecosystem unhealthy afterwards", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseEcosystemUnhealthyAfterwards, - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseCompleted - }) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - t.Run("handle completed blueprint", func(t *testing.T) { // given repoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 77b56615..05228f35 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -59,8 +59,6 @@ const ( StatusPhaseBlueprintApplicationFailed StatusPhase = "blueprintApplicationFailed" // StatusPhaseBlueprintApplied indicates that the blueprint was applied but the ecosystem is not healthy yet. StatusPhaseBlueprintApplied StatusPhase = "blueprintApplied" - // StatusPhaseEcosystemUnhealthyAfterwards shows that the ecosystem got not healthy again after applying the blueprint. - StatusPhaseEcosystemUnhealthyAfterwards StatusPhase = "ecosystemUnhealthyAfterwards" // StatusPhaseFailed marks that an error occurred during processing of the blueprint. StatusPhaseFailed StatusPhase = "failed" // StatusPhaseCompleted marks the blueprint as successfully applied. @@ -402,7 +400,6 @@ func (spec *BlueprintSpec) CheckEcosystemHealthAfterwards(healthResult ecosystem if conditionChanged { spec.Events = append(spec.Events, event) } - spec.Status = StatusPhaseEcosystemUnhealthyAfterwards return NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy after applying the blueprint", healthResult) } } @@ -429,12 +426,10 @@ func (spec *BlueprintSpec) MarkBlueprintApplied() { // CompletePostProcessing is used to mark the blueprint as completed or failed , depending on the blueprint application result. func (spec *BlueprintSpec) CompletePostProcessing() { + // this function will not be called, if the ecosystem is not healthy switch spec.Status { case StatusPhaseApplyEcosystemConfigFailed: fallthrough - case StatusPhaseEcosystemUnhealthyAfterwards: - spec.Status = StatusPhaseFailed - spec.Events = append(spec.Events, ExecutionFailedEvent{err: errors.New("ecosystem is unhealthy")}) case StatusPhaseInProgress: spec.Status = StatusPhaseFailed err := errors.New(handleInProgressMsg) diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 5250da02..a46d731b 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -730,20 +730,6 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { assert.Equal(t, ExecutionFailedEvent{errors.New(handleInProgressMsg)}, spec.Events[0]) }) - t.Run("status change on failure EcosystemUnhealthyAfterwards -> Failed", func(t *testing.T) { - // given - spec := &BlueprintSpec{ - Status: StatusPhaseEcosystemUnhealthyAfterwards, - } - // when - spec.CompletePostProcessing() - // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseFailed, - Events: []Event{ExecutionFailedEvent{errors.New("ecosystem is unhealthy")}}, - }) - }) - t.Run("status change on failure ApplicationFailed -> Failed", func(t *testing.T) { // given spec := &BlueprintSpec{ From 486d5919776cdaa107ca71f3bdfb7bf95471d056 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 16:19:16 +0200 Subject: [PATCH 025/119] #121 check if changes need to be made or early exit We need this later to skip some steps while applying the blueprint. --- pkg/application/blueprintSpecChangeUseCase.go | 2 ++ pkg/domain/blueprintSpec.go | 7 +++++-- pkg/domain/blueprintSpec_test.go | 16 ++++++++++++++ pkg/domain/stateDiff.go | 21 +++++++++++++++++++ pkg/domain/stateDiffComponent.go | 9 ++++++++ pkg/domain/stateDiffDogu.go | 13 ++++++++++++ 6 files changed, 66 insertions(+), 2 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index e3cdf0f5..d68db09a 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -87,6 +87,8 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. return err } + // always check health here, even if we already know here, that we don't need to apply anything + // because we need to update the health condition err = useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprint) if err != nil { return err diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 05228f35..31a2a9cb 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -358,8 +358,11 @@ func (spec *BlueprintSpec) CheckEcosystemHealthUpfront(healthResult ecosystem.He // ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. func (spec *BlueprintSpec) ShouldBeApplied() bool { - // TODO: also check if an early-exit is possible if no changes need to be applied, see PR #29 - return !spec.Config.DryRun + // wrote it in the long form to reduce complexity + if spec.Config.DryRun { + return false + } + return spec.StateDiff.HasChanges() } func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index a46d731b..7643c3da 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -777,9 +777,25 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { Config: BlueprintConfiguration{ DryRun: false, }, + StateDiff: StateDiff{ + GlobalConfigDiffs: []GlobalConfigEntryDiff{ + { + Key: "test", + NeededAction: ConfigActionSet, + }, + }, + }, } assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) + t.Run("should not be applied without any changes", func(t *testing.T) { + spec := &BlueprintSpec{ + Config: BlueprintConfiguration{ + DryRun: false, + }, + } + assert.Falsef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) t.Run("should not be applied due to dry run", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ diff --git a/pkg/domain/stateDiff.go b/pkg/domain/stateDiff.go index 19019870..0e45a570 100644 --- a/pkg/domain/stateDiff.go +++ b/pkg/domain/stateDiff.go @@ -35,3 +35,24 @@ const ( func (a Action) IsDoguProxyAction() bool { return a == ActionUpdateDoguProxyBodySize || a == ActionUpdateDoguProxyAdditionalConfig || a == ActionUpdateDoguProxyRewriteTarget } + +func (diff StateDiff) HasChanges() bool { + return diff.DoguDiffs.HasChanges() || + diff.ComponentDiffs.HasChanges() || + diff.GlobalConfigDiffs.HasChanges() || + diff.HasDoguConfigChanges() +} + +func (diff StateDiff) HasDoguConfigChanges() bool { + for _, configDiff := range diff.DoguConfigDiffs { + if configDiff.HasChanges() { + return true + } + } + for _, configDiff := range diff.SensitiveDoguConfigDiffs { + if configDiff.HasChanges() { + return true + } + } + return false +} diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go index 822c5303..6af33f41 100644 --- a/pkg/domain/stateDiffComponent.go +++ b/pkg/domain/stateDiffComponent.go @@ -22,6 +22,15 @@ func (diffs ComponentDiffs) GetComponentDiffByName(name common.SimpleComponentNa return ComponentDiff{} } +func (diffs ComponentDiffs) HasChanges() bool { + for _, diff := range diffs { + if diff.HasChanges() { + return true + } + } + return false +} + // ComponentDiff represents the Diff for a single expected Component to the current ecosystem.ComponentInstallation. type ComponentDiff struct { // Name contains the component's name. diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index eb3956b6..427ef047 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -12,6 +12,15 @@ import ( // DoguDiffs contains the Diff for all expected Dogus to the current ecosystem.DoguInstallations. type DoguDiffs []DoguDiff +func (diffs DoguDiffs) HasChanges() bool { + for _, diff := range diffs { + if diff.HasChanges() { + return true + } + } + return false +} + // DoguDiff represents the Diff for a single expected Dogu to the current ecosystem.DoguInstallation. type DoguDiff struct { DoguName cescommons.SimpleName @@ -30,6 +39,10 @@ type DoguDiffState struct { AdditionalMounts []ecosystem.AdditionalMount } +func (diff DoguDiff) HasChanges() bool { + return len(diff.NeededActions) != 0 +} + // String returns a string representation of the DoguDiff. func (diff *DoguDiff) String() string { return fmt.Sprintf( From 807cd045833ae774388a4fec67c81dd58a66b82c Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 19:26:31 +0200 Subject: [PATCH 026/119] #121 make self upgrade non-blocking We now throw an error to trigger a reconciliation. It is also now safe to just retry. We can now see the exact state of the self-upgrade via the new generated state diff and the component CR install versions. --- .../reconciler/blueprint_controller.go | 4 + pkg/application/blueprintSpecChangeUseCase.go | 4 +- pkg/application/selfUpgradeUseCase.go | 114 +++----- pkg/application/selfUpgradeUseCase_test.go | 252 ++++++------------ pkg/bootstrap.go | 2 +- pkg/domain/blueprintSpec.go | 18 +- pkg/domain/blueprintSpec_test.go | 24 +- pkg/domain/errors.go | 8 + 8 files changed, 170 insertions(+), 256 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 1ca9eba1..f30df4eb 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -61,6 +61,7 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { var notFoundError *domainservice.NotFoundError var invalidBlueprintError *domain.InvalidBlueprintError var healthError *domain.UnhealthyEcosystemError + var awaitSelfUpgradeError *domain.AwaitSelfUpgradeError switch { case errors.As(err, &internalError): errLogger.Error(err, "An internal error occurred and can maybe be fixed by retrying it later") @@ -83,6 +84,9 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { case errors.As(err, &healthError): errLogger.Info("Ecosystem is unhealthy. Retry later") return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + case errors.As(err, &awaitSelfUpgradeError): + errLogger.Info(err.Error()) + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil default: errLogger.Error(err, "An unknown error type occurred. Retry with default backoff") return ctrl.Result{}, err // automatic requeue because of non-nil err diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index d68db09a..7db60431 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -50,6 +50,7 @@ func NewBlueprintSpecChangeUseCase( // Returns a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write or +// a domain.AwaitSelfUpgradeError if we need to wait for a self-upgrade or // a domain.InvalidBlueprintError if the blueprint is invalid. func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.Context, blueprintId string) error { logger := log.FromContext(givenCtx). @@ -101,6 +102,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C err = useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) if err != nil { + // could be a domain.AwaitSelfUpgradeError to trigger another reconcile return err } @@ -118,8 +120,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseAwaitSelfUpgrade: - return useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) case domain.StatusPhaseSelfUpgradeCompleted: return useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) case domain.StatusPhaseEcosystemConfigApplied: diff --git a/pkg/application/selfUpgradeUseCase.go b/pkg/application/selfUpgradeUseCase.go index 54aa9f81..b6e86935 100644 --- a/pkg/application/selfUpgradeUseCase.go +++ b/pkg/application/selfUpgradeUseCase.go @@ -2,23 +2,22 @@ package application import ( "context" - "errors" "fmt" "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "sigs.k8s.io/controller-runtime/pkg/log" ) +const awaitSelfUpgradeErrorMsg = "await self upgrade" + type SelfUpgradeUseCase struct { blueprintRepo domainservice.BlueprintSpecRepository componentRepo componentInstallationRepository componentUseCase componentInstallationUseCase blueprintOperatorName common.SimpleComponentName - healthConfigProvider healthConfigProvider } func NewSelfUpgradeUseCase( @@ -26,14 +25,12 @@ func NewSelfUpgradeUseCase( componentRepo componentInstallationRepository, componentUseCase componentInstallationUseCase, blueprintOperatorName common.SimpleComponentName, - healthConfigProvider healthConfigProvider, ) *SelfUpgradeUseCase { return &SelfUpgradeUseCase{ blueprintRepo: blueprintRepo, componentRepo: componentRepo, componentUseCase: componentUseCase, blueprintOperatorName: blueprintOperatorName, - healthConfigProvider: healthConfigProvider, } } @@ -41,101 +38,66 @@ func NewSelfUpgradeUseCase( // can check if the self upgrade was successful after a restart. // It always sets the fitting status in the blueprint spec. func (useCase *SelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplySelfUpgrade") - ownDiff := blueprint.StateDiff.ComponentDiffs.GetComponentDiffByName(useCase.blueprintOperatorName) - - if !ownDiff.HasChanges() { - logger.Info("self upgrade not needed") - blueprint.MarkSelfUpgradeCompleted() - err := useCase.blueprintRepo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot save blueprint spec %q to skip self upgrade: %w", blueprint.Id, err) - } - return nil - } - ownComponent, err := useCase.componentRepo.GetByName(ctx, useCase.blueprintOperatorName) if err != nil && !domainservice.IsNotFoundError(err) { - // ignore not found errors as this is ok if the component was not installed via a component CR + // ignore not found errors as this is ok if the component was not installed via a component CR before // only return if other errors happen, e.g. InternalError return fmt.Errorf("cannot load component installation for %q from ecosystem: %w", useCase.blueprintOperatorName, err) } - // use extra vars to avoid nil pointer dereferences of the component - var expectedVersion, actualVersion *semver.Version - if ownComponent != nil { - expectedVersion = ownComponent.ExpectedVersion - actualVersion = ownComponent.ActualVersion - } - if !ownDiff.IsExpectedVersion(expectedVersion) { - return useCase.doSelfUpgrade(ctx, blueprint, ownDiff, ownComponent) - // the operator waits for termination, unless there was an error, so we can return here - } + needsToApply, isCompleted := checkStateOfSelfUpgrade(ownDiff, ownComponent) - if !ownDiff.IsExpectedVersion(actualVersion) { - err = useCase.awaitInstallationConfirmation(ctx, blueprint) + if isCompleted { + blueprint.MarkSelfUpgradeCompleted() + err = useCase.blueprintRepo.Update(ctx, blueprint) if err != nil { - return err + return fmt.Errorf("cannot save blueprint spec %q after self upgrading the operator: %w", blueprint.Id, err) } + return nil } - - blueprint.MarkSelfUpgradeCompleted() + // if not done, set conditions accordingly + blueprint.MarkWaitingForSelfUpgrade() err = useCase.blueprintRepo.Update(ctx, blueprint) if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after self upgrading the operator: %w", blueprint.Id, err) + return fmt.Errorf("cannot persist blueprint spec %q to mark it waiting for self upgrade: %w", blueprint.Id, err) } - logger.Info("self upgrade successful") - return nil + if needsToApply { + return useCase.doSelfUpgrade(ctx, ownDiff, ownComponent) + } + return &domain.AwaitSelfUpgradeError{Message: awaitSelfUpgradeErrorMsg} } -func (useCase *SelfUpgradeUseCase) doSelfUpgrade(ctx context.Context, blueprintSpec *domain.BlueprintSpec, ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) error { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.doSelfUpgrade") - logger.Info("self upgrade needed, apply self upgrade") - blueprintSpec.MarkWaitingForSelfUpgrade() - err := useCase.blueprintRepo.Update(ctx, blueprintSpec) - if err != nil { - return fmt.Errorf("cannot persist blueprint spec %q to mark it waiting for self upgrade: %w", blueprintSpec.Id, err) +func checkStateOfSelfUpgrade(ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) (needsToApply, isCompleted bool) { + // use extra vars to avoid nil pointer dereferences of the component + var versionSetForInstallation, installedVersion *semver.Version + if ownComponent != nil { + versionSetForInstallation = ownComponent.ExpectedVersion + installedVersion = ownComponent.ActualVersion } - err = useCase.applySelfUpgrade(ctx, ownDiff, ownComponent) - if err != nil { - return err + + if ownDiff.IsExpectedVersion(installedVersion) { + // if component CR status.installedVersion already says: our wanted version is installed + return false, true + } + if ownDiff.IsExpectedVersion(versionSetForInstallation) { + // update is already triggered but not done + return false, false + } else { + // no update triggered yet + // trigger update and trigger later reconciliation via error + return true, false } - logger.Info("await termination for self upgrade. Check the component-CR for the installation status") - useCase.waitForTermination(ctx) - return nil // this code is never reached as we wait for termination before } -func (useCase *SelfUpgradeUseCase) applySelfUpgrade(ctx context.Context, ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) error { +func (useCase *SelfUpgradeUseCase) doSelfUpgrade(ctx context.Context, ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) error { + logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.doSelfUpgrade") + logger.Info("self upgrade needed, apply self upgrade") err := useCase.componentUseCase.applyComponentState(ctx, ownDiff, ownComponent) if err != nil { return fmt.Errorf("an error occurred while applying the self-upgrade to the ecosystem: %w", err) } - return nil -} - -func (useCase *SelfUpgradeUseCase) waitForTermination(ctx context.Context) { - <-ctx.Done() -} - -func (useCase *SelfUpgradeUseCase) awaitInstallationConfirmation(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - config, err := useCase.healthConfigProvider.GetWaitConfig(ctx) - if err != nil { - return fmt.Errorf("could not retrieve wait interval config for self upgrade: %w", err) - } - _, err = util.RetryUntilSuccessOrCancellation(ctx, config.Interval, func(ctx context.Context) (*interface{}, error, bool) { - ownComponent, err := useCase.componentRepo.GetByName(ctx, useCase.blueprintOperatorName) - if err != nil { - return nil, fmt.Errorf("could not reload component for version confirmation: %w", err), false - } - ownDiff := blueprintSpec.StateDiff.ComponentDiffs.GetComponentDiffByName(useCase.blueprintOperatorName) - return nil, nil, !ownDiff.IsExpectedVersion(ownComponent.ActualVersion) // retry if true - }) - if err != nil && !errors.Is(err, ctx.Err()) { - // ignore cancellation error as this can happen, if the operator is getting restarted more than once (e.g. maybe because of a cluster failure) - return fmt.Errorf("error while waiting for version confirmation: %w", err) - } - return nil + return &domain.AwaitSelfUpgradeError{Message: awaitSelfUpgradeErrorMsg} } diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go index 1a55cdf9..e2c69f2c 100644 --- a/pkg/application/selfUpgradeUseCase_test.go +++ b/pkg/application/selfUpgradeUseCase_test.go @@ -9,9 +9,8 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" "testing" - "time" ) func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { @@ -21,8 +20,22 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { version1, _ := semver.NewVersion("1.0") version2, _ := semver.NewVersion("2.0") internalTestError := domainservice.NewInternalError(assert.AnError, "internal error") - waitConfig := ecosystem.WaitConfig{Interval: 5 * time.Second} + NoActionV2ComponentDiff := domain.ComponentDiff{ + Name: blueprintOperatorName, + Actual: domain.ComponentDiffState{ + Version: version2, + }, + Expected: domain.ComponentDiffState{ + Version: version2, + }, + NeededActions: []domain.Action{}, + } + NoActionV2StateDiff := domain.StateDiff{ + ComponentDiffs: []domain.ComponentDiff{ + NoActionV2ComponentDiff, + }, + } UpgradeToV2ComponentDiff := domain.ComponentDiff{ Name: blueprintOperatorName, Actual: domain.ComponentDiffState{ @@ -38,16 +51,29 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { UpgradeToV2ComponentDiff, }, } + InstallV2ComponentDiff := domain.ComponentDiff{ + Name: blueprintOperatorName, + Actual: domain.ComponentDiffState{}, + Expected: domain.ComponentDiffState{ + Version: version2, + }, + NeededActions: []domain.Action{domain.ActionInstall}, + } + InstallV2StateDiff := domain.StateDiff{ + ComponentDiffs: []domain.ComponentDiff{ + InstallV2ComponentDiff, + }, + } - t.Run("apply upgrade until termination", func(t *testing.T) { + t.Run("apply upgrade and trigger reconcile", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, + StateDiff: upgradeToV2StateDiff, + Conditions: &[]domain.Condition{}, } component := &ecosystem.ComponentInstallation{ @@ -55,124 +81,101 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { ActualVersion: version1, } - timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // but usually cancel - defer cancelCtx() - - blueprintRepo.EXPECT().Update(mock.Anything, blueprint). - Return(nil). - Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { - assert.Equal(t, domain.StatusPhaseAwaitSelfUpgrade, blueprint.Status) - }).Once() // only once as the operator will terminate and will set status completed later. + // only once as the operator will terminate and will set status completed later. + blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil).Once() + componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() + componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, component).Return(nil) - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component, nil).Once() - componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, component).Return(nil). - Run(func(_ context.Context, _ domain.ComponentDiff, _ *ecosystem.ComponentInstallation) { - // check that the status is set beforehand, as we cannot guarantee that we can set it afterward before termination - assert.Equal(t, domain.StatusPhaseAwaitSelfUpgrade, blueprint.Status) - cancelCtx() - }) - - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) - assert.NoError(t, err) + var awaitError *domain.AwaitSelfUpgradeError + assert.ErrorAs(t, err, &awaitError) + assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) - t.Run("verify installation after termination", func(t *testing.T) { + t.Run("apply upgrade with missing component cr", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseAwaitSelfUpgrade, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version2, + StateDiff: InstallV2StateDiff, + Conditions: &[]domain.Condition{}, } + var nilComponent *ecosystem.ComponentInstallation - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() // no reload for actual version check - blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { - require.Equal(t, domain.StatusPhaseSelfUpgradeCompleted, blueprint.Status) - }) + // only once as the operator will terminate and will set status completed later. + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Once() + componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(nil, domainservice.NewNotFoundError(assert.AnError, "test-error")).Once() + componentUseCase.EXPECT().applyComponentState(testCtx, InstallV2ComponentDiff, nilComponent).Return(nil) err := useCase.HandleSelfUpgrade(testCtx, blueprint) - assert.NoError(t, err) + var awaitError *domain.AwaitSelfUpgradeError + assert.ErrorAs(t, err, &awaitError) + assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) - t.Run("await installation confirmation after termination", func(t *testing.T) { + t.Run("check if self-upgrade is done -> not yet", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseAwaitSelfUpgrade, + StateDiff: NoActionV2StateDiff, + Conditions: &[]domain.Condition{}, } - component1 := &ecosystem.ComponentInstallation{ + component := &ecosystem.ComponentInstallation{ ExpectedVersion: version2, ActualVersion: version1, } - component2 := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version2, - } - timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as - defer cancelCtx() - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component1, nil).Once() - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component2, nil).Once() - configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, nil) - blueprintRepo.EXPECT().Update(timeoutCtx, blueprint).Return(nil).Run(func(ctx context.Context, blueprintSpec *domain.BlueprintSpec) { - require.Equal(t, domain.StatusPhaseSelfUpgradeCompleted, blueprint.Status) - }) + componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) - assert.NoError(t, err) + var awaitError *domain.AwaitSelfUpgradeError + assert.ErrorAs(t, err, &awaitError) + assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) - t.Run("apply upgrade with missing component cr", func(t *testing.T) { + t.Run("check if self-upgrade is done -> done", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, + StateDiff: NoActionV2StateDiff, + Conditions: &[]domain.Condition{}, } - timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // but usually cancel - defer cancelCtx() + component := &ecosystem.ComponentInstallation{ + ExpectedVersion: version2, + ActualVersion: version2, + } - componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(nil, domainservice.NewNotFoundError(assert.AnError, "test-error")) - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil) - var nilComponent *ecosystem.ComponentInstallation - componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, nilComponent).Return(nil).Run( - func(_ context.Context, _ domain.ComponentDiff, _ *ecosystem.ComponentInstallation) { - cancelCtx() - }, - ) + componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) + err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) t.Run("cannot load component", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, @@ -190,8 +193,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ Id: blueprintId, @@ -215,8 +217,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, @@ -236,95 +237,16 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { assert.ErrorContains(t, err, "an error occurred while applying the self-upgrade to the ecosystem") }) - t.Run("cannot save blueprint to skip self upgrade", func(t *testing.T) { + t.Run("cannot save blueprint to complete self upgrade", func(t *testing.T) { blueprintRepo := newMockBlueprintSpecRepository(t) componentRepo := newMockComponentInstallationRepository(t) componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) + useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - StateDiff: domain.StateDiff{}, - } - - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(assert.AnError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot save blueprint spec \""+blueprintId+"\" to skip self upgrade") - }) - - t.Run("error awaiting version confirmation, cannot load component", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseAwaitSelfUpgrade, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version1, - } - timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as - defer cancelCtx() - - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component, nil).Once() - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(nil, assert.AnError).Once() - configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, nil) - - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "error while waiting for version confirmation") - assert.ErrorContains(t, err, "could not reload component for version confirmation") - }) - - t.Run("error awaiting version confirmation, cannot load wait config", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseAwaitSelfUpgrade, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version1, - } - timeoutCtx, cancelCtx := context.WithTimeout(testCtx, time.Second) // no timeout should happen as - defer cancelCtx() - - componentRepo.EXPECT().GetByName(timeoutCtx, blueprintOperatorName).Return(component, nil).Once() - configProvider.EXPECT().GetWaitConfig(timeoutCtx).Return(waitConfig, assert.AnError) - - err := useCase.HandleSelfUpgrade(timeoutCtx, blueprint) - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "could not retrieve wait interval config for self upgrade") - }) - - t.Run("error saving blueprint after awaiting version confirmation", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - configProvider := newMockHealthConfigProvider(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName, configProvider) - - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - StateDiff: upgradeToV2StateDiff, - Status: domain.StatusPhaseAwaitSelfUpgrade, + Id: blueprintId, + StateDiff: NoActionV2StateDiff, + Conditions: &[]domain.Condition{}, } component := &ecosystem.ComponentInstallation{ diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index bcbbd7e1..4e2f7440 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -89,7 +89,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintSpecRepository, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) doguRestartUseCase := application.NewDoguRestartUseCase(doguInstallationRepo, blueprintSpecRepository, restartRepository) - selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintSpecRepository, componentInstallationRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName, healthConfigRepo) + selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintSpecRepository, componentInstallationRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( blueprintSpecRepository, blueprintValidationUseCase, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 31a2a9cb..37c6a53e 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -321,6 +321,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( // because the blueprint could be executable even after a change of the blueprint. // Therefore, a check with "conditionChanged" is not enough to prevent, that we regenerate all events on every reconcile. + //TODO: We could set all diff-related conditions here, so that don't need to call every step even if the state diff says "no change" return nil } @@ -366,17 +367,26 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { } func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { - if spec.Status != StatusPhaseAwaitSelfUpgrade { - spec.Status = StatusPhaseAwaitSelfUpgrade + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionSelfUpgradeCompleted, + Status: metav1.ConditionFalse, + Message: "", + }) + if conditionChanged { spec.Events = append(spec.Events, AwaitSelfUpgradeEvent{}) } } func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { - if spec.Status != StatusPhaseSelfUpgradeCompleted { - spec.Status = StatusPhaseSelfUpgradeCompleted + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionSelfUpgradeCompleted, + Status: metav1.ConditionTrue, + Message: "", + }) + if conditionChanged { spec.Events = append(spec.Events, SelfUpgradeCompletedEvent{}) } + spec.Status = StatusPhaseSelfUpgradeCompleted } // CheckEcosystemHealthAfterwards checks with the given health result if the ecosystem is healthy and the blueprint was therefore successful. diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 7643c3da..42cf1b6b 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -766,7 +766,6 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { blueprint.ValidateDynamically(givenErr) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionValid)) require.Equal(t, 1, len(blueprint.Events)) }) } @@ -809,29 +808,34 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { func TestBlueprintSpec_MarkWaitingForSelfUpgrade(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{} + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } blueprint.MarkWaitingForSelfUpgrade() - assert.Equal(t, StatusPhaseAwaitSelfUpgrade, blueprint.Status) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event{AwaitSelfUpgradeEvent{}}, blueprint.Events) }) t.Run("repeated call -> no event", func(t *testing.T) { blueprint := BlueprintSpec{ - Status: StatusPhaseAwaitSelfUpgrade, + Conditions: &[]Condition{}, } + blueprint.MarkWaitingForSelfUpgrade() + blueprint.Events = []Event(nil) blueprint.MarkWaitingForSelfUpgrade() - assert.Equal(t, StatusPhaseAwaitSelfUpgrade, blueprint.Status) - assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) + assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if condition did not change") }) } func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { blueprint := BlueprintSpec{ - Status: StatusPhaseAwaitSelfUpgrade, + Status: StatusPhaseAwaitSelfUpgrade, + Conditions: &[]Condition{}, } blueprint.MarkSelfUpgradeCompleted() @@ -841,11 +845,15 @@ func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { t.Run("repeated call -> no event", func(t *testing.T) { blueprint := BlueprintSpec{ - Status: StatusPhaseSelfUpgradeCompleted, + Status: StatusPhaseSelfUpgradeCompleted, + Conditions: &[]Condition{}, } + blueprint.MarkSelfUpgradeCompleted() + blueprint.Events = []Event(nil) blueprint.MarkSelfUpgradeCompleted() + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, StatusPhaseSelfUpgradeCompleted, blueprint.Status) assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") }) diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index 1ee71abf..24a9a5cc 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -52,3 +52,11 @@ func NewUnhealthyEcosystemError( ) *UnhealthyEcosystemError { return &UnhealthyEcosystemError{WrappedError: wrappedError, Message: message, healthResult: healthResult} } + +type AwaitSelfUpgradeError struct { + Message string +} + +func (e *AwaitSelfUpgradeError) Error() string { + return e.Message +} From 217e00856d9de75e47a4c18d53a2352bb45df811 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Thu, 14 Aug 2025 19:48:50 +0200 Subject: [PATCH 027/119] #121 remove self upgrade statuses --- pkg/application/blueprintSpecChangeUseCase.go | 7 ++++-- .../blueprintSpecChangeUseCase_test.go | 11 ++++----- pkg/domain/blueprintSpec.go | 24 +++++++------------ pkg/domain/blueprintSpec_test.go | 8 +++---- 4 files changed, 22 insertions(+), 28 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 7db60431..67bafcd2 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -106,6 +106,11 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } + err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) + if err != nil { + return err + } + // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. for blueprint.Status != domain.StatusPhaseCompleted { err := useCase.handleChange(ctx, blueprint) @@ -120,8 +125,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseSelfUpgradeCompleted: - return useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) case domain.StatusPhaseEcosystemConfigApplied: return useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) case domain.StatusPhaseApplyEcosystemConfigFailed: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 4da9b773..3df40de8 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -3,12 +3,13 @@ package application import ( "context" "errors" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/stretchr/testify/mock" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/log" - "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -73,9 +74,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseEcosystemConfigApplied }) - selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseSelfUpgradeCompleted - }) + selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil) applyMock.EXPECT().ApplyBlueprintSpec(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseBlueprintApplied @@ -325,8 +324,8 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseSelfUpgradeCompleted, + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(testCtx, blueprintSpec.Id).Return(assert.AnError) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 37c6a53e..3ebb2cfc 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -3,6 +3,9 @@ package domain import ( "errors" "fmt" + "maps" + "slices" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -10,8 +13,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "maps" - "slices" ) type BlueprintSpec struct { @@ -40,8 +41,7 @@ const ( ConditionConfigApplied = "ConfigApplied" ConditionDogusApplied = "DogusApplied" ConditionComponentsApplied = "ComponentsApplied" - // how do we watch restarts? - ConditionBlueprintApplied = "BlueprintApplied" + ConditionBlueprintApplied = "BlueprintApplied" ) type StatusPhase string @@ -49,10 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseAwaitSelfUpgrade marks that the blueprint operator waits for termination for a self upgrade. - StatusPhaseAwaitSelfUpgrade StatusPhase = "awaitSelfUpgrade" - // StatusPhaseSelfUpgradeCompleted marks that the blueprint operator itself got successfully upgraded. - StatusPhaseSelfUpgradeCompleted StatusPhase = "selfUpgradeCompleted" // StatusPhaseInProgress marks that the blueprint is currently being processed. StatusPhaseInProgress StatusPhase = "inProgress" // StatusPhaseBlueprintApplicationFailed shows that the blueprint application failed. @@ -368,9 +364,9 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionSelfUpgradeCompleted, - Status: metav1.ConditionFalse, - Message: "", + Type: ConditionSelfUpgradeCompleted, + Status: metav1.ConditionFalse, + Reason: "await self upgrade", }) if conditionChanged { spec.Events = append(spec.Events, AwaitSelfUpgradeEvent{}) @@ -379,14 +375,12 @@ func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionSelfUpgradeCompleted, - Status: metav1.ConditionTrue, - Message: "", + Type: ConditionSelfUpgradeCompleted, + Status: metav1.ConditionTrue, }) if conditionChanged { spec.Events = append(spec.Events, SelfUpgradeCompletedEvent{}) } - spec.Status = StatusPhaseSelfUpgradeCompleted } // CheckEcosystemHealthAfterwards checks with the given health result if the ecosystem is healthy and the blueprint was therefore successful. diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 42cf1b6b..f9885d3a 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -3,10 +3,11 @@ package domain import ( "errors" "fmt" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "k8s.io/apimachinery/pkg/api/meta" - "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -834,18 +835,16 @@ func TestBlueprintSpec_MarkWaitingForSelfUpgrade(t *testing.T) { func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { blueprint := BlueprintSpec{ - Status: StatusPhaseAwaitSelfUpgrade, Conditions: &[]Condition{}, } blueprint.MarkSelfUpgradeCompleted() - assert.Equal(t, StatusPhaseSelfUpgradeCompleted, blueprint.Status) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event{SelfUpgradeCompletedEvent{}}, blueprint.Events) }) t.Run("repeated call -> no event", func(t *testing.T) { blueprint := BlueprintSpec{ - Status: StatusPhaseSelfUpgradeCompleted, Conditions: &[]Condition{}, } @@ -854,7 +853,6 @@ func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { blueprint.MarkSelfUpgradeCompleted() assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) - assert.Equal(t, StatusPhaseSelfUpgradeCompleted, blueprint.Status) assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") }) } From 1e4c0e1fc35da4f6823be61903bdd3a3d7834f7b Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Fri, 15 Aug 2025 08:25:24 +0200 Subject: [PATCH 028/119] #121 remove StatusPhaseApplyEcosystemConfig We still need to rework the process of setting config. This will be done in further commits. --- pkg/application/blueprintSpecChangeUseCase.go | 2 +- pkg/application/ecosystemConfigUseCase.go | 5 ++- .../ecosystemConfigUseCase_test.go | 37 +++++++++++-------- pkg/domain/blueprintSpec.go | 13 +++++-- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 67bafcd2..7ffeadb6 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -111,7 +111,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } - // without any error, the blueprint spec is always ready to be further evaluated, therefore call this function again to do that. + //TODO: remove this loop, when all use cases are reworked without the use of status for blueprint.Status != domain.StatusPhaseCompleted { err := useCase.handleChange(ctx, blueprint) if err != nil { diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 73998352..142129bb 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" + "maps" + "slices" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-registry-lib/config" - "maps" "sigs.k8s.io/controller-runtime/pkg/log" - "slices" ) type EcosystemConfigUseCase struct { diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 6d00e086..ec877ab6 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -1,6 +1,9 @@ package application import ( + "maps" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" liberrors "github.com/cloudogu/ces-commons-lib/errors" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -9,8 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "maps" - "testing" + "k8s.io/apimachinery/pkg/api/meta" ) const ( @@ -162,6 +164,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -207,6 +210,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -255,6 +259,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -581,39 +586,41 @@ func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { } func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { - t.Run("should set status and event apply config", func(t *testing.T) { + t.Run("should set condition and event apply config", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} - expectedSpec := &domain.BlueprintSpec{} - expectedSpec.Status = domain.StatusPhaseApplyEcosystemConfig - expectedSpec.Events = append(spec.Events, domain.ApplyEcosystemConfigEvent{}) + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().Update(testCtx, expectedSpec).Return(nil) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.markApplyConfigStart(testCtx, spec) + err := sut.markApplyConfigStart(testCtx, blueprint) // then require.NoError(t, err) + + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) }) t.Run("should return an error on update error", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} - expectedSpec := &domain.BlueprintSpec{} - expectedSpec.Status = domain.StatusPhaseApplyEcosystemConfig - expectedSpec.Events = append(spec.Events, domain.ApplyEcosystemConfigEvent{}) + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().Update(testCtx, expectedSpec).Return(assert.AnError) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.markApplyConfigStart(testCtx, spec) + err := sut.markApplyConfigStart(testCtx, blueprint) // then require.Error(t, err) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 3ebb2cfc..bf499124 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -59,8 +59,6 @@ const ( StatusPhaseFailed StatusPhase = "failed" // StatusPhaseCompleted marks the blueprint as successfully applied. StatusPhaseCompleted StatusPhase = "completed" - // StatusPhaseApplyEcosystemConfig indicates that the apply ecosystem config phase is active. - StatusPhaseApplyEcosystemConfig StatusPhase = "applyEcosystemConfig" // StatusPhaseApplyEcosystemConfigFailed indicates that the phase to apply ecosystem config failed. StatusPhaseApplyEcosystemConfigFailed StatusPhase = "applyEcosystemConfigFailed" // StatusPhaseEcosystemConfigApplied indicates that the phase to apply ecosystem config succeeded. @@ -516,8 +514,15 @@ func (spec *BlueprintSpec) GetDogusThatNeedARestart() []cescommons.SimpleName { } func (spec *BlueprintSpec) StartApplyEcosystemConfig() { - spec.Status = StatusPhaseApplyEcosystemConfig - spec.Events = append(spec.Events, ApplyEcosystemConfigEvent{}) + event := ApplyEcosystemConfigEvent{} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionConfigApplied, + Status: metav1.ConditionFalse, + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, ApplyEcosystemConfigEvent{}) + } } func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { From 498763dcc3c133bddd0d05b94ec38d9c410ff2fe Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Fri, 15 Aug 2025 20:23:34 +0200 Subject: [PATCH 029/119] #121 remove config status phases --- pkg/application/blueprintSpecChangeUseCase.go | 9 +- .../blueprintSpecChangeUseCase_test.go | 4 +- pkg/application/ecosystemConfigUseCase.go | 30 +---- .../ecosystemConfigUseCase_test.go | 112 +++++++++++------- pkg/domain/blueprintSpec.go | 25 ++-- pkg/domain/stateDiff.go | 13 +- 6 files changed, 103 insertions(+), 90 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 7ffeadb6..3a4f4f76 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -111,6 +111,11 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } + err = useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) + if err != nil { + return err + } + //TODO: remove this loop, when all use cases are reworked without the use of status for blueprint.Status != domain.StatusPhaseCompleted { err := useCase.handleChange(ctx, blueprint) @@ -125,10 +130,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseEcosystemConfigApplied: - return useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) - case domain.StatusPhaseApplyEcosystemConfigFailed: - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseInProgress: // should only happen if the system was interrupted, normally this state will be updated to blueprintApplied or BlueprintApplicationFailed return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 3df40de8..811d5522 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -71,9 +71,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { }) }) applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil) - ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseEcosystemConfigApplied - }) + ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil) selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil) applyMock.EXPECT().ApplyBlueprintSpec(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 142129bb..6d40630f 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -37,27 +37,7 @@ func NewEcosystemConfigUseCase( // ApplyConfig fetches the dogu and global config stateDiff of the blueprint and applies these keys to the repositories. func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig") - - doguConfigDiffs := blueprint.StateDiff.DoguConfigDiffs - isEmptyDoguDiff := len(doguConfigDiffs) == 0 - if isEmptyDoguDiff { - logger.Info("dogu config diffs are empty...") - } - - sensitiveDoguConfigDiffs := blueprint.StateDiff.SensitiveDoguConfigDiffs - isEmptySensitiveDiff := len(sensitiveDoguConfigDiffs) == 0 - if isEmptySensitiveDiff { - logger.Info("sensitive dogu config diffs are empty...") - } - - globalConfigDiffs := blueprint.StateDiff.GlobalConfigDiffs - isEmptyGlobalDiff := len(globalConfigDiffs) == 0 - if isEmptyGlobalDiff { - logger.Info("global config diffs are empty...") - } - - if isEmptyDoguDiff && isEmptyGlobalDiff && isEmptySensitiveDiff { + if !blueprint.StateDiff.HasConfigChanges() { return useCase.markConfigApplied(ctx, blueprint) } @@ -66,7 +46,6 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, err) } - // do not apply further configs if error happens, we don't want to corrupt the system more than needed. err = applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply normal dogu config: %w", err)) @@ -75,7 +54,7 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply sensitive dogu config: %w", err)) } - err = useCase.applyGlobalConfigDiffs(ctx, globalConfigDiffs.GetGlobalConfigDiffsByAction()) + err = useCase.applyGlobalConfigDiffs(ctx, blueprint.StateDiff.GlobalConfigDiffs.GetGlobalConfigDiffsByAction()) if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply global config: %w", err)) } @@ -174,15 +153,16 @@ func (useCase *EcosystemConfigUseCase) handleFailedApplyEcosystemConfig(ctx cont WithName("EcosystemConfigUseCase.handleFailedApplyEcosystemConfig"). WithValues("blueprintId", blueprint.Id) + // sets condition blueprint.MarkApplyEcosystemConfigFailed(err) repoErr := useCase.blueprintRepository.Update(ctx, blueprint) if repoErr != nil { repoErr = errors.Join(repoErr, err) logger.Error(repoErr, "cannot mark blueprint config apply as failed") - return fmt.Errorf("cannot mark blueprint config apply as failed while handling %q status: %w", blueprint.Status, repoErr) + return fmt.Errorf("cannot mark blueprint config apply as failed: %w", repoErr) } - return nil + return err } func (useCase *EcosystemConfigUseCase) markConfigApplied(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index ec877ab6..748ad6bd 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -16,9 +16,8 @@ import ( ) const ( - redmine = cescommons.SimpleName("redmine") - cas = cescommons.SimpleName("cas") - testBlueprintID = "blueprint1" + redmine = cescommons.SimpleName("redmine") + cas = cescommons.SimpleName("cas") ) var emptyDoguList []cescommons.SimpleName @@ -110,6 +109,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -125,15 +125,14 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseEcosystemConfigApplied, blueprint.Status) }) t.Run("should return on mark apply config start error", func(t *testing.T) { // given - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -143,6 +142,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, } + blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(assert.AnError).Times(1) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(1) @@ -152,17 +152,14 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then - require.NoError(t, err) - assert.Equal(t, blueprint.Status, domain.StatusPhaseApplyEcosystemConfigFailed) + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "cannot mark blueprint as applying config") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) }) t.Run("error applying dogu config", func(t *testing.T) { // given - blueprintRepoMock := newMockBlueprintSpecRepository(t) - doguConfigMock := newMockDoguConfigRepository(t) - sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) - globalConfigMock := newMockGlobalConfigRepository(t) - blueprint := &domain.BlueprintSpec{ Conditions: &[]domain.Condition{}, StateDiff: domain.StateDiff{ @@ -177,6 +174,10 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, } + blueprintRepoMock := newMockBlueprintSpecRepository(t) + sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) + globalConfigMock := newMockGlobalConfigRepository(t) + doguConfigMock := newMockDoguConfigRepository(t) // Just check if the routine hits the repos. Check values in concrete test of methods. doguConfigMock.EXPECT(). GetAllExisting(testCtx, []cescommons.SimpleName{cas, redmine}). @@ -193,14 +194,14 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "could not apply normal dogu config") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) - assert.Contains(t, blueprint.Events[1].Message(), "could not apply normal dogu config") - // cannot check for dogu name here as the order of the events is not fixed. It could be either redmine or cas - assert.Contains(t, blueprint.Events[1].Message(), "could not persist config for dogu") - assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") + assert.Contains(t, blueprint.Events[1].Message(), err.Error()) }) t.Run("error applying sensitive config", func(t *testing.T) { // given @@ -242,8 +243,12 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "could not apply sensitive dogu config") + + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) assert.Contains(t, blueprint.Events[1].Message(), "could not apply sensitive dogu config") @@ -291,8 +296,12 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, blueprint.Status) + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "could not apply global config") + + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) assert.Contains(t, blueprint.Events[1].Message(), "could not apply global config") @@ -544,44 +553,47 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { } func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { - t.Run("should set applied status and event", func(t *testing.T) { + t.Run("should set applied condition and event", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} - expectedSpec := &domain.BlueprintSpec{} - expectedSpec.Status = domain.StatusPhaseEcosystemConfigApplied - expectedSpec.Events = append(spec.Events, domain.EcosystemConfigAppliedEvent{}) + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().Update(testCtx, expectedSpec).Return(nil) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.markConfigApplied(testCtx, spec) + err := sut.markConfigApplied(testCtx, blueprint) // then require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) }) t.Run("should return an error on update error", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} - expectedSpec := &domain.BlueprintSpec{} - expectedSpec.Status = domain.StatusPhaseEcosystemConfigApplied - expectedSpec.Events = append(spec.Events, domain.EcosystemConfigAppliedEvent{}) - blueprintRepoMock := newMockBlueprintSpecRepository(t) + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } - blueprintRepoMock.EXPECT().Update(testCtx, expectedSpec).Return(assert.AnError) + blueprintRepoMock := newMockBlueprintSpecRepository(t) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.markConfigApplied(testCtx, spec) + err := sut.markConfigApplied(testCtx, blueprint) // then - require.Error(t, err) assert.ErrorContains(t, err, "failed to mark ecosystem config applied") assert.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) }) } @@ -630,9 +642,11 @@ func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { } func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { - t.Run("should set applied status and event", func(t *testing.T) { + t.Run("should set applied condition and event", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprintRepoMock.EXPECT().Update(testCtx, mock.IsType(&domain.BlueprintSpec{})).Return(nil) @@ -640,17 +654,22 @@ func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} // when - err := sut.handleFailedApplyEcosystemConfig(testCtx, spec, assert.AnError) + err := sut.handleFailedApplyEcosystemConfig(testCtx, blueprint, assert.AnError) // then - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseApplyEcosystemConfigFailed, spec.Status) - assert.IsType(t, domain.ApplyEcosystemConfigFailedEvent{}, spec.Events[0]) + require.Error(t, err) + condition := meta.FindStatusCondition(*blueprint.Conditions, domain.ConditionConfigApplied) + require.NotNil(t, condition) + assert.Equal(t, err.Error(), condition.Message) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.IsType(t, domain.ApplyEcosystemConfigFailedEvent{}, blueprint.Events[0]) }) t.Run("should return error on update error", func(t *testing.T) { // given - spec := &domain.BlueprintSpec{} + spec := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprintRepoMock.EXPECT().Update(testCtx, mock.IsType(&domain.BlueprintSpec{})).Return(assert.AnError) @@ -662,7 +681,8 @@ func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { // then require.Error(t, err) - assert.ErrorContains(t, err, "cannot mark blueprint config apply as failed while handling \"applyEcosystemConfigFailed\" status") + assert.ErrorContains(t, err, "cannot mark blueprint config apply as failed") + assert.ErrorContains(t, err, assert.AnError.Error()) assert.ErrorIs(t, err, assert.AnError) }) } diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index bf499124..232833a7 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -59,10 +59,6 @@ const ( StatusPhaseFailed StatusPhase = "failed" // StatusPhaseCompleted marks the blueprint as successfully applied. StatusPhaseCompleted StatusPhase = "completed" - // StatusPhaseApplyEcosystemConfigFailed indicates that the phase to apply ecosystem config failed. - StatusPhaseApplyEcosystemConfigFailed StatusPhase = "applyEcosystemConfigFailed" - // StatusPhaseEcosystemConfigApplied indicates that the phase to apply ecosystem config succeeded. - StatusPhaseEcosystemConfigApplied StatusPhase = "ecosystemConfigApplied" // StatusPhaseRestartsTriggered indicates that a restart has been triggered for all Dogus that needed a restart. // Restarts are needed when the Dogu config changes. StatusPhaseRestartsTriggered StatusPhase = "restartsTriggered" @@ -433,8 +429,6 @@ func (spec *BlueprintSpec) MarkBlueprintApplied() { func (spec *BlueprintSpec) CompletePostProcessing() { // this function will not be called, if the ecosystem is not healthy switch spec.Status { - case StatusPhaseApplyEcosystemConfigFailed: - fallthrough case StatusPhaseInProgress: spec.Status = StatusPhaseFailed err := errors.New(handleInProgressMsg) @@ -526,13 +520,24 @@ func (spec *BlueprintSpec) StartApplyEcosystemConfig() { } func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { - spec.Status = StatusPhaseApplyEcosystemConfigFailed - spec.Events = append(spec.Events, ApplyEcosystemConfigFailedEvent{err: err}) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionConfigApplied, + Status: metav1.ConditionFalse, + Message: err.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, ApplyEcosystemConfigFailedEvent{err: err}) + } } func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { - spec.Status = StatusPhaseEcosystemConfigApplied - spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionConfigApplied, + Status: metav1.ConditionTrue, + }) + if conditionChanged { + spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) + } } const handleInProgressMsg = "cannot handle blueprint in state " + string(StatusPhaseInProgress) + diff --git a/pkg/domain/stateDiff.go b/pkg/domain/stateDiff.go index 0e45a570..fc9b777e 100644 --- a/pkg/domain/stateDiff.go +++ b/pkg/domain/stateDiff.go @@ -39,8 +39,13 @@ func (a Action) IsDoguProxyAction() bool { func (diff StateDiff) HasChanges() bool { return diff.DoguDiffs.HasChanges() || diff.ComponentDiffs.HasChanges() || - diff.GlobalConfigDiffs.HasChanges() || - diff.HasDoguConfigChanges() + diff.HasConfigChanges() +} + +func (diff StateDiff) HasConfigChanges() bool { + return diff.GlobalConfigDiffs.HasChanges() || + diff.HasDoguConfigChanges() || + diff.HasSensitiveDoguConfigChanges() } func (diff StateDiff) HasDoguConfigChanges() bool { @@ -49,6 +54,10 @@ func (diff StateDiff) HasDoguConfigChanges() bool { return true } } + return false +} + +func (diff StateDiff) HasSensitiveDoguConfigChanges() bool { for _, configDiff := range diff.SensitiveDoguConfigDiffs { if configDiff.HasChanges() { return true From 30a6efb30ff129bbfbf3cb8623624fc32d9ebfeb Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Fri, 15 Aug 2025 21:10:39 +0200 Subject: [PATCH 030/119] #121 write down ideas for state diff --- pkg/domain/blueprintSpec.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 232833a7..05a6c05f 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -311,7 +311,28 @@ func (spec *BlueprintSpec) DetermineStateDiff( // because the blueprint could be executable even after a change of the blueprint. // Therefore, a check with "conditionChanged" is not enough to prevent, that we regenerate all events on every reconcile. - //TODO: We could set all diff-related conditions here, so that don't need to call every step even if the state diff says "no change" + //TODO: We could set all diff-related conditions here + // Con: this could override Reason and Message. + // They will be set again when we try to apply. + // We also could check, if there is a reason and a message and just change nothing then if there is still a diff. + // Pro: we don't need to update the blueprint so often -> big update here and only setting conditionTrue while applying things + // Con: StateDiff as it is could problematic because it hides conflict errors + // (is this a real problem if we set it anyways in the next run?). + // The idea is, that we just check the needed cluster-state when we try to apply. + // Then there is no central stateDiff anymore, just some independent ApplyUseCases. + // I am not sure with this yet. + // Con: a central stateDiff could be problematic because we then check every state and + // we cannot optimize it if we know, which watch triggered the reconciliation. + + //TODO: The state diff will be generated at every run and will be written on the blueprint-CR.Status. + // After every apply, the state diff will be empty and therefore will be deleted on the blueprint-CR. + // It is only useful for dry-run. + // Maybe we just not write it in the status anymore? How to debug then? + // The old blueprint-process was hard to understand because there was no overview. + // A separate BlueprintExecution-CR could be a solution but i am not sure, if we have enough time for that and if it is the right choice. + // CR could have the diff as it's spec. + // It is a single execution like k8s-jobs. + // The operator will always spawn more blueprintExecutions if they fail or there is a diff left after applying. return nil } From 2026c364a5e91b0014bf757c3ab27792f5b873d6 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Fri, 15 Aug 2025 21:37:59 +0200 Subject: [PATCH 031/119] #121 remove StatusPhaseInProgress That was easier than expected. The logic to fail if the status is already in progress was completely in the changeUseCase and therefore very easier to remove. I removed the whole process of setting the blueprint "in progress" and there is no replacement via conditions. The reason for that is, that we want specific conditions for applying dogus and components separately. --- pkg/application/applyBlueprintSpecUseCase.go | 16 +---- .../applyBlueprintSpecUseCase_test.go | 67 +++---------------- pkg/application/blueprintSpecChangeUseCase.go | 3 - .../blueprintSpecChangeUseCase_test.go | 48 ------------- pkg/domain/blueprintSpec.go | 18 ----- pkg/domain/blueprintSpec_test.go | 27 -------- pkg/domain/events.go | 15 +---- pkg/domain/events_test.go | 9 +-- 8 files changed, 18 insertions(+), 185 deletions(-) diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index f281edf9..01af87e9 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -105,17 +106,13 @@ func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.ApplyBlueprintSpec") logger.Info("start applying blueprint to the cluster") - err := useCase.startApplying(ctx, blueprint) - if err != nil { - return err - } applyError := useCase.componentInstallUseCase.ApplyComponentStates(ctx, blueprint) if applyError != nil { return useCase.handleApplyFailedError(ctx, blueprint, applyError) } - _, err = useCase.componentInstallUseCase.WaitForHealthyComponents(ctx) + _, err := useCase.componentInstallUseCase.WaitForHealthyComponents(ctx) if err != nil { return useCase.handleApplyFailedError(ctx, blueprint, err) } @@ -145,15 +142,6 @@ func (useCase *ApplyBlueprintSpecUseCase) handleApplyFailedError(ctx context.Con return applyError } -func (useCase *ApplyBlueprintSpecUseCase) startApplying(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - blueprintSpec.StartApplying() - err := useCase.repo.Update(ctx, blueprintSpec) - if err != nil { - return fmt.Errorf("cannot mark blueprint as in progress: %w", err) - } - return nil -} - // markBlueprintApplicationFailed marks the blueprint application as failed. // Returns the error which leads to the failed blueprint needs to be provided. func (useCase *ApplyBlueprintSpecUseCase) markBlueprintApplicationFailed(ctx context.Context, blueprintSpec *domain.BlueprintSpec, err error) error { diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index bea3cb29..8bb468e4 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -2,6 +2,8 @@ package application import ( "context" + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" @@ -9,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "testing" ) func TestNewApplyBlueprintSpecUseCase(t *testing.T) { @@ -26,27 +27,9 @@ func TestNewApplyBlueprintSpecUseCase(t *testing.T) { assert.Equal(t, healthMock, sut.healthUseCase) } -func TestApplyBlueprintSpecUseCase_markInProgress(t *testing.T) { - t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) - installUseCaseMock := newMockDoguInstallationUseCase(t) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.startApplying(testCtx, spec) - - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseInProgress, spec.Status) - }) -} - func TestApplyBlueprintSpecUseCase_markBlueprintApplicationFailed(t *testing.T) { t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseInProgress, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(nil) @@ -60,9 +43,7 @@ func TestApplyBlueprintSpecUseCase_markBlueprintApplicationFailed(t *testing.T) }) t.Run("repo error", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseInProgress, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) @@ -78,9 +59,7 @@ func TestApplyBlueprintSpecUseCase_markBlueprintApplicationFailed(t *testing.T) func TestApplyBlueprintSpecUseCase_markBlueprintApplied(t *testing.T) { t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseInProgress, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(nil) @@ -94,9 +73,7 @@ func TestApplyBlueprintSpecUseCase_markBlueprintApplied(t *testing.T) { }) t.Run("repo error", func(t *testing.T) { - spec := &domain.BlueprintSpec{ - Status: domain.StatusPhaseInProgress, - } + spec := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) @@ -111,19 +88,10 @@ func TestApplyBlueprintSpecUseCase_markBlueprintApplied(t *testing.T) { } func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { - statusTransitions := map[int]domain.StatusPhase{ - 1: domain.StatusPhaseInProgress, - 2: domain.StatusPhaseBlueprintApplied, - } t.Run("ok", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - var counter = 0 - repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { - counter++ - assert.Equal(t, statusTransitions[counter], spec.Status) - return nil - }).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) @@ -142,7 +110,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { t.Run("error waiting for dogu health", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) @@ -159,23 +127,10 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) }) - t.Run("fail to mark in progress", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseInProgress, blueprint.Status) - }) - t.Run("fail to apply component state", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) @@ -190,7 +145,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { t.Run("fail to wait for component health", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) @@ -206,7 +161,7 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { t.Run("fail to apply dogu state", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 3a4f4f76..6a2458d8 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -130,9 +130,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { switch blueprint.Status { - case domain.StatusPhaseInProgress: - // should only happen if the system was interrupted, normally this state will be updated to blueprintApplied or BlueprintApplicationFailed - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) case domain.StatusPhaseBlueprintApplied: return useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprint) case domain.StatusPhaseRestartsTriggered: diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 811d5522..aac8e809 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -333,54 +333,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { require.ErrorIs(t, actualErr, assert.AnError) }) - t.Run("handle in progress blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseInProgress, - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintSpec.Id).Return(nil) - // when - actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, actualErr) - }) - - t.Run("handle error when blueprint is in progress", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseInProgress, - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintSpec.Id).Return(assert.AnError) - // when - actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.ErrorIs(t, actualErr, assert.AnError) - }) - t.Run("handle error after blueprint was applied", func(t *testing.T) { // given repoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 05a6c05f..ed24f4af 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -49,8 +49,6 @@ type StatusPhase string const ( // StatusPhaseNew marks a newly created blueprint-CR. StatusPhaseNew StatusPhase = "" - // StatusPhaseInProgress marks that the blueprint is currently being processed. - StatusPhaseInProgress StatusPhase = "inProgress" // StatusPhaseBlueprintApplicationFailed shows that the blueprint application failed. StatusPhaseBlueprintApplicationFailed StatusPhase = "blueprintApplicationFailed" // StatusPhaseBlueprintApplied indicates that the blueprint was applied but the ecosystem is not healthy yet. @@ -426,13 +424,6 @@ func (spec *BlueprintSpec) CheckEcosystemHealthAfterwards(healthResult ecosystem } } -// StartApplying marks the blueprint as in progress, which indicates, that the system started applying the blueprint. -// This state is used to detect complete failures as this state will only stay persisted if the process failed before setting the state to blueprint applied. -func (spec *BlueprintSpec) StartApplying() { - spec.Status = StatusPhaseInProgress - spec.Events = append(spec.Events, InProgressEvent{}) -} - // MarkBlueprintApplicationFailed sets the blueprint state to application failed, which indicates that the blueprint could not be applied completely. // In reaction to this, further post-processing will happen. func (spec *BlueprintSpec) MarkBlueprintApplicationFailed(err error) { @@ -450,11 +441,6 @@ func (spec *BlueprintSpec) MarkBlueprintApplied() { func (spec *BlueprintSpec) CompletePostProcessing() { // this function will not be called, if the ecosystem is not healthy switch spec.Status { - case StatusPhaseInProgress: - spec.Status = StatusPhaseFailed - err := errors.New(handleInProgressMsg) - spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) - case StatusPhaseBlueprintApplicationFailed: spec.Status = StatusPhaseFailed spec.Events = append(spec.Events, ExecutionFailedEvent{err: errors.New("could not apply blueprint")}) @@ -560,7 +546,3 @@ func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) } } - -const handleInProgressMsg = "cannot handle blueprint in state " + string(StatusPhaseInProgress) + - " as this state shows that the appliance of the blueprint was interrupted before it could update the state " + - "to either " + string(StatusPhaseFailed) + " or " + string(StatusPhaseCompleted) diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index f9885d3a..1b82deca 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -666,20 +666,6 @@ func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { }) } -func TestBlueprintSpec_StartApplying(t *testing.T) { - t.Run("ok", func(t *testing.T) { - // given - spec := &BlueprintSpec{} - // when - spec.StartApplying() - // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseInProgress, - Events: []Event{InProgressEvent{}}, - }) - }) -} - func TestBlueprintSpec_MarkBlueprintApplicationFailed(t *testing.T) { // given spec := &BlueprintSpec{} @@ -718,19 +704,6 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { }) }) - t.Run("status change on failure InProgress -> Failed", func(t *testing.T) { - // given - spec := &BlueprintSpec{ - Status: StatusPhaseInProgress, - } - // when - spec.CompletePostProcessing() - // then - assert.Equal(t, StatusPhaseFailed, spec.Status) - require.Equal(t, 1, len(spec.Events)) - assert.Equal(t, ExecutionFailedEvent{errors.New(handleInProgressMsg)}, spec.Events[0]) - }) - t.Run("status change on failure ApplicationFailed -> Failed", func(t *testing.T) { // given spec := &BlueprintSpec{ diff --git a/pkg/domain/events.go b/pkg/domain/events.go index bfdf438a..7de1ae5a 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -2,10 +2,11 @@ package domain import ( "fmt" - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "slices" "strings" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" ) type Event interface { @@ -240,16 +241,6 @@ func (e BlueprintApplicationPreProcessedEvent) Message() string { return "" } -type InProgressEvent struct{} - -func (e InProgressEvent) Name() string { - return "InProgress" -} - -func (e InProgressEvent) Message() string { - return "" -} - type BlueprintAppliedEvent struct{} func (e BlueprintAppliedEvent) Name() string { diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 5ea6dccb..7331e496 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -2,10 +2,11 @@ package domain import ( "fmt" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" - "testing" ) func TestEvents(t *testing.T) { @@ -144,12 +145,6 @@ func TestEvents(t *testing.T) { expectedName: "BlueprintApplicationPreProcessed", expectedMessage: "", }, - { - name: "In progress", - event: InProgressEvent{}, - expectedName: "InProgress", - expectedMessage: "", - }, { name: "blueprint applied", event: BlueprintAppliedEvent{}, From a7a6875060d386d9294531d37428fdc178e25727 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 19 Aug 2025 12:38:29 +0200 Subject: [PATCH 032/119] #121 unify health checks, extract component apply Health Checks There were problems with the health conditions and events. We always execute all steps of the blueprint process. Therefore, if a dogu gets unhealthy after the blueprint completed, the message would be sth like: "ecosystem unhealthy upfront", which is misleading. This happened with conditions and events as well. Another problem was, that we had partial health checks. Setting the condition after this health checks would lead to a deletion of the former message which potentially included more info than e.g. only dogus. Because of that, we now have only one method for health checks, which sets the condition and unified health events if something changed. We now also set the condition to unknown if an error happens while we fetch the health states. This function gets called multiple times in the blueprint process. Maybe we can optimize it further, so we only do a health check in the same process again if we changed something in the meantime, or we just reconcile again after applying sth, so that the initial health check gets called again, but then we need to calculate the state diff again. Component Apply Applying dogus and components and waiting for their health was one big step in the former process. Now health checks and applying components are their own separate steps. Previously, the process failed without any retry if the "in progress" status was and another reconciliation happened. This is not the case anymore. The consequence of this is, that the blueprint will not stop applying if any error happens (unless the error happens everytime). Removing the "blueprintApplicationFailed", "failed", "completed" and "blueprintApplied" statuses are the next steps of the refactoring. We also should move applying dogus to its own use case or rename the current one. --- .../v2/blueprintSpecCRRepository_test.go | 5 - .../reconciler/blueprint_controller.go | 5 +- pkg/application/applyBlueprintSpecUseCase.go | 84 +---- .../applyBlueprintSpecUseCase_test.go | 211 +----------- pkg/application/applyComponentsUseCase.go | 42 +++ .../applyComponentsUseCase_test.go | 95 ++++++ pkg/application/blueprintSpecChangeUseCase.go | 28 +- .../blueprintSpecChangeUseCase_test.go | 80 ++++- .../componentInstallationUseCase.go | 1 + pkg/application/doguInstallationUseCase.go | 1 + pkg/application/ecosystemHealthUseCase.go | 45 ++- .../ecosystemHealthUseCase_test.go | 245 +++++++++++--- pkg/application/interfaces.go | 9 +- .../mock_applyComponentUseCase_test.go | 84 +++++ .../mock_ecosystemHealthUseCase_test.go | 35 +- pkg/application/selfUpgradeUseCase.go | 1 + pkg/application/selfUpgradeUseCase_test.go | 3 +- pkg/application/stateDiffUseCase.go | 1 + pkg/application/stateDiffUseCase_test.go | 3 +- pkg/bootstrap.go | 8 +- pkg/domain/blueprintSpec.go | 91 ++--- pkg/domain/blueprintSpec_test.go | 313 +++++++----------- pkg/domain/ecosystem/componentHealth.go | 5 +- pkg/domain/events.go | 61 ++-- pkg/domain/events_test.go | 31 +- 25 files changed, 839 insertions(+), 648 deletions(-) create mode 100644 pkg/application/applyComponentsUseCase.go create mode 100644 pkg/application/applyComponentsUseCase_test.go create mode 100644 pkg/application/mock_applyComponentUseCase_test.go diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index b350b133..5eb12dcb 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -16,7 +16,6 @@ import ( bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) @@ -315,14 +314,10 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { events = append(events, domain.StateDiffDoguDeterminedEvent{}, domain.StateDiffComponentDeterminedEvent{}, - domain.EcosystemHealthyUpfrontEvent{}, - domain.EcosystemUnhealthyUpfrontEvent{HealthResult: ecosystem.HealthResult{}}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffComponentDetermined", "component state diff determined: 0 actions ()") - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EcosystemHealthyUpfront", "dogu health ignored: false; component health ignored: false") - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "EcosystemUnhealthyUpfront", "ecosystem health:\n 0 dogu(s) are unhealthy: \n 0 component(s) are unhealthy: ") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecInvalid", "test-error") // when diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index f30df4eb..a63b10c6 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -3,12 +3,13 @@ package reconciler import ( "context" "errors" + "strings" + "time" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/application" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "strings" - "time" "github.com/go-logr/logr" ctrl "sigs.k8s.io/controller-runtime" diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index 01af87e9..f5d20aa2 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -12,79 +12,23 @@ import ( // ApplyBlueprintSpecUseCase contains all use cases which are needed for or around applying // the new ecosystem state after the determining the state diff. type ApplyBlueprintSpecUseCase struct { - repo blueprintSpecRepository - doguInstallUseCase doguInstallationUseCase - healthUseCase ecosystemHealthUseCase - componentInstallUseCase componentInstallationUseCase + repo blueprintSpecRepository + doguInstallUseCase doguInstallationUseCase + healthUseCase ecosystemHealthUseCase } func NewApplyBlueprintSpecUseCase( repo blueprintSpecRepository, doguInstallUseCase doguInstallationUseCase, healthUseCase ecosystemHealthUseCase, - componentInstallUseCase componentInstallationUseCase, ) *ApplyBlueprintSpecUseCase { return &ApplyBlueprintSpecUseCase{ - repo: repo, - doguInstallUseCase: doguInstallUseCase, - healthUseCase: healthUseCase, - componentInstallUseCase: componentInstallUseCase, + repo: repo, + doguInstallUseCase: doguInstallUseCase, + healthUseCase: healthUseCase, } } -// CheckEcosystemHealthUpfront checks the ecosystem health before applying the blueprint and sets the related status in the blueprint. -// Returns domain.UnhealthyEcosystemError if the ecosystem is currently unhealthy or -// returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or -// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state or -// There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthUpfront") - - logger.Info("check ecosystem health") - healthResult, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint.Config.IgnoreDoguHealth, blueprint.Config.IgnoreComponentHealth) - if err != nil { - return fmt.Errorf("cannot check ecosystem health upfront of applying the blueprint %q: %w", blueprint.Id, err) - } - healthErr := blueprint.CheckEcosystemHealthUpfront(healthResult) - // persist blueprint even with error, because it will set conditions - err = useCase.repo.Update(ctx, blueprint) - if err != nil { - // healthErr can be ignored here. We have a more serious problem if we cannot persist the blueprint - // the health check will be repeated anyway - return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprint.Id, err) - } - - return healthErr -} - -// CheckEcosystemHealthAfterwards waits for a healthy ecosystem health after applying the blueprint and sets the related status in the blueprint. -// Returns domain.UnhealthyEcosystemError if the ecosystem is currently unhealthy or -// returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or -// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. -// There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.CheckEcosystemHealthAfterwards") - - logger.Info("check ecosystem health") - - // do not ignore the health states of dogus and components here, as we want to set the blueprint status according to the result. - // The blueprint is already executed here. - healthResult, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, false, false) - if err != nil { - return fmt.Errorf("cannot check ecosystem health after applying the blueprint %q: %w", blueprint.Id, err) - } - healthErr := blueprint.CheckEcosystemHealthAfterwards(healthResult) - - err = useCase.repo.Update(ctx, blueprint) - if err != nil { - // healthErr can be ignored here. We have a more serious problem if we cannot persist the blueprint - // the health check will be repeated anyway - return fmt.Errorf("cannot save blueprint spec %q after checking the ecosystem health: %w", blueprint.Id, err) - } - - return healthErr -} - // PostProcessBlueprintApplication makes changes to the environment after applying the blueprint. // returns a domainservice.InternalError on any error. func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { @@ -105,19 +49,7 @@ func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx co func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.ApplyBlueprintSpec") - logger.Info("start applying blueprint to the cluster") - - applyError := useCase.componentInstallUseCase.ApplyComponentStates(ctx, blueprint) - if applyError != nil { - return useCase.handleApplyFailedError(ctx, blueprint, applyError) - } - - _, err := useCase.componentInstallUseCase.WaitForHealthyComponents(ctx) - if err != nil { - return useCase.handleApplyFailedError(ctx, blueprint, err) - } - - applyError = useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) + applyError := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) if applyError != nil { return useCase.handleApplyFailedError(ctx, blueprint, applyError) } @@ -125,7 +57,7 @@ func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context // FIXME: this health check is blocking. I think we need to split the apply logic into multiple steps to // we have to wait for all dogus to be healthy // otherwise service account creation might fail because dogus are restarted right after this step - _, err = useCase.doguInstallUseCase.WaitForHealthyDogus(ctx) + _, err := useCase.doguInstallUseCase.WaitForHealthyDogus(ctx) if err != nil { return useCase.handleApplyFailedError(ctx, blueprint, err) } diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index 8bb468e4..be8e9a07 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -7,22 +7,17 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestNewApplyBlueprintSpecUseCase(t *testing.T) { repoMock := newMockBlueprintSpecRepository(t) installUseCaseMock := newMockDoguInstallationUseCase(t) - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) healthMock := newMockEcosystemHealthUseCase(t) - sut := NewApplyBlueprintSpecUseCase(repoMock, installUseCaseMock, healthMock, componentInstallUseCaseMock) + sut := NewApplyBlueprintSpecUseCase(repoMock, installUseCaseMock, healthMock) assert.Equal(t, installUseCaseMock, sut.doguInstallUseCase) - assert.Equal(t, componentInstallUseCaseMock, sut.componentInstallUseCase) assert.Equal(t, repoMock, sut.repo) assert.Equal(t, healthMock, sut.healthUseCase) } @@ -96,11 +91,8 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, nil) - componentInstallUseCase := newMockComponentInstallationUseCase(t) - componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - componentInstallUseCase.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCase} + useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} err := useCase.ApplyBlueprintSpec(testCtx, blueprint) @@ -115,42 +107,8 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, assert.AnError) - componentInstallUseCase := newMockComponentInstallationUseCase(t) - componentInstallUseCase.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - componentInstallUseCase.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) - - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCase} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) - }) - - t.Run("fail to apply component state", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil, componentInstallUseCase: componentInstallUseCaseMock} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) - }) - - t.Run("fail to wait for component health", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: nil, componentInstallUseCase: componentInstallUseCaseMock} + useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} err := useCase.ApplyBlueprintSpec(testCtx, blueprint) @@ -162,13 +120,9 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCaseMock} + useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} err := useCase.ApplyBlueprintSpec(testCtx, blueprint) @@ -188,13 +142,9 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { return assert.AnError } }) - - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - componentInstallUseCaseMock.EXPECT().WaitForHealthyComponents(testCtx).Return(ecosystem.ComponentHealthResult{}, nil) installUseCaseMock := newMockDoguInstallationUseCase(t) installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock, componentInstallUseCase: componentInstallUseCaseMock} + useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} err := useCase.ApplyBlueprintSpec(testCtx, blueprint) @@ -203,160 +153,13 @@ func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { }) } -func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront(t *testing.T) { - t.Run("should fail to get health result", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - } - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, assert.AnError) - - sut := NewApplyBlueprintSpecUseCase(nil, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot check ecosystem health upfront of applying the blueprint \"blueprint1\"") - }) - t.Run("should fail to update blueprint spec", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - } - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, false, false).Return(ecosystem.HealthResult{}, nil) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot save blueprint spec \"blueprint1\" after checking the ecosystem health") - }) - t.Run("should succeed, ignoring dogu and component health", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: true, - IgnoreComponentHealth: true, - }} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, true, true).Return(ecosystem.HealthResult{}, nil) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) - - // then - require.NoError(t, err) - }) - t.Run("should succeed", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(mock.Anything, false, false).Return(ecosystem.HealthResult{}, nil) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthUpfront(testCtx, blueprint) - - // then - require.NoError(t, err) - }) -} - -func TestApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards(t *testing.T) { - t.Run("should fail to get health result", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - } - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, assert.AnError) - - sut := NewApplyBlueprintSpecUseCase(nil, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot check ecosystem health after applying the blueprint \"blueprint1\"") - }) - - t.Run("should fail to update blueprint spec", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - } - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, nil) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot save blueprint spec \"blueprint1\" after checking the ecosystem health") - }) - - t.Run("should succeed", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, - } - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - healthMock := newMockEcosystemHealthUseCase(t) - healthMock.EXPECT().CheckEcosystemHealth(testCtx, false, false).Return(ecosystem.HealthResult{}, nil) - - sut := NewApplyBlueprintSpecUseCase(repoMock, nil, healthMock, nil) - - // when - err := sut.CheckEcosystemHealthAfterwards(testCtx, blueprint) - - // then - require.NoError(t, err) - condition := meta.FindStatusCondition(*blueprint.Conditions, domain.ConditionEcosystemHealthy) - require.NotNil(t, condition) - assert.Equal(t, metav1.ConditionTrue, condition.Status) - }) -} - func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) { t.Run("ok", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) + useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil) err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) @@ -371,7 +174,7 @@ func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) repoMock := newMockBlueprintSpecRepository(t) repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil, nil) + useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil) err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go new file mode 100644 index 00000000..fc435dea --- /dev/null +++ b/pkg/application/applyComponentsUseCase.go @@ -0,0 +1,42 @@ +package application + +import ( + "context" + "errors" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" +) + +// ApplyComponentsUseCase can handle component installations, updates and deletions. +type ApplyComponentsUseCase struct { + repo blueprintSpecRepository + componentUseCase componentInstallationUseCase +} + +func NewApplyComponentsUseCase( + repo blueprintSpecRepository, + componentUseCase componentInstallationUseCase, +) *ApplyComponentsUseCase { + return &ApplyComponentsUseCase{ + repo: repo, + componentUseCase: componentUseCase, + } +} + +// ApplyComponents applies components if necessary. +// The conditions in the blueprint will be set accordingly. +// returns domainservice.ConflictError if there was a concurrent update to the blueprint or +// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. +func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.componentUseCase.ApplyComponentStates(ctx, blueprint) + changed := blueprint.SetComponentAppliedCondition(err) + + if changed { + updateErr := useCase.repo.Update(ctx, blueprint) + if updateErr != nil { + return fmt.Errorf("cannot update condition while applying conditions: %w", errors.Join(updateErr, err)) + } + } + return err +} diff --git a/pkg/application/applyComponentsUseCase_test.go b/pkg/application/applyComponentsUseCase_test.go new file mode 100644 index 00000000..078c6ed3 --- /dev/null +++ b/pkg/application/applyComponentsUseCase_test.go @@ -0,0 +1,95 @@ +package application + +import ( + "testing" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" +) + +func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { + t.Run("ok", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + StateDiff: domain.StateDiff{ + ComponentDiffs: []domain.ComponentDiff{ + { + Name: "k8s-dogu-operator", + NeededActions: []domain.Action{ + domain.ActionUpgrade, + }, + }, + }, + }, + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) + useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) + + err := useCase.ApplyComponents(testCtx, blueprint) + + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) + }) + + t.Run("no update without condition change", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + StateDiff: domain.StateDiff{}, + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() + componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil).Twice() + useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) + + err := useCase.ApplyComponents(testCtx, blueprint) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + err = useCase.ApplyComponents(testCtx, blueprint) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + }) + + t.Run("fail to apply components", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) + useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) + + err := useCase.ApplyComponents(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionComponentsApplied)) + }) + + t.Run("fail to update blueprint", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) + componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) + useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) + + err := useCase.ApplyComponents(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + }) +} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 6a2458d8..c2a77948 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -19,6 +19,8 @@ type BlueprintSpecChangeUseCase struct { ecosystemConfigUseCase ecosystemConfigUseCase doguRestartUseCase doguRestartUseCase selfUpgradeUseCase selfUpgradeUseCase + applyComponentUseCase applyComponentUseCase + healthUseCase ecosystemHealthUseCase } func NewBlueprintSpecChangeUseCase( @@ -30,7 +32,8 @@ func NewBlueprintSpecChangeUseCase( ecosystemConfigUseCase ecosystemConfigUseCase, doguRestartUseCase doguRestartUseCase, selfUpgradeUseCase selfUpgradeUseCase, - + applyComponentUseCase applyComponentUseCase, + ecosystemHealthUseCase ecosystemHealthUseCase, ) *BlueprintSpecChangeUseCase { return &BlueprintSpecChangeUseCase{ repo: repo, @@ -41,6 +44,8 @@ func NewBlueprintSpecChangeUseCase( ecosystemConfigUseCase: ecosystemConfigUseCase, doguRestartUseCase: doguRestartUseCase, selfUpgradeUseCase: selfUpgradeUseCase, + applyComponentUseCase: applyComponentUseCase, + healthUseCase: ecosystemHealthUseCase, } } @@ -90,7 +95,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } // always check health here, even if we already know here, that we don't need to apply anything // because we need to update the health condition - err = useCase.applyUseCase.CheckEcosystemHealthUpfront(ctx, blueprint) + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) if err != nil { return err } @@ -100,21 +105,34 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return nil } + // === Apply from here on === err = useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) if err != nil { // could be a domain.AwaitSelfUpgradeError to trigger another reconcile return err } - err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) if err != nil { return err } - + err = useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) + if err != nil { + return err + } + // check after applying components + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } err = useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) if err != nil { return err } + // check after installing or updating dogus + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } //TODO: remove this loop, when all use cases are reworked without the use of status for blueprint.Status != domain.StatusPhaseCompleted { @@ -133,7 +151,7 @@ func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blu case domain.StatusPhaseBlueprintApplied: return useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprint) case domain.StatusPhaseRestartsTriggered: - err := useCase.applyUseCase.CheckEcosystemHealthAfterwards(ctx, blueprint) + _, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index aac8e809..27a27430 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -41,7 +41,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, @@ -104,7 +107,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) expectedError := &domainservice.InternalError{ WrappedError: nil, @@ -130,7 +136,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -159,7 +168,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -188,7 +200,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -218,7 +233,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -254,7 +272,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -298,7 +319,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -319,7 +343,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -343,7 +370,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, @@ -372,7 +402,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -397,7 +430,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -419,7 +455,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ Id: blueprintId, @@ -442,7 +481,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -464,7 +506,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -489,7 +534,10 @@ func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase) + componentUseCase := newMockApplyComponentUseCase(t) + healthUseCase := newMockEcosystemHealthUseCase(t) + useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, diff --git a/pkg/application/componentInstallationUseCase.go b/pkg/application/componentInstallationUseCase.go index 9da61e81..d41fac5e 100644 --- a/pkg/application/componentInstallationUseCase.go +++ b/pkg/application/componentInstallationUseCase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index c1e7aaf6..a96b7d9f 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -3,6 +3,7 @@ package application import ( "context" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" diff --git a/pkg/application/ecosystemHealthUseCase.go b/pkg/application/ecosystemHealthUseCase.go index 77e31256..7783fb36 100644 --- a/pkg/application/ecosystemHealthUseCase.go +++ b/pkg/application/ecosystemHealthUseCase.go @@ -3,28 +3,69 @@ package application import ( "context" "errors" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" ) type EcosystemHealthUseCase struct { doguUseCase doguInstallationUseCase componentUseCase componentInstallationUseCase + blueprintRepo blueprintSpecRepository } func NewEcosystemHealthUseCase( doguUseCase doguInstallationUseCase, componentUseCase componentInstallationUseCase, + blueprintRepo blueprintSpecRepository, ) *EcosystemHealthUseCase { return &EcosystemHealthUseCase{ doguUseCase: doguUseCase, componentUseCase: componentUseCase, + blueprintRepo: blueprintRepo, + } +} + +// CheckEcosystemHealth checks the ecosystem health once and sets the health condition accordingly. +// Returns the health result. +// Returns a domain.UnhealthyEcosystemError and the ecosystem.HealthResult if the ecosystem is unhealthy or +// returns a domainservice.ConflictError if there was a conflicting update to the blueprint or +// returns a domainservice.InternalError if the health status could not be determined or the there was any another problem. +func (useCase *EcosystemHealthUseCase) CheckEcosystemHealth( + ctx context.Context, + blueprint *domain.BlueprintSpec, +) (ecosystem.HealthResult, error) { + health, determineHealthError := useCase.getEcosystemHealth( + ctx, + blueprint.Config.IgnoreDoguHealth, + blueprint.Config.IgnoreComponentHealth, + ) + infoChanged := blueprint.HandleHealthResult(health, determineHealthError) + if infoChanged { + updateErr := useCase.blueprintRepo.Update(ctx, blueprint) + if updateErr != nil { + return ecosystem.HealthResult{}, fmt.Errorf( + "could not update health condition after health check: %w", + errors.Join(updateErr, determineHealthError), + ) + } + } + if determineHealthError == nil && !health.AllHealthy() { + return health, domain.NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy", health) } + + return health, determineHealthError } -// CheckEcosystemHealth checks the ecosystem health once. +// getEcosystemHealth checks the ecosystem health once. // Returns a HealthResult even if parts are unhealthy or // returns an error if the health state could not be fetched. -func (useCase *EcosystemHealthUseCase) CheckEcosystemHealth(ctx context.Context, ignoreDoguHealth bool, ignoreComponentHealth bool) (ecosystem.HealthResult, error) { +func (useCase *EcosystemHealthUseCase) getEcosystemHealth( + ctx context.Context, + ignoreDoguHealth bool, + ignoreComponentHealth bool, +) (ecosystem.HealthResult, error) { var doguHealth ecosystem.DoguHealthResult var doguHealthErr error if !ignoreDoguHealth { diff --git a/pkg/application/ecosystemHealthUseCase_test.go b/pkg/application/ecosystemHealthUseCase_test.go index 572c544d..7aea1414 100644 --- a/pkg/application/ecosystemHealthUseCase_test.go +++ b/pkg/application/ecosystemHealthUseCase_test.go @@ -1,49 +1,220 @@ package application import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "testing" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" ) +var ( + healthyDogu = map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.AvailableHealthStatus: {"postgresql"}, + } + mixedDoguHealth = map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.AvailableHealthStatus: {"postgresql"}, + ecosystem.UnavailableHealthStatus: {"postfix"}, + ecosystem.PendingHealthStatus: {"scm"}, + } + healthyComponent = map[ecosystem.HealthStatus][]common.SimpleComponentName{ + ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, + } + mixedComponentHealth = map[ecosystem.HealthStatus][]common.SimpleComponentName{ + ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, + ecosystem.UnavailableHealthStatus: {"k8s-etcd"}, + ecosystem.PendingHealthStatus: {"k8s-service-discovery"}, + ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, + } +) + func TestNewEcosystemHealthUseCase(t *testing.T) { doguUseCase := newMockDoguInstallationUseCase(t) componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) assert.Same(t, doguUseCase, useCase.doguUseCase) assert.Same(t, componentUseCase, useCase.componentUseCase) } func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { - t.Run("ok", func(t *testing.T) { + t.Run("all healthy", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: false, + IgnoreComponentHealth: false, + }, + } + + doguHealth := ecosystem.DoguHealthResult{ + DogusByStatus: healthyDogu, + } + componentHealth := ecosystem.ComponentHealthResult{ + ComponentsByStatus: healthyComponent, + } + doguUseCase := newMockDoguInstallationUseCase(t) + doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) + componentUseCase := newMockComponentInstallationUseCase(t) + componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) + blueprintRepo := newMockBlueprintSpecRepository(t) + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + + health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) + + require.NoError(t, err) + assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + }) + + t.Run("unhealthy", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: false, + IgnoreComponentHealth: false, + }, + } + doguHealth := ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postgresql"}, - ecosystem.UnavailableHealthStatus: {"postfix"}, - ecosystem.PendingHealthStatus: {"scm"}, + DogusByStatus: mixedDoguHealth, + } + componentHealth := ecosystem.ComponentHealthResult{ + ComponentsByStatus: mixedComponentHealth, + } + doguUseCase := newMockDoguInstallationUseCase(t) + doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) + componentUseCase := newMockComponentInstallationUseCase(t) + componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) + blueprintRepo := newMockBlueprintSpecRepository(t) + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + + health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) + + assert.Error(t, err) + assert.ErrorContains(t, err, "ecosystem is unhealthy") + assert.ErrorContains(t, err, "2 dogu(s) are unhealthy: postfix, scm") + assert.ErrorContains(t, err, "3 component(s) are unhealthy: k8s-dogu-operator, k8s-etcd, k8s-service-discovery") + assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + }) + + t.Run("error updating blueprint", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: false, + IgnoreComponentHealth: false, }, } + + doguHealth := ecosystem.DoguHealthResult{ + DogusByStatus: mixedDoguHealth, + } componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"k8s-etcd"}, - ecosystem.PendingHealthStatus: {"k8s-service-discovery"}, - ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, + ComponentsByStatus: mixedComponentHealth, + } + doguUseCase := newMockDoguInstallationUseCase(t) + doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) + componentUseCase := newMockComponentInstallationUseCase(t) + componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) + blueprintRepo := newMockBlueprintSpecRepository(t) + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + + health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) + + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "could not update health condition after health check") + assert.Equal(t, ecosystem.HealthResult{}, health) + }) + + t.Run("error getting health", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: false, + IgnoreComponentHealth: false, + }, + } + + doguHealth := ecosystem.DoguHealthResult{} + componentHealth := ecosystem.ComponentHealthResult{} + + doguUseCase := newMockDoguInstallationUseCase(t) + doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, assert.AnError) + componentUseCase := newMockComponentInstallationUseCase(t) + componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, assert.AnError) + blueprintRepo := newMockBlueprintSpecRepository(t) + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + + _, err := useCase.CheckEcosystemHealth(testCtx, blueprint) + + assert.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionPresentAndEqual( + *blueprint.Conditions, domain.ConditionEcosystemHealthy, metav1.ConditionUnknown, + )) + }) + + t.Run("no update without health change", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: false, + IgnoreComponentHealth: false, }, } + + doguHealth := ecosystem.DoguHealthResult{ + DogusByStatus: mixedDoguHealth, + } + componentHealth := ecosystem.ComponentHealthResult{ + ComponentsByStatus: mixedComponentHealth, + } + doguUseCase := newMockDoguInstallationUseCase(t) + doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil).Twice() + componentUseCase := newMockComponentInstallationUseCase(t) + componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil).Twice() + blueprintRepo := newMockBlueprintSpecRepository(t) + blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Once() + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + + _, err := useCase.CheckEcosystemHealth(testCtx, blueprint) + assert.ErrorContains(t, err, "ecosystem is unhealthy") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + _, err = useCase.CheckEcosystemHealth(testCtx, blueprint) //no repo.Update called again + assert.ErrorContains(t, err, "ecosystem is unhealthy") + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + }) +} + +func TestEcosystemHealthUseCase_getEcosystemHealth(t *testing.T) { + t.Run("ok", func(t *testing.T) { + doguHealth := ecosystem.DoguHealthResult{ + DogusByStatus: mixedDoguHealth, + } + componentHealth := ecosystem.ComponentHealthResult{ + ComponentsByStatus: mixedComponentHealth, + } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) componentUseCase := newMockComponentInstallationUseCase(t) componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) - health, err := useCase.CheckEcosystemHealth(testCtx, false, false) + health, err := useCase.getEcosystemHealth(testCtx, false, false) require.NoError(t, err) assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) @@ -51,18 +222,14 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { t.Run("ok, ignore dogu health", func(t *testing.T) { componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"k8s-etcd"}, - ecosystem.PendingHealthStatus: {"k8s-service-discovery"}, - ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, - }, + ComponentsByStatus: mixedComponentHealth, } componentUseCase := newMockComponentInstallationUseCase(t) componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) - useCase := NewEcosystemHealthUseCase(nil, componentUseCase) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(nil, componentUseCase, blueprintRepo) - health, err := useCase.CheckEcosystemHealth(testCtx, true, false) + health, err := useCase.getEcosystemHealth(testCtx, true, false) require.NoError(t, err) assert.Equal(t, ecosystem.HealthResult{ComponentHealth: componentHealth}, health) @@ -70,17 +237,14 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { t.Run("ok, ignore component health", func(t *testing.T) { doguHealth := ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postgresql"}, - ecosystem.UnavailableHealthStatus: {"postfix"}, - ecosystem.PendingHealthStatus: {"scm"}, - }, + DogusByStatus: mixedDoguHealth, } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - useCase := NewEcosystemHealthUseCase(doguUseCase, nil) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(doguUseCase, nil, blueprintRepo) - health, err := useCase.CheckEcosystemHealth(testCtx, false, true) + health, err := useCase.getEcosystemHealth(testCtx, false, true) require.NoError(t, err) assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth}, health) @@ -88,39 +252,32 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { t.Run("error checking dogu health", func(t *testing.T) { componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"k8s-etcd"}, - ecosystem.PendingHealthStatus: {"k8s-service-discovery"}, - ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, - }, + ComponentsByStatus: mixedComponentHealth, } componentUseCase := newMockComponentInstallationUseCase(t) componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(ecosystem.DoguHealthResult{}, assert.AnError) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) - _, err := useCase.CheckEcosystemHealth(testCtx, false, false) + _, err := useCase.getEcosystemHealth(testCtx, false, false) require.ErrorIs(t, err, assert.AnError) }) t.Run("error checking component health", func(t *testing.T) { doguHealth := ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postgresql"}, - ecosystem.UnavailableHealthStatus: {"postfix"}, - ecosystem.PendingHealthStatus: {"scm"}, - }, + DogusByStatus: mixedDoguHealth, } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) componentUseCase := newMockComponentInstallationUseCase(t) componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(ecosystem.ComponentHealthResult{}, assert.AnError) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase) + blueprintRepo := newMockBlueprintSpecRepository(t) + useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) - _, err := useCase.CheckEcosystemHealth(testCtx, false, false) + _, err := useCase.getEcosystemHealth(testCtx, false, false) require.ErrorIs(t, err, assert.AnError) }) diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 5ee6c4d0..bb9815ba 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -2,6 +2,7 @@ package application import ( "context" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" @@ -30,6 +31,10 @@ type doguRestartUseCase interface { TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error } +type applyComponentUseCase interface { + ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error +} + type componentInstallationUseCase interface { ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) @@ -38,14 +43,12 @@ type componentInstallationUseCase interface { } type applyBlueprintSpecUseCase interface { - CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error - CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error } type ecosystemHealthUseCase interface { - CheckEcosystemHealth(ctx context.Context, ignoreDoguHealth bool, ignoreComponentHealth bool) (ecosystem.HealthResult, error) + CheckEcosystemHealth(context.Context, *domain.BlueprintSpec) (ecosystem.HealthResult, error) } type selfUpgradeUseCase interface { diff --git a/pkg/application/mock_applyComponentUseCase_test.go b/pkg/application/mock_applyComponentUseCase_test.go new file mode 100644 index 00000000..4214fcd5 --- /dev/null +++ b/pkg/application/mock_applyComponentUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockApplyComponentUseCase is an autogenerated mock type for the applyComponentUseCase type +type mockApplyComponentUseCase struct { + mock.Mock +} + +type mockApplyComponentUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockApplyComponentUseCase) EXPECT() *mockApplyComponentUseCase_Expecter { + return &mockApplyComponentUseCase_Expecter{mock: &_m.Mock} +} + +// ApplyComponents provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyComponentUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for ApplyComponents") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockApplyComponentUseCase_ApplyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponents' +type mockApplyComponentUseCase_ApplyComponents_Call struct { + *mock.Call +} + +// ApplyComponents is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyComponentUseCase_Expecter) ApplyComponents(ctx interface{}, blueprint interface{}) *mockApplyComponentUseCase_ApplyComponents_Call { + return &mockApplyComponentUseCase_ApplyComponents_Call{Call: _e.mock.On("ApplyComponents", ctx, blueprint)} +} + +func (_c *mockApplyComponentUseCase_ApplyComponents_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyComponentUseCase_ApplyComponents_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockApplyComponentUseCase_ApplyComponents_Call) Return(_a0 error) *mockApplyComponentUseCase_ApplyComponents_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockApplyComponentUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyComponentUseCase_ApplyComponents_Call { + _c.Call.Return(run) + return _c +} + +// newMockApplyComponentUseCase creates a new instance of mockApplyComponentUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockApplyComponentUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockApplyComponentUseCase { + mock := &mockApplyComponentUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/application/mock_ecosystemHealthUseCase_test.go b/pkg/application/mock_ecosystemHealthUseCase_test.go index a0b5e2b1..29a31aa3 100644 --- a/pkg/application/mock_ecosystemHealthUseCase_test.go +++ b/pkg/application/mock_ecosystemHealthUseCase_test.go @@ -5,7 +5,9 @@ package application import ( context "context" + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + mock "github.com/stretchr/testify/mock" ) @@ -22,9 +24,9 @@ func (_m *mockEcosystemHealthUseCase) EXPECT() *mockEcosystemHealthUseCase_Expec return &mockEcosystemHealthUseCase_Expecter{mock: &_m.Mock} } -// CheckEcosystemHealth provides a mock function with given fields: ctx, ignoreDoguHealth, ignoreComponentHealth -func (_m *mockEcosystemHealthUseCase) CheckEcosystemHealth(ctx context.Context, ignoreDoguHealth bool, ignoreComponentHealth bool) (ecosystem.HealthResult, error) { - ret := _m.Called(ctx, ignoreDoguHealth, ignoreComponentHealth) +// CheckEcosystemHealth provides a mock function with given fields: _a0, _a1 +func (_m *mockEcosystemHealthUseCase) CheckEcosystemHealth(_a0 context.Context, _a1 *domain.BlueprintSpec) (ecosystem.HealthResult, error) { + ret := _m.Called(_a0, _a1) if len(ret) == 0 { panic("no return value specified for CheckEcosystemHealth") @@ -32,17 +34,17 @@ func (_m *mockEcosystemHealthUseCase) CheckEcosystemHealth(ctx context.Context, var r0 ecosystem.HealthResult var r1 error - if rf, ok := ret.Get(0).(func(context.Context, bool, bool) (ecosystem.HealthResult, error)); ok { - return rf(ctx, ignoreDoguHealth, ignoreComponentHealth) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) (ecosystem.HealthResult, error)); ok { + return rf(_a0, _a1) } - if rf, ok := ret.Get(0).(func(context.Context, bool, bool) ecosystem.HealthResult); ok { - r0 = rf(ctx, ignoreDoguHealth, ignoreComponentHealth) + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) ecosystem.HealthResult); ok { + r0 = rf(_a0, _a1) } else { r0 = ret.Get(0).(ecosystem.HealthResult) } - if rf, ok := ret.Get(1).(func(context.Context, bool, bool) error); ok { - r1 = rf(ctx, ignoreDoguHealth, ignoreComponentHealth) + if rf, ok := ret.Get(1).(func(context.Context, *domain.BlueprintSpec) error); ok { + r1 = rf(_a0, _a1) } else { r1 = ret.Error(1) } @@ -56,16 +58,15 @@ type mockEcosystemHealthUseCase_CheckEcosystemHealth_Call struct { } // CheckEcosystemHealth is a helper method to define mock.On call -// - ctx context.Context -// - ignoreDoguHealth bool -// - ignoreComponentHealth bool -func (_e *mockEcosystemHealthUseCase_Expecter) CheckEcosystemHealth(ctx interface{}, ignoreDoguHealth interface{}, ignoreComponentHealth interface{}) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { - return &mockEcosystemHealthUseCase_CheckEcosystemHealth_Call{Call: _e.mock.On("CheckEcosystemHealth", ctx, ignoreDoguHealth, ignoreComponentHealth)} +// - _a0 context.Context +// - _a1 *domain.BlueprintSpec +func (_e *mockEcosystemHealthUseCase_Expecter) CheckEcosystemHealth(_a0 interface{}, _a1 interface{}) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { + return &mockEcosystemHealthUseCase_CheckEcosystemHealth_Call{Call: _e.mock.On("CheckEcosystemHealth", _a0, _a1)} } -func (_c *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call) Run(run func(ctx context.Context, ignoreDoguHealth bool, ignoreComponentHealth bool)) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { +func (_c *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call) Run(run func(_a0 context.Context, _a1 *domain.BlueprintSpec)) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(bool), args[2].(bool)) + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) }) return _c } @@ -75,7 +76,7 @@ func (_c *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call) Return(_a0 ecosy return _c } -func (_c *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call) RunAndReturn(run func(context.Context, bool, bool) (ecosystem.HealthResult, error)) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { +func (_c *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) (ecosystem.HealthResult, error)) *mockEcosystemHealthUseCase_CheckEcosystemHealth_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/selfUpgradeUseCase.go b/pkg/application/selfUpgradeUseCase.go index b6e86935..f7dee243 100644 --- a/pkg/application/selfUpgradeUseCase.go +++ b/pkg/application/selfUpgradeUseCase.go @@ -3,6 +3,7 @@ package application import ( "context" "fmt" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go index e2c69f2c..2eafe6f6 100644 --- a/pkg/application/selfUpgradeUseCase_test.go +++ b/pkg/application/selfUpgradeUseCase_test.go @@ -2,6 +2,8 @@ package application import ( "context" + "testing" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -10,7 +12,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "k8s.io/apimachinery/pkg/api/meta" - "testing" ) func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index 6fbd778f..f91e430a 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 6ce6a804..24f66745 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -1,6 +1,8 @@ package application import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -10,7 +12,6 @@ import ( "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) var ( diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index 4e2f7440..e2873b02 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -2,6 +2,7 @@ package pkg import ( "fmt" + adapterconfigk8s "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/config/kubernetes" v2 "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/blueprintcr/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/restartcr" @@ -84,8 +85,9 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name stateDiffUseCase := application.NewStateDiffUseCase(blueprintSpecRepository, doguInstallationRepo, componentInstallationRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintSpecRepository, doguInstallationRepo, healthConfigRepo) componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintSpecRepository, componentInstallationRepo, healthConfigRepo) - ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase) - applyBlueprintSpecUseCase := application.NewApplyBlueprintSpecUseCase(blueprintSpecRepository, doguInstallationUseCase, ecosystemHealthUseCase, componentInstallationUseCase) + ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintSpecRepository) + applyBlueprintSpecUseCase := application.NewApplyBlueprintSpecUseCase(blueprintSpecRepository, doguInstallationUseCase, ecosystemHealthUseCase) + applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintSpecRepository, componentInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintSpecRepository, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) doguRestartUseCase := application.NewDoguRestartUseCase(doguInstallationRepo, blueprintSpecRepository, restartRepository) @@ -97,6 +99,8 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name applyBlueprintSpecUseCase, ConfigUseCase, doguRestartUseCase, selfUpgradeUseCase, + applyComponentUseCase, + ecosystemHealthUseCase, ) blueprintReconciler := reconciler.NewBlueprintReconciler(blueprintChangeUseCase) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index ed24f4af..115f2da4 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -39,8 +39,8 @@ const ( ConditionEcosystemHealthy = "EcosystemHealthy" ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" ConditionConfigApplied = "ConfigApplied" - ConditionDogusApplied = "DogusApplied" ConditionComponentsApplied = "ComponentsApplied" + ConditionDogusApplied = "DogusApplied" ConditionBlueprintApplied = "BlueprintApplied" ) @@ -334,36 +334,50 @@ func (spec *BlueprintSpec) DetermineStateDiff( return nil } -// CheckEcosystemHealthUpfront checks if the ecosystem is healthy with the given health result and sets the next status phase depending on that. -func (spec *BlueprintSpec) CheckEcosystemHealthUpfront(healthResult ecosystem.HealthResult) error { - // healthResult does not contain dogu info if IgnoreDoguHealth flag is set. (no need to load all doguInstallations then) - // Therefore we don't need to exclude dogus while checking with AllHealthy() +// HandleHealthResult sets the healthCondition accordingly to the healthResult and a possible error. +// if an error is given, the condition will be set to unknown. +// The function returns true if the condition changed, otherwise false. +func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResult, err error) bool { + if err != nil { + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionUnknown, + Reason: "CannotCheckHealth", + Message: err.Error(), + }) + return conditionChanged + } + if healthResult.AllHealthy() { - event := EcosystemHealthyUpfrontEvent{ + event := EcosystemHealthyEvent{ doguHealthIgnored: spec.Config.IgnoreDoguHealth, componentHealthIgnored: spec.Config.IgnoreComponentHealth, } conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, Status: metav1.ConditionTrue, + Reason: "Healthy", Message: event.Message(), }) if conditionChanged { spec.Events = append(spec.Events, event) } - return nil - } else { - event := EcosystemUnhealthyUpfrontEvent{HealthResult: healthResult} - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionEcosystemHealthy, - Status: metav1.ConditionFalse, - Message: event.Message(), - }) - if conditionChanged { - spec.Events = append(spec.Events, event) - } - return NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy before applying the blueprint", healthResult) + return conditionChanged + } + + event := EcosystemUnhealthyEvent{ + HealthResult: healthResult, + } + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionEcosystemHealthy, + Status: metav1.ConditionFalse, + Reason: "Unhealthy", + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) } + return conditionChanged } // ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. @@ -396,32 +410,33 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { } } -// CheckEcosystemHealthAfterwards checks with the given health result if the ecosystem is healthy and the blueprint was therefore successful. -func (spec *BlueprintSpec) CheckEcosystemHealthAfterwards(healthResult ecosystem.HealthResult) error { - if healthResult.AllHealthy() { - event := EcosystemHealthyAfterwardsEvent{} - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionEcosystemHealthy, - Status: metav1.ConditionTrue, - Message: event.Message(), - }) - if conditionChanged { - spec.Events = append(spec.Events, event) - } - - return nil - } else { - event := EcosystemUnhealthyAfterwardsEvent{HealthResult: healthResult} +// SetComponentAppliedCondition informs the user about the state of the component apply. +// If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. +// Returns true if the condition changed, otherwise false. +func (spec *BlueprintSpec) SetComponentAppliedCondition(err error) bool { + if err != nil { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ - Type: ConditionEcosystemHealthy, + Type: ConditionComponentsApplied, Status: metav1.ConditionFalse, - Message: event.Message(), + Reason: "CannotApply", + Message: err.Error(), }) if conditionChanged { - spec.Events = append(spec.Events, event) + spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) } - return NewUnhealthyEcosystemError(nil, "ecosystem is unhealthy after applying the blueprint", healthResult) + return conditionChanged + } + event := ComponentsAppliedEvent{Diffs: spec.StateDiff.ComponentDiffs} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionComponentsApplied, + Status: metav1.ConditionTrue, + Reason: "Applied", + Message: event.Message(), + }) + if conditionChanged && spec.StateDiff.ComponentDiffs.HasChanges() { + spec.Events = append(spec.Events, event) } + return conditionChanged } // MarkBlueprintApplicationFailed sets the blueprint state to application failed, which indicates that the blueprint could not be applied completely. diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 1b82deca..fa91dd49 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -8,6 +8,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -473,199 +474,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }) } -func TestBlueprintSpec_CheckEcosystemHealthUpfront(t *testing.T) { - t.Run("all healthy", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{} - err := blueprint.CheckEcosystemHealthUpfront(health) - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionEcosystemHealthy)) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) - require.NotNil(t, condition) - assert.Equal(t, "dogu health ignored: false; component health ignored: false", condition.Message) - - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, EcosystemHealthyUpfrontEvent{ - doguHealthIgnored: false, - componentHealthIgnored: false, - }, blueprint.Events[0]) - }) - - t.Run("no healthy event if condition status stays the same", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{} - - err := blueprint.CheckEcosystemHealthUpfront(health) - require.NoError(t, err) - - //reset events - blueprint.Events = []Event{} - require.Equal(t, 0, len(blueprint.Events)) - // call again and check if another event was created - err = blueprint.CheckEcosystemHealthUpfront(health) - require.NoError(t, err) - require.Equal(t, 0, len(blueprint.Events)) - }) - - t.Run("some unhealthy", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix"}, - ecosystem.UnavailableHealthStatus: {"ldap"}, - ecosystem.PendingHealthStatus: {"postgresql"}, - }, - }, - ComponentHealth: ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.AvailableHealthStatus: {"dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"component-operator"}, - ecosystem.PendingHealthStatus: {"service-discovery"}, - }, - }, - } - err := blueprint.CheckEcosystemHealthUpfront(health) - require.Error(t, err) - assert.ErrorContains(t, err, "ecosystem is unhealthy before applying the blueprint") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionEcosystemHealthy)) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) - require.NotNil(t, condition) - assert.Contains(t, condition.Message, "2 dogu(s) are unhealthy: ldap, postgresql") - assert.Contains(t, condition.Message, "2 component(s) are unhealthy: component-operator, service-discovery") - - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, EcosystemUnhealthyUpfrontEvent{HealthResult: health}, blueprint.Events[0]) - }) - - t.Run("no unhealthy event if condition status stays the same", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.UnavailableHealthStatus: {"ldap"}, - }, - }, - } - - err := blueprint.CheckEcosystemHealthUpfront(health) - require.Error(t, err) - - //reset events - blueprint.Events = []Event{} - require.Equal(t, 0, len(blueprint.Events)) - // call again and check if another event was created - err = blueprint.CheckEcosystemHealthUpfront(health) - require.Error(t, err) - require.Equal(t, 0, len(blueprint.Events)) - }) -} - -func TestBlueprintSpec_CheckEcosystemHealthAfterwards(t *testing.T) { - t.Run("all healthy", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{} - - err := blueprint.CheckEcosystemHealthAfterwards(health) - - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionEcosystemHealthy)) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) - require.NotNil(t, condition) - - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, EcosystemHealthyAfterwardsEvent{}, blueprint.Events[0]) - }) - - t.Run("no healthy event if condition status stays the same", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{} - - err := blueprint.CheckEcosystemHealthAfterwards(health) - require.NoError(t, err) - - //reset events - blueprint.Events = []Event{} - require.Equal(t, 0, len(blueprint.Events)) - - // call again and check if another event was created - err = blueprint.CheckEcosystemHealthAfterwards(health) - require.NoError(t, err) - require.Equal(t, 0, len(blueprint.Events)) - }) - - t.Run("some unhealthy", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.AvailableHealthStatus: {"postfix"}, - ecosystem.UnavailableHealthStatus: {"ldap"}, - ecosystem.PendingHealthStatus: {"postgresql"}, - }, - }, - ComponentHealth: ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.AvailableHealthStatus: {"dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"component-operator"}, - ecosystem.PendingHealthStatus: {"service-discovery"}, - }, - }, - } - - err := blueprint.CheckEcosystemHealthAfterwards(health) - - require.Error(t, err) - assert.ErrorContains(t, err, "ecosystem is unhealthy after applying the blueprint") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionEcosystemHealthy)) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) - require.NotNil(t, condition) - assert.Contains(t, condition.Message, "2 dogu(s) are unhealthy: ldap, postgresql") - assert.Contains(t, condition.Message, "2 component(s) are unhealthy: component-operator, service-discovery") - - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, EcosystemUnhealthyAfterwardsEvent{HealthResult: health}, blueprint.Events[0]) - }) - - t.Run("no unhealthy event if condition status stays the same", func(t *testing.T) { - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } - health := ecosystem.HealthResult{ - DoguHealth: ecosystem.DoguHealthResult{ - DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ - ecosystem.UnavailableHealthStatus: {"ldap"}, - }, - }, - } - - err := blueprint.CheckEcosystemHealthAfterwards(health) - require.Error(t, err) - - //reset events - blueprint.Events = []Event{} - require.Equal(t, 0, len(blueprint.Events)) - // call again and check if another event was created - err = blueprint.CheckEcosystemHealthAfterwards(health) - require.Error(t, err) - require.Equal(t, 0, len(blueprint.Events)) - }) -} - func TestBlueprintSpec_MarkBlueprintApplicationFailed(t *testing.T) { // given spec := &BlueprintSpec{} @@ -899,3 +707,122 @@ func TestBlueprintSpec_GetDogusThatNeedARestart(t *testing.T) { }) } } + +func TestBlueprintSpec_HandleHealthResult(t *testing.T) { + t.Run("healthy", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } + + changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, nil) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + assert.Equal(t, metav1.ConditionTrue, condition.Status) + assert.Equal(t, "Healthy", condition.Reason) + assert.Equal(t, "dogu health ignored: false; component health ignored: false", condition.Message) + }) + + t.Run("unhealthy", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } + health := ecosystem.HealthResult{ + DoguHealth: ecosystem.DoguHealthResult{ + DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ + ecosystem.UnavailableHealthStatus: {"ldap"}, + }, + }, + } + + changed := blueprint.HandleHealthResult(health, nil) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + assert.Equal(t, metav1.ConditionFalse, condition.Status) + assert.Equal(t, "Unhealthy", condition.Reason) + assert.Contains(t, condition.Message, "ecosystem health:") + assert.Contains(t, condition.Message, "1 dogu(s) are unhealthy: ldap") + assert.Contains(t, condition.Message, "0 component(s) are unhealthy:") + }) + + t.Run("error given, condition Unknown", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } + + changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, assert.AnError) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + assert.Equal(t, metav1.ConditionUnknown, condition.Status) + assert.Equal(t, "CannotCheckHealth", condition.Reason) + assert.Equal(t, assert.AnError.Error(), condition.Message) + }) + + t.Run("no condition change", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + } + + changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, assert.AnError) + assert.True(t, changed, "condition should change after the first call") + changed = blueprint.HandleHealthResult(ecosystem.HealthResult{}, assert.AnError) + assert.False(t, changed, "condition should not change here") + }) +} + +func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { + diff := StateDiff{ + ComponentDiffs: ComponentDiffs{ + ComponentDiff{ + Name: "k8s-dogu-operator", + NeededActions: []Action{ + ActionUpgrade, ActionSwitchComponentNamespace, + }, + }, + }, + } + + t.Run("applied", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetComponentAppliedCondition(nil) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) + assert.Equal(t, metav1.ConditionTrue, condition.Status) + assert.Equal(t, "Applied", condition.Reason) + assert.Equal(t, "components applied: \"k8s-dogu-operator\": [upgrade, component namespace switch]", condition.Message) + }) + + t.Run("error", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetComponentAppliedCondition(assert.AnError) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) + assert.Equal(t, metav1.ConditionFalse, condition.Status) + assert.Equal(t, "CannotApply", condition.Reason) + assert.Equal(t, assert.AnError.Error(), condition.Message) + }) + + t.Run("no condition change", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetComponentAppliedCondition(assert.AnError) + assert.True(t, changed) + changed = blueprint.SetComponentAppliedCondition(assert.AnError) + assert.False(t, changed) + }) +} diff --git a/pkg/domain/ecosystem/componentHealth.go b/pkg/domain/ecosystem/componentHealth.go index 132b908d..2fbabd99 100644 --- a/pkg/domain/ecosystem/componentHealth.go +++ b/pkg/domain/ecosystem/componentHealth.go @@ -2,11 +2,12 @@ package ecosystem import ( "fmt" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "slices" "strings" "time" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" ) @@ -49,7 +50,7 @@ func CalculateComponentHealthResult(installedComponents map[common.SimpleCompone for _, required := range requiredComponents { _, installed := installedComponents[required.Name] if !installed { - result.ComponentsByStatus[NotInstalledHealthStatus] = append(result.ComponentsByStatus[NotInstalledHealthStatus], common.SimpleComponentName(required.Name)) + result.ComponentsByStatus[NotInstalledHealthStatus] = append(result.ComponentsByStatus[NotInstalledHealthStatus], required.Name) } } for _, component := range installedComponents { diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 7de1ae5a..d0f0bcfa 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -1,12 +1,14 @@ package domain import ( + "bytes" "fmt" "slices" "strings" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" ) type Event interface { @@ -195,28 +197,28 @@ func (s StateDiffDoguDeterminedEvent) Message() string { return fmt.Sprintf("dogu state diff determined: %d actions (%s)", amount, message) } -type EcosystemHealthyUpfrontEvent struct { +type EcosystemHealthyEvent struct { doguHealthIgnored bool componentHealthIgnored bool } -func (d EcosystemHealthyUpfrontEvent) Name() string { - return "EcosystemHealthyUpfront" +func (d EcosystemHealthyEvent) Name() string { + return "EcosystemHealthy" } -func (d EcosystemHealthyUpfrontEvent) Message() string { +func (d EcosystemHealthyEvent) Message() string { return fmt.Sprintf("dogu health ignored: %t; component health ignored: %t", d.doguHealthIgnored, d.componentHealthIgnored) } -type EcosystemUnhealthyUpfrontEvent struct { +type EcosystemUnhealthyEvent struct { HealthResult ecosystem.HealthResult } -func (d EcosystemUnhealthyUpfrontEvent) Name() string { - return "EcosystemUnhealthyUpfront" +func (d EcosystemUnhealthyEvent) Name() string { + return "EcosystemUnhealthy" } -func (d EcosystemUnhealthyUpfrontEvent) Message() string { +func (d EcosystemUnhealthyEvent) Message() string { return d.HealthResult.String() } @@ -241,36 +243,37 @@ func (e BlueprintApplicationPreProcessedEvent) Message() string { return "" } -type BlueprintAppliedEvent struct{} - -func (e BlueprintAppliedEvent) Name() string { - return "BlueprintApplied" +type ComponentsAppliedEvent struct { + Diffs ComponentDiffs } -func (e BlueprintAppliedEvent) Message() string { - return "waiting for ecosystem health" -} - -type EcosystemHealthyAfterwardsEvent struct{} - -func (e EcosystemHealthyAfterwardsEvent) Name() string { - return "EcosystemHealthyAfterwards" +func (e ComponentsAppliedEvent) Name() string { + return "ComponentsApplied" } -func (e EcosystemHealthyAfterwardsEvent) Message() string { - return "" +func (e ComponentsAppliedEvent) Message() string { + var buffer bytes.Buffer + buffer.WriteString("components applied: ") + var details []string + for _, diff := range e.Diffs { + actionsAsStrings := util.Map(diff.NeededActions, func(action Action) string { + return string(action) + }) + actions := strings.Join(actionsAsStrings, ", ") + details = append(details, fmt.Sprintf("%q: [%v]", diff.Name, actions)) + } + buffer.WriteString(strings.Join(details, ", ")) + return buffer.String() } -type EcosystemUnhealthyAfterwardsEvent struct { - HealthResult ecosystem.HealthResult -} +type BlueprintAppliedEvent struct{} -func (e EcosystemUnhealthyAfterwardsEvent) Name() string { - return "EcosystemUnhealthyAfterwards" +func (e BlueprintAppliedEvent) Name() string { + return "BlueprintApplied" } -func (e EcosystemUnhealthyAfterwardsEvent) Message() string { - return e.HealthResult.String() +func (e BlueprintAppliedEvent) Message() string { + return "waiting for ecosystem health" } type ExecutionFailedEvent struct { diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 7331e496..91f38002 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -30,25 +30,25 @@ func TestEvents(t *testing.T) { }, { name: "ecosystem healthy", - event: EcosystemHealthyUpfrontEvent{}, - expectedName: "EcosystemHealthyUpfront", + event: EcosystemHealthyEvent{}, + expectedName: "EcosystemHealthy", expectedMessage: "dogu health ignored: false; component health ignored: false", }, { name: "ignore dogu health", - event: EcosystemHealthyUpfrontEvent{doguHealthIgnored: true}, - expectedName: "EcosystemHealthyUpfront", + event: EcosystemHealthyEvent{doguHealthIgnored: true}, + expectedName: "EcosystemHealthy", expectedMessage: "dogu health ignored: true; component health ignored: false", }, { name: "ignore component health", - event: EcosystemHealthyUpfrontEvent{componentHealthIgnored: true}, - expectedName: "EcosystemHealthyUpfront", + event: EcosystemHealthyEvent{componentHealthIgnored: true}, + expectedName: "EcosystemHealthy", expectedMessage: "dogu health ignored: false; component health ignored: true", }, { name: "ecosystem unhealthy upfront", - event: EcosystemUnhealthyUpfrontEvent{ + event: EcosystemUnhealthyEvent{ HealthResult: ecosystem.HealthResult{ DoguHealth: ecosystem.DoguHealthResult{ DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ @@ -59,7 +59,7 @@ func TestEvents(t *testing.T) { }, }, }, - expectedName: "EcosystemUnhealthyUpfront", + expectedName: "EcosystemUnhealthy", expectedMessage: "ecosystem health:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", }, { @@ -145,6 +145,21 @@ func TestEvents(t *testing.T) { expectedName: "BlueprintApplicationPreProcessed", expectedMessage: "", }, + { + name: "components applied", + event: ComponentsAppliedEvent{ + Diffs: ComponentDiffs{ + { + Name: "dogu-operator", + NeededActions: []Action{ + ActionUpgrade, ActionSwitchComponentNamespace, + }, + }, + }, + }, + expectedName: "ComponentsApplied", + expectedMessage: "components applied: \"dogu-operator\": [upgrade, component namespace switch]", + }, { name: "blueprint applied", event: BlueprintAppliedEvent{}, From 70ced3bf009fea924deaf24e2eaacf4ed7f3d2a1 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Tue, 19 Aug 2025 16:46:21 +0200 Subject: [PATCH 033/119] #121 extract dogu apply Like components in the last commit, dogus will now be applied in their own use case separately from health checks etc. The code for dogus and components is now nearly identical. We don't need to refactor this duplication as we want to remove components anyway. The next step will be to remove the dogu restarts as they will be done by the dogu operator in the future. --- pkg/application/applyBlueprintSpecUseCase.go | 61 -------- .../applyBlueprintSpecUseCase_test.go | 133 ----------------- pkg/application/applyComponentsUseCase.go | 4 +- pkg/application/applyDogusUseCase.go | 42 ++++++ pkg/application/applyDogusUseCase_test.go | 94 ++++++++++++ pkg/application/blueprintSpecChangeUseCase.go | 9 +- .../blueprintSpecChangeUseCase_test.go | 61 +++++--- .../blueprintSpecValidationUseCase.go | 1 + .../blueprintSpecValidationUseCase_test.go | 3 +- .../componentInstallationUseCase_test.go | 5 +- .../doguInstallationUseCase_test.go | 5 +- pkg/application/doguRestartUseCase.go | 1 + pkg/application/doguRestartUseCase_test.go | 3 +- pkg/application/effectiveBlueprintUseCase.go | 1 + pkg/application/interfaces.go | 7 +- .../mock_applyBlueprintSpecUseCase_test.go | 141 ------------------ .../mock_applyComponentUseCase_test.go | 2 +- .../mock_applyComponentsUseCase_test.go | 84 +++++++++++ .../mock_applyDogusUseCase_test.go | 84 +++++++++++ pkg/bootstrap.go | 3 +- pkg/domain/blueprintSpec.go | 33 +++- pkg/domain/blueprintSpec_test.go | 79 +++++++++- pkg/domain/events.go | 34 +++-- pkg/domain/events_test.go | 21 ++- 24 files changed, 514 insertions(+), 397 deletions(-) create mode 100644 pkg/application/applyDogusUseCase.go create mode 100644 pkg/application/applyDogusUseCase_test.go create mode 100644 pkg/application/mock_applyComponentsUseCase_test.go create mode 100644 pkg/application/mock_applyDogusUseCase_test.go diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go index f5d20aa2..0d4f2c9c 100644 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ b/pkg/application/applyBlueprintSpecUseCase.go @@ -2,11 +2,9 @@ package application import ( "context" - "errors" "fmt" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "sigs.k8s.io/controller-runtime/pkg/log" ) // ApplyBlueprintSpecUseCase contains all use cases which are needed for or around applying @@ -41,62 +39,3 @@ func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx co return nil } - -// ApplyBlueprintSpec applies the expected state to the ecosystem. It will stop if any unexpected error happens and sets blueprint status. -// Returns domainservice.ConflictError if there was a concurrent update to the blueprint spec or other resources or -// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. -// There is no error, if the ecosystem is unhealthy as this gets reflected in the blueprint spec status. -func (useCase *ApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ApplyBlueprintSpecUseCase.ApplyBlueprintSpec") - - applyError := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) - if applyError != nil { - return useCase.handleApplyFailedError(ctx, blueprint, applyError) - } - - // FIXME: this health check is blocking. I think we need to split the apply logic into multiple steps to - // we have to wait for all dogus to be healthy - // otherwise service account creation might fail because dogus are restarted right after this step - _, err := useCase.doguInstallUseCase.WaitForHealthyDogus(ctx) - if err != nil { - return useCase.handleApplyFailedError(ctx, blueprint, err) - } - - logger.Info("blueprint successfully applied to the cluster") - return useCase.markBlueprintApplied(ctx, blueprint) -} - -func (useCase *ApplyBlueprintSpecUseCase) handleApplyFailedError(ctx context.Context, blueprintSpec *domain.BlueprintSpec, applyError error) error { - err := useCase.markBlueprintApplicationFailed(ctx, blueprintSpec, applyError) - if err != nil { - return err - } - return applyError -} - -// markBlueprintApplicationFailed marks the blueprint application as failed. -// Returns the error which leads to the failed blueprint needs to be provided. -func (useCase *ApplyBlueprintSpecUseCase) markBlueprintApplicationFailed(ctx context.Context, blueprintSpec *domain.BlueprintSpec, err error) error { - logger := log.FromContext(ctx). - WithName("ApplyBlueprintSpecUseCase.markBlueprintApplicationFailed"). - WithValues("blueprintId", blueprintSpec.Id) - - blueprintSpec.MarkBlueprintApplicationFailed(err) - repoErr := useCase.repo.Update(ctx, blueprintSpec) - - if repoErr != nil { - repoErr = errors.Join(repoErr, err) - logger.Error(repoErr, "cannot mark blueprint as failed") - return fmt.Errorf("cannot mark blueprint as failed while handling %q status: %w", blueprintSpec.Status, repoErr) - } - return nil -} - -func (useCase *ApplyBlueprintSpecUseCase) markBlueprintApplied(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - blueprintSpec.MarkBlueprintApplied() - err := useCase.repo.Update(ctx, blueprintSpec) - if err != nil { - return fmt.Errorf("cannot mark blueprint as waiting for a healthy ecosystem: %w", err) - } - return nil -} diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go index be8e9a07..12531a2a 100644 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ b/pkg/application/applyBlueprintSpecUseCase_test.go @@ -1,11 +1,9 @@ package application import ( - "context" "testing" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,137 +20,6 @@ func TestNewApplyBlueprintSpecUseCase(t *testing.T) { assert.Equal(t, healthMock, sut.healthUseCase) } -func TestApplyBlueprintSpecUseCase_markBlueprintApplicationFailed(t *testing.T) { - t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) - installUseCaseMock := newMockDoguInstallationUseCase(t) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.markBlueprintApplicationFailed(testCtx, spec, assert.AnError) - - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) - }) - - t.Run("repo error", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) - installUseCaseMock := newMockDoguInstallationUseCase(t) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.markBlueprintApplicationFailed(testCtx, spec, assert.AnError) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, spec.Status) - }) -} - -func TestApplyBlueprintSpecUseCase_markBlueprintApplied(t *testing.T) { - t.Run("ok", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(nil) - installUseCaseMock := newMockDoguInstallationUseCase(t) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.markBlueprintApplied(testCtx, spec) - - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseBlueprintApplied, spec.Status) - }) - - t.Run("repo error", func(t *testing.T) { - spec := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, spec).Return(assert.AnError) - installUseCaseMock := newMockDoguInstallationUseCase(t) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.markBlueprintApplied(testCtx, spec) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplied, spec.Status) - }) -} - -func TestApplyBlueprintSpecUseCase_ApplyBlueprintSpec(t *testing.T) { - t.Run("ok", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) - installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, nil) - - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseBlueprintApplied, blueprint.Status) - }) - t.Run("error waiting for dogu health", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - - installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) - installUseCaseMock.EXPECT().WaitForHealthyDogus(testCtx).Return(ecosystem.DoguHealthResult{}, assert.AnError) - - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) - }) - - t.Run("fail to apply dogu state", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) - }) - - t.Run("fail to apply state and fail to mark execution failed", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - repoMock := newMockBlueprintSpecRepository(t) - counter := 0 - repoMock.EXPECT().Update(testCtx, blueprint).RunAndReturn(func(ctx context.Context, spec *domain.BlueprintSpec) error { - counter++ - if counter == 1 { - return nil - } else { - return assert.AnError - } - }) - installUseCaseMock := newMockDoguInstallationUseCase(t) - installUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) - useCase := ApplyBlueprintSpecUseCase{repo: repoMock, doguInstallUseCase: installUseCaseMock} - - err := useCase.ApplyBlueprintSpec(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Equal(t, domain.StatusPhaseBlueprintApplicationFailed, blueprint.Status) - }) -} - func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) { t.Run("ok", func(t *testing.T) { blueprint := &domain.BlueprintSpec{} diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go index fc435dea..4283d367 100644 --- a/pkg/application/applyComponentsUseCase.go +++ b/pkg/application/applyComponentsUseCase.go @@ -30,12 +30,12 @@ func NewApplyComponentsUseCase( // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { err := useCase.componentUseCase.ApplyComponentStates(ctx, blueprint) - changed := blueprint.SetComponentAppliedCondition(err) + changed := blueprint.SetComponentsAppliedCondition(err) if changed { updateErr := useCase.repo.Update(ctx, blueprint) if updateErr != nil { - return fmt.Errorf("cannot update condition while applying conditions: %w", errors.Join(updateErr, err)) + return fmt.Errorf("cannot update condition while applying components: %w", errors.Join(updateErr, err)) } } return err diff --git a/pkg/application/applyDogusUseCase.go b/pkg/application/applyDogusUseCase.go new file mode 100644 index 00000000..a951cdfd --- /dev/null +++ b/pkg/application/applyDogusUseCase.go @@ -0,0 +1,42 @@ +package application + +import ( + "context" + "errors" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" +) + +// ApplyDogusUseCase can handle dogu installations, updates and deletions. +type ApplyDogusUseCase struct { + repo blueprintSpecRepository + doguInstallUseCase doguInstallationUseCase +} + +func NewApplyDogusUseCase( + repo blueprintSpecRepository, + doguInstallUseCase doguInstallationUseCase, +) *ApplyDogusUseCase { + return &ApplyDogusUseCase{ + repo: repo, + doguInstallUseCase: doguInstallUseCase, + } +} + +// ApplyDogus applies dogus if necessary. +// The conditions in the blueprint will be set accordingly. +// returns domainservice.ConflictError if there was a concurrent update to the blueprint or +// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. +func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) + changed := blueprint.SetDogusAppliedCondition(err) + + if changed { + updateErr := useCase.repo.Update(ctx, blueprint) + if updateErr != nil { + return fmt.Errorf("cannot update condition while applying dogus: %w", errors.Join(updateErr, err)) + } + } + return err +} diff --git a/pkg/application/applyDogusUseCase_test.go b/pkg/application/applyDogusUseCase_test.go new file mode 100644 index 00000000..59ac1a7b --- /dev/null +++ b/pkg/application/applyDogusUseCase_test.go @@ -0,0 +1,94 @@ +package application + +import ( + "testing" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" +) + +func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { + t.Run("ok", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + StateDiff: domain.StateDiff{ + DoguDiffs: domain.DoguDiffs{ + { + DoguName: "cas", + NeededActions: []domain.Action{ + domain.ActionUpgrade, + }, + }, + }, + }, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) + useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.ApplyDogus(testCtx, blueprint) + + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}, blueprint.Events[0]) + }) + + t.Run("no update without condition change", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) + useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.ApplyDogus(testCtx, blueprint) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + err = useCase.ApplyDogus(testCtx, blueprint) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + }) + + t.Run("fail to apply dogus", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) + useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.ApplyDogus(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionDogusApplied)) + }) + + t.Run("fail to update blueprint", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) + useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.ApplyDogus(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + }) +} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index c2a77948..205a6028 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -19,7 +19,8 @@ type BlueprintSpecChangeUseCase struct { ecosystemConfigUseCase ecosystemConfigUseCase doguRestartUseCase doguRestartUseCase selfUpgradeUseCase selfUpgradeUseCase - applyComponentUseCase applyComponentUseCase + applyComponentUseCase applyComponentsUseCase + applyDogusUseCase applyDogusUseCase healthUseCase ecosystemHealthUseCase } @@ -32,7 +33,8 @@ func NewBlueprintSpecChangeUseCase( ecosystemConfigUseCase ecosystemConfigUseCase, doguRestartUseCase doguRestartUseCase, selfUpgradeUseCase selfUpgradeUseCase, - applyComponentUseCase applyComponentUseCase, + applyComponentUseCase applyComponentsUseCase, + applyDogusUseCase applyDogusUseCase, ecosystemHealthUseCase ecosystemHealthUseCase, ) *BlueprintSpecChangeUseCase { return &BlueprintSpecChangeUseCase{ @@ -45,6 +47,7 @@ func NewBlueprintSpecChangeUseCase( doguRestartUseCase: doguRestartUseCase, selfUpgradeUseCase: selfUpgradeUseCase, applyComponentUseCase: applyComponentUseCase, + applyDogusUseCase: applyDogusUseCase, healthUseCase: ecosystemHealthUseCase, } } @@ -124,7 +127,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C if err != nil { return err } - err = useCase.applyUseCase.ApplyBlueprintSpec(ctx, blueprint) + err = useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 27a27430..5ba4ea47 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -6,6 +6,7 @@ import ( "testing" cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/mock" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,9 +43,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, @@ -73,14 +75,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: metav1.ConditionTrue, }) }) - applyMock.EXPECT().CheckEcosystemHealthUpfront(mock.Anything, blueprintSpec).Return(nil) + healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, nil) ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil) selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil) - applyMock.EXPECT().ApplyBlueprintSpec(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseBlueprintApplied - }) - applyMock.EXPECT().CheckEcosystemHealthAfterwards(mock.Anything, blueprintSpec).Return(nil) + healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, nil) applyMock.EXPECT().PostProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseCompleted @@ -108,9 +106,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) expectedError := &domainservice.InternalError{ WrappedError: nil, @@ -137,9 +136,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -169,9 +169,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -201,9 +202,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -234,9 +236,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -273,9 +276,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -299,7 +303,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Status: metav1.ConditionTrue, }) }) - applyMock.EXPECT().CheckEcosystemHealthUpfront(testCtx, "testBlueprint1").Return(assert.AnError) + healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, updatedSpec).Return(ecosystem.HealthResult{}, assert.AnError) // when err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") @@ -320,9 +324,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -344,9 +349,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -371,16 +377,17 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, Status: domain.StatusPhaseBlueprintApplied, } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().CheckEcosystemHealthAfterwards(testCtx, testBlueprintId).Return(assert.AnError) + healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(nil). Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseRestartsTriggered @@ -403,9 +410,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -431,9 +439,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -456,9 +465,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ Id: blueprintId, @@ -482,9 +492,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -507,9 +518,10 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -535,9 +547,10 @@ func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) + doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, diff --git a/pkg/application/blueprintSpecValidationUseCase.go b/pkg/application/blueprintSpecValidationUseCase.go index 4780002c..642be8c6 100644 --- a/pkg/application/blueprintSpecValidationUseCase.go +++ b/pkg/application/blueprintSpecValidationUseCase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "sigs.k8s.io/controller-runtime/pkg/log" diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index e662c521..385cca49 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -3,6 +3,8 @@ package application import ( "context" "errors" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -11,7 +13,6 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/meta" - "testing" ) var redmineQualifiedDoguName = cescommons.QualifiedName{ diff --git a/pkg/application/componentInstallationUseCase_test.go b/pkg/application/componentInstallationUseCase_test.go index 9b206fd6..e0576df1 100644 --- a/pkg/application/componentInstallationUseCase_test.go +++ b/pkg/application/componentInstallationUseCase_test.go @@ -3,6 +3,9 @@ package application import ( "context" "fmt" + "testing" + "time" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -10,8 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "testing" - "time" ) const ( diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 50009aa2..5eb83ffc 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -3,6 +3,9 @@ package application import ( "context" "fmt" + "testing" + "time" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -10,8 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" - "testing" - "time" ) const blueprintId = "blueprint1" diff --git a/pkg/application/doguRestartUseCase.go b/pkg/application/doguRestartUseCase.go index 8e23513c..adb57258 100644 --- a/pkg/application/doguRestartUseCase.go +++ b/pkg/application/doguRestartUseCase.go @@ -2,6 +2,7 @@ package application import ( "context" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" diff --git a/pkg/application/doguRestartUseCase_test.go b/pkg/application/doguRestartUseCase_test.go index 5e7e726a..41c51b89 100644 --- a/pkg/application/doguRestartUseCase_test.go +++ b/pkg/application/doguRestartUseCase_test.go @@ -2,6 +2,8 @@ package application import ( "context" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -9,7 +11,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) var ( diff --git a/pkg/application/effectiveBlueprintUseCase.go b/pkg/application/effectiveBlueprintUseCase.go index 3b96508b..cb302d1f 100644 --- a/pkg/application/effectiveBlueprintUseCase.go +++ b/pkg/application/effectiveBlueprintUseCase.go @@ -3,6 +3,7 @@ package application import ( "context" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index bb9815ba..2f9dc2fe 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -31,7 +31,11 @@ type doguRestartUseCase interface { TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error } -type applyComponentUseCase interface { +type applyDogusUseCase interface { + ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error +} + +type applyComponentsUseCase interface { ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -44,7 +48,6 @@ type componentInstallationUseCase interface { type applyBlueprintSpecUseCase interface { PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error - ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error } type ecosystemHealthUseCase interface { diff --git a/pkg/application/mock_applyBlueprintSpecUseCase_test.go b/pkg/application/mock_applyBlueprintSpecUseCase_test.go index 703631e2..4823af4f 100644 --- a/pkg/application/mock_applyBlueprintSpecUseCase_test.go +++ b/pkg/application/mock_applyBlueprintSpecUseCase_test.go @@ -22,147 +22,6 @@ func (_m *mockApplyBlueprintSpecUseCase) EXPECT() *mockApplyBlueprintSpecUseCase return &mockApplyBlueprintSpecUseCase_Expecter{mock: &_m.Mock} } -// ApplyBlueprintSpec provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyBlueprintSpecUseCase) ApplyBlueprintSpec(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for ApplyBlueprintSpec") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyBlueprintSpec' -type mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call struct { - *mock.Call -} - -// ApplyBlueprintSpec is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyBlueprintSpecUseCase_Expecter) ApplyBlueprintSpec(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { - return &mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call{Call: _e.mock.On("ApplyBlueprintSpec", ctx, blueprint)} -} - -func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) Return(_a0 error) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_ApplyBlueprintSpec_Call { - _c.Call.Return(run) - return _c -} - -// CheckEcosystemHealthAfterwards provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthAfterwards(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for CheckEcosystemHealthAfterwards") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckEcosystemHealthAfterwards' -type mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call struct { - *mock.Call -} - -// CheckEcosystemHealthAfterwards is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthAfterwards(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { - return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call{Call: _e.mock.On("CheckEcosystemHealthAfterwards", ctx, blueprint)} -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) Return(_a0 error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthAfterwards_Call { - _c.Call.Return(run) - return _c -} - -// CheckEcosystemHealthUpfront provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyBlueprintSpecUseCase) CheckEcosystemHealthUpfront(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for CheckEcosystemHealthUpfront") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckEcosystemHealthUpfront' -type mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call struct { - *mock.Call -} - -// CheckEcosystemHealthUpfront is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyBlueprintSpecUseCase_Expecter) CheckEcosystemHealthUpfront(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { - return &mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call{Call: _e.mock.On("CheckEcosystemHealthUpfront", ctx, blueprint)} -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) Return(_a0 error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_CheckEcosystemHealthUpfront_Call { - _c.Call.Return(run) - return _c -} - // PostProcessBlueprintApplication provides a mock function with given fields: ctx, blueprint func (_m *mockApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { ret := _m.Called(ctx, blueprint) diff --git a/pkg/application/mock_applyComponentUseCase_test.go b/pkg/application/mock_applyComponentUseCase_test.go index 4214fcd5..207e7d39 100644 --- a/pkg/application/mock_applyComponentUseCase_test.go +++ b/pkg/application/mock_applyComponentUseCase_test.go @@ -9,7 +9,7 @@ import ( mock "github.com/stretchr/testify/mock" ) -// mockApplyComponentUseCase is an autogenerated mock type for the applyComponentUseCase type +// mockApplyComponentUseCase is an autogenerated mock type for the applyComponentsUseCase type type mockApplyComponentUseCase struct { mock.Mock } diff --git a/pkg/application/mock_applyComponentsUseCase_test.go b/pkg/application/mock_applyComponentsUseCase_test.go new file mode 100644 index 00000000..f94ba2dc --- /dev/null +++ b/pkg/application/mock_applyComponentsUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockApplyComponentsUseCase is an autogenerated mock type for the applyComponentsUseCase type +type mockApplyComponentsUseCase struct { + mock.Mock +} + +type mockApplyComponentsUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockApplyComponentsUseCase) EXPECT() *mockApplyComponentsUseCase_Expecter { + return &mockApplyComponentsUseCase_Expecter{mock: &_m.Mock} +} + +// ApplyComponents provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for ApplyComponents") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockApplyComponentsUseCase_ApplyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponents' +type mockApplyComponentsUseCase_ApplyComponents_Call struct { + *mock.Call +} + +// ApplyComponents is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyComponentsUseCase_Expecter) ApplyComponents(ctx interface{}, blueprint interface{}) *mockApplyComponentsUseCase_ApplyComponents_Call { + return &mockApplyComponentsUseCase_ApplyComponents_Call{Call: _e.mock.On("ApplyComponents", ctx, blueprint)} +} + +func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyComponentsUseCase_ApplyComponents_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Return(_a0 error) *mockApplyComponentsUseCase_ApplyComponents_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyComponentsUseCase_ApplyComponents_Call { + _c.Call.Return(run) + return _c +} + +// newMockApplyComponentsUseCase creates a new instance of mockApplyComponentsUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockApplyComponentsUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockApplyComponentsUseCase { + mock := &mockApplyComponentsUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/application/mock_applyDogusUseCase_test.go b/pkg/application/mock_applyDogusUseCase_test.go new file mode 100644 index 00000000..f2ad0d11 --- /dev/null +++ b/pkg/application/mock_applyDogusUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockApplyDogusUseCase is an autogenerated mock type for the applyDogusUseCase type +type mockApplyDogusUseCase struct { + mock.Mock +} + +type mockApplyDogusUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockApplyDogusUseCase) EXPECT() *mockApplyDogusUseCase_Expecter { + return &mockApplyDogusUseCase_Expecter{mock: &_m.Mock} +} + +// ApplyDogus provides a mock function with given fields: ctx, blueprint +func (_m *mockApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for ApplyDogus") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockApplyDogusUseCase_ApplyDogus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyDogus' +type mockApplyDogusUseCase_ApplyDogus_Call struct { + *mock.Call +} + +// ApplyDogus is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockApplyDogusUseCase_Expecter) ApplyDogus(ctx interface{}, blueprint interface{}) *mockApplyDogusUseCase_ApplyDogus_Call { + return &mockApplyDogusUseCase_ApplyDogus_Call{Call: _e.mock.On("ApplyDogus", ctx, blueprint)} +} + +func (_c *mockApplyDogusUseCase_ApplyDogus_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyDogusUseCase_ApplyDogus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockApplyDogusUseCase_ApplyDogus_Call) Return(_a0 error) *mockApplyDogusUseCase_ApplyDogus_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockApplyDogusUseCase_ApplyDogus_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyDogusUseCase_ApplyDogus_Call { + _c.Call.Return(run) + return _c +} + +// newMockApplyDogusUseCase creates a new instance of mockApplyDogusUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockApplyDogusUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockApplyDogusUseCase { + mock := &mockApplyDogusUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index e2873b02..79ab8ea2 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -88,9 +88,9 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintSpecRepository) applyBlueprintSpecUseCase := application.NewApplyBlueprintSpecUseCase(blueprintSpecRepository, doguInstallationUseCase, ecosystemHealthUseCase) applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintSpecRepository, componentInstallationUseCase) + applyDogusUseCase := application.NewApplyDogusUseCase(blueprintSpecRepository, doguInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintSpecRepository, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) doguRestartUseCase := application.NewDoguRestartUseCase(doguInstallationRepo, blueprintSpecRepository, restartRepository) - selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintSpecRepository, componentInstallationRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( @@ -100,6 +100,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name doguRestartUseCase, selfUpgradeUseCase, applyComponentUseCase, + applyDogusUseCase, ecosystemHealthUseCase, ) blueprintReconciler := reconciler.NewBlueprintReconciler(blueprintChangeUseCase) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 115f2da4..0297b584 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -410,10 +410,10 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { } } -// SetComponentAppliedCondition informs the user about the state of the component apply. +// SetComponentsAppliedCondition informs the user about the state of the component apply. // If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. // Returns true if the condition changed, otherwise false. -func (spec *BlueprintSpec) SetComponentAppliedCondition(err error) bool { +func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { if err != nil { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionComponentsApplied, @@ -439,6 +439,35 @@ func (spec *BlueprintSpec) SetComponentAppliedCondition(err error) bool { return conditionChanged } +// SetDogusAppliedCondition informs the user about the state of the dogu apply. +// If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. +// Returns true if the condition changed, otherwise false. +func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { + if err != nil { + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "CannotApply", + Message: err.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) + } + return conditionChanged + } + event := DogusAppliedEvent{Diffs: spec.StateDiff.DoguDiffs} + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionTrue, + Reason: "Applied", + Message: event.Message(), + }) + if conditionChanged && spec.StateDiff.DoguDiffs.HasChanges() { + spec.Events = append(spec.Events, event) + } + return conditionChanged +} + // MarkBlueprintApplicationFailed sets the blueprint state to application failed, which indicates that the blueprint could not be applied completely. // In reaction to this, further post-processing will happen. func (spec *BlueprintSpec) MarkBlueprintApplicationFailed(err error) { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index fa91dd49..342e8ce1 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -790,13 +790,15 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { StateDiff: diff, } - changed := blueprint.SetComponentAppliedCondition(nil) + changed := blueprint.SetComponentsAppliedCondition(nil) assert.True(t, changed) condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Applied", condition.Reason) assert.Equal(t, "components applied: \"k8s-dogu-operator\": [upgrade, component namespace switch]", condition.Message) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, ComponentsAppliedEvent{Diffs: diff.ComponentDiffs}, blueprint.Events[0]) }) t.Run("error", func(t *testing.T) { @@ -805,7 +807,7 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { StateDiff: diff, } - changed := blueprint.SetComponentAppliedCondition(assert.AnError) + changed := blueprint.SetComponentsAppliedCondition(assert.AnError) assert.True(t, changed) condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) @@ -820,9 +822,78 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { StateDiff: diff, } - changed := blueprint.SetComponentAppliedCondition(assert.AnError) + changed := blueprint.SetComponentsAppliedCondition(assert.AnError) + assert.True(t, changed) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) + blueprint.Events = nil + + changed = blueprint.SetComponentsAppliedCondition(assert.AnError) + assert.False(t, changed) + require.Equal(t, 0, len(blueprint.Events)) + }) +} + +func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { + diff := StateDiff{ + DoguDiffs: DoguDiffs{ + DoguDiff{ + DoguName: "cas", + NeededActions: []Action{ + ActionUpgrade, ActionSwitchDoguNamespace, + }, + }, + }, + } + + t.Run("applied", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetDogusAppliedCondition(nil) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) + assert.Equal(t, metav1.ConditionTrue, condition.Status) + assert.Equal(t, "Applied", condition.Reason) + assert.Equal(t, "dogus applied: \"cas\": [upgrade, dogu namespace switch]", condition.Message) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, DogusAppliedEvent{Diffs: diff.DoguDiffs}, blueprint.Events[0]) + }) + + t.Run("error", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetDogusAppliedCondition(assert.AnError) + + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) + assert.Equal(t, metav1.ConditionFalse, condition.Status) + assert.Equal(t, "CannotApply", condition.Reason) + assert.Equal(t, assert.AnError.Error(), condition.Message) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) + }) + + t.Run("no condition change", func(t *testing.T) { + blueprint := BlueprintSpec{ + Conditions: &[]Condition{}, + StateDiff: diff, + } + + changed := blueprint.SetDogusAppliedCondition(assert.AnError) assert.True(t, changed) - changed = blueprint.SetComponentAppliedCondition(assert.AnError) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) + blueprint.Events = nil + + changed = blueprint.SetDogusAppliedCondition(assert.AnError) assert.False(t, changed) + require.Equal(t, 0, len(blueprint.Events)) }) } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index d0f0bcfa..0455c0a2 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -232,17 +232,6 @@ func (b BlueprintDryRunEvent) Message() string { return "Executed blueprint in dry run mode. Remove flag to continue" } -type BlueprintApplicationPreProcessedEvent struct { -} - -func (e BlueprintApplicationPreProcessedEvent) Name() string { - return "BlueprintApplicationPreProcessed" -} - -func (e BlueprintApplicationPreProcessedEvent) Message() string { - return "" -} - type ComponentsAppliedEvent struct { Diffs ComponentDiffs } @@ -266,6 +255,29 @@ func (e ComponentsAppliedEvent) Message() string { return buffer.String() } +type DogusAppliedEvent struct { + Diffs DoguDiffs +} + +func (e DogusAppliedEvent) Name() string { + return "DogusApplied" +} + +func (e DogusAppliedEvent) Message() string { + var buffer bytes.Buffer + buffer.WriteString("dogus applied: ") + var details []string + for _, diff := range e.Diffs { + actionsAsStrings := util.Map(diff.NeededActions, func(action Action) string { + return string(action) + }) + actions := strings.Join(actionsAsStrings, ", ") + details = append(details, fmt.Sprintf("%q: [%v]", diff.DoguName, actions)) + } + buffer.WriteString(strings.Join(details, ", ")) + return buffer.String() +} + type BlueprintAppliedEvent struct{} func (e BlueprintAppliedEvent) Name() string { diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 91f38002..606862d0 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -139,12 +139,6 @@ func TestEvents(t *testing.T) { expectedName: "SensitiveDoguConfigDiffDetermined", expectedMessage: "sensitive dogu config diff determined: 2 changes (\"none\": 1, \"remove\": 1, \"set\": 1)", }, - { - name: "blueprint application pre-processed", - event: BlueprintApplicationPreProcessedEvent{}, - expectedName: "BlueprintApplicationPreProcessed", - expectedMessage: "", - }, { name: "components applied", event: ComponentsAppliedEvent{ @@ -160,6 +154,21 @@ func TestEvents(t *testing.T) { expectedName: "ComponentsApplied", expectedMessage: "components applied: \"dogu-operator\": [upgrade, component namespace switch]", }, + { + name: "dogus applied", + event: DogusAppliedEvent{ + Diffs: DoguDiffs{ + { + DoguName: "jenkins", + NeededActions: []Action{ + ActionUpgrade, ActionSwitchDoguNamespace, + }, + }, + }, + }, + expectedName: "DogusApplied", + expectedMessage: "dogus applied: \"jenkins\": [upgrade, dogu namespace switch]", + }, { name: "blueprint applied", event: BlueprintAppliedEvent{}, From 5d99e3113938a4483352128d227c2448e7ce9bc9 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 09:21:09 +0200 Subject: [PATCH 034/119] #121 remove dogu restarts Dogu restarts were necessary because they did not use changed config right away. In the special case of the global config, all dogus needed to be restarted. Now, we decided that the dogu operator should restart dogus right away if config changed. It should not be the responsibility of the blueprint operator anymore. To avoid restarting dogus twice if config changes and an upgrade is needed, the blueprint operator could set a flag in the dogu-CRs, so that no automatic restart happens. This is an optimization and can happen later. --- .../kubernetes/restartcr/doguRestartRepo.go | 36 - .../restartcr/doguRestartRepo_test.go | 67 -- .../kubernetes/restartcr/interfaces.go | 9 - .../mock_DoguRestartInterface_test.go | 696 ------------------ pkg/application/blueprintSpecChangeUseCase.go | 34 +- .../blueprintSpecChangeUseCase_test.go | 86 +-- pkg/application/doguRestartUseCase.go | 65 -- pkg/application/doguRestartUseCase_test.go | 345 --------- pkg/application/interfaces.go | 8 - .../mock_applyComponentUseCase_test.go | 84 --- .../mock_doguRestartRepository_test.go | 84 --- .../mock_doguRestartUseCase_test.go | 84 --- pkg/bootstrap.go | 5 - pkg/domain/blueprintSpec.go | 16 - pkg/domain/blueprintSpec_test.go | 70 -- pkg/domainservice/adapterInterfaces.go | 6 +- .../mock_DoguRestartRepository_test.go | 84 --- 17 files changed, 19 insertions(+), 1760 deletions(-) delete mode 100644 pkg/adapter/kubernetes/restartcr/doguRestartRepo.go delete mode 100644 pkg/adapter/kubernetes/restartcr/doguRestartRepo_test.go delete mode 100644 pkg/adapter/kubernetes/restartcr/interfaces.go delete mode 100644 pkg/adapter/kubernetes/restartcr/mock_DoguRestartInterface_test.go delete mode 100644 pkg/application/doguRestartUseCase.go delete mode 100644 pkg/application/doguRestartUseCase_test.go delete mode 100644 pkg/application/mock_applyComponentUseCase_test.go delete mode 100644 pkg/application/mock_doguRestartRepository_test.go delete mode 100644 pkg/application/mock_doguRestartUseCase_test.go delete mode 100644 pkg/domainservice/mock_DoguRestartRepository_test.go diff --git a/pkg/adapter/kubernetes/restartcr/doguRestartRepo.go b/pkg/adapter/kubernetes/restartcr/doguRestartRepo.go deleted file mode 100644 index da860a77..00000000 --- a/pkg/adapter/kubernetes/restartcr/doguRestartRepo.go +++ /dev/null @@ -1,36 +0,0 @@ -package restartcr - -import ( - "context" - "errors" - "fmt" - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - v2 "github.com/cloudogu/k8s-dogu-lib/v2/api/v2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type doguRestartRepository struct { - restartInterface DoguRestartInterface -} - -func NewDoguRestartRepository(restartInterface DoguRestartInterface) *doguRestartRepository { - return &doguRestartRepository{restartInterface: restartInterface} -} - -func (d doguRestartRepository) RestartAll(ctx context.Context, names []cescommons.SimpleName) error { - var createErrors []error - for _, doguName := range names { - _, err := d.restartInterface.Create(ctx, &v2.DoguRestart{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("%s-", string(doguName)), - }, - Spec: v2.DoguRestartSpec{ - DoguName: string(doguName), - }, - }, metav1.CreateOptions{}) - if err != nil { - createErrors = append(createErrors, err) - } - } - return errors.Join(createErrors...) -} diff --git a/pkg/adapter/kubernetes/restartcr/doguRestartRepo_test.go b/pkg/adapter/kubernetes/restartcr/doguRestartRepo_test.go deleted file mode 100644 index 2581d66a..00000000 --- a/pkg/adapter/kubernetes/restartcr/doguRestartRepo_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package restartcr - -import ( - "context" - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - v2 "github.com/cloudogu/k8s-dogu-lib/v2/api/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "testing" -) - -func Test_doguRestartRepository_RestartAll(t *testing.T) { - t.Run("no error on restart all", func(t *testing.T) { - // given - testContext := context.Background() - testDoguSimpleName := cescommons.SimpleName("testdogu") - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - mockDoguRestartInterface := NewMockDoguRestartInterface(t) - expectedDoguRestartToCreate := &v2.DoguRestart{ObjectMeta: metav1.ObjectMeta{GenerateName: "testdogu-"}, Spec: v2.DoguRestartSpec{DoguName: "testdogu"}} - - mockDoguRestartInterface.EXPECT().Create(testContext, expectedDoguRestartToCreate, metav1.CreateOptions{}).Return(nil, nil) - - restartRepository := NewDoguRestartRepository(mockDoguRestartInterface) - - // when - err := restartRepository.RestartAll(testContext, dogusThatNeedARestart) - - // then - require.NoError(t, err) - }) - - t.Run("no error on empty restart all", func(t *testing.T) { - // given - testContext := context.Background() - dogusThatNeedARestart := []cescommons.SimpleName{} - mockDoguRestartInterface := NewMockDoguRestartInterface(t) - - restartRepository := NewDoguRestartRepository(mockDoguRestartInterface) - - // when - err := restartRepository.RestartAll(testContext, dogusThatNeedARestart) - - // then - require.NoError(t, err) - }) - - t.Run("fail on error at create", func(t *testing.T) { - // given - testContext := context.Background() - testDoguSimpleName := cescommons.SimpleName("testdogu") - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - mockDoguRestartInterface := NewMockDoguRestartInterface(t) - expectedDoguRestartToCreate := &v2.DoguRestart{ObjectMeta: metav1.ObjectMeta{GenerateName: "testdogu-"}, Spec: v2.DoguRestartSpec{DoguName: "testdogu"}} - - mockDoguRestartInterface.EXPECT().Create(testContext, expectedDoguRestartToCreate, metav1.CreateOptions{}).Return(nil, assert.AnError) - - restartRepository := NewDoguRestartRepository(mockDoguRestartInterface) - - // when - err := restartRepository.RestartAll(testContext, dogusThatNeedARestart) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) -} diff --git a/pkg/adapter/kubernetes/restartcr/interfaces.go b/pkg/adapter/kubernetes/restartcr/interfaces.go deleted file mode 100644 index 3e10865a..00000000 --- a/pkg/adapter/kubernetes/restartcr/interfaces.go +++ /dev/null @@ -1,9 +0,0 @@ -package restartcr - -import ( - ecosystemclient "github.com/cloudogu/k8s-dogu-lib/v2/client" -) - -type DoguRestartInterface interface { - ecosystemclient.DoguRestartInterface -} diff --git a/pkg/adapter/kubernetes/restartcr/mock_DoguRestartInterface_test.go b/pkg/adapter/kubernetes/restartcr/mock_DoguRestartInterface_test.go deleted file mode 100644 index 78b4e3bf..00000000 --- a/pkg/adapter/kubernetes/restartcr/mock_DoguRestartInterface_test.go +++ /dev/null @@ -1,696 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package restartcr - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - types "k8s.io/apimachinery/pkg/types" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - v2 "github.com/cloudogu/k8s-dogu-lib/v2/api/v2" - - watch "k8s.io/apimachinery/pkg/watch" -) - -// MockDoguRestartInterface is an autogenerated mock type for the DoguRestartInterface type -type MockDoguRestartInterface struct { - mock.Mock -} - -type MockDoguRestartInterface_Expecter struct { - mock *mock.Mock -} - -func (_m *MockDoguRestartInterface) EXPECT() *MockDoguRestartInterface_Expecter { - return &MockDoguRestartInterface_Expecter{mock: &_m.Mock} -} - -// Create provides a mock function with given fields: ctx, dogu, opts -func (_m *MockDoguRestartInterface) Create(ctx context.Context, dogu *v2.DoguRestart, opts v1.CreateOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, dogu, opts) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.CreateOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, dogu, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.CreateOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, dogu, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v2.DoguRestart, v1.CreateOptions) error); ok { - r1 = rf(ctx, dogu, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type MockDoguRestartInterface_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - dogu *v2.DoguRestart -// - opts v1.CreateOptions -func (_e *MockDoguRestartInterface_Expecter) Create(ctx interface{}, dogu interface{}, opts interface{}) *MockDoguRestartInterface_Create_Call { - return &MockDoguRestartInterface_Create_Call{Call: _e.mock.On("Create", ctx, dogu, opts)} -} - -func (_c *MockDoguRestartInterface_Create_Call) Run(run func(ctx context.Context, dogu *v2.DoguRestart, opts v1.CreateOptions)) *MockDoguRestartInterface_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v2.DoguRestart), args[2].(v1.CreateOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Create_Call) Return(_a0 *v2.DoguRestart, _a1 error) *MockDoguRestartInterface_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_Create_Call) RunAndReturn(run func(context.Context, *v2.DoguRestart, v1.CreateOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, name, opts -func (_m *MockDoguRestartInterface) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, v1.DeleteOptions) error); ok { - r0 = rf(ctx, name, opts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockDoguRestartInterface_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type MockDoguRestartInterface_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts v1.DeleteOptions -func (_e *MockDoguRestartInterface_Expecter) Delete(ctx interface{}, name interface{}, opts interface{}) *MockDoguRestartInterface_Delete_Call { - return &MockDoguRestartInterface_Delete_Call{Call: _e.mock.On("Delete", ctx, name, opts)} -} - -func (_c *MockDoguRestartInterface_Delete_Call) Run(run func(ctx context.Context, name string, opts v1.DeleteOptions)) *MockDoguRestartInterface_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(v1.DeleteOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Delete_Call) Return(_a0 error) *MockDoguRestartInterface_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockDoguRestartInterface_Delete_Call) RunAndReturn(run func(context.Context, string, v1.DeleteOptions) error) *MockDoguRestartInterface_Delete_Call { - _c.Call.Return(run) - return _c -} - -// DeleteCollection provides a mock function with given fields: ctx, opts, listOpts -func (_m *MockDoguRestartInterface) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - ret := _m.Called(ctx, opts, listOpts) - - if len(ret) == 0 { - panic("no return value specified for DeleteCollection") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, v1.DeleteOptions, v1.ListOptions) error); ok { - r0 = rf(ctx, opts, listOpts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockDoguRestartInterface_DeleteCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCollection' -type MockDoguRestartInterface_DeleteCollection_Call struct { - *mock.Call -} - -// DeleteCollection is a helper method to define mock.On call -// - ctx context.Context -// - opts v1.DeleteOptions -// - listOpts v1.ListOptions -func (_e *MockDoguRestartInterface_Expecter) DeleteCollection(ctx interface{}, opts interface{}, listOpts interface{}) *MockDoguRestartInterface_DeleteCollection_Call { - return &MockDoguRestartInterface_DeleteCollection_Call{Call: _e.mock.On("DeleteCollection", ctx, opts, listOpts)} -} - -func (_c *MockDoguRestartInterface_DeleteCollection_Call) Run(run func(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions)) *MockDoguRestartInterface_DeleteCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(v1.DeleteOptions), args[2].(v1.ListOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_DeleteCollection_Call) Return(_a0 error) *MockDoguRestartInterface_DeleteCollection_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockDoguRestartInterface_DeleteCollection_Call) RunAndReturn(run func(context.Context, v1.DeleteOptions, v1.ListOptions) error) *MockDoguRestartInterface_DeleteCollection_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: ctx, name, opts -func (_m *MockDoguRestartInterface) Get(ctx context.Context, name string, opts v1.GetOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, v1.GetOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, name, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, string, v1.GetOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, name, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, v1.GetOptions) error); ok { - r1 = rf(ctx, name, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type MockDoguRestartInterface_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts v1.GetOptions -func (_e *MockDoguRestartInterface_Expecter) Get(ctx interface{}, name interface{}, opts interface{}) *MockDoguRestartInterface_Get_Call { - return &MockDoguRestartInterface_Get_Call{Call: _e.mock.On("Get", ctx, name, opts)} -} - -func (_c *MockDoguRestartInterface_Get_Call) Run(run func(ctx context.Context, name string, opts v1.GetOptions)) *MockDoguRestartInterface_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(v1.GetOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Get_Call) Return(_a0 *v2.DoguRestart, _a1 error) *MockDoguRestartInterface_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_Get_Call) RunAndReturn(run func(context.Context, string, v1.GetOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_Get_Call { - _c.Call.Return(run) - return _c -} - -// List provides a mock function with given fields: ctx, opts -func (_m *MockDoguRestartInterface) List(ctx context.Context, opts v1.ListOptions) (*v2.DoguRestartList, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for List") - } - - var r0 *v2.DoguRestartList - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) (*v2.DoguRestartList, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) *v2.DoguRestartList); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestartList) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, v1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' -type MockDoguRestartInterface_List_Call struct { - *mock.Call -} - -// List is a helper method to define mock.On call -// - ctx context.Context -// - opts v1.ListOptions -func (_e *MockDoguRestartInterface_Expecter) List(ctx interface{}, opts interface{}) *MockDoguRestartInterface_List_Call { - return &MockDoguRestartInterface_List_Call{Call: _e.mock.On("List", ctx, opts)} -} - -func (_c *MockDoguRestartInterface_List_Call) Run(run func(ctx context.Context, opts v1.ListOptions)) *MockDoguRestartInterface_List_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(v1.ListOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_List_Call) Return(_a0 *v2.DoguRestartList, _a1 error) *MockDoguRestartInterface_List_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_List_Call) RunAndReturn(run func(context.Context, v1.ListOptions) (*v2.DoguRestartList, error)) *MockDoguRestartInterface_List_Call { - _c.Call.Return(run) - return _c -} - -// Patch provides a mock function with given fields: ctx, name, pt, data, opts, subresources -func (_m *MockDoguRestartInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (*v2.DoguRestart, error) { - _va := make([]interface{}, len(subresources)) - for _i := range subresources { - _va[_i] = subresources[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, name, pt, data, opts) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Patch") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, v1.PatchOptions, ...string) (*v2.DoguRestart, error)); ok { - return rf(ctx, name, pt, data, opts, subresources...) - } - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, v1.PatchOptions, ...string) *v2.DoguRestart); ok { - r0 = rf(ctx, name, pt, data, opts, subresources...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, types.PatchType, []byte, v1.PatchOptions, ...string) error); ok { - r1 = rf(ctx, name, pt, data, opts, subresources...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_Patch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Patch' -type MockDoguRestartInterface_Patch_Call struct { - *mock.Call -} - -// Patch is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - pt types.PatchType -// - data []byte -// - opts v1.PatchOptions -// - subresources ...string -func (_e *MockDoguRestartInterface_Expecter) Patch(ctx interface{}, name interface{}, pt interface{}, data interface{}, opts interface{}, subresources ...interface{}) *MockDoguRestartInterface_Patch_Call { - return &MockDoguRestartInterface_Patch_Call{Call: _e.mock.On("Patch", - append([]interface{}{ctx, name, pt, data, opts}, subresources...)...)} -} - -func (_c *MockDoguRestartInterface_Patch_Call) Run(run func(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string)) *MockDoguRestartInterface_Patch_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-5) - for i, a := range args[5:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), args[1].(string), args[2].(types.PatchType), args[3].([]byte), args[4].(v1.PatchOptions), variadicArgs...) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Patch_Call) Return(result *v2.DoguRestart, err error) *MockDoguRestartInterface_Patch_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *MockDoguRestartInterface_Patch_Call) RunAndReturn(run func(context.Context, string, types.PatchType, []byte, v1.PatchOptions, ...string) (*v2.DoguRestart, error)) *MockDoguRestartInterface_Patch_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, dogu, opts -func (_m *MockDoguRestartInterface) Update(ctx context.Context, dogu *v2.DoguRestart, opts v1.UpdateOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, dogu, opts) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, dogu, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, dogu, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) error); ok { - r1 = rf(ctx, dogu, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type MockDoguRestartInterface_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - dogu *v2.DoguRestart -// - opts v1.UpdateOptions -func (_e *MockDoguRestartInterface_Expecter) Update(ctx interface{}, dogu interface{}, opts interface{}) *MockDoguRestartInterface_Update_Call { - return &MockDoguRestartInterface_Update_Call{Call: _e.mock.On("Update", ctx, dogu, opts)} -} - -func (_c *MockDoguRestartInterface_Update_Call) Run(run func(ctx context.Context, dogu *v2.DoguRestart, opts v1.UpdateOptions)) *MockDoguRestartInterface_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v2.DoguRestart), args[2].(v1.UpdateOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Update_Call) Return(_a0 *v2.DoguRestart, _a1 error) *MockDoguRestartInterface_Update_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_Update_Call) RunAndReturn(run func(context.Context, *v2.DoguRestart, v1.UpdateOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_Update_Call { - _c.Call.Return(run) - return _c -} - -// UpdateSpecWithRetry provides a mock function with given fields: ctx, doguRestart, modifySpecFn, opts -func (_m *MockDoguRestartInterface) UpdateSpecWithRetry(ctx context.Context, doguRestart *v2.DoguRestart, modifySpecFn func(v2.DoguRestartSpec) v2.DoguRestartSpec, opts v1.UpdateOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, doguRestart, modifySpecFn, opts) - - if len(ret) == 0 { - panic("no return value specified for UpdateSpecWithRetry") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartSpec) v2.DoguRestartSpec, v1.UpdateOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, doguRestart, modifySpecFn, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartSpec) v2.DoguRestartSpec, v1.UpdateOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, doguRestart, modifySpecFn, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartSpec) v2.DoguRestartSpec, v1.UpdateOptions) error); ok { - r1 = rf(ctx, doguRestart, modifySpecFn, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_UpdateSpecWithRetry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateSpecWithRetry' -type MockDoguRestartInterface_UpdateSpecWithRetry_Call struct { - *mock.Call -} - -// UpdateSpecWithRetry is a helper method to define mock.On call -// - ctx context.Context -// - doguRestart *v2.DoguRestart -// - modifySpecFn func(v2.DoguRestartSpec) v2.DoguRestartSpec -// - opts v1.UpdateOptions -func (_e *MockDoguRestartInterface_Expecter) UpdateSpecWithRetry(ctx interface{}, doguRestart interface{}, modifySpecFn interface{}, opts interface{}) *MockDoguRestartInterface_UpdateSpecWithRetry_Call { - return &MockDoguRestartInterface_UpdateSpecWithRetry_Call{Call: _e.mock.On("UpdateSpecWithRetry", ctx, doguRestart, modifySpecFn, opts)} -} - -func (_c *MockDoguRestartInterface_UpdateSpecWithRetry_Call) Run(run func(ctx context.Context, doguRestart *v2.DoguRestart, modifySpecFn func(v2.DoguRestartSpec) v2.DoguRestartSpec, opts v1.UpdateOptions)) *MockDoguRestartInterface_UpdateSpecWithRetry_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v2.DoguRestart), args[2].(func(v2.DoguRestartSpec) v2.DoguRestartSpec), args[3].(v1.UpdateOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateSpecWithRetry_Call) Return(result *v2.DoguRestart, err error) *MockDoguRestartInterface_UpdateSpecWithRetry_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateSpecWithRetry_Call) RunAndReturn(run func(context.Context, *v2.DoguRestart, func(v2.DoguRestartSpec) v2.DoguRestartSpec, v1.UpdateOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_UpdateSpecWithRetry_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatus provides a mock function with given fields: ctx, dogu, opts -func (_m *MockDoguRestartInterface) UpdateStatus(ctx context.Context, dogu *v2.DoguRestart, opts v1.UpdateOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, dogu, opts) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatus") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, dogu, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, dogu, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v2.DoguRestart, v1.UpdateOptions) error); ok { - r1 = rf(ctx, dogu, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_UpdateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatus' -type MockDoguRestartInterface_UpdateStatus_Call struct { - *mock.Call -} - -// UpdateStatus is a helper method to define mock.On call -// - ctx context.Context -// - dogu *v2.DoguRestart -// - opts v1.UpdateOptions -func (_e *MockDoguRestartInterface_Expecter) UpdateStatus(ctx interface{}, dogu interface{}, opts interface{}) *MockDoguRestartInterface_UpdateStatus_Call { - return &MockDoguRestartInterface_UpdateStatus_Call{Call: _e.mock.On("UpdateStatus", ctx, dogu, opts)} -} - -func (_c *MockDoguRestartInterface_UpdateStatus_Call) Run(run func(ctx context.Context, dogu *v2.DoguRestart, opts v1.UpdateOptions)) *MockDoguRestartInterface_UpdateStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v2.DoguRestart), args[2].(v1.UpdateOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateStatus_Call) Return(_a0 *v2.DoguRestart, _a1 error) *MockDoguRestartInterface_UpdateStatus_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateStatus_Call) RunAndReturn(run func(context.Context, *v2.DoguRestart, v1.UpdateOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_UpdateStatus_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusWithRetry provides a mock function with given fields: ctx, doguRestart, modifyStatusFn, opts -func (_m *MockDoguRestartInterface) UpdateStatusWithRetry(ctx context.Context, doguRestart *v2.DoguRestart, modifyStatusFn func(v2.DoguRestartStatus) v2.DoguRestartStatus, opts v1.UpdateOptions) (*v2.DoguRestart, error) { - ret := _m.Called(ctx, doguRestart, modifyStatusFn, opts) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusWithRetry") - } - - var r0 *v2.DoguRestart - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartStatus) v2.DoguRestartStatus, v1.UpdateOptions) (*v2.DoguRestart, error)); ok { - return rf(ctx, doguRestart, modifyStatusFn, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartStatus) v2.DoguRestartStatus, v1.UpdateOptions) *v2.DoguRestart); ok { - r0 = rf(ctx, doguRestart, modifyStatusFn, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v2.DoguRestart) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v2.DoguRestart, func(v2.DoguRestartStatus) v2.DoguRestartStatus, v1.UpdateOptions) error); ok { - r1 = rf(ctx, doguRestart, modifyStatusFn, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_UpdateStatusWithRetry_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusWithRetry' -type MockDoguRestartInterface_UpdateStatusWithRetry_Call struct { - *mock.Call -} - -// UpdateStatusWithRetry is a helper method to define mock.On call -// - ctx context.Context -// - doguRestart *v2.DoguRestart -// - modifyStatusFn func(v2.DoguRestartStatus) v2.DoguRestartStatus -// - opts v1.UpdateOptions -func (_e *MockDoguRestartInterface_Expecter) UpdateStatusWithRetry(ctx interface{}, doguRestart interface{}, modifyStatusFn interface{}, opts interface{}) *MockDoguRestartInterface_UpdateStatusWithRetry_Call { - return &MockDoguRestartInterface_UpdateStatusWithRetry_Call{Call: _e.mock.On("UpdateStatusWithRetry", ctx, doguRestart, modifyStatusFn, opts)} -} - -func (_c *MockDoguRestartInterface_UpdateStatusWithRetry_Call) Run(run func(ctx context.Context, doguRestart *v2.DoguRestart, modifyStatusFn func(v2.DoguRestartStatus) v2.DoguRestartStatus, opts v1.UpdateOptions)) *MockDoguRestartInterface_UpdateStatusWithRetry_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v2.DoguRestart), args[2].(func(v2.DoguRestartStatus) v2.DoguRestartStatus), args[3].(v1.UpdateOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateStatusWithRetry_Call) Return(result *v2.DoguRestart, err error) *MockDoguRestartInterface_UpdateStatusWithRetry_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *MockDoguRestartInterface_UpdateStatusWithRetry_Call) RunAndReturn(run func(context.Context, *v2.DoguRestart, func(v2.DoguRestartStatus) v2.DoguRestartStatus, v1.UpdateOptions) (*v2.DoguRestart, error)) *MockDoguRestartInterface_UpdateStatusWithRetry_Call { - _c.Call.Return(run) - return _c -} - -// Watch provides a mock function with given fields: ctx, opts -func (_m *MockDoguRestartInterface) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for Watch") - } - - var r0 watch.Interface - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) (watch.Interface, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) watch.Interface); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(watch.Interface) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, v1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockDoguRestartInterface_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch' -type MockDoguRestartInterface_Watch_Call struct { - *mock.Call -} - -// Watch is a helper method to define mock.On call -// - ctx context.Context -// - opts v1.ListOptions -func (_e *MockDoguRestartInterface_Expecter) Watch(ctx interface{}, opts interface{}) *MockDoguRestartInterface_Watch_Call { - return &MockDoguRestartInterface_Watch_Call{Call: _e.mock.On("Watch", ctx, opts)} -} - -func (_c *MockDoguRestartInterface_Watch_Call) Run(run func(ctx context.Context, opts v1.ListOptions)) *MockDoguRestartInterface_Watch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(v1.ListOptions)) - }) - return _c -} - -func (_c *MockDoguRestartInterface_Watch_Call) Return(_a0 watch.Interface, _a1 error) *MockDoguRestartInterface_Watch_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockDoguRestartInterface_Watch_Call) RunAndReturn(run func(context.Context, v1.ListOptions) (watch.Interface, error)) *MockDoguRestartInterface_Watch_Call { - _c.Call.Return(run) - return _c -} - -// NewMockDoguRestartInterface creates a new instance of MockDoguRestartInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockDoguRestartInterface(t interface { - mock.TestingT - Cleanup(func()) -}) *MockDoguRestartInterface { - mock := &MockDoguRestartInterface{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 205a6028..c56f3ea1 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -6,7 +6,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) @@ -17,7 +16,6 @@ type BlueprintSpecChangeUseCase struct { stateDiff stateDiffUseCase applyUseCase applyBlueprintSpecUseCase ecosystemConfigUseCase ecosystemConfigUseCase - doguRestartUseCase doguRestartUseCase selfUpgradeUseCase selfUpgradeUseCase applyComponentUseCase applyComponentsUseCase applyDogusUseCase applyDogusUseCase @@ -31,7 +29,6 @@ func NewBlueprintSpecChangeUseCase( stateDiff stateDiffUseCase, applyUseCase applyBlueprintSpecUseCase, ecosystemConfigUseCase ecosystemConfigUseCase, - doguRestartUseCase doguRestartUseCase, selfUpgradeUseCase selfUpgradeUseCase, applyComponentUseCase applyComponentsUseCase, applyDogusUseCase applyDogusUseCase, @@ -44,7 +41,6 @@ func NewBlueprintSpecChangeUseCase( stateDiff: stateDiff, applyUseCase: applyUseCase, ecosystemConfigUseCase: ecosystemConfigUseCase, - doguRestartUseCase: doguRestartUseCase, selfUpgradeUseCase: selfUpgradeUseCase, applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDogusUseCase, @@ -137,35 +133,11 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } - //TODO: remove this loop, when all use cases are reworked without the use of status - for blueprint.Status != domain.StatusPhaseCompleted { - err := useCase.handleChange(ctx, blueprint) - if err != nil { - return err - } + err = useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) + if err != nil { + return err } logger.Info("blueprint successfully applied") return nil } - -func (useCase *BlueprintSpecChangeUseCase) handleChange(ctx context.Context, blueprint *domain.BlueprintSpec) error { - switch blueprint.Status { - case domain.StatusPhaseBlueprintApplied: - return useCase.doguRestartUseCase.TriggerDoguRestarts(ctx, blueprint) - case domain.StatusPhaseRestartsTriggered: - _, err := useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err - } - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) - case domain.StatusPhaseBlueprintApplicationFailed: - return useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) - case domain.StatusPhaseCompleted: - return nil - case domain.StatusPhaseFailed: - return nil - default: - return fmt.Errorf("could not handle unknown status of blueprint") - } -} diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 5ba4ea47..50441321 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -2,7 +2,6 @@ package application import ( "context" - "errors" "testing" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -40,13 +39,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, @@ -83,10 +81,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { blueprint.Status = domain.StatusPhaseCompleted }) - doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseRestartsTriggered - }) // when err := useCase.HandleUntilApplied(ctxWithLogger, testBlueprintId) @@ -103,13 +97,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) expectedError := &domainservice.InternalError{ WrappedError: nil, @@ -133,13 +126,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -166,13 +158,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -199,13 +190,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -233,13 +223,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -273,13 +262,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) updatedSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -321,13 +309,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -346,13 +333,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -374,13 +360,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, @@ -388,10 +373,6 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { } repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) - doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(testCtx, testBlueprintId).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseRestartsTriggered - }) // when err := useCase.HandleUntilApplied(testCtx, testBlueprintId) @@ -407,13 +388,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) blueprintSpec := &domain.BlueprintSpec{ Id: "testBlueprint1", @@ -436,13 +416,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -462,13 +441,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ Id: blueprintId, @@ -489,13 +467,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -515,13 +492,12 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { stateDiffMock := newMockStateDiffUseCase(t) applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) componentUseCase := newMockApplyComponentUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) + applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ Id: "testBlueprint1", @@ -534,35 +510,3 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { require.ErrorContains(t, err, "could not handle unknown status of blueprint") }) } - -func TestBlueprintSpecChangeUseCase_triggerDoguRestarts(t *testing.T) { - t.Run("handle error in TriggerDoguRestarts", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - doguRestartUseCaseMock := newMockDoguRestartUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, doguRestartUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: testBlueprintId, - Status: domain.StatusPhaseBlueprintApplied, - } - repoMock.EXPECT().GetById(mock.Anything, "testBlueprint1").Return(blueprintSpec, nil) - doguRestartUseCaseMock.EXPECT().TriggerDoguRestarts(mock.Anything, testBlueprintId).Return(errors.New("testerror")) - - // when - err := useCase.HandleUntilApplied(testCtx, testBlueprintId) - // then - require.Error(t, err) - assert.Equal(t, "testerror", err.Error()) - }) -} diff --git a/pkg/application/doguRestartUseCase.go b/pkg/application/doguRestartUseCase.go deleted file mode 100644 index adb57258..00000000 --- a/pkg/application/doguRestartUseCase.go +++ /dev/null @@ -1,65 +0,0 @@ -package application - -import ( - "context" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -type DoguRestartUseCase struct { - doguInstallationRepository doguInstallationRepository - blueprintSpecRepo blueprintSpecRepository - restartRepository doguRestartRepository -} - -func NewDoguRestartUseCase(doguInstallationRepository doguInstallationRepository, blueprintSpecRepo blueprintSpecRepository, restartRepository doguRestartRepository) *DoguRestartUseCase { - return &DoguRestartUseCase{doguInstallationRepository: doguInstallationRepository, blueprintSpecRepo: blueprintSpecRepo, restartRepository: restartRepository} -} - -func (useCase *DoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("DoguRestartUseCase.TriggerDoguRestarts") - - logger.Info("searching for Dogus that need a restart...") - var dogusThatNeedARestart []cescommons.SimpleName - var err error - if blueprint.StateDiff.GlobalConfigDiffs.HasChanges() { - logger.Info("restarting all installed Dogus...") - err = useCase.restartAllInstalledDogus(ctx) - if err != nil { - return domainservice.NewInternalError(err, "could not restart all installed Dogus") - } - } else { - dogusThatNeedARestart = blueprint.GetDogusThatNeedARestart() - if len(dogusThatNeedARestart) > 0 { - logger.Info("restarting Dogus...") - restartError := useCase.restartRepository.RestartAll(ctx, dogusThatNeedARestart) - if restartError != nil { - return domainservice.NewInternalError(err, "could not restart Dogus") - } - } else { - logger.Info("no Dogu restarts necessary") - } - } - - blueprint.Status = domain.StatusPhaseRestartsTriggered - err = useCase.blueprintSpecRepo.Update(ctx, blueprint) - if err != nil { - return domainservice.NewInternalError(err, "could not update blueprint spec") - } - return nil -} - -func (useCase *DoguRestartUseCase) restartAllInstalledDogus(ctx context.Context) error { - installedDogus, getInstalledDogusError := useCase.doguInstallationRepository.GetAll(ctx) - if getInstalledDogusError != nil { - return domainservice.NewInternalError(getInstalledDogusError, "could not get all installed Dogus") - } - var installedDogusSimpleNames []cescommons.SimpleName - for _, installation := range installedDogus { - installedDogusSimpleNames = append(installedDogusSimpleNames, installation.Name.SimpleName) - } - return useCase.restartRepository.RestartAll(ctx, installedDogusSimpleNames) -} diff --git a/pkg/application/doguRestartUseCase_test.go b/pkg/application/doguRestartUseCase_test.go deleted file mode 100644 index 41c51b89..00000000 --- a/pkg/application/doguRestartUseCase_test.go +++ /dev/null @@ -1,345 +0,0 @@ -package application - -import ( - "context" - "testing" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - testDoguSimpleName = cescommons.SimpleName("testDogu1") -) - -func TestDoguRestartUseCase_TriggerDoguRestarts(t *testing.T) { - t.Run("no dogu restarts triggered on blueprint with empty state diff", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) - - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.NoError(t, err) - }) - - t.Run("dogu restarts triggered on blueprint with non-empty state diff", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{{ - Key: "testkey", - Actual: domain.GlobalConfigValueState{Value: "changed", Exists: true}, - Expected: domain.GlobalConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet, - }}, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - installedDogu := ecosystem.DoguInstallation{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: testDoguSimpleName}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - Status: "installed", - Health: ecosystem.AvailableHealthStatus, - UpgradeConfig: ecosystem.UpgradeConfig{AllowNamespaceSwitch: false}, - PersistenceContext: nil, - } - installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - testDoguSimpleName: &installedDogu, - } - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - installationRepository.EXPECT().GetAll(testContext).Return(installedDogus, nil) - restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) - blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) - - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.NoError(t, err) - }) - - t.Run("fail on get all dogus from repository error", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{{ - Key: "testkey", - Actual: domain.GlobalConfigValueState{Value: "changed", Exists: true}, - Expected: domain.GlobalConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet, - }}, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - installationRepository.EXPECT().GetAll(testContext).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, assert.AnError) - - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "could not restart all installed Dogus: could not get all installed Dogus:") - }) - - t.Run("fail on repository restart all error", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{{ - Key: "testkey", - Actual: domain.GlobalConfigValueState{Value: "changed", Exists: true}, - Expected: domain.GlobalConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet, - }}, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - installedDogu := ecosystem.DoguInstallation{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: testDoguSimpleName}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - Status: "installed", - Health: ecosystem.AvailableHealthStatus, - UpgradeConfig: ecosystem.UpgradeConfig{AllowNamespaceSwitch: false}, - PersistenceContext: nil, - } - installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - testDoguSimpleName: &installedDogu, - } - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - installationRepository.EXPECT().GetAll(testContext).Return(installedDogus, nil) - restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(assert.AnError) - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "could not restart all installed Dogus") - }) - - t.Run("restart some dogus", func(t *testing.T) { - // given - - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ - testDoguSimpleName: {{ - Key: common.DoguConfigKey{DoguName: testDoguSimpleName, Key: "testKey"}, - Actual: domain.DoguConfigValueState{Value: "changed", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet}}, - }, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - } - testDogu := domain.Dogu{ - Name: cescommons.QualifiedName{SimpleName: testDoguSimpleName, Namespace: "testing"}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - TargetState: 0, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{testDogu}}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(nil) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.NoError(t, err) - }) - - t.Run("fail on dogu restart for some dogus", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ - testDoguSimpleName: {{ - Key: common.DoguConfigKey{DoguName: testDoguSimpleName, Key: "testKey"}, - Actual: domain.DoguConfigValueState{Value: "changed", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet}}, - }, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - } - testDogu := domain.Dogu{ - Name: cescommons.QualifiedName{SimpleName: testDoguSimpleName, Namespace: "testing"}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - TargetState: 0, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{testDogu}}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - installationRepository := newMockDoguInstallationRepository(t) - restartRepository := newMockDoguRestartRepository(t) - restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(assert.AnError) - restartUseCase := NewDoguRestartUseCase(installationRepository, nil, restartRepository) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "could not restart Dogus") - }) - - t.Run("fail on error in blueprint spec update", func(t *testing.T) { - // given - testContext := context.Background() - testStateDiff := domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: domain.ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ - testDoguSimpleName: {{ - Key: common.DoguConfigKey{DoguName: testDoguSimpleName, Key: "testKey"}, - Actual: domain.DoguConfigValueState{Value: "changed", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "initial", Exists: true}, - NeededAction: domain.ConfigActionSet}}, - }, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - } - testDogu := domain.Dogu{ - Name: cescommons.QualifiedName{SimpleName: testDoguSimpleName, Namespace: "testing"}, - Version: core.Version{Raw: "1.0.0-1", Major: 1, Extra: 1}, - TargetState: 0, - } - blueprint := &domain.BlueprintSpec{ - Id: testBlueprintId, - Blueprint: domain.Blueprint{}, - BlueprintMask: domain.BlueprintMask{}, - EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{testDogu}}, - StateDiff: testStateDiff, - Config: domain.BlueprintConfiguration{}, - Status: "", - PersistenceContext: nil, - Events: nil, - } - dogusThatNeedARestart := []cescommons.SimpleName{testDoguSimpleName} - installationRepository := newMockDoguInstallationRepository(t) - blueprintSpecRepo := newMockBlueprintSpecRepository(t) - restartRepository := newMockDoguRestartRepository(t) - restartRepository.EXPECT().RestartAll(testContext, dogusThatNeedARestart).Return(nil) - restartUseCase := NewDoguRestartUseCase(installationRepository, blueprintSpecRepo, restartRepository) - blueprintSpecRepo.EXPECT().Update(testContext, blueprint).Return(assert.AnError) - - // when - err := restartUseCase.TriggerDoguRestarts(testContext, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "could not update blueprint spec") - }) -} diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 2f9dc2fe..db6cef8b 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -27,10 +27,6 @@ type doguInstallationUseCase interface { ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error } -type doguRestartUseCase interface { - TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error -} - type applyDogusUseCase interface { ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -115,10 +111,6 @@ type sensitiveConfigRefReader interface { domainservice.SensitiveConfigRefReader } -type doguRestartRepository interface { - domainservice.DoguRestartRepository -} - // validateDependenciesDomainUseCase is an interface for the domain service for better testability type validateDependenciesDomainUseCase interface { ValidateDependenciesForAllDogus(ctx context.Context, effectiveBlueprint domain.EffectiveBlueprint) error diff --git a/pkg/application/mock_applyComponentUseCase_test.go b/pkg/application/mock_applyComponentUseCase_test.go deleted file mode 100644 index 207e7d39..00000000 --- a/pkg/application/mock_applyComponentUseCase_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - mock "github.com/stretchr/testify/mock" -) - -// mockApplyComponentUseCase is an autogenerated mock type for the applyComponentsUseCase type -type mockApplyComponentUseCase struct { - mock.Mock -} - -type mockApplyComponentUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockApplyComponentUseCase) EXPECT() *mockApplyComponentUseCase_Expecter { - return &mockApplyComponentUseCase_Expecter{mock: &_m.Mock} -} - -// ApplyComponents provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyComponentUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for ApplyComponents") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyComponentUseCase_ApplyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponents' -type mockApplyComponentUseCase_ApplyComponents_Call struct { - *mock.Call -} - -// ApplyComponents is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyComponentUseCase_Expecter) ApplyComponents(ctx interface{}, blueprint interface{}) *mockApplyComponentUseCase_ApplyComponents_Call { - return &mockApplyComponentUseCase_ApplyComponents_Call{Call: _e.mock.On("ApplyComponents", ctx, blueprint)} -} - -func (_c *mockApplyComponentUseCase_ApplyComponents_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyComponentUseCase_ApplyComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyComponentUseCase_ApplyComponents_Call) Return(_a0 error) *mockApplyComponentUseCase_ApplyComponents_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyComponentUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyComponentUseCase_ApplyComponents_Call { - _c.Call.Return(run) - return _c -} - -// newMockApplyComponentUseCase creates a new instance of mockApplyComponentUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockApplyComponentUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockApplyComponentUseCase { - mock := &mockApplyComponentUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_doguRestartRepository_test.go b/pkg/application/mock_doguRestartRepository_test.go deleted file mode 100644 index 46a256fb..00000000 --- a/pkg/application/mock_doguRestartRepository_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - dogu "github.com/cloudogu/ces-commons-lib/dogu" - mock "github.com/stretchr/testify/mock" -) - -// mockDoguRestartRepository is an autogenerated mock type for the doguRestartRepository type -type mockDoguRestartRepository struct { - mock.Mock -} - -type mockDoguRestartRepository_Expecter struct { - mock *mock.Mock -} - -func (_m *mockDoguRestartRepository) EXPECT() *mockDoguRestartRepository_Expecter { - return &mockDoguRestartRepository_Expecter{mock: &_m.Mock} -} - -// RestartAll provides a mock function with given fields: _a0, _a1 -func (_m *mockDoguRestartRepository) RestartAll(_a0 context.Context, _a1 []dogu.SimpleName) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for RestartAll") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []dogu.SimpleName) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockDoguRestartRepository_RestartAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestartAll' -type mockDoguRestartRepository_RestartAll_Call struct { - *mock.Call -} - -// RestartAll is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 []dogu.SimpleName -func (_e *mockDoguRestartRepository_Expecter) RestartAll(_a0 interface{}, _a1 interface{}) *mockDoguRestartRepository_RestartAll_Call { - return &mockDoguRestartRepository_RestartAll_Call{Call: _e.mock.On("RestartAll", _a0, _a1)} -} - -func (_c *mockDoguRestartRepository_RestartAll_Call) Run(run func(_a0 context.Context, _a1 []dogu.SimpleName)) *mockDoguRestartRepository_RestartAll_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]dogu.SimpleName)) - }) - return _c -} - -func (_c *mockDoguRestartRepository_RestartAll_Call) Return(_a0 error) *mockDoguRestartRepository_RestartAll_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockDoguRestartRepository_RestartAll_Call) RunAndReturn(run func(context.Context, []dogu.SimpleName) error) *mockDoguRestartRepository_RestartAll_Call { - _c.Call.Return(run) - return _c -} - -// newMockDoguRestartRepository creates a new instance of mockDoguRestartRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockDoguRestartRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *mockDoguRestartRepository { - mock := &mockDoguRestartRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_doguRestartUseCase_test.go b/pkg/application/mock_doguRestartUseCase_test.go deleted file mode 100644 index 7a9a7393..00000000 --- a/pkg/application/mock_doguRestartUseCase_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - mock "github.com/stretchr/testify/mock" -) - -// mockDoguRestartUseCase is an autogenerated mock type for the doguRestartUseCase type -type mockDoguRestartUseCase struct { - mock.Mock -} - -type mockDoguRestartUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockDoguRestartUseCase) EXPECT() *mockDoguRestartUseCase_Expecter { - return &mockDoguRestartUseCase_Expecter{mock: &_m.Mock} -} - -// TriggerDoguRestarts provides a mock function with given fields: ctx, blueprint -func (_m *mockDoguRestartUseCase) TriggerDoguRestarts(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for TriggerDoguRestarts") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockDoguRestartUseCase_TriggerDoguRestarts_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TriggerDoguRestarts' -type mockDoguRestartUseCase_TriggerDoguRestarts_Call struct { - *mock.Call -} - -// TriggerDoguRestarts is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockDoguRestartUseCase_Expecter) TriggerDoguRestarts(ctx interface{}, blueprint interface{}) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { - return &mockDoguRestartUseCase_TriggerDoguRestarts_Call{Call: _e.mock.On("TriggerDoguRestarts", ctx, blueprint)} -} - -func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) Return(_a0 error) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockDoguRestartUseCase_TriggerDoguRestarts_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockDoguRestartUseCase_TriggerDoguRestarts_Call { - _c.Call.Return(run) - return _c -} - -// newMockDoguRestartUseCase creates a new instance of mockDoguRestartUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockDoguRestartUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockDoguRestartUseCase { - mock := &mockDoguRestartUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index 79ab8ea2..40699e14 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -5,7 +5,6 @@ import ( adapterconfigk8s "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/config/kubernetes" v2 "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/blueprintcr/v2" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/restartcr" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/sensitiveconfigref" "github.com/cloudogu/k8s-registry-lib/repository" remotedogudescriptor "github.com/cloudogu/remote-dogu-descriptor-lib/repository" @@ -75,8 +74,6 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name doguInstallationRepo := dogucr.NewDoguInstallationRepo(dogusInterface.Dogus(namespace)) componentInstallationRepo := componentcr.NewComponentInstallationRepo(componentsInterface.Components(namespace)) healthConfigRepo := adapterhealthconfig.NewHealthConfigProvider(ecosystemClientSet.CoreV1().ConfigMaps(namespace)) - doguRestartAdapter := dogusInterface.DoguRestarts(namespace) - restartRepository := restartcr.NewDoguRestartRepository(doguRestartAdapter) validateDependenciesUseCase := domainservice.NewValidateDependenciesDomainUseCase(remoteDoguRegistry) validateMountsUseCase := domainservice.NewValidateAdditionalMountsDomainUseCase(remoteDoguRegistry) @@ -90,14 +87,12 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintSpecRepository, componentInstallationUseCase) applyDogusUseCase := application.NewApplyDogusUseCase(blueprintSpecRepository, doguInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintSpecRepository, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) - doguRestartUseCase := application.NewDoguRestartUseCase(doguInstallationRepo, blueprintSpecRepository, restartRepository) selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintSpecRepository, componentInstallationRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( blueprintSpecRepository, blueprintValidationUseCase, effectiveBlueprintUseCase, stateDiffUseCase, applyBlueprintSpecUseCase, ConfigUseCase, - doguRestartUseCase, selfUpgradeUseCase, applyComponentUseCase, applyDogusUseCase, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 0297b584..c1c3c07f 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -57,9 +57,6 @@ const ( StatusPhaseFailed StatusPhase = "failed" // StatusPhaseCompleted marks the blueprint as successfully applied. StatusPhaseCompleted StatusPhase = "completed" - // StatusPhaseRestartsTriggered indicates that a restart has been triggered for all Dogus that needed a restart. - // Restarts are needed when the Dogu config changes. - StatusPhaseRestartsTriggered StatusPhase = "restartsTriggered" ) type BlueprintConfiguration struct { @@ -545,19 +542,6 @@ func getActionNotAllowedError(action Action) *InvalidBlueprintError { } } -func (spec *BlueprintSpec) GetDogusThatNeedARestart() []cescommons.SimpleName { - var dogusThatNeedRestart []cescommons.SimpleName - dogusInEffectiveBlueprint := spec.EffectiveBlueprint.Dogus - for _, dogu := range dogusInEffectiveBlueprint { - //TODO: test this - if spec.StateDiff.DoguConfigDiffs[dogu.Name.SimpleName].HasChanges() || - spec.StateDiff.SensitiveDoguConfigDiffs[dogu.Name.SimpleName].HasChanges() { - dogusThatNeedRestart = append(dogusThatNeedRestart, dogu.Name.SimpleName) - } - } - return dogusThatNeedRestart -} - func (spec *BlueprintSpec) StartApplyEcosystemConfig() { event := ApplyEcosystemConfigEvent{} conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 342e8ce1..ac42e478 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -638,76 +638,6 @@ func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { }) } -func TestBlueprintSpec_GetDogusThatNeedARestart(t *testing.T) { - testDogu1 := Dogu{Name: cescommons.QualifiedName{Namespace: "testNamespace", SimpleName: "testDogu1"}} - testBlueprint1 := Blueprint{Dogus: []Dogu{testDogu1}} - testDoguConfigDiffsChanged := []DoguConfigEntryDiff{{ - Actual: DoguConfigValueState{}, - Expected: DoguConfigValueState{Value: "testValue", Exists: true}, - NeededAction: ConfigActionSet, - }} - testDoguConfigDiffsActionNone := []DoguConfigEntryDiff{{ - NeededAction: ConfigActionNone, - }} - - testDoguConfigChangeDiffChanged := StateDiff{ - DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{testDogu1.Name.SimpleName: testDoguConfigDiffsChanged}, - } - testDoguConfigChangeDiffActionNone := StateDiff{ - DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{testDogu1.Name.SimpleName: testDoguConfigDiffsActionNone}, - } - - type fields struct { - Blueprint Blueprint - EffectiveBlueprint EffectiveBlueprint - StateDiff StateDiff - } - tests := []struct { - name string - fields fields - want []cescommons.SimpleName - }{ - { - name: "return nothing on empty blueprint", - fields: fields{}, - want: nil, - }, - { - name: "return nothing on no config change", - fields: fields{Blueprint: testBlueprint1}, - want: nil, - }, - { - name: "return dogu on dogu config change", - fields: fields{ - Blueprint: testBlueprint1, - EffectiveBlueprint: EffectiveBlueprint(testBlueprint1), - StateDiff: testDoguConfigChangeDiffChanged, - }, - want: []cescommons.SimpleName{testDogu1.Name.SimpleName}, - }, - { - name: "return nothing on dogu config unchanged", - fields: fields{ - Blueprint: testBlueprint1, - EffectiveBlueprint: EffectiveBlueprint(testBlueprint1), - StateDiff: testDoguConfigChangeDiffActionNone, - }, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - spec := &BlueprintSpec{ - Blueprint: tt.fields.Blueprint, - EffectiveBlueprint: tt.fields.EffectiveBlueprint, - StateDiff: tt.fields.StateDiff, - } - assert.Equalf(t, tt.want, spec.GetDogusThatNeedARestart(), "GetDogusThatNeedARestart()") - }) - } -} - func TestBlueprintSpec_HandleHealthResult(t *testing.T) { t.Run("healthy", func(t *testing.T) { blueprint := BlueprintSpec{ diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index 66c847c0..87833b2f 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -95,11 +96,6 @@ type DoguToLoad struct { Version string } -type DoguRestartRepository interface { - // RestartAll restarts all provided Dogus - RestartAll(context.Context, []cescommons.SimpleName) error -} - // GlobalConfigRepository is used to get the whole global config of the ecosystem to make changes and persist it as a whole. type GlobalConfigRepository interface { // Get retrieves the whole global config. diff --git a/pkg/domainservice/mock_DoguRestartRepository_test.go b/pkg/domainservice/mock_DoguRestartRepository_test.go deleted file mode 100644 index b5f5dd47..00000000 --- a/pkg/domainservice/mock_DoguRestartRepository_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package domainservice - -import ( - context "context" - - dogu "github.com/cloudogu/ces-commons-lib/dogu" - mock "github.com/stretchr/testify/mock" -) - -// MockDoguRestartRepository is an autogenerated mock type for the DoguRestartRepository type -type MockDoguRestartRepository struct { - mock.Mock -} - -type MockDoguRestartRepository_Expecter struct { - mock *mock.Mock -} - -func (_m *MockDoguRestartRepository) EXPECT() *MockDoguRestartRepository_Expecter { - return &MockDoguRestartRepository_Expecter{mock: &_m.Mock} -} - -// RestartAll provides a mock function with given fields: _a0, _a1 -func (_m *MockDoguRestartRepository) RestartAll(_a0 context.Context, _a1 []dogu.SimpleName) error { - ret := _m.Called(_a0, _a1) - - if len(ret) == 0 { - panic("no return value specified for RestartAll") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, []dogu.SimpleName) error); ok { - r0 = rf(_a0, _a1) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockDoguRestartRepository_RestartAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestartAll' -type MockDoguRestartRepository_RestartAll_Call struct { - *mock.Call -} - -// RestartAll is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 []dogu.SimpleName -func (_e *MockDoguRestartRepository_Expecter) RestartAll(_a0 interface{}, _a1 interface{}) *MockDoguRestartRepository_RestartAll_Call { - return &MockDoguRestartRepository_RestartAll_Call{Call: _e.mock.On("RestartAll", _a0, _a1)} -} - -func (_c *MockDoguRestartRepository_RestartAll_Call) Run(run func(_a0 context.Context, _a1 []dogu.SimpleName)) *MockDoguRestartRepository_RestartAll_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]dogu.SimpleName)) - }) - return _c -} - -func (_c *MockDoguRestartRepository_RestartAll_Call) Return(_a0 error) *MockDoguRestartRepository_RestartAll_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockDoguRestartRepository_RestartAll_Call) RunAndReturn(run func(context.Context, []dogu.SimpleName) error) *MockDoguRestartRepository_RestartAll_Call { - _c.Call.Return(run) - return _c -} - -// NewMockDoguRestartRepository creates a new instance of MockDoguRestartRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockDoguRestartRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *MockDoguRestartRepository { - mock := &MockDoguRestartRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} From 8372290e5b369bd0fdbfd5aecd4d05fa599b9836 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 09:22:51 +0200 Subject: [PATCH 035/119] #121 fix tests after renaming mock --- .../blueprintSpecChangeUseCase_test.go | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 50441321..567990cf 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -40,7 +40,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -98,7 +98,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -127,7 +127,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -159,7 +159,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -191,7 +191,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -224,7 +224,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -263,7 +263,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -310,7 +310,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -334,7 +334,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -361,7 +361,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -389,7 +389,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -417,7 +417,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -442,7 +442,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -468,7 +468,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, @@ -493,7 +493,7 @@ func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { applyMock := newMockApplyBlueprintSpecUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentUseCase(t) + componentUseCase := newMockApplyComponentsUseCase(t) doguUseCase := newMockApplyDogusUseCase(t) healthUseCase := newMockEcosystemHealthUseCase(t) useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, From cdcdce090560d64f5d5420e7fc2d586b741c4d1e Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 10:36:41 +0200 Subject: [PATCH 036/119] #121 update changelog --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9270d6c..dfff3f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#119] *breaking* sensitive dogu config can now only be referenced with secrets - it was not safe to have these values in clear text in the blueprint - [#119] we now support blueprint v2 CRs -- [#121] health checks before and after applying the blueprint are now non-blocking +- [#121] all health checks are now non-blocking +- [#121] there are in general no steps anymore, which will block the reconciliation loop beyond some HTTP-Requests +- [#121] *breaking* blueprints will now be executed as a continuous process + - the operator will now detect changes and will enforce the content of the blueprint +- [#121] *breaking* the current state will now be reflected via conditions instead of the `statusPhase` field +- [#121] *breaking* events were reworked, some events are now more general, some events got removed completely + - Note, that events are for humans. You should not compute them for automation as they have no consistency guarantees. ### Removed - [#119] *breaking* no support for v1 blueprint CRs anymore - make sure to persist your blueprints before upgrading - you need to transform your blueprints to the new v2 format yourself - [#121] remove maintenance mode +- [#121] *breaking* dogus will not be restarted by the blueprint operator anymore + - this is now the responsibility of the dogu operator ## [v2.7.0] - 2025-07-17 ### Fixed From d77f1973c24ff21ac8d9bfe6c93aa7c6028c6362 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 11:35:53 +0200 Subject: [PATCH 037/119] #121 rename post-processing to completeBp; remove last status phases The reason for the post-processing was to deactivate the maintenance mode and to mark the blueprint as completed or failed. Now, it would only be called if all other steps before it were successful, so we not even needed to handle error states anymore. Therefore, I renamed it to completeBlueprint. The last status phases could also be removed because with the refactoring of the apply-process for dogus and components, they aren't needed any longer. --- .../v2/blueprintSpecCRRepository.go | 3 +- pkg/application/applyBlueprintSpecUseCase.go | 41 -- .../applyBlueprintSpecUseCase_test.go | 50 -- pkg/application/blueprintSpecChangeUseCase.go | 6 +- .../blueprintSpecChangeUseCase_test.go | 499 ------------------ .../blueprintSpecValidationUseCase_test.go | 7 +- pkg/application/completeBlueprintUseCase.go | 35 ++ .../completeBlueprintUseCase_test.go | 64 +++ pkg/application/interfaces.go | 4 +- .../mock_applyBlueprintSpecUseCase_test.go | 84 --- .../mock_completeBlueprintUseCase_test.go | 84 +++ pkg/application/stateDiffUseCase.go | 4 +- pkg/application/stateDiffUseCase_test.go | 2 +- pkg/bootstrap.go | 30 +- pkg/domain/blueprintSpec.go | 67 +-- pkg/domain/blueprintSpec_test.go | 69 +-- 16 files changed, 252 insertions(+), 797 deletions(-) delete mode 100644 pkg/application/applyBlueprintSpecUseCase.go delete mode 100644 pkg/application/applyBlueprintSpecUseCase_test.go create mode 100644 pkg/application/completeBlueprintUseCase.go create mode 100644 pkg/application/completeBlueprintUseCase_test.go delete mode 100644 pkg/application/mock_applyBlueprintSpecUseCase_test.go create mode 100644 pkg/application/mock_completeBlueprintUseCase_test.go diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index e8f3780c..f1ff659f 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" serializerv2 "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/blueprintcr/v2/serializer" corev1 "k8s.io/api/core/v1" @@ -74,7 +75,6 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) AllowDoguNamespaceSwitch: blueprintCR.Spec.AllowDoguNamespaceSwitch, DryRun: blueprintCR.Spec.DryRun, }, - Status: domain.StatusPhase(blueprintCR.Status.Phase), } blueprint, blueprintErr := serializerv2.ConvertToBlueprintDomain(blueprintCR.Spec.Blueprint) @@ -118,7 +118,6 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.Blueprin CreationTimestamp: metav1.Time{}, }, Status: v2.BlueprintStatus{ - Phase: v2.StatusPhase(spec.Status), EffectiveBlueprint: effectiveBlueprint, StateDiff: serializerv2.ConvertToStateDiffDTO(spec.StateDiff), }, diff --git a/pkg/application/applyBlueprintSpecUseCase.go b/pkg/application/applyBlueprintSpecUseCase.go deleted file mode 100644 index 0d4f2c9c..00000000 --- a/pkg/application/applyBlueprintSpecUseCase.go +++ /dev/null @@ -1,41 +0,0 @@ -package application - -import ( - "context" - "fmt" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" -) - -// ApplyBlueprintSpecUseCase contains all use cases which are needed for or around applying -// the new ecosystem state after the determining the state diff. -type ApplyBlueprintSpecUseCase struct { - repo blueprintSpecRepository - doguInstallUseCase doguInstallationUseCase - healthUseCase ecosystemHealthUseCase -} - -func NewApplyBlueprintSpecUseCase( - repo blueprintSpecRepository, - doguInstallUseCase doguInstallationUseCase, - healthUseCase ecosystemHealthUseCase, -) *ApplyBlueprintSpecUseCase { - return &ApplyBlueprintSpecUseCase{ - repo: repo, - doguInstallUseCase: doguInstallUseCase, - healthUseCase: healthUseCase, - } -} - -// PostProcessBlueprintApplication makes changes to the environment after applying the blueprint. -// returns a domainservice.InternalError on any error. -func (useCase *ApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { - blueprint.CompletePostProcessing() - - err := useCase.repo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot update blueprint spec %q while post-processing blueprint application: %w", blueprint.Id, err) - } - - return nil -} diff --git a/pkg/application/applyBlueprintSpecUseCase_test.go b/pkg/application/applyBlueprintSpecUseCase_test.go deleted file mode 100644 index 12531a2a..00000000 --- a/pkg/application/applyBlueprintSpecUseCase_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package application - -import ( - "testing" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewApplyBlueprintSpecUseCase(t *testing.T) { - repoMock := newMockBlueprintSpecRepository(t) - installUseCaseMock := newMockDoguInstallationUseCase(t) - healthMock := newMockEcosystemHealthUseCase(t) - - sut := NewApplyBlueprintSpecUseCase(repoMock, installUseCaseMock, healthMock) - - assert.Equal(t, installUseCaseMock, sut.doguInstallUseCase) - assert.Equal(t, repoMock, sut.repo) - assert.Equal(t, healthMock, sut.healthUseCase) -} - -func TestApplyBlueprintSpecUseCase_PostProcessBlueprintApplication(t *testing.T) { - t.Run("ok", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil) - - err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) - - require.NoError(t, err) - - assert.Equal(t, domain.StatusPhaseCompleted, blueprint.Status) - assert.Len(t, blueprint.Events, 1) - assert.Contains(t, blueprint.Events, domain.CompletedEvent{}, blueprint.Events) - }) - t.Run("repo error while saving", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{} - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - useCase := NewApplyBlueprintSpecUseCase(repoMock, nil, nil) - - err := useCase.PostProcessBlueprintApplication(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - }) -} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index c56f3ea1..434b00b4 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -14,7 +14,7 @@ type BlueprintSpecChangeUseCase struct { validation blueprintSpecValidationUseCase effectiveBlueprint effectiveBlueprintUseCase stateDiff stateDiffUseCase - applyUseCase applyBlueprintSpecUseCase + applyUseCase completeBlueprintUseCase ecosystemConfigUseCase ecosystemConfigUseCase selfUpgradeUseCase selfUpgradeUseCase applyComponentUseCase applyComponentsUseCase @@ -27,7 +27,7 @@ func NewBlueprintSpecChangeUseCase( validation blueprintSpecValidationUseCase, effectiveBlueprint effectiveBlueprintUseCase, stateDiff stateDiffUseCase, - applyUseCase applyBlueprintSpecUseCase, + applyUseCase completeBlueprintUseCase, ecosystemConfigUseCase ecosystemConfigUseCase, selfUpgradeUseCase selfUpgradeUseCase, applyComponentUseCase applyComponentsUseCase, @@ -133,7 +133,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return err } - err = useCase.applyUseCase.PostProcessBlueprintApplication(ctx, blueprint) + err = useCase.applyUseCase.CompleteBlueprint(ctx, blueprint) if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 567990cf..713403cf 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -3,510 +3,11 @@ package application import ( "context" "testing" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/stretchr/testify/mock" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) var testCtx = context.Background() var testBlueprintId = "testBlueprint1" func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { - //FIXME: this test runs endless at the moment. Refactoring the changeUseCase is the last step - require.True(t, false, "tests deactivated until the refactoring is done.") - - t.Run("do all steps with blueprint", func(t *testing.T) { - // given - logger := log.FromContext(testCtx). - WithName("BlueprintSpecChangeUseCase.HandleUntilApplied"). - WithValues("blueprintId", blueprintId) - log.SetLogger(logger) - ctxWithLogger := log.IntoContext(testCtx, logger) - - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: testBlueprintId, - Status: domain.StatusPhaseNew, - } - repoMock.EXPECT().GetById(mock.Anything, testBlueprintId).Return(blueprintSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionValid, - Status: metav1.ConditionTrue, - }) - }) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(mock.Anything, blueprintSpec).Return(nil) - validationMock.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionValid, - Status: metav1.ConditionTrue, - }) - }) - stateDiffMock.EXPECT().DetermineStateDiff(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionExecutable, - Status: metav1.ConditionTrue, - }) - }) - healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, nil) - ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(mock.Anything, blueprintSpec).Return(nil) - selfUpgradeUseCase.EXPECT().HandleSelfUpgrade(mock.Anything, blueprintSpec).Return(nil) - healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(mock.Anything, blueprintSpec).Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseCompleted - }) - - // when - err := useCase.HandleUntilApplied(ctxWithLogger, testBlueprintId) - // then - require.NoError(t, err) - assert.Equal(t, domain.StatusPhaseCompleted, blueprintSpec.Status) - }) - - t.Run("cannot load blueprint spec initially", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - expectedError := &domainservice.InternalError{ - WrappedError: nil, - Message: "test-error", - } - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(nil, expectedError) - - // when - err := useCase.HandleUntilApplied(testCtx, blueprintId) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, expectedError) - }) - - t.Run("new with static validation error", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - Blueprint: domain.Blueprint{Dogus: []domain.Dogu{ - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "DoguWithNoVersion"}, TargetState: domain.TargetStatePresent}, - }}, - }, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(assert.AnError) - - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - - t.Run("new with error calculating effective blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - updatedSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - } - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(assert.AnError) - - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - - t.Run("new with dynamic validation error", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - updatedSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - } - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) - validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(assert.AnError) - - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - - t.Run("new with error determining state diff", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - updatedSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - } - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) - validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionValid, - Status: metav1.ConditionTrue, - }) - }) - stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(assert.AnError) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - - t.Run("new with error checking dogu health", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - updatedSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, - } - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(updatedSpec, nil) - validationMock.EXPECT().ValidateBlueprintSpecStatically(testCtx, "testBlueprint1").Return(nil) - effectiveBlueprintMock.EXPECT().CalculateEffectiveBlueprint(testCtx, "testBlueprint1").Return(nil) - validationMock.EXPECT().ValidateBlueprintSpecDynamically(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionValid, - Status: metav1.ConditionTrue, - }) - }) - stateDiffMock.EXPECT().DetermineStateDiff(testCtx, "testBlueprint1").Return(nil). - Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - meta.SetStatusCondition(blueprint.Conditions, metav1.Condition{ - Type: domain.ConditionExecutable, - Status: metav1.ConditionTrue, - }) - }) - healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, updatedSpec).Return(ecosystem.HealthResult{}, assert.AnError) - - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - - t.Run("handle invalid blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - - t.Run("handle error apply registry config", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - ecosystemConfigUseCaseMock.EXPECT().ApplyConfig(testCtx, blueprintSpec.Id).Return(assert.AnError) - // when - actualErr := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.ErrorIs(t, actualErr, assert.AnError) - }) - - t.Run("handle error after blueprint was applied", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: testBlueprintId, - Status: domain.StatusPhaseBlueprintApplied, - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - healthUseCase.EXPECT().CheckEcosystemHealth(mock.Anything, blueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) - - // when - err := useCase.HandleUntilApplied(testCtx, testBlueprintId) - // then - require.ErrorIs(t, err, assert.AnError) - }) - - t.Run("handle ecosystem healthy afterwards", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - blueprintSpec := &domain.BlueprintSpec{ - Id: "testBlueprint1", - } - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(blueprintSpec, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, "testBlueprint1").Return(nil).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - blueprint.Status = domain.StatusPhaseCompleted - }) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - - t.Run("handle completed blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseCompleted, - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - - t.Run("handle blueprint application failed", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - repoMock.EXPECT().GetById(testCtx, blueprintId).Return(&domain.BlueprintSpec{ - Id: blueprintId, - Status: domain.StatusPhaseBlueprintApplicationFailed, - }, nil) - applyMock.EXPECT().PostProcessBlueprintApplication(testCtx, blueprintId).Return(nil) - // when - err := useCase.HandleUntilApplied(testCtx, blueprintId) - // then - require.NoError(t, err) - }) - - t.Run("handle failed blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseFailed, - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.NoError(t, err) - }) - - t.Run("handle unknown status in blueprint", func(t *testing.T) { - // given - repoMock := newMockBlueprintSpecRepository(t) - validationMock := newMockBlueprintSpecValidationUseCase(t) - effectiveBlueprintMock := newMockEffectiveBlueprintUseCase(t) - stateDiffMock := newMockStateDiffUseCase(t) - applyMock := newMockApplyBlueprintSpecUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCase := newMockSelfUpgradeUseCase(t) - componentUseCase := newMockApplyComponentsUseCase(t) - doguUseCase := newMockApplyDogusUseCase(t) - healthUseCase := newMockEcosystemHealthUseCase(t) - useCase := NewBlueprintSpecChangeUseCase(repoMock, validationMock, effectiveBlueprintMock, stateDiffMock, - applyMock, ecosystemConfigUseCaseMock, selfUpgradeUseCase, componentUseCase, doguUseCase, healthUseCase) - repoMock.EXPECT().GetById(testCtx, "testBlueprint1").Return(&domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: "unknown", - }, nil) - // when - err := useCase.HandleUntilApplied(testCtx, "testBlueprint1") - // then - require.Error(t, err) - require.ErrorContains(t, err, "could not handle unknown status of blueprint") - }) } diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index 385cca49..8658a7a8 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -23,8 +23,7 @@ var redmineQualifiedDoguName = cescommons.QualifiedName{ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { //given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, + Id: "testBlueprint1", } repoMock := newMockBlueprintSpecRepository(t) @@ -48,7 +47,6 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing //given blueprint := &domain.BlueprintSpec{ //missing ID - Status: domain.StatusPhaseNew, } repoMock := newMockBlueprintSpecRepository(t) @@ -77,8 +75,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_repoError(t *testi t.Run("error while saving blueprint spec", func(t *testing.T) { //given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - Status: domain.StatusPhaseNew, + Id: "testBlueprint1", } repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() diff --git a/pkg/application/completeBlueprintUseCase.go b/pkg/application/completeBlueprintUseCase.go new file mode 100644 index 00000000..c8a0c746 --- /dev/null +++ b/pkg/application/completeBlueprintUseCase.go @@ -0,0 +1,35 @@ +package application + +import ( + "context" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" +) + +// CompleteBlueprintUseCase contains all use cases which are needed for or around applying +// the new ecosystem state after the determining the state diff. +type CompleteBlueprintUseCase struct { + repo blueprintSpecRepository +} + +func NewCompleteBlueprintUseCase( + repo blueprintSpecRepository, +) *CompleteBlueprintUseCase { + return &CompleteBlueprintUseCase{ + repo: repo, + } +} + +// CompleteBlueprint handles the completion of the blueprint after all other steps were successful. +// returns a domainservice.InternalError on any error. +func (useCase *CompleteBlueprintUseCase) CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + changed := blueprint.Complete() + if changed { + err := useCase.repo.Update(ctx, blueprint) + if err != nil { + return fmt.Errorf("cannot update blueprint to complete it: %w", err) + } + } + return nil +} diff --git a/pkg/application/completeBlueprintUseCase_test.go b/pkg/application/completeBlueprintUseCase_test.go new file mode 100644 index 00000000..f98bbebe --- /dev/null +++ b/pkg/application/completeBlueprintUseCase_test.go @@ -0,0 +1,64 @@ +package application + +import ( + "testing" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/meta" +) + +func TestNewApplyBlueprintSpecUseCase(t *testing.T) { + repoMock := newMockBlueprintSpecRepository(t) + + sut := NewCompleteBlueprintUseCase(repoMock) + + assert.Equal(t, repoMock, sut.repo) +} + +func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { + t.Run("ok", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + useCase := NewCompleteBlueprintUseCase(repoMock) + + err := useCase.CompleteBlueprint(testCtx, blueprint) + + require.NoError(t, err) + + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionCompleted)) + }) + t.Run("no change if already completed", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() + useCase := NewCompleteBlueprintUseCase(repoMock) + + err := useCase.CompleteBlueprint(testCtx, blueprint) + require.NoError(t, err) + err = useCase.CompleteBlueprint(testCtx, blueprint) + require.NoError(t, err) + assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionCompleted)) + }) + t.Run("repo error while saving", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: &[]domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) + useCase := NewCompleteBlueprintUseCase(repoMock) + + err := useCase.CompleteBlueprint(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + }) +} diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index db6cef8b..9a2f63f0 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -42,8 +42,8 @@ type componentInstallationUseCase interface { applyComponentState(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error } -type applyBlueprintSpecUseCase interface { - PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error +type completeBlueprintUseCase interface { + CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error } type ecosystemHealthUseCase interface { diff --git a/pkg/application/mock_applyBlueprintSpecUseCase_test.go b/pkg/application/mock_applyBlueprintSpecUseCase_test.go deleted file mode 100644 index 4823af4f..00000000 --- a/pkg/application/mock_applyBlueprintSpecUseCase_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - mock "github.com/stretchr/testify/mock" -) - -// mockApplyBlueprintSpecUseCase is an autogenerated mock type for the applyBlueprintSpecUseCase type -type mockApplyBlueprintSpecUseCase struct { - mock.Mock -} - -type mockApplyBlueprintSpecUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockApplyBlueprintSpecUseCase) EXPECT() *mockApplyBlueprintSpecUseCase_Expecter { - return &mockApplyBlueprintSpecUseCase_Expecter{mock: &_m.Mock} -} - -// PostProcessBlueprintApplication provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyBlueprintSpecUseCase) PostProcessBlueprintApplication(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for PostProcessBlueprintApplication") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PostProcessBlueprintApplication' -type mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call struct { - *mock.Call -} - -// PostProcessBlueprintApplication is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyBlueprintSpecUseCase_Expecter) PostProcessBlueprintApplication(ctx interface{}, blueprint interface{}) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { - return &mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call{Call: _e.mock.On("PostProcessBlueprintApplication", ctx, blueprint)} -} - -func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) Return(_a0 error) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyBlueprintSpecUseCase_PostProcessBlueprintApplication_Call { - _c.Call.Return(run) - return _c -} - -// newMockApplyBlueprintSpecUseCase creates a new instance of mockApplyBlueprintSpecUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockApplyBlueprintSpecUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockApplyBlueprintSpecUseCase { - mock := &mockApplyBlueprintSpecUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_completeBlueprintUseCase_test.go b/pkg/application/mock_completeBlueprintUseCase_test.go new file mode 100644 index 00000000..f52c4837 --- /dev/null +++ b/pkg/application/mock_completeBlueprintUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockCompleteBlueprintUseCase is an autogenerated mock type for the completeBlueprintUseCase type +type mockCompleteBlueprintUseCase struct { + mock.Mock +} + +type mockCompleteBlueprintUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockCompleteBlueprintUseCase) EXPECT() *mockCompleteBlueprintUseCase_Expecter { + return &mockCompleteBlueprintUseCase_Expecter{mock: &_m.Mock} +} + +// CompleteBlueprint provides a mock function with given fields: ctx, blueprint +func (_m *mockCompleteBlueprintUseCase) CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for CompleteBlueprint") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockCompleteBlueprintUseCase_CompleteBlueprint_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CompleteBlueprint' +type mockCompleteBlueprintUseCase_CompleteBlueprint_Call struct { + *mock.Call +} + +// CompleteBlueprint is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockCompleteBlueprintUseCase_Expecter) CompleteBlueprint(ctx interface{}, blueprint interface{}) *mockCompleteBlueprintUseCase_CompleteBlueprint_Call { + return &mockCompleteBlueprintUseCase_CompleteBlueprint_Call{Call: _e.mock.On("CompleteBlueprint", ctx, blueprint)} +} + +func (_c *mockCompleteBlueprintUseCase_CompleteBlueprint_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockCompleteBlueprintUseCase_CompleteBlueprint_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockCompleteBlueprintUseCase_CompleteBlueprint_Call) Return(_a0 error) *mockCompleteBlueprintUseCase_CompleteBlueprint_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockCompleteBlueprintUseCase_CompleteBlueprint_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockCompleteBlueprintUseCase_CompleteBlueprint_Call { + _c.Call.Return(run) + return _c +} + +// newMockCompleteBlueprintUseCase creates a new instance of mockCompleteBlueprintUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockCompleteBlueprintUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockCompleteBlueprintUseCase { + mock := &mockCompleteBlueprintUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index f91e430a..0b28d322 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -74,13 +74,13 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri return fmt.Errorf("could not determine state diff: %w", err) } - logger.Info("determine state diff to the cloudogu ecosystem", "blueprintStatus", blueprint.Status) + logger.Info("determine state diff to the cloudogu ecosystem") stateDiffError := blueprint.DetermineStateDiff(ecosystemState, referencedSensitiveConfig) var invalidError *domain.InvalidBlueprintError if errors.As(stateDiffError, &invalidError) { // do not return here as with this error the blueprint status and events should be persisted as normal. } else if stateDiffError != nil { - return fmt.Errorf("failed to determine state diff for blueprint %q: %w", blueprint.Id, stateDiffError) + return fmt.Errorf("failed to determine state diff: %w", stateDiffError) } err = useCase.blueprintSpecRepo.Update(ctx, blueprint) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 24f66745..feaa66e4 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -278,7 +278,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // then require.Error(t, err) - assert.ErrorContains(t, err, "failed to determine state diff for blueprint \"testBlueprint1\"") + assert.ErrorContains(t, err, "failed to determine state diff") }) t.Run("should fail to update blueprint", func(t *testing.T) { // given diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index 40699e14..1477cc79 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -53,7 +53,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name if err != nil { return nil, fmt.Errorf("failed to create components interface: %w", err) } - blueprintSpecRepository := v2.NewBlueprintSpecRepository( + blueprintRepo := v2.NewBlueprintSpecRepository( ecosystemClientSet.EcosystemV1Alpha1().Blueprints(namespace), eventRecorder, ) @@ -71,26 +71,26 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name k8sGlobalConfigRepo := repository.NewGlobalConfigRepository(ecosystemClientSet.CoreV1().ConfigMaps(namespace)) globalConfigRepoAdapter := adapterconfigk8s.NewGlobalConfigRepository(*k8sGlobalConfigRepo) - doguInstallationRepo := dogucr.NewDoguInstallationRepo(dogusInterface.Dogus(namespace)) - componentInstallationRepo := componentcr.NewComponentInstallationRepo(componentsInterface.Components(namespace)) + doguRepo := dogucr.NewDoguInstallationRepo(dogusInterface.Dogus(namespace)) + componentRepo := componentcr.NewComponentInstallationRepo(componentsInterface.Components(namespace)) healthConfigRepo := adapterhealthconfig.NewHealthConfigProvider(ecosystemClientSet.CoreV1().ConfigMaps(namespace)) validateDependenciesUseCase := domainservice.NewValidateDependenciesDomainUseCase(remoteDoguRegistry) validateMountsUseCase := domainservice.NewValidateAdditionalMountsDomainUseCase(remoteDoguRegistry) - blueprintValidationUseCase := application.NewBlueprintSpecValidationUseCase(blueprintSpecRepository, validateDependenciesUseCase, validateMountsUseCase) - effectiveBlueprintUseCase := application.NewEffectiveBlueprintUseCase(blueprintSpecRepository) - stateDiffUseCase := application.NewStateDiffUseCase(blueprintSpecRepository, doguInstallationRepo, componentInstallationRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) - doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintSpecRepository, doguInstallationRepo, healthConfigRepo) - componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintSpecRepository, componentInstallationRepo, healthConfigRepo) - ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintSpecRepository) - applyBlueprintSpecUseCase := application.NewApplyBlueprintSpecUseCase(blueprintSpecRepository, doguInstallationUseCase, ecosystemHealthUseCase) - applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintSpecRepository, componentInstallationUseCase) - applyDogusUseCase := application.NewApplyDogusUseCase(blueprintSpecRepository, doguInstallationUseCase) - ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintSpecRepository, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) - selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintSpecRepository, componentInstallationRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) + blueprintValidationUseCase := application.NewBlueprintSpecValidationUseCase(blueprintRepo, validateDependenciesUseCase, validateMountsUseCase) + effectiveBlueprintUseCase := application.NewEffectiveBlueprintUseCase(blueprintRepo) + stateDiffUseCase := application.NewStateDiffUseCase(blueprintRepo, doguRepo, componentRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) + doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, healthConfigRepo) + componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintRepo, componentRepo, healthConfigRepo) + ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintRepo) + applyBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) + applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintRepo, componentInstallationUseCase) + applyDogusUseCase := application.NewApplyDogusUseCase(blueprintRepo, doguInstallationUseCase) + ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) + selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( - blueprintSpecRepository, blueprintValidationUseCase, + blueprintRepo, blueprintValidationUseCase, effectiveBlueprintUseCase, stateDiffUseCase, applyBlueprintSpecUseCase, ConfigUseCase, selfUpgradeUseCase, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index c1c3c07f..4bbae701 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -22,7 +22,6 @@ type BlueprintSpec struct { EffectiveBlueprint EffectiveBlueprint StateDiff StateDiff Config BlueprintConfiguration - Status StatusPhase Conditions *[]Condition // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. @@ -41,22 +40,13 @@ const ( ConditionConfigApplied = "ConfigApplied" ConditionComponentsApplied = "ComponentsApplied" ConditionDogusApplied = "DogusApplied" - ConditionBlueprintApplied = "BlueprintApplied" + ConditionCompleted = "Completed" ) -type StatusPhase string - -const ( - // StatusPhaseNew marks a newly created blueprint-CR. - StatusPhaseNew StatusPhase = "" - // StatusPhaseBlueprintApplicationFailed shows that the blueprint application failed. - StatusPhaseBlueprintApplicationFailed StatusPhase = "blueprintApplicationFailed" - // StatusPhaseBlueprintApplied indicates that the blueprint was applied but the ecosystem is not healthy yet. - StatusPhaseBlueprintApplied StatusPhase = "blueprintApplied" - // StatusPhaseFailed marks that an error occurred during processing of the blueprint. - StatusPhaseFailed StatusPhase = "failed" - // StatusPhaseCompleted marks the blueprint as successfully applied. - StatusPhaseCompleted StatusPhase = "completed" +var ( + notAllowedComponentActions = []Action{ActionDowngrade, ActionSwitchComponentNamespace} + // ActionSwitchDoguNamespace is an exception and should be handled with the blueprint config. + notAllowedDoguActions = []Action{ActionDowngrade, ActionSwitchDoguNamespace} ) type BlueprintConfiguration struct { @@ -465,39 +455,6 @@ func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { return conditionChanged } -// MarkBlueprintApplicationFailed sets the blueprint state to application failed, which indicates that the blueprint could not be applied completely. -// In reaction to this, further post-processing will happen. -func (spec *BlueprintSpec) MarkBlueprintApplicationFailed(err error) { - spec.Status = StatusPhaseBlueprintApplicationFailed - spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) -} - -// MarkBlueprintApplied sets the blueprint state to blueprint applied, which indicates that the blueprint was applied successful and further steps can happen then. -func (spec *BlueprintSpec) MarkBlueprintApplied() { - spec.Status = StatusPhaseBlueprintApplied - spec.Events = append(spec.Events, BlueprintAppliedEvent{}) -} - -// CompletePostProcessing is used to mark the blueprint as completed or failed , depending on the blueprint application result. -func (spec *BlueprintSpec) CompletePostProcessing() { - // this function will not be called, if the ecosystem is not healthy - switch spec.Status { - case StatusPhaseBlueprintApplicationFailed: - spec.Status = StatusPhaseFailed - spec.Events = append(spec.Events, ExecutionFailedEvent{err: errors.New("could not apply blueprint")}) - default: - //if healthy - spec.Status = StatusPhaseCompleted - spec.Events = append(spec.Events, CompletedEvent{}) - } - -} - -var notAllowedComponentActions = []Action{ActionDowngrade, ActionSwitchComponentNamespace} - -// ActionSwitchDoguNamespace is an exception and should be handled with the blueprint config. -var notAllowedDoguActions = []Action{ActionDowngrade, ActionSwitchDoguNamespace} - func (spec *BlueprintSpec) validateStateDiff() error { var invalidBlueprintErrors []error @@ -574,3 +531,17 @@ func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) } } + +// Complete is used to mark the blueprint as completed and to inform the user. +// Returns true if anything changed, false otherwise. +func (spec *BlueprintSpec) Complete() bool { + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionCompleted, + Status: metav1.ConditionTrue, + Reason: "Completed", + }) + if conditionChanged { + spec.Events = append(spec.Events, CompletedEvent{}) + } + return conditionChanged +} diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index ac42e478..2f8ffa4c 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -1,8 +1,6 @@ package domain import ( - "errors" - "fmt" "testing" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -38,12 +36,10 @@ var premiumNexus = cescommons.QualifiedName{ func Test_BlueprintSpec_Validate_allOk(t *testing.T) { spec := BlueprintSpec{Id: "29.11.2023"} - require.Equal(t, StatusPhaseNew, spec.Status, "Status new should be the default") err := spec.ValidateStatically() require.Nil(t, err) - assert.Equal(t, StatusPhaseNew, spec.Status) require.Equal(t, 0, len(spec.Events)) } @@ -474,56 +470,39 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }) } -func TestBlueprintSpec_MarkBlueprintApplicationFailed(t *testing.T) { - // given - spec := &BlueprintSpec{} - err := fmt.Errorf("test-error") - // when - spec.MarkBlueprintApplicationFailed(err) - // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseBlueprintApplicationFailed, - Events: []Event{ExecutionFailedEvent{err: err}}, - }) -} - -func TestBlueprintSpec_MarkBlueprintApplied(t *testing.T) { - // given - spec := &BlueprintSpec{} - // when - spec.MarkBlueprintApplied() - // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseBlueprintApplied, - Events: []Event{BlueprintAppliedEvent{}}, - }) -} - func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { - t.Run("status change on success EcosystemHealthyAfterwards -> Completed", func(t *testing.T) { + t.Run("ok with event", func(t *testing.T) { // given - spec := &BlueprintSpec{} + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, + } // when - spec.CompletePostProcessing() + changed := blueprint.Complete() // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseCompleted, - Events: []Event{CompletedEvent{}}, - }) + assert.True(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionCompleted) + assert.Equal(t, metav1.ConditionTrue, condition.Status) + assert.Equal(t, "Completed", condition.Reason) + assert.Equal(t, "", condition.Message) + assert.Equal(t, []Event{CompletedEvent{}}, blueprint.Events) }) - - t.Run("status change on failure ApplicationFailed -> Failed", func(t *testing.T) { + t.Run("no change if executed twice", func(t *testing.T) { // given - spec := &BlueprintSpec{ - Status: StatusPhaseBlueprintApplicationFailed, + blueprint := &BlueprintSpec{ + Conditions: &[]Condition{}, } // when - spec.CompletePostProcessing() + changed := blueprint.Complete() + assert.True(t, changed) + blueprint.Events = nil + changed = blueprint.Complete() // then - assert.Equal(t, spec, &BlueprintSpec{ - Status: StatusPhaseFailed, - Events: []Event{ExecutionFailedEvent{errors.New("could not apply blueprint")}}, - }) + assert.False(t, changed) + condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionCompleted) + assert.Equal(t, metav1.ConditionTrue, condition.Status) + assert.Equal(t, "Completed", condition.Reason) + assert.Equal(t, "", condition.Message) + assert.Equal(t, 0, len(blueprint.Events)) }) } From a30adaa7dfce835df852fea161ad5bdf8472c827 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 11:55:30 +0200 Subject: [PATCH 038/119] #121 load and persist conditions via repository --- .../v2/blueprintSpecCRRepository.go | 2 ++ .../v2/blueprintSpecCRRepository_test.go | 30 +++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index f1ff659f..15d038ff 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -69,6 +69,7 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) Id: blueprintId, EffectiveBlueprint: effectiveBlueprint, StateDiff: stateDiff, + Conditions: &blueprintCR.Status.Conditions, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: blueprintCR.Spec.IgnoreDoguHealth, IgnoreComponentHealth: blueprintCR.Spec.IgnoreComponentHealth, @@ -120,6 +121,7 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.Blueprin Status: v2.BlueprintStatus{ EffectiveBlueprint: effectiveBlueprint, StateDiff: serializerv2.ConvertToStateDiffDTO(spec.StateDiff), + Conditions: *spec.Conditions, }, } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index 5eb12dcb..cf583b22 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -19,7 +19,17 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) -var ctx = context.Background() +var ( + ctx = context.Background() + testCondition = metav1.Condition{ + Type: domain.ConditionCompleted, + Status: metav1.ConditionUnknown, + ObservedGeneration: 1, + LastTransitionTime: metav1.Time{}, + Reason: "Completed", + Message: "test", + } +) func Test_blueprintSpecRepo_GetById(t *testing.T) { blueprintId := "MyBlueprint" @@ -40,7 +50,9 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { IgnoreDoguHealth: true, DryRun: true, }, - Status: bpv2.BlueprintStatus{}, + Status: bpv2.BlueprintStatus{ + Conditions: []metav1.Condition{testCondition}, + }, } restClientMock.EXPECT().Get(ctx, blueprintId, metav1.GetOptions{}).Return(cr, nil) @@ -60,6 +72,7 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { }, StateDiff: domain.StateDiff{}, PersistenceContext: persistenceContext, + Conditions: &[]domain.Condition{testCondition}, }, spec) }) @@ -150,14 +163,14 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { restClientMock := newMockBlueprintInterface(t) eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) - //FIXME: I removed the status field in this test. Do not forget to add a condition to this test instead expectedStatus := bpv2.BlueprintStatus{ EffectiveBlueprint: bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, Config: bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + Conditions: []metav1.Condition{testCondition}, } restClientMock.EXPECT(). UpdateStatus(ctx, mock.Anything, metav1.UpdateOptions{}). @@ -173,6 +186,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, + Conditions: &[]domain.Condition{testCondition}, }) // then @@ -227,7 +241,8 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Components: []bpv2.Component{}, Config: bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + Conditions: []metav1.Condition{}, } expectedError := k8sErrors.NewConflict( schema.GroupResource{Group: "blueprints", Resource: blueprintId}, @@ -248,6 +263,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, + Conditions: &[]domain.Condition{}, }) // then @@ -268,7 +284,8 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Components: []bpv2.Component{}, Config: bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + Conditions: []metav1.Condition{}, } expectedError := fmt.Errorf("test-error") restClientMock.EXPECT(). @@ -285,6 +302,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, + Conditions: &[]domain.Condition{}, }) // then From 10940cf350b314092320b6220c05a708e34debf1 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 12:27:40 +0200 Subject: [PATCH 039/119] #121 fix event test --- .../blueprintcr/v2/blueprintSpecCRRepository_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index cf583b22..bd21f784 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -341,7 +341,12 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { // when persistenceContext := make(map[string]interface{}) persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} - spec := &domain.BlueprintSpec{Id: blueprintId, Events: events, PersistenceContext: persistenceContext} + spec := &domain.BlueprintSpec{ + Id: blueprintId, + Events: events, + PersistenceContext: persistenceContext, + Conditions: &[]domain.Condition{}, + } err := repo.Update(ctx, spec) // then From 85c962093d64e8ab038c116c07ed423f13d82394 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 13:19:33 +0200 Subject: [PATCH 040/119] #121 always write a reason in conditions --- go.mod | 2 +- go.sum | 2 ++ .../blueprintcr/v2/blueprintSpecCRRepository.go | 7 ++++++- pkg/application/blueprintSpecChangeUseCase.go | 2 +- pkg/application/effectiveBlueprintUseCase.go | 4 ---- pkg/domain/blueprintSpec.go | 16 +++++++++++----- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index d1cd738a..d1ce455a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073 + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index 921a657b..5c0c02d7 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSA github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073 h1:4E+BOxsDuidq2uwWyQI2VsPPvD34rUXaKDSm2O50ffU= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a h1:aj5qH5Ejn+TstaHjt+8Y6vigIY5uVl59IWPVzaFlczU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 15d038ff..3f6a3ebb 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -65,11 +65,16 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) return nil, err } + conditions := &blueprintCR.Status.Conditions + if conditions == nil { + conditions = &[]domain.Condition{} + } + blueprintSpec := &domain.BlueprintSpec{ Id: blueprintId, EffectiveBlueprint: effectiveBlueprint, StateDiff: stateDiff, - Conditions: &blueprintCR.Status.Conditions, + Conditions: conditions, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: blueprintCR.Spec.IgnoreDoguHealth, IgnoreComponentHealth: blueprintCR.Spec.IgnoreComponentHealth, diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 434b00b4..ba74b125 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -58,11 +58,11 @@ func NewBlueprintSpecChangeUseCase( // a domain.InvalidBlueprintError if the blueprint is invalid. func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.Context, blueprintId string) error { logger := log.FromContext(givenCtx). - WithName("BlueprintSpecChangeUseCase.HandleUntilApplied"). WithValues("blueprintId", blueprintId) // set the logger in the context to make use of structured logging // we will give this ctx in every use case, therefore all of them will include the values given here ctx := log.IntoContext(givenCtx, logger) + logger = logger.WithName("BlueprintSpecChangeUseCase.HandleUntilApplied") logger.Info("getting changed blueprint") // log with id blueprint, err := useCase.repo.GetById(ctx, blueprintId) diff --git a/pkg/application/effectiveBlueprintUseCase.go b/pkg/application/effectiveBlueprintUseCase.go index cb302d1f..40ee9d48 100644 --- a/pkg/application/effectiveBlueprintUseCase.go +++ b/pkg/application/effectiveBlueprintUseCase.go @@ -6,8 +6,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - - "sigs.k8s.io/controller-runtime/pkg/log" ) type EffectiveBlueprintUseCase struct { @@ -23,8 +21,6 @@ func NewEffectiveBlueprintUseCase(blueprintSpecRepo domainservice.BlueprintSpecR // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write. func (useCase *EffectiveBlueprintUseCase) CalculateEffectiveBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("EffectiveBlueprintUseCase.CalculateEffectiveBlueprint") - logger.Info("calculate effective blueprint", "blueprintStatus") calcError := blueprint.CalculateEffectiveBlueprint() err := useCase.blueprintSpecRepo.Update(ctx, blueprint) if err != nil { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 4bbae701..2bcc131e 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -83,7 +83,7 @@ func (spec *BlueprintSpec) ValidateStatically() error { meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, - Reason: "blueprint invalid", + Reason: "Invalid", Message: err.Error(), }) } @@ -129,7 +129,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, - Reason: "inconsistent blueprint", + Reason: "Inconsistent", Message: err.Error(), }) if conditionChanged { @@ -139,6 +139,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionTrue, + Reason: "Valid", }) } } @@ -161,7 +162,7 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, - Reason: "inconsistent blueprint", + Reason: "Inconsistent", Message: validationError.Error(), }) if conditionChanged { @@ -279,7 +280,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionExecutable, Status: metav1.ConditionFalse, - Reason: "forbidden operations needed", + Reason: "ForbiddenOperations", Message: invalidBlueprintError.Error(), }) if conditionChanged { @@ -291,6 +292,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionExecutable, Status: metav1.ConditionTrue, + Reason: "Executable", }) //TODO: we cannot just deduplicate the events here by detecting a condition change, // because the blueprint could be executable even after a change of the blueprint. @@ -380,7 +382,7 @@ func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionSelfUpgradeCompleted, Status: metav1.ConditionFalse, - Reason: "await self upgrade", + Reason: "AwaitSelfUpgrade", }) if conditionChanged { spec.Events = append(spec.Events, AwaitSelfUpgradeEvent{}) @@ -391,6 +393,7 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionSelfUpgradeCompleted, Status: metav1.ConditionTrue, + Reason: "Completed", }) if conditionChanged { spec.Events = append(spec.Events, SelfUpgradeCompletedEvent{}) @@ -504,6 +507,7 @@ func (spec *BlueprintSpec) StartApplyEcosystemConfig() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionFalse, + Reason: "Applying", Message: event.Message(), }) if conditionChanged { @@ -515,6 +519,7 @@ func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionFalse, + Reason: "ApplyingFailed", Message: err.Error(), }) if conditionChanged { @@ -526,6 +531,7 @@ func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionTrue, + Reason: "Applied", }) if conditionChanged { spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) From ac920733b8e3d087070da95c7efaa9794d8287f0 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 20 Aug 2025 15:23:48 +0200 Subject: [PATCH 041/119] #121 remove unused blocking health checks; reduce log output The blocking health checks were not in use anymore. As the operator will now work continuously, we should not log as much per reconciliation. Often it is enough to log the errors and reasons, why the blueprint cannot finish yet (often health). --- .../v2/blueprintSpecCRRepository.go | 2 +- .../reconciler/blueprint_controller.go | 3 +- pkg/application/blueprintSpecChangeUseCase.go | 4 +- .../blueprintSpecValidationUseCase.go | 4 +- .../componentInstallationUseCase.go | 46 +------ .../componentInstallationUseCase_test.go | 128 ------------------ pkg/application/doguInstallationUseCase.go | 44 ------ .../doguInstallationUseCase_test.go | 106 --------------- pkg/application/ecosystemHealthUseCase.go | 3 + pkg/application/interfaces.go | 2 - .../mock_componentInstallationUseCase_test.go | 56 -------- .../mock_doguInstallationUseCase_test.go | 56 -------- pkg/application/stateDiffUseCase.go | 16 +-- .../validateAdditionalMountsDomainUseCase.go | 9 +- .../validateDependenciesDomainUseCase.go | 27 +++- 15 files changed, 44 insertions(+), 462 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 3f6a3ebb..6e6eb65f 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -130,7 +130,7 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.Blueprin }, } - logger.Info("update blueprint CR status") + logger.V(2).Info("update blueprint CR status") CRAfterUpdate, err := repo.blueprintClient.UpdateStatus(ctx, updatedBlueprint, metav1.UpdateOptions{}) if err != nil { diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index a63b10c6..2a7c796a 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -83,10 +83,11 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { errLogger.Info("Blueprint is invalid, therefore there will be no further evaluation.") return ctrl.Result{}, nil case errors.As(err, &healthError): + // really normal case errLogger.Info("Ecosystem is unhealthy. Retry later") return ctrl.Result{RequeueAfter: 10 * time.Second}, nil case errors.As(err, &awaitSelfUpgradeError): - errLogger.Info(err.Error()) + errLogger.Info("wait for self upgrade") return ctrl.Result{RequeueAfter: 5 * time.Second}, nil default: errLogger.Error(err, "An unknown error type occurred. Retry with default backoff") diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index ba74b125..ec933f4e 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -64,7 +64,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C ctx := log.IntoContext(givenCtx, logger) logger = logger.WithName("BlueprintSpecChangeUseCase.HandleUntilApplied") - logger.Info("getting changed blueprint") // log with id + logger.V(2).Info("getting changed blueprint") // log with id blueprint, err := useCase.repo.GetById(ctx, blueprintId) if err != nil { errMsg := "cannot load blueprint spec" @@ -72,7 +72,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return fmt.Errorf("%s: %w", errMsg, err) } - logger.Info("handle blueprint") + logger.V(1).Info("handle blueprint") err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) if err != nil { diff --git a/pkg/application/blueprintSpecValidationUseCase.go b/pkg/application/blueprintSpecValidationUseCase.go index 642be8c6..c86f7bc0 100644 --- a/pkg/application/blueprintSpecValidationUseCase.go +++ b/pkg/application/blueprintSpecValidationUseCase.go @@ -36,7 +36,7 @@ func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(c logger := log.FromContext(ctx). WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecStatically") - logger.Info("statically validate blueprint spec") + logger.V(1).Info("statically validate blueprint spec") invalidBlueprintError := blueprint.ValidateStatically() err := useCase.repo.Update(ctx, blueprint) @@ -56,7 +56,7 @@ func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecStatically(c func (useCase *BlueprintSpecValidationUseCase) ValidateBlueprintSpecDynamically(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx). WithName("BlueprintSpecValidationUseCase.ValidateBlueprintSpecDynamically") - logger.Info("dynamically validate blueprint spec") + logger.V(1).Info("dynamically validate blueprint spec") validationError := errors.Join( useCase.validateDependenciesUseCase.ValidateDependenciesForAllDogus(ctx, blueprint.EffectiveBlueprint), diff --git a/pkg/application/componentInstallationUseCase.go b/pkg/application/componentInstallationUseCase.go index d41fac5e..d41633b3 100644 --- a/pkg/application/componentInstallationUseCase.go +++ b/pkg/application/componentInstallationUseCase.go @@ -9,7 +9,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -42,7 +41,7 @@ func NewComponentInstallationUseCase( func (useCase *ComponentInstallationUseCase) CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) { logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.CheckComponentHealth") - logger.Info("check component health...") + logger.V(2).Info("check component health...") installedComponents, err := useCase.componentRepo.GetAll(ctx) if err != nil { return ecosystem.ComponentHealthResult{}, fmt.Errorf("cannot retrieve installed components: %w", err) @@ -56,49 +55,6 @@ func (useCase *ComponentInstallationUseCase) CheckComponentHealth(ctx context.Co return ecosystem.CalculateComponentHealthResult(installedComponents, requiredComponents), nil } -func (useCase *ComponentInstallationUseCase) WaitForHealthyComponents(ctx context.Context) (ecosystem.ComponentHealthResult, error) { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.WaitForHealthyComponents") - - waitConfig, err := useCase.healthConfigProvider.GetWaitConfig(ctx) - if err != nil { - return ecosystem.ComponentHealthResult{}, fmt.Errorf("failed to get health check interval: %w", err) - } - - logger.Info("start waiting for component health") - healthResult, err := util.RetryUntilSuccessOrCancellation( - ctx, - waitConfig.Interval, - useCase.checkComponentHealthStatesRetryable, - ) - var result ecosystem.ComponentHealthResult - if healthResult == nil { - result = ecosystem.ComponentHealthResult{} - } else { - result = *healthResult - } - - if err != nil { - err = fmt.Errorf("stop waiting for component health: %w", err) - logger.Error(err, "stop waiting for component health because of an error or time out") - } else { - logger.Info("finished waiting for component health") - } - - return result, err -} - -func (useCase *ComponentInstallationUseCase) checkComponentHealthStatesRetryable(ctx context.Context) (result *ecosystem.ComponentHealthResult, err error, shouldRetry bool) { - // use named return values to make their meaning clear - health, err := useCase.CheckComponentHealth(ctx) - if err != nil { - // no retry on error while loading components - return &ecosystem.ComponentHealthResult{}, err, false - } - result = &health - shouldRetry = !health.AllHealthy() - return -} - // ApplyComponentStates applies the expected component state from the Blueprint to the ecosystem. // Fail-fast here, so that the possible damage is as small as possible. func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { diff --git a/pkg/application/componentInstallationUseCase_test.go b/pkg/application/componentInstallationUseCase_test.go index e0576df1..462909e7 100644 --- a/pkg/application/componentInstallationUseCase_test.go +++ b/pkg/application/componentInstallationUseCase_test.go @@ -1,17 +1,14 @@ package application import ( - "context" "fmt" "testing" - "time" "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -471,128 +468,3 @@ func TestComponentInstallationUseCase_CheckComponentHealth(t *testing.T) { }) } } - -func TestComponentInstallationUseCase_WaitForHealthyComponents(t *testing.T) { - type fields struct { - componentRepoFn func(t *testing.T) componentInstallationRepository - healthConfigRepoFn func(t *testing.T) healthConfigProvider - } - tests := []struct { - name string - ctx context.Context - fields fields - want ecosystem.ComponentHealthResult - wantErr assert.ErrorAssertionFunc - }{ - { - name: "should fail to get health check interval", - ctx: testCtx, - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - repoMock := newMockComponentInstallationRepository(t) - return repoMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - providerMock := newMockHealthConfigProvider(t) - providerMock.EXPECT().GetWaitConfig(testCtx).Return(ecosystem.WaitConfig{}, assert.AnError) - return providerMock - }, - }, - want: ecosystem.ComponentHealthResult{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "failed to get health check interval", i) - }, - }, - { - name: "should fail to check component health", - ctx: testCtx, - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - repoMock := newMockComponentInstallationRepository(t) - repoMock.EXPECT().GetAll(mock.Anything).Return(nil, assert.AnError) - return repoMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - providerMock := newMockHealthConfigProvider(t) - providerMock.EXPECT().GetWaitConfig(testCtx).Return(ecosystem.WaitConfig{Interval: time.Second}, nil) - return providerMock - }, - }, - want: ecosystem.ComponentHealthResult{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "stop waiting for component health", i) - }, - }, - { - name: "should fail after context cancellation", - ctx: func() context.Context { - ctx, cancel := context.WithCancel(testCtx) - cancel() - return ctx - }(), - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - componentMock := newMockComponentInstallationRepository(t) - componentMock.EXPECT().GetAll(mock.Anything). - Return(map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, nil) - return componentMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - healthConfigMock := newMockHealthConfigProvider(t) - healthConfigMock.EXPECT().GetWaitConfig(mock.Anything).Return(ecosystem.WaitConfig{Interval: 1}, nil) - healthConfigMock.EXPECT().GetRequiredComponents(mock.Anything). - Return([]ecosystem.RequiredComponent{{Name: "k8s-dogu-operator"}}, nil) - return healthConfigMock - }, - }, - want: ecosystem.ComponentHealthResult{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "stop waiting for component health: context canceled", i) - }, - }, - { - name: "should be successful after retry", - ctx: testCtx, - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - componentMock := newMockComponentInstallationRepository(t) - unsuccessfulCall := componentMock.EXPECT().GetAll(mock.Anything). - Return(map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, nil).Once() - componentMock.EXPECT().GetAll(mock.Anything). - Return(map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - "k8s-dogu-operator": { - - Name: common.QualifiedComponentName{SimpleName: "k8s-dogu-operator", Namespace: testNamespace}, Health: ecosystem.AvailableHealthStatus, - }, - }, nil). - Once().NotBefore(unsuccessfulCall) - return componentMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - healthConfigMock := newMockHealthConfigProvider(t) - healthConfigMock.EXPECT().GetWaitConfig(testCtx).Return(ecosystem.WaitConfig{Interval: time.Second}, nil) - healthConfigMock.EXPECT().GetRequiredComponents(mock.Anything). - Return([]ecosystem.RequiredComponent{{Name: "k8s-dogu-operator"}}, nil) - return healthConfigMock - }, - }, - want: ecosystem.ComponentHealthResult{ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.AvailableHealthStatus: {"k8s-dogu-operator"}, - }}, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - useCase := &ComponentInstallationUseCase{ - componentRepo: tt.fields.componentRepoFn(t), - healthConfigProvider: tt.fields.healthConfigRepoFn(t), - } - got, err := useCase.WaitForHealthyComponents(tt.ctx) - tt.wantErr(t, err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index a96b7d9f..debc916d 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -8,7 +8,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "golang.org/x/exp/maps" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -42,49 +41,6 @@ func (useCase *DoguInstallationUseCase) CheckDoguHealth(ctx context.Context) (ec return ecosystem.CalculateDoguHealthResult(maps.Values(installedDogus)), nil } -func (useCase *DoguInstallationUseCase) WaitForHealthyDogus(ctx context.Context) (ecosystem.DoguHealthResult, error) { - logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.WaitForHealthyDogus") - - waitConfig, err := useCase.waitConfigProvider.GetWaitConfig(ctx) - if err != nil { - return ecosystem.DoguHealthResult{}, fmt.Errorf("failed to get health check interval: %w", err) - } - - logger.Info("start waiting for dogu health") - healthResult, err := util.RetryUntilSuccessOrCancellation( - ctx, - waitConfig.Interval, - useCase.checkDoguHealthStatesRetryable, - ) - var result ecosystem.DoguHealthResult - if healthResult == nil { - result = ecosystem.DoguHealthResult{} - } else { - result = *healthResult - } - - if err != nil { - err = fmt.Errorf("stop waiting for dogu health: %w", err) - logger.Error(err, "stop waiting for dogu health because of an error or time out") - } else { - logger.Info("finished waiting for dogu health") - } - - return result, err -} - -func (useCase *DoguInstallationUseCase) checkDoguHealthStatesRetryable(ctx context.Context) (result *ecosystem.DoguHealthResult, err error, shouldRetry bool) { - // use named return values to make their meaning clear - health, err := useCase.CheckDoguHealth(ctx) - if err != nil { - // no retry if error while loading dogus - return &ecosystem.DoguHealthResult{}, err, false - } - result = &health - shouldRetry = !health.AllHealthy() - return -} - // ApplyDoguStates applies the expected dogu state from the Blueprint to the ecosystem. // Fail-fast here, so that the possible damage is as small as possible. func (useCase *DoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 5eb83ffc..856ea564 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -1,10 +1,8 @@ package application import ( - "context" "fmt" "testing" - "time" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" @@ -676,107 +674,3 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { require.ErrorContains(t, err, "an error occurred while applying dogu state to the ecosystem") }) } - -func TestDoguInstallationUseCase_WaitForHealthyDogus(t *testing.T) { - t.Run("ok", func(t *testing.T) { - t.Parallel() - // given - doguRepoMock := newMockDoguInstallationRepository(t) - timedCtx, cancel := context.WithTimeout(testCtx, 10*time.Millisecond) - defer cancel() - doguRepoMock.EXPECT().GetAll(timedCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - - waitConfigMock := newMockHealthWaitConfigProvider(t) - waitConfigMock.EXPECT().GetWaitConfig(timedCtx).Return(ecosystem.WaitConfig{Interval: time.Millisecond}, nil) - - sut := DoguInstallationUseCase{ - blueprintSpecRepo: nil, - doguRepo: doguRepoMock, - waitConfigProvider: waitConfigMock, - } - - // when - result, err := sut.WaitForHealthyDogus(timedCtx) - - // then - require.NoError(t, err) - assert.True(t, result.AllHealthy()) - }) - - t.Run("fail to get health check interval", func(t *testing.T) { - t.Parallel() - // given - waitConfigMock := newMockHealthWaitConfigProvider(t) - waitConfigMock.EXPECT().GetWaitConfig(testCtx).Return(ecosystem.WaitConfig{}, assert.AnError) - - sut := DoguInstallationUseCase{ - blueprintSpecRepo: nil, - doguRepo: nil, - waitConfigProvider: waitConfigMock, - } - - // when - _, err := sut.WaitForHealthyDogus(testCtx) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "failed to get health check interval") - }) - - t.Run("timeout", func(t *testing.T) { - t.Parallel() - // given - doguRepoMock := newMockDoguInstallationRepository(t) - timedCtx, cancel := context.WithTimeout(testCtx, 0*time.Millisecond) - defer cancel() - // return unhealthy result - doguRepoMock.EXPECT().GetAll(timedCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "postgresql": {Health: ecosystem.DoguStatusInstalling}, - }, nil).Maybe() - - waitConfigMock := newMockHealthWaitConfigProvider(t) - waitConfigMock.EXPECT().GetWaitConfig(timedCtx).Return(ecosystem.WaitConfig{Interval: 5 * time.Millisecond}, nil) - - sut := DoguInstallationUseCase{ - blueprintSpecRepo: nil, - doguRepo: doguRepoMock, - waitConfigProvider: waitConfigMock, - } - - // when - result, err := sut.WaitForHealthyDogus(timedCtx) - - // then - assert.Error(t, err) - assert.ErrorIs(t, err, context.DeadlineExceeded) - assert.Equal(t, ecosystem.DoguHealthResult{}, result) - }) - - t.Run("cannot load dogus", func(t *testing.T) { - t.Parallel() - // given - doguRepoMock := newMockDoguInstallationRepository(t) - timedCtx, cancel := context.WithTimeout(testCtx, 10*time.Millisecond) - defer cancel() - doguRepoMock.EXPECT().GetAll(timedCtx).Return(nil, assert.AnError).Maybe() - - waitConfigMock := newMockHealthWaitConfigProvider(t) - waitConfigMock.EXPECT().GetWaitConfig(timedCtx).Return(ecosystem.WaitConfig{Interval: time.Millisecond}, nil) - - sut := DoguInstallationUseCase{ - blueprintSpecRepo: nil, - doguRepo: doguRepoMock, - waitConfigProvider: waitConfigMock, - } - - // when - result, err := sut.WaitForHealthyDogus(timedCtx) - - // then - assert.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.Equal(t, ecosystem.DoguHealthResult{}, result) - }) - -} diff --git a/pkg/application/ecosystemHealthUseCase.go b/pkg/application/ecosystemHealthUseCase.go index 7783fb36..9db804d8 100644 --- a/pkg/application/ecosystemHealthUseCase.go +++ b/pkg/application/ecosystemHealthUseCase.go @@ -7,6 +7,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "sigs.k8s.io/controller-runtime/pkg/log" ) type EcosystemHealthUseCase struct { @@ -66,6 +67,8 @@ func (useCase *EcosystemHealthUseCase) getEcosystemHealth( ignoreDoguHealth bool, ignoreComponentHealth bool, ) (ecosystem.HealthResult, error) { + logger := log.FromContext(ctx).WithName("EcosystemHealthUseCase.getEcosystemHealth") + logger.V(1).Info("check ecosystem health...") var doguHealth ecosystem.DoguHealthResult var doguHealthErr error if !ignoreDoguHealth { diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 9a2f63f0..2c4a925a 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -23,7 +23,6 @@ type stateDiffUseCase interface { type doguInstallationUseCase interface { CheckDoguHealth(ctx context.Context) (ecosystem.DoguHealthResult, error) - WaitForHealthyDogus(ctx context.Context) (ecosystem.DoguHealthResult, error) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -38,7 +37,6 @@ type applyComponentsUseCase interface { type componentInstallationUseCase interface { ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) - WaitForHealthyComponents(ctx context.Context) (ecosystem.ComponentHealthResult, error) applyComponentState(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error } diff --git a/pkg/application/mock_componentInstallationUseCase_test.go b/pkg/application/mock_componentInstallationUseCase_test.go index 41cd7c9d..8b7f2e4a 100644 --- a/pkg/application/mock_componentInstallationUseCase_test.go +++ b/pkg/application/mock_componentInstallationUseCase_test.go @@ -127,62 +127,6 @@ func (_c *mockComponentInstallationUseCase_CheckComponentHealth_Call) RunAndRetu return _c } -// WaitForHealthyComponents provides a mock function with given fields: ctx -func (_m *mockComponentInstallationUseCase) WaitForHealthyComponents(ctx context.Context) (ecosystem.ComponentHealthResult, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for WaitForHealthyComponents") - } - - var r0 ecosystem.ComponentHealthResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.ComponentHealthResult, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.ComponentHealthResult); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.ComponentHealthResult) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentInstallationUseCase_WaitForHealthyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WaitForHealthyComponents' -type mockComponentInstallationUseCase_WaitForHealthyComponents_Call struct { - *mock.Call -} - -// WaitForHealthyComponents is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockComponentInstallationUseCase_Expecter) WaitForHealthyComponents(ctx interface{}) *mockComponentInstallationUseCase_WaitForHealthyComponents_Call { - return &mockComponentInstallationUseCase_WaitForHealthyComponents_Call{Call: _e.mock.On("WaitForHealthyComponents", ctx)} -} - -func (_c *mockComponentInstallationUseCase_WaitForHealthyComponents_Call) Run(run func(ctx context.Context)) *mockComponentInstallationUseCase_WaitForHealthyComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockComponentInstallationUseCase_WaitForHealthyComponents_Call) Return(_a0 ecosystem.ComponentHealthResult, _a1 error) *mockComponentInstallationUseCase_WaitForHealthyComponents_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentInstallationUseCase_WaitForHealthyComponents_Call) RunAndReturn(run func(context.Context) (ecosystem.ComponentHealthResult, error)) *mockComponentInstallationUseCase_WaitForHealthyComponents_Call { - _c.Call.Return(run) - return _c -} - // applyComponentState provides a mock function with given fields: _a0, _a1, _a2 func (_m *mockComponentInstallationUseCase) applyComponentState(_a0 context.Context, _a1 domain.ComponentDiff, _a2 *ecosystem.ComponentInstallation) error { ret := _m.Called(_a0, _a1, _a2) diff --git a/pkg/application/mock_doguInstallationUseCase_test.go b/pkg/application/mock_doguInstallationUseCase_test.go index 34583e49..94600fc9 100644 --- a/pkg/application/mock_doguInstallationUseCase_test.go +++ b/pkg/application/mock_doguInstallationUseCase_test.go @@ -127,62 +127,6 @@ func (_c *mockDoguInstallationUseCase_CheckDoguHealth_Call) RunAndReturn(run fun return _c } -// WaitForHealthyDogus provides a mock function with given fields: ctx -func (_m *mockDoguInstallationUseCase) WaitForHealthyDogus(ctx context.Context) (ecosystem.DoguHealthResult, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for WaitForHealthyDogus") - } - - var r0 ecosystem.DoguHealthResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.DoguHealthResult, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.DoguHealthResult); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.DoguHealthResult) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockDoguInstallationUseCase_WaitForHealthyDogus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WaitForHealthyDogus' -type mockDoguInstallationUseCase_WaitForHealthyDogus_Call struct { - *mock.Call -} - -// WaitForHealthyDogus is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockDoguInstallationUseCase_Expecter) WaitForHealthyDogus(ctx interface{}) *mockDoguInstallationUseCase_WaitForHealthyDogus_Call { - return &mockDoguInstallationUseCase_WaitForHealthyDogus_Call{Call: _e.mock.On("WaitForHealthyDogus", ctx)} -} - -func (_c *mockDoguInstallationUseCase_WaitForHealthyDogus_Call) Run(run func(ctx context.Context)) *mockDoguInstallationUseCase_WaitForHealthyDogus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockDoguInstallationUseCase_WaitForHealthyDogus_Call) Return(_a0 ecosystem.DoguHealthResult, _a1 error) *mockDoguInstallationUseCase_WaitForHealthyDogus_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockDoguInstallationUseCase_WaitForHealthyDogus_Call) RunAndReturn(run func(context.Context) (ecosystem.DoguHealthResult, error)) *mockDoguInstallationUseCase_WaitForHealthyDogus_Call { - _c.Call.Return(run) - return _c -} - // newMockDoguInstallationUseCase creates a new instance of mockDoguInstallationUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func newMockDoguInstallationUseCase(t interface { diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index 0b28d322..d927d112 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -52,7 +52,7 @@ func NewStateDiffUseCase( func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("StateDiffUseCase.DetermineStateDiff") - logger.Info("load referenced sensitive config") + logger.V(2).Info("load referenced sensitive config") // load referenced config before collecting ecosystem state // if an error happens here, we save a lot of heavy work referencedSensitiveConfig, err := useCase.sensitiveConfigRefReader.GetValues( @@ -68,13 +68,13 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri return err } - logger.Info("collect ecosystem state for state diff") + logger.V(2).Info("collect ecosystem state for state diff") ecosystemState, err := useCase.collectEcosystemState(ctx, blueprint.EffectiveBlueprint) if err != nil { return fmt.Errorf("could not determine state diff: %w", err) } - logger.Info("determine state diff to the cloudogu ecosystem") + logger.V(2).Info("determine state diff to the cloudogu ecosystem") stateDiffError := blueprint.DetermineStateDiff(ecosystemState, referencedSensitiveConfig) var invalidError *domain.InvalidBlueprintError if errors.As(stateDiffError, &invalidError) { @@ -98,18 +98,18 @@ func (useCase *StateDiffUseCase) collectEcosystemState(ctx context.Context, effe // TODO: collect ecosystem state in parallel (like for ecosystem health) if we have time // load current dogus and components - logger.Info("collect installed dogus") + logger.V(2).Info("collect installed dogus") installedDogus, doguErr := useCase.doguInstallationRepo.GetAll(ctx) - logger.Info("collect installed components") + logger.V(2).Info("collect installed components") installedComponents, componentErr := useCase.componentInstallationRepo.GetAll(ctx) // load current config - logger.Info("collect needed global config") + logger.V(2).Info("collect needed global config") globalConfig, globalConfigErr := useCase.globalConfigRepo.Get(ctx) - logger.Info("collect needed dogu config") + logger.V(2).Info("collect needed dogu config") configByDogu, doguConfigErr := useCase.doguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedConfig()) - logger.Info("collect needed sensitive dogu config") + logger.V(2).Info("collect needed sensitive dogu config") sensitiveConfigByDogu, sensitiveConfigErr := useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) joinedError := errors.Join(doguErr, componentErr, globalConfigErr, doguConfigErr, sensitiveConfigErr) diff --git a/pkg/domainservice/validateAdditionalMountsDomainUseCase.go b/pkg/domainservice/validateAdditionalMountsDomainUseCase.go index efc1c9ef..5cbfde08 100644 --- a/pkg/domainservice/validateAdditionalMountsDomainUseCase.go +++ b/pkg/domainservice/validateAdditionalMountsDomainUseCase.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" + "slices" + "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "sigs.k8s.io/controller-runtime/pkg/log" - "slices" ) type ValidateAdditionalMountsDomainUseCase struct { @@ -30,15 +31,15 @@ func (useCase *ValidateAdditionalMountsDomainUseCase) ValidateAdditionalMounts(c logger := log.FromContext(ctx).WithName("ValidateAdditionalMountsDomainUseCase.ValidateAdditionalMounts") dogusWithMounts := filterDogusWithAdditionalMounts(effectiveBlueprint.GetWantedDogus()) if len(dogusWithMounts) == 0 { - logger.Info("skip additional mounts validation as no dogus have additional mounts") + logger.V(2).Info("skip additional mounts validation as no dogus have additional mounts") return nil } - logger.Info("load dogu specifications...", "dogusWithMounts", dogusWithMounts) + logger.V(2).Info("load dogu specifications...", "dogusWithMounts", dogusWithMounts) doguSpecs, err := loadDoguSpecifications(ctx, useCase.remoteDoguRegistry, dogusWithMounts) if err != nil { return err } - logger.Info("dogu specifications loaded", "specs", doguSpecs) + logger.V(2).Info("dogu specifications loaded", "specs", doguSpecs) var errorList []error for _, wantedDogu := range dogusWithMounts { diff --git a/pkg/domainservice/validateDependenciesDomainUseCase.go b/pkg/domainservice/validateDependenciesDomainUseCase.go index 13942864..906a055c 100644 --- a/pkg/domainservice/validateDependenciesDomainUseCase.go +++ b/pkg/domainservice/validateDependenciesDomainUseCase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -42,7 +43,7 @@ func (useCase *ValidateDependenciesDomainUseCase) ValidateDependenciesForAllDogu Version: dogu.Version, } }) - logger.Info("load dogu specifications...", "wantedDogus", wantedDogus) + logger.V(2).Info("load dogu specifications...", "wantedDogus", wantedDogus) doguSpecsOfWantedDogus, err := useCase.remoteDoguRegistry.GetDogus(ctx, dogusToLoad) if err != nil { var notFoundError *NotFoundError @@ -52,12 +53,12 @@ func (useCase *ValidateDependenciesDomainUseCase) ValidateDependenciesForAllDogu return &InternalError{WrappedError: err, Message: "cannot load dogu specifications from remote registry for dogu dependency validation"} } } - logger.Info("dogu specifications loaded", "specs", doguSpecsOfWantedDogus) + logger.V(2).Info("dogu specifications loaded", "specs", doguSpecsOfWantedDogus) var errorList []error for _, wantedDogu := range wantedDogus { dependencyDoguSpec := doguSpecsOfWantedDogus[wantedDogu.Name] - logger.Info(fmt.Sprintf("check dependencies of %q in version %q", wantedDogu.Name, wantedDogu.Version.Raw)) + logger.V(2).Info(fmt.Sprintf("check dependencies of %q in version %q", wantedDogu.Name, wantedDogu.Version.Raw)) err = useCase.checkDoguDependencies(ctx, wantedDogus, doguSpecsOfWantedDogus, dependencyDoguSpec.Dependencies) if err != nil { errorList = append(errorList, fmt.Errorf("dependencies for dogu '%s' are not satisfied in blueprint: %w", wantedDogu.Name, err)) @@ -83,9 +84,15 @@ func (useCase *ValidateDependenciesDomainUseCase) checkDoguDependencies( var problems []error for _, dependencyOfWantedDogu := range dependenciesOfWantedDogu { - logger.Info(fmt.Sprintf("check dependency %q in version %q...", dependencyOfWantedDogu.Name, dependencyOfWantedDogu.Version)) + logger.V(2).Info(fmt.Sprintf( + "check dependency %q in version %q...", + dependencyOfWantedDogu.Name, dependencyOfWantedDogu.Version, + )) if dependencyOfWantedDogu.Type != core.DependencyTypeDogu { - logger.Info(fmt.Sprintf("dogu has a dependency %q of type %q. At the moment only dogu dependencies are validated.", dependencyOfWantedDogu.Name, dependencyOfWantedDogu.Type)) + logger.V(1).Info(fmt.Sprintf( + "dogu has a dependency %q of type %q. At the moment only dogu dependencies are validated.", + dependencyOfWantedDogu.Name, dependencyOfWantedDogu.Type, + )) continue } @@ -98,9 +105,15 @@ func (useCase *ValidateDependenciesDomainUseCase) checkDoguDependencies( // We only have to check if nginx-static and nginx-ingress are present. if dependencyOfWantedDogu.Name == nginxDependencyName { if !checkNginxIngressAndStatic(wantedDogus) { - problems = append(problems, fmt.Errorf("dogu has %q dependency but %q and %q are missing in the effective blueprint", nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName)) + problems = append(problems, fmt.Errorf( + "dogu has %q dependency but %q and %q are missing in the effective blueprint", + nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName, + )) } - logger.Info(fmt.Sprintf("dogu has dependency %q. %q and %q are available.", nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName)) + logger.V(2).Info(fmt.Sprintf( + "dogu has dependency %q. %q and %q are available.", + nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName, + )) continue } From a5df1fc93ace6451c48d3a80745dd92be41864d9 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Fri, 22 Aug 2025 18:54:37 +0200 Subject: [PATCH 042/119] #121 handle DogusApplied conditions after determining StateDiff This is very complex, because we set this condition in state diff and while applying dogus. We don't want to override the error message given by applyDogus with the state diff msg. --- pkg/domain/blueprintSpec.go | 113 +++++++++- pkg/domain/blueprintSpec_test.go | 353 +++++++++++++++++++++++++++++++ 2 files changed, 456 insertions(+), 10 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 2bcc131e..00c8ff60 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -41,6 +41,8 @@ const ( ConditionComponentsApplied = "ComponentsApplied" ConditionDogusApplied = "DogusApplied" ConditionCompleted = "Completed" + + ReasonCannotApply = "CannotApply" ) var ( @@ -62,7 +64,7 @@ type BlueprintConfiguration struct { // ValidateStatically checks the blueprintSpec for semantic errors and sets the status to the result. // Here will be only checked, what can be checked without any external information, e.g. without dogu specification. -// returns a domain.InvalidBlueprintError if blueprint is invalid +// changed a domain.InvalidBlueprintError if blueprint is invalid // or nil otherwise. func (spec *BlueprintSpec) ValidateStatically() error { var errorList []error @@ -240,7 +242,7 @@ func (spec *BlueprintSpec) MissingConfigReferences(error error) { // installedDogus are a map in the form of simpleDoguName->*DoguInstallation. There should be no nil values. // The StateDiff is an 'as is' representation, therefore no error is thrown, e.g. if dogu namespaces are different and namespace changes are not allowed. // If there are not allowed actions should be considered at the start of the execution of the blueprint. -// returns an error if the BlueprintSpec is not in the necessary state to determine the stateDiff. +// changed an error if the BlueprintSpec is not in the necessary state to determine the stateDiff. func (spec *BlueprintSpec) DetermineStateDiff( ecosystemState ecosystem.EcosystemState, referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, @@ -248,7 +250,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( doguDiffs := determineDoguDiffs(spec.EffectiveBlueprint.Dogus, ecosystemState.InstalledDogus) compDiffs, err := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) if err != nil { - // FIXME: a proper state and event should be set, so that this error don't lead to an endless retry. + // FIXME: a proper state and event should be set, so that this error doesn't lead to an endless retry. // The error here occurs, if a targetState is not properly set in components. We can remove this case // when we introduce the absent flag in the domain or we just ignore this error like for dogu targetState return err @@ -269,7 +271,8 @@ func (spec *BlueprintSpec) DetermineStateDiff( GlobalConfigDiffs: globalConfigDiffs, } - spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) + //TODO: we need the possible error from the use case to set the condition to Unknown + spec.setDogusAppliedConditionAfterStateDiff(nil) spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) spec.Events = append(spec.Events, GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: spec.StateDiff.GlobalConfigDiffs}) spec.Events = append(spec.Events, NewDoguConfigDiffDeterminedEvent(spec.StateDiff.DoguConfigDiffs)) @@ -313,9 +316,9 @@ func (spec *BlueprintSpec) DetermineStateDiff( //TODO: The state diff will be generated at every run and will be written on the blueprint-CR.Status. // After every apply, the state diff will be empty and therefore will be deleted on the blueprint-CR. - // It is only useful for dry-run. + // It is only useful for dry-run or error states, where no further work happens. // Maybe we just not write it in the status anymore? How to debug then? - // The old blueprint-process was hard to understand because there was no overview. + // The old classic blueprint-process was hard to understand because there was no overview. // A separate BlueprintExecution-CR could be a solution but i am not sure, if we have enough time for that and if it is the right choice. // CR could have the diff as it's spec. // It is a single execution like k8s-jobs. @@ -325,7 +328,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( // HandleHealthResult sets the healthCondition accordingly to the healthResult and a possible error. // if an error is given, the condition will be set to unknown. -// The function returns true if the condition changed, otherwise false. +// The function changed true if the condition changed, otherwise false. func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResult, err error) bool { if err != nil { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ @@ -369,7 +372,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul return conditionChanged } -// ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. +// ShouldBeApplied changed true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. func (spec *BlueprintSpec) ShouldBeApplied() bool { // wrote it in the long form to reduce complexity if spec.Config.DryRun { @@ -400,6 +403,96 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { } } +// setDogusAppliedConditionAfterStateDiff sets the ConditionDogusApplied based on the diff, and it's current state. +// This function gets called after creating the stateDiff. The ConditionDogusApplied could be applied in a previous run +// either by the state diff or later while applying the dogus. +// If there is a condition from the DoguApply-step, then do not override it, unless it is obviously outdated. +// +// decision table: +// current condition withDiff withoutDiff DiffError +// ======================================================= +// none NeedToApply Applied Unknown +// Unknown NeedToApply Applied Unknown +// NeedToApply NeedToApply Applied Unknown +// Applied NeedToApply no change Unknown +// CannotApply no change no change Unknown +func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) bool { + condition := meta.FindStatusCondition(*spec.Conditions, ConditionDogusApplied) + + // current condition DiffError + // =========================== + // none Unknown + // Unknown Unknown + // NeedToApply Unknown + // Applied Unknown + // CannotApply Unknown + if diffErr != nil { + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionUnknown, + Reason: "CannotDetermineStateDiff", + Message: diffErr.Error(), + }) + return conditionChanged + } + + // current condition withDiff withoutDiff + // ============================================ + // none NeedToApply Applied + // Unknown NeedToApply Applied + // NeedToApply NeedToApply Applied + if condition == nil || condition.Status == metav1.ConditionUnknown || condition.Reason == "NeedToApply" { + if spec.StateDiff.DoguDiffs.HasChanges() { + return spec.setDogusNeedToApply() + } else { + event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "Applied", + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } + return conditionChanged + } + } + // current condition withDiff withoutDiff + // ============================================ + // CannotApply no change no change + if condition.Reason == ReasonCannotApply { + return false + } + + // current condition withDiff withoutDiff + // ============================================ + // Applied NeedToApply no change + if condition.Reason == "Applied" { + if spec.StateDiff.DoguDiffs.HasChanges() { + return spec.setDogusNeedToApply() + } else { + return false + } + } + // should never happen + return false +} + +func (spec *BlueprintSpec) setDogusNeedToApply() bool { + event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) + conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: event.Message(), + }) + if conditionChanged { + spec.Events = append(spec.Events, event) + } + return conditionChanged +} + // SetComponentsAppliedCondition informs the user about the state of the component apply. // If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. // Returns true if the condition changed, otherwise false. @@ -408,7 +501,7 @@ func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionComponentsApplied, Status: metav1.ConditionFalse, - Reason: "CannotApply", + Reason: ReasonCannotApply, Message: err.Error(), }) if conditionChanged { @@ -437,7 +530,7 @@ func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionFalse, - Reason: "CannotApply", + Reason: ReasonCannotApply, Message: err.Error(), }) if conditionChanged { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 2f8ffa4c..3fef3177 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -806,3 +806,356 @@ func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { require.Equal(t, 0, len(blueprint.Events)) }) } + +//func TestBlueprintSpec_setComponentsAppliedConditionAfterStateDiff(t *testing.T) { +// // decision table: +// // current condition withDiff withoutDiff DiffError +// // ======================================================= +// // none NeedToApply Applied Unknown +// // Unknown NeedToApply Applied Unknown +// // NeedToApply NeedToApply Applied Unknown +// // Applied NeedToApply no change Unknown +// // CannotApply no change no change Unknown +// t.Run("no condition before but changes", func(t *testing.T) { +// diff := StateDiff{ +// DoguDiffs: DoguDiffs{ +// DoguDiff{ +// DoguName: "cas", +// NeededActions: []Action{ +// ActionUpgrade, ActionSwitchDoguNamespace, +// }, +// }, +// }, +// } +// blueprint := BlueprintSpec{ +// Conditions: &[]Condition{}, +// StateDiff: diff, +// } +// +// blueprint.setDogusAppliedConditionAfterStateDiff() +// +// event := newStateDiffDoguEvent(diff.DoguDiffs) +// require.Equal(t, 1, len(blueprint.Events)) +// assert.Equal(t, event, blueprint.Events[0]) +// condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) +// assert.Equal(t, metav1.ConditionFalse, condition.Status) +// assert.Equal(t, "NeedToApply", condition.Reason) +// assert.Equal(t, event.Message(), condition.Message) +// }) +// t.Run("no condition before and no changes", func(t *testing.T) { +// diff := StateDiff{ +// DoguDiffs: DoguDiffs{ +// DoguDiff{ +// DoguName: "cas", +// NeededActions: nil, +// }, +// }, +// } +// blueprint := BlueprintSpec{ +// Conditions: &[]Condition{}, +// StateDiff: diff, +// } +// +// blueprint.setDogusAppliedConditionAfterStateDiff() +// +// event := newStateDiffDoguEvent(diff.DoguDiffs) +// require.Equal(t, 1, len(blueprint.Events)) +// assert.Equal(t, event, blueprint.Events[0]) +// condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) +// assert.Equal(t, metav1.ConditionFalse, condition.Status) +// assert.Equal(t, "Applied", condition.Reason) +// assert.Equal(t, event.Message(), condition.Message) +// }) +//} + +func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { + // current condition withDiff withoutDiff DiffError + // ======================================================= + // none NeedToApply Applied Unknown + // Unknown NeedToApply Applied Unknown + // NeedToApply NeedToApply Applied Unknown + // Applied NeedToApply no change Unknown + // CannotApply no change no change Unknown + + diffEventMsg := "dogu state diff determined: 2 actions (\"dogu namespace switch\": 1, \"upgrade\": 1)" + noDiffEventMsg := "dogu state diff determined: 0 actions ()" + type given struct { + hasDiff bool + condition Condition + diffErr error + } + type expected struct { + changed bool + condition *Condition + hasEvent bool + } + tests := []struct { + name string + given given + expected expected + }{ + { + name: "none, diff -> NeedToApply, event", + given: given{ + hasDiff: true, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "none, no diff -> Applied, event", + given: given{ + hasDiff: false, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "Applied", + Message: noDiffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "Unknown, diff -> NeedToApply, event", + given: given{ + hasDiff: true, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionUnknown, + Reason: "CannotDetermineStateDiff", + }, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "Unknown, no diff -> Applied, event", + given: given{ + hasDiff: false, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "Applied", + Message: noDiffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "NeedToApply, diff -> NeedToApply, event", + given: given{ + hasDiff: true, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionTrue, + Reason: "NeedToApply", + Message: "other msg", + }, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "NeedToApply, diff -> NeedToApply, no event", + given: given{ + hasDiff: true, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + }, + expected: expected{ + changed: false, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + hasEvent: false, + }, + }, + { + name: "NeedToApply, no diff -> Applied, event", + given: given{ + hasDiff: false, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: "error msg", + }, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "Applied", + Message: noDiffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "Applied, diff -> NeedToApply, event", + given: given{ + hasDiff: true, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionTrue, + Reason: "Applied", + Message: "other msg", + }, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "NeedToApply", + Message: diffEventMsg, + }, + hasEvent: true, + }, + }, + { + name: "Applied, no diff -> no change", + given: given{ + hasDiff: false, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: "Applied", + Message: "any msg", + }, + }, + expected: expected{ + changed: false, + hasEvent: false, + }, + }, + { + name: "CannotApply, no diff -> no change", + given: given{ + hasDiff: false, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: ReasonCannotApply, + Message: "error msg", + }, + }, + expected: expected{ + changed: false, + hasEvent: false, + }, + }, + { + name: "CannotApply, diff -> no change", + given: given{ + hasDiff: true, + condition: Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionFalse, + Reason: ReasonCannotApply, + Message: "error msg", + }, + }, + expected: expected{ + changed: false, + hasEvent: false, + }, + }, + { + name: "error given", + given: given{ + hasDiff: false, + diffErr: assert.AnError, + }, + expected: expected{ + changed: true, + condition: &Condition{ + Type: ConditionDogusApplied, + Status: metav1.ConditionUnknown, + Reason: "CannotDetermineStateDiff", + Message: assert.AnError.Error(), + }, + hasEvent: false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + //given + spec := &BlueprintSpec{ + Conditions: &[]Condition{tt.given.condition}, + } + if tt.given.hasDiff { + spec.StateDiff = StateDiff{ + DoguDiffs: DoguDiffs{ + DoguDiff{ + DoguName: "redmine", + NeededActions: []Action{ + ActionUpgrade, ActionSwitchDoguNamespace, + }, + }, + }, + } + } + //when + hasChanged := spec.setDogusAppliedConditionAfterStateDiff(tt.given.diffErr) + //then + assert.Equal(t, tt.expected.changed, hasChanged, "function should return %v", hasChanged) + //condition + condition := meta.FindStatusCondition(*spec.Conditions, ConditionDogusApplied) + if tt.expected.changed && tt.expected.condition != nil { + require.NotNil(t, condition) + assert.Equal(t, tt.expected.condition.Status, condition.Status) + assert.Equal(t, tt.expected.condition.Message, condition.Message) + assert.Equal(t, tt.expected.condition.Reason, condition.Reason) + } else { + conditions := *spec.Conditions + condition := conditions[0] + assert.Equal(t, tt.given.condition, condition) + } + //events + if tt.expected.hasEvent { + require.Equal(t, 1, len(spec.Events), "should have an event") + assert.Equal(t, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs), spec.Events[0]) + } + }) + } +} From 8673e62b2e9049aad02ce46ad3f916592d673976 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 27 Aug 2025 15:27:14 +0200 Subject: [PATCH 043/119] #121 remove retry while loading dogu.jsons We want retries only via reconciliation to make the code easier and non-blocking. We can also reduce errors via a cache. --- pkg/adapter/doguregistry/cache.go | 1 + pkg/adapter/doguregistry/remote.go | 37 ++++++++++-------------- pkg/adapter/doguregistry/remote_test.go | 3 +- pkg/application/stateDiffUseCase_test.go | 8 +++-- pkg/domain/blueprintSpec.go | 4 ++- 5 files changed, 27 insertions(+), 26 deletions(-) create mode 100644 pkg/adapter/doguregistry/cache.go diff --git a/pkg/adapter/doguregistry/cache.go b/pkg/adapter/doguregistry/cache.go new file mode 100644 index 00000000..bd367ae8 --- /dev/null +++ b/pkg/adapter/doguregistry/cache.go @@ -0,0 +1 @@ +package doguregistry diff --git a/pkg/adapter/doguregistry/remote.go b/pkg/adapter/doguregistry/remote.go index b482ff1e..db5a6819 100644 --- a/pkg/adapter/doguregistry/remote.go +++ b/pkg/adapter/doguregistry/remote.go @@ -3,17 +3,13 @@ package doguregistry import ( "context" "errors" - "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/retry-lib/retry" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) -var maxTries = 20 - type Remote struct { repository remoteDoguDescriptorRepository } @@ -23,27 +19,24 @@ func NewRemote(repository remoteDoguDescriptorRepository) *Remote { } func (r *Remote) GetDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, error) { - dogu := &core.Dogu{} - err := retry.OnError(maxTries, cloudoguerrors.IsConnectionError, func() error { - var err error - dogu, err = r.repository.Get(ctx, qualifiedDoguVersion) - return err - }) + // do not retry here. If any error happens, just reconcile later. We only do retries in application level. + // This makes the code way easier and non-blocking. + dogu, err := r.repository.Get(ctx, qualifiedDoguVersion) if err != nil { - // this is ugly, maybe do it better in cesapp-lib? if cloudoguerrors.IsNotFoundError(err) { - return nil, &domainservice.NotFoundError{ - WrappedError: err, - Message: fmt.Sprintf("dogu %q with version %q could not be found", qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw), - } - } - - return nil, &domainservice.InternalError{ - WrappedError: err, - Message: fmt.Sprintf("failed to get dogu %q with version %q", qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw), + return nil, domainservice.NewNotFoundError( + err, + "dogu %q with version %q could not be found", + qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, + ) + } else { + return nil, domainservice.NewInternalError( + err, + "failed to get dogu %q with version %q", + qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, + ) } } - return dogu, nil } diff --git a/pkg/adapter/doguregistry/remote_test.go b/pkg/adapter/doguregistry/remote_test.go index e8e46b53..e6f623db 100644 --- a/pkg/adapter/doguregistry/remote_test.go +++ b/pkg/adapter/doguregistry/remote_test.go @@ -3,13 +3,14 @@ package doguregistry import ( "context" "fmt" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func TestNewRemote(t *testing.T) { diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index feaa66e4..7a0d2824 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -282,7 +282,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should fail to update blueprint", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, + } blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) @@ -319,7 +322,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { t.Run("should succeed for dogu diff", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 00c8ff60..74070249 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -22,7 +22,9 @@ type BlueprintSpec struct { EffectiveBlueprint EffectiveBlueprint StateDiff StateDiff Config BlueprintConfiguration - Conditions *[]Condition + //FIXME: check if we can use a non-pointer type. The only reason for a pointer was the meta.SetStatusCondition function + // The pointer is really ugly, because we need to explicitly set conditions in every test + Conditions *[]Condition // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. // This field should be ignored in the whole domain. From 46f390f07fc8eff29c40a7d499423264df8e23cf Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 27 Aug 2025 17:26:19 +0200 Subject: [PATCH 044/119] #121 add cache for dogu descriptors We yet need to write tests for this. If the operator reconciles very often, this cache helps a lot to reduce network pressure on the registry and can help with network problems --- go.mod | 3 +- go.sum | 4 +- pkg/adapter/doguregistry/cache.go | 77 +++++++++++++++++++++++++++++++ pkg/bootstrap.go | 14 +++++- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d1ce455a..e24615bc 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,8 @@ require ( github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 - github.com/cloudogu/retry-lib v0.1.0 github.com/go-logr/logr v1.4.2 + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 @@ -39,6 +39,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect + github.com/cloudogu/retry-lib v0.1.0 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/errdefs v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5c0c02d7..7d05ffc2 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/cloudogu/ces-commons-lib v0.2.0 h1:yOEZWFl4W9N3J/6fok4svE3UufK5GQQtyx github.com/cloudogu/ces-commons-lib v0.2.0/go.mod h1:4rvR2RTDDaz5a6OZ1fW27G0MOnl5I3ackeiHxt4gn3o= github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSAaYi7U= github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073 h1:4E+BOxsDuidq2uwWyQI2VsPPvD34rUXaKDSm2O50ffU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250805100727-05e6e1b84073/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a h1:aj5qH5Ejn+TstaHjt+8Y6vigIY5uVl59IWPVzaFlczU= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= @@ -319,6 +317,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= diff --git a/pkg/adapter/doguregistry/cache.go b/pkg/adapter/doguregistry/cache.go index bd367ae8..b85bfeef 100644 --- a/pkg/adapter/doguregistry/cache.go +++ b/pkg/adapter/doguregistry/cache.go @@ -1 +1,78 @@ package doguregistry + +import ( + "context" + "fmt" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/cesapp-lib/core" + gocache "github.com/patrickmn/go-cache" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// TODO: We should move this implementation in the dogu-descriptor-lib, so others can profit from it as well. + +type Cache struct { + repository remoteDoguDescriptorRepository + cache *gocache.Cache +} + +func NewCache(repository remoteDoguDescriptorRepository, cache *gocache.Cache) *Cache { + return &Cache{ + repository: repository, + cache: cache, + } +} + +func (c *Cache) GetLatest(ctx context.Context, name cescommons.QualifiedName) (*core.Dogu, error) { + // we cannot cache latest for sure + dogu, err := c.repository.GetLatest(ctx, name) + if err != nil { + return nil, err + } + version, err := core.ParseVersion(dogu.Version) + if err != nil { + return nil, fmt.Errorf("cannot populate cache as latest dogu version cannot be parsed from response: %w", err) + } + c.SetCached( + cescommons.QualifiedVersion{ + Name: name, + Version: version, + }, + dogu, + ) + return dogu, nil +} + +func (c *Cache) Get(ctx context.Context, version cescommons.QualifiedVersion) (*core.Dogu, error) { + logger := log.FromContext(ctx).WithName("DoguDescriptorCache") + dogu, found := c.GetCached(version) + if found { + logger.V(2).Info("dogu descriptor cache hit", "dogu", version) + return dogu, nil + } + + // cache missed + dogu, err := c.repository.Get(ctx, version) + if err != nil { + return nil, err + } + c.SetCached(version, dogu) + return dogu, nil +} + +func cacheKeyFromReference(qualifiedDoguVersion cescommons.QualifiedVersion) string { + return fmt.Sprintf("%s:%s", qualifiedDoguVersion.Name.String(), qualifiedDoguVersion.Version.String()) +} + +func (c *Cache) GetCached(qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, bool) { + cached, found := c.cache.Get(cacheKeyFromReference(qualifiedDoguVersion)) + if found { + return cached.(*core.Dogu), found + } + return nil, found +} + +func (c *Cache) SetCached(qualifiedDoguVersion cescommons.QualifiedVersion, dogu *core.Dogu) { + c.cache.SetDefault(cacheKeyFromReference(qualifiedDoguVersion), dogu) +} diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index 1477cc79..d1122aea 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -2,12 +2,14 @@ package pkg import ( "fmt" + "time" adapterconfigk8s "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/config/kubernetes" v2 "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/blueprintcr/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/sensitiveconfigref" "github.com/cloudogu/k8s-registry-lib/repository" remotedogudescriptor "github.com/cloudogu/remote-dogu-descriptor-lib/repository" + "github.com/patrickmn/go-cache" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -38,6 +40,13 @@ var blueprintOperatorName = common.QualifiedComponentName{ SimpleName: "k8s-blueprint-operator", } +var ( + // we will often load the same dogu descriptors as long as the blueprint will be applied. + // Therefore, the cache should live long enough, so that a complete installation can profit from it. + doguDescriptorCacheExpiration = 15 * time.Minute + doguDescriptorCacheCleanUpInterval = 15 * time.Minute +) + // Bootstrap creates the ApplicationContext and does all dependency injection of the whole application. func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, namespace string) (*ApplicationContext, error) { ecosystemClientSet, err := createEcosystemClientSet(restConfig) @@ -120,8 +129,9 @@ func createRemoteDoguRegistry() (*doguregistry.Remote, error) { if err != nil { return nil, fmt.Errorf("failed to create new remote dogu repository: %w", err) } - - return doguregistry.NewRemote(doguRemoteRepository), nil + goCache := cache.New(doguDescriptorCacheExpiration, doguDescriptorCacheCleanUpInterval) + registryCache := doguregistry.NewCache(doguRemoteRepository, goCache) + return doguregistry.NewRemote(registryCache), nil } func createEcosystemClientSet(restConfig *rest.Config) (*adapterk8s.ClientSet, error) { From fd4dd647f6be68ac94e0e1b6283d53b15b56c439 Mon Sep 17 00:00:00 2001 From: alexander-dammeier Date: Wed, 27 Aug 2025 17:28:05 +0200 Subject: [PATCH 045/119] #121 fix tests --- pkg/application/stateDiffUseCase_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 7a0d2824..6fd3b52c 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -442,7 +442,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { t.Run("should succeed for global config diff", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Global: domain.GlobalConfig{ @@ -509,7 +510,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { t.Run("should succeed for dogu config diff", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ @@ -596,7 +598,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { t.Run("should succeed for sensitive dogu config diff", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", + Id: "testBlueprint1", + Conditions: &[]domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ From 0d549580ac868e93e11342c9a43a1e2e221710a1 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Sat, 30 Aug 2025 11:26:03 +0200 Subject: [PATCH 046/119] #121 change slice-pointer of conditions to normal slice --- pkg/domain/blueprintSpec.go | 50 ++++++------- pkg/domain/blueprintSpec_test.go | 124 +++++++++++-------------------- 2 files changed, 68 insertions(+), 106 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 74070249..20243813 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -22,9 +22,7 @@ type BlueprintSpec struct { EffectiveBlueprint EffectiveBlueprint StateDiff StateDiff Config BlueprintConfiguration - //FIXME: check if we can use a non-pointer type. The only reason for a pointer was the meta.SetStatusCondition function - // The pointer is really ugly, because we need to explicitly set conditions in every test - Conditions *[]Condition + Conditions []Condition // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. // This field should be ignored in the whole domain. @@ -84,7 +82,7 @@ func (spec *BlueprintSpec) ValidateStatically() error { Message: "blueprint spec is invalid", } spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) - meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "Invalid", @@ -130,7 +128,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError WrappedError: possibleInvalidDependenciesError, Message: "blueprint spec is invalid", } - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "Inconsistent", @@ -140,7 +138,7 @@ func (spec *BlueprintSpec) ValidateDynamically(possibleInvalidDependenciesError spec.Events = append(spec.Events, BlueprintSpecInvalidEvent{ValidationError: err}) } } else { - meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionTrue, Reason: "Valid", @@ -163,7 +161,7 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { } validationError := spec.EffectiveBlueprint.validateOnlyConfigForDogusInBlueprint() if validationError != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionValid, Status: metav1.ConditionFalse, Reason: "Inconsistent", @@ -282,7 +280,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( invalidBlueprintError := spec.validateStateDiff() if invalidBlueprintError != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionExecutable, Status: metav1.ConditionFalse, Reason: "ForbiddenOperations", @@ -294,7 +292,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( return invalidBlueprintError } - meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionExecutable, Status: metav1.ConditionTrue, Reason: "Executable", @@ -333,7 +331,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( // The function changed true if the condition changed, otherwise false. func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResult, err error) bool { if err != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, Status: metav1.ConditionUnknown, Reason: "CannotCheckHealth", @@ -347,7 +345,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul doguHealthIgnored: spec.Config.IgnoreDoguHealth, componentHealthIgnored: spec.Config.IgnoreComponentHealth, } - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, Status: metav1.ConditionTrue, Reason: "Healthy", @@ -362,7 +360,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul event := EcosystemUnhealthyEvent{ HealthResult: healthResult, } - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, Status: metav1.ConditionFalse, Reason: "Unhealthy", @@ -384,7 +382,7 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { } func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionSelfUpgradeCompleted, Status: metav1.ConditionFalse, Reason: "AwaitSelfUpgrade", @@ -395,7 +393,7 @@ func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { } func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionSelfUpgradeCompleted, Status: metav1.ConditionTrue, Reason: "Completed", @@ -419,7 +417,7 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { // Applied NeedToApply no change Unknown // CannotApply no change no change Unknown func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) bool { - condition := meta.FindStatusCondition(*spec.Conditions, ConditionDogusApplied) + condition := meta.FindStatusCondition(spec.Conditions, ConditionDogusApplied) // current condition DiffError // =========================== @@ -429,7 +427,7 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) // Applied Unknown // CannotApply Unknown if diffErr != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionUnknown, Reason: "CannotDetermineStateDiff", @@ -448,7 +446,7 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) return spec.setDogusNeedToApply() } else { event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionFalse, Reason: "Applied", @@ -483,7 +481,7 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) func (spec *BlueprintSpec) setDogusNeedToApply() bool { event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionFalse, Reason: "NeedToApply", @@ -500,7 +498,7 @@ func (spec *BlueprintSpec) setDogusNeedToApply() bool { // Returns true if the condition changed, otherwise false. func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { if err != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionComponentsApplied, Status: metav1.ConditionFalse, Reason: ReasonCannotApply, @@ -512,7 +510,7 @@ func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { return conditionChanged } event := ComponentsAppliedEvent{Diffs: spec.StateDiff.ComponentDiffs} - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionComponentsApplied, Status: metav1.ConditionTrue, Reason: "Applied", @@ -529,7 +527,7 @@ func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { // Returns true if the condition changed, otherwise false. func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { if err != nil { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionFalse, Reason: ReasonCannotApply, @@ -541,7 +539,7 @@ func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { return conditionChanged } event := DogusAppliedEvent{Diffs: spec.StateDiff.DoguDiffs} - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionDogusApplied, Status: metav1.ConditionTrue, Reason: "Applied", @@ -599,7 +597,7 @@ func getActionNotAllowedError(action Action) *InvalidBlueprintError { func (spec *BlueprintSpec) StartApplyEcosystemConfig() { event := ApplyEcosystemConfigEvent{} - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionFalse, Reason: "Applying", @@ -611,7 +609,7 @@ func (spec *BlueprintSpec) StartApplyEcosystemConfig() { } func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionFalse, Reason: "ApplyingFailed", @@ -623,7 +621,7 @@ func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { } func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionConfigApplied, Status: metav1.ConditionTrue, Reason: "Applied", @@ -636,7 +634,7 @@ func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { // Complete is used to mark the blueprint as completed and to inform the user. // Returns true if anything changed, false otherwise. func (spec *BlueprintSpec) Complete() bool { - conditionChanged := meta.SetStatusCondition(spec.Conditions, metav1.Condition{ + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionCompleted, Status: metav1.ConditionTrue, Reason: "Completed", diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 3fef3177..fe1c892d 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -44,13 +44,11 @@ func Test_BlueprintSpec_Validate_allOk(t *testing.T) { } func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { - spec := BlueprintSpec{ - Conditions: &[]Condition{}, - } + spec := BlueprintSpec{} err := spec.ValidateStatically() - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionValid)) + assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionValid)) require.NotNil(t, err, "No ID definition should lead to an error") var invalidError *InvalidBlueprintError assert.ErrorAs(t, err, &invalidError) @@ -248,7 +246,8 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { err := spec.CalculateEffectiveBlueprint() assert.ErrorContains(t, err, "setting config for dogu \"my-dogu\" is not allowed as it will not be installed with the blueprint") - assert.Equal(t, 0, len(spec.Events)) + assert.Equal(t, 1, len(spec.Events)) + assert.Equal(t, "BlueprintSpecInvalid", spec.Events[0].Name()) }) t.Run("add additionalMounts", func(t *testing.T) { dogus := []Dogu{ @@ -290,7 +289,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Dogus: []Dogu{}, Components: []Component{}, }, - Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -309,7 +307,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, } - assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionExecutable)) + assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) require.NoError(t, err) require.Equal(t, 5, len(spec.Events)) assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) @@ -340,7 +338,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: true, }, - Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -358,7 +355,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*spec.Conditions, ConditionExecutable)) + assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) }) t.Run("invalid blueprint state with not allowed dogu namespace switch", func(t *testing.T) { @@ -377,7 +374,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { Config: BlueprintConfiguration{ AllowDoguNamespaceSwitch: false, }, - Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ @@ -394,7 +390,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) + assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") }) @@ -413,7 +409,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, - Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -429,7 +424,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) + assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"component namespace switch\" is not allowed") }) @@ -448,7 +443,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { }, }, }, - Conditions: &[]Condition{}, } clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, @@ -464,7 +458,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) // then - assert.True(t, meta.IsStatusConditionFalse(*spec.Conditions, ConditionExecutable)) + assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) assert.ErrorContains(t, err, "action \"downgrade\" is not allowed") }) @@ -473,14 +467,12 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { t.Run("ok with event", func(t *testing.T) { // given - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := &BlueprintSpec{} // when changed := blueprint.Complete() // then assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionCompleted) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionCompleted) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Completed", condition.Reason) assert.Equal(t, "", condition.Message) @@ -488,9 +480,7 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { }) t.Run("no change if executed twice", func(t *testing.T) { // given - blueprint := &BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := &BlueprintSpec{} // when changed := blueprint.Complete() assert.True(t, changed) @@ -498,7 +488,7 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { changed = blueprint.Complete() // then assert.False(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionCompleted) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionCompleted) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Completed", condition.Reason) assert.Equal(t, "", condition.Message) @@ -508,21 +498,17 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { func TestBlueprintSpec_ValidateDynamically(t *testing.T) { t.Run("all ok, no errors", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} blueprint.ValidateDynamically(nil) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionValid)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, ConditionValid)) require.Equal(t, 0, len(blueprint.Events)) }) t.Run("given dependency error", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} givenErr := assert.AnError blueprint.ValidateDynamically(givenErr) @@ -569,73 +555,61 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { func TestBlueprintSpec_MarkWaitingForSelfUpgrade(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} blueprint.MarkWaitingForSelfUpgrade() - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event{AwaitSelfUpgradeEvent{}}, blueprint.Events) }) t.Run("repeated call -> no event", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} blueprint.MarkWaitingForSelfUpgrade() blueprint.Events = []Event(nil) blueprint.MarkWaitingForSelfUpgrade() - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if condition did not change") }) } func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} blueprint.MarkSelfUpgradeCompleted() - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event{SelfUpgradeCompletedEvent{}}, blueprint.Events) }) t.Run("repeated call -> no event", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} blueprint.MarkSelfUpgradeCompleted() blueprint.Events = []Event(nil) blueprint.MarkSelfUpgradeCompleted() - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, ConditionSelfUpgradeCompleted)) assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") }) } func TestBlueprintSpec_HandleHealthResult(t *testing.T) { t.Run("healthy", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, nil) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionEcosystemHealthy) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Healthy", condition.Reason) assert.Equal(t, "dogu health ignored: false; component health ignored: false", condition.Message) }) t.Run("unhealthy", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} health := ecosystem.HealthResult{ DoguHealth: ecosystem.DoguHealthResult{ DogusByStatus: map[ecosystem.HealthStatus][]cescommons.SimpleName{ @@ -647,7 +621,7 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { changed := blueprint.HandleHealthResult(health, nil) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionEcosystemHealthy) assert.Equal(t, metav1.ConditionFalse, condition.Status) assert.Equal(t, "Unhealthy", condition.Reason) assert.Contains(t, condition.Message, "ecosystem health:") @@ -656,23 +630,19 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { }) t.Run("error given, condition Unknown", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, assert.AnError) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionEcosystemHealthy) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionEcosystemHealthy) assert.Equal(t, metav1.ConditionUnknown, condition.Status) assert.Equal(t, "CannotCheckHealth", condition.Reason) assert.Equal(t, assert.AnError.Error(), condition.Message) }) t.Run("no condition change", func(t *testing.T) { - blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - } + blueprint := BlueprintSpec{} changed := blueprint.HandleHealthResult(ecosystem.HealthResult{}, assert.AnError) assert.True(t, changed, "condition should change after the first call") @@ -695,14 +665,13 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { t.Run("applied", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetComponentsAppliedCondition(nil) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionComponentsApplied) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Applied", condition.Reason) assert.Equal(t, "components applied: \"k8s-dogu-operator\": [upgrade, component namespace switch]", condition.Message) @@ -712,14 +681,13 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { t.Run("error", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetComponentsAppliedCondition(assert.AnError) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionComponentsApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionComponentsApplied) assert.Equal(t, metav1.ConditionFalse, condition.Status) assert.Equal(t, "CannotApply", condition.Reason) assert.Equal(t, assert.AnError.Error(), condition.Message) @@ -727,8 +695,7 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { t.Run("no condition change", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetComponentsAppliedCondition(assert.AnError) @@ -757,14 +724,13 @@ func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { t.Run("applied", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetDogusAppliedCondition(nil) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionDogusApplied) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Applied", condition.Reason) assert.Equal(t, "dogus applied: \"cas\": [upgrade, dogu namespace switch]", condition.Message) @@ -774,14 +740,13 @@ func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { t.Run("error", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetDogusAppliedCondition(assert.AnError) assert.True(t, changed) - condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, ConditionDogusApplied) assert.Equal(t, metav1.ConditionFalse, condition.Status) assert.Equal(t, "CannotApply", condition.Reason) assert.Equal(t, assert.AnError.Error(), condition.Message) @@ -791,8 +756,7 @@ func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { t.Run("no condition change", func(t *testing.T) { blueprint := BlueprintSpec{ - Conditions: &[]Condition{}, - StateDiff: diff, + StateDiff: diff, } changed := blueprint.SetDogusAppliedCondition(assert.AnError) @@ -1121,7 +1085,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { t.Run(tt.name, func(t *testing.T) { //given spec := &BlueprintSpec{ - Conditions: &[]Condition{tt.given.condition}, + Conditions: []Condition{tt.given.condition}, } if tt.given.hasDiff { spec.StateDiff = StateDiff{ @@ -1140,14 +1104,14 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { //then assert.Equal(t, tt.expected.changed, hasChanged, "function should return %v", hasChanged) //condition - condition := meta.FindStatusCondition(*spec.Conditions, ConditionDogusApplied) + condition := meta.FindStatusCondition(spec.Conditions, ConditionDogusApplied) if tt.expected.changed && tt.expected.condition != nil { require.NotNil(t, condition) assert.Equal(t, tt.expected.condition.Status, condition.Status) assert.Equal(t, tt.expected.condition.Message, condition.Message) assert.Equal(t, tt.expected.condition.Reason, condition.Reason) } else { - conditions := *spec.Conditions + conditions := spec.Conditions condition := conditions[0] assert.Equal(t, tt.given.condition, condition) } From 643e9a84114e067a09415cb9de229860d50fb650 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Sat, 30 Aug 2025 12:03:17 +0200 Subject: [PATCH 047/119] #121 add TODO --- pkg/domain/blueprintSpec_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index fe1c892d..6bdbb428 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -771,6 +771,7 @@ func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { }) } +// TODO: write tests //func TestBlueprintSpec_setComponentsAppliedConditionAfterStateDiff(t *testing.T) { // // decision table: // // current condition withDiff withoutDiff DiffError From 7224d316086137a72d91cc02b3ae247af75591f0 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 4 Sep 2025 10:02:12 +0200 Subject: [PATCH 048/119] #121 remove cache and use local dogu descriptor repository --- go.mod | 1 - go.sum | 2 - pkg/adapter/doguregistry/cache.go | 78 ------ .../doguregistry/doguDescriptorRepository.go | 99 ++++++++ .../doguDescriptorRepository_test.go | 227 ++++++++++++++++++ pkg/adapter/doguregistry/interfaces.go | 12 +- ...mock_localDoguDescriptorRepository_test.go | 146 +++++++++++ pkg/adapter/doguregistry/remote.go | 55 ----- pkg/adapter/doguregistry/remote_test.go | 157 ------------ .../v2/blueprintSpecCRRepository.go | 6 +- pkg/bootstrap.go | 19 +- 11 files changed, 491 insertions(+), 311 deletions(-) delete mode 100644 pkg/adapter/doguregistry/cache.go create mode 100644 pkg/adapter/doguregistry/doguDescriptorRepository.go create mode 100644 pkg/adapter/doguregistry/doguDescriptorRepository_test.go create mode 100644 pkg/adapter/doguregistry/mock_localDoguDescriptorRepository_test.go delete mode 100644 pkg/adapter/doguregistry/remote.go delete mode 100644 pkg/adapter/doguregistry/remote_test.go diff --git a/go.mod b/go.mod index e24615bc..a1e3d0c4 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/cloudogu/k8s-registry-lib v0.5.1 github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 github.com/go-logr/logr v1.4.2 - github.com/patrickmn/go-cache v2.1.0+incompatible github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 diff --git a/go.sum b/go.sum index 7d05ffc2..52ca4ea0 100644 --- a/go.sum +++ b/go.sum @@ -317,8 +317,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= diff --git a/pkg/adapter/doguregistry/cache.go b/pkg/adapter/doguregistry/cache.go deleted file mode 100644 index b85bfeef..00000000 --- a/pkg/adapter/doguregistry/cache.go +++ /dev/null @@ -1,78 +0,0 @@ -package doguregistry - -import ( - "context" - "fmt" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/cesapp-lib/core" - gocache "github.com/patrickmn/go-cache" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -// TODO: We should move this implementation in the dogu-descriptor-lib, so others can profit from it as well. - -type Cache struct { - repository remoteDoguDescriptorRepository - cache *gocache.Cache -} - -func NewCache(repository remoteDoguDescriptorRepository, cache *gocache.Cache) *Cache { - return &Cache{ - repository: repository, - cache: cache, - } -} - -func (c *Cache) GetLatest(ctx context.Context, name cescommons.QualifiedName) (*core.Dogu, error) { - // we cannot cache latest for sure - dogu, err := c.repository.GetLatest(ctx, name) - if err != nil { - return nil, err - } - version, err := core.ParseVersion(dogu.Version) - if err != nil { - return nil, fmt.Errorf("cannot populate cache as latest dogu version cannot be parsed from response: %w", err) - } - c.SetCached( - cescommons.QualifiedVersion{ - Name: name, - Version: version, - }, - dogu, - ) - return dogu, nil -} - -func (c *Cache) Get(ctx context.Context, version cescommons.QualifiedVersion) (*core.Dogu, error) { - logger := log.FromContext(ctx).WithName("DoguDescriptorCache") - dogu, found := c.GetCached(version) - if found { - logger.V(2).Info("dogu descriptor cache hit", "dogu", version) - return dogu, nil - } - - // cache missed - dogu, err := c.repository.Get(ctx, version) - if err != nil { - return nil, err - } - c.SetCached(version, dogu) - return dogu, nil -} - -func cacheKeyFromReference(qualifiedDoguVersion cescommons.QualifiedVersion) string { - return fmt.Sprintf("%s:%s", qualifiedDoguVersion.Name.String(), qualifiedDoguVersion.Version.String()) -} - -func (c *Cache) GetCached(qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, bool) { - cached, found := c.cache.Get(cacheKeyFromReference(qualifiedDoguVersion)) - if found { - return cached.(*core.Dogu), found - } - return nil, found -} - -func (c *Cache) SetCached(qualifiedDoguVersion cescommons.QualifiedVersion, dogu *core.Dogu) { - c.cache.SetDefault(cacheKeyFromReference(qualifiedDoguVersion), dogu) -} diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go new file mode 100644 index 00000000..eed50cc8 --- /dev/null +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -0,0 +1,99 @@ +package doguregistry + +import ( + "context" + "errors" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" + "github.com/cloudogu/cesapp-lib/core" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +type DoguDescriptorRepository struct { + remoteRepository remoteDoguDescriptorRepository + localRepository localDoguDescriptorRepository +} + +func NewDoguDescriptorRepository(remoteRepository remoteDoguDescriptorRepository, localRepository localDoguDescriptorRepository) *DoguDescriptorRepository { + return &DoguDescriptorRepository{remoteRepository: remoteRepository, localRepository: localRepository} +} + +func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, error) { + logger := log.FromContext(ctx). + WithName("DoguDescriptorRepository.GetDogu"). + WithValues("dogu", qualifiedDoguVersion.Name.SimpleName) + + // Try to get the dogu from the local repository first. + dogu := r.getLocalDogu(ctx, qualifiedDoguVersion, logger) + if dogu != nil { + return dogu, nil + } + + dogu, err := r.getRemoteDogu(ctx, qualifiedDoguVersion, dogu) + if err != nil { + return nil, err + } + + // TODO: doesn't work with "old" dogu operator + //err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) + if err != nil { + // just log the error, no need to fail the reconcilation + logger.Info("failed to add dogu to local repository", + "error", err, + "dogu", qualifiedDoguVersion.Name.SimpleName, + "version", qualifiedDoguVersion.Version.Raw) + } + return dogu, nil +} + +func (r *DoguDescriptorRepository) getRemoteDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion, dogu *core.Dogu) (*core.Dogu, error) { + // do not retry here. If any error happens, just reconcile later. We only do retries in application level. + // This makes the code way easier and non-blocking. + dogu, err := r.remoteRepository.Get(ctx, qualifiedDoguVersion) + if err != nil { + if cloudoguerrors.IsNotFoundError(err) { + return nil, domainservice.NewNotFoundError( + err, + "dogu %q with version %q could not be found", + qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, + ) + } else { + return nil, domainservice.NewInternalError( + err, + "failed to get dogu %q with version %q", + qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, + ) + } + } + return dogu, nil +} + +func (r *DoguDescriptorRepository) getLocalDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion, logger logr.Logger) *core.Dogu { + dogu, err := r.localRepository.Get(ctx, cescommons.NewSimpleNameVersion(qualifiedDoguVersion.Name.SimpleName, qualifiedDoguVersion.Version)) + if err == nil { + // TODO: move to V(2) later + logger.Info("local dogu descriptor hit", "dogu", qualifiedDoguVersion.Name.SimpleName) + return dogu + } else { + // TODO: move to V(2) later + logger.Info("local dogu descriptor miss", "error", err) + return nil + } +} + +func (r *DoguDescriptorRepository) GetDogus(ctx context.Context, dogusToLoad []cescommons.QualifiedVersion) (map[cescommons.QualifiedName]*core.Dogu, error) { + dogus := make(map[cescommons.QualifiedName]*core.Dogu) + + var errs []error + for _, doguRef := range dogusToLoad { + dogu, err := r.GetDogu(ctx, doguRef) + errs = append(errs, err) + + dogus[doguRef.Name] = dogu + } + + return dogus, errors.Join(errs...) +} diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository_test.go b/pkg/adapter/doguregistry/doguDescriptorRepository_test.go new file mode 100644 index 00000000..d450be9e --- /dev/null +++ b/pkg/adapter/doguregistry/doguDescriptorRepository_test.go @@ -0,0 +1,227 @@ +package doguregistry + +import ( + "context" + "fmt" + "testing" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" + "github.com/cloudogu/cesapp-lib/core" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestNewRemote(t *testing.T) { + // given + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + + // when + actual := NewDoguDescriptorRepository(remoteRepoMock, localRepoMock) + + // then + assert.NotEmpty(t, actual) +} + +func TestRemote_GetDogu(t *testing.T) { + t.Run("should return not found error on remote miss", func(t *testing.T) { + // given + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + version, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + qSimpleDoguVersion := cescommons.SimpleNameVersion{Name: "my-dogu", Version: version} + // no local hit + localRepoMock.EXPECT().Get(context.TODO(), qSimpleDoguVersion).Return(nil, assert.AnError) + qDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, + Version: version, + } + remoteRepoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(nil, cloudoguerrors.NewNotFoundError(cloudoguerrors.Error{})) + + sut := &DoguDescriptorRepository{remoteRepoMock, localRepoMock} + + // when + actual, err := sut.GetDogu(context.TODO(), qDoguVersion) + + // then + require.Error(t, err) + assert.Nil(t, actual) + assert.ErrorContains(t, err, "dogu \"testing/my-dogu\" with version \"1.2.3\" could not be found") + expectedErr := &domainservice.NotFoundError{} + assert.ErrorAs(t, err, &expectedErr) + }) + t.Run("should return internal error on remote error", func(t *testing.T) { + // given + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + + version, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + // no local hit + qSimpleDoguVersion := cescommons.SimpleNameVersion{Name: "my-dogu", Version: version} + localRepoMock.EXPECT().Get(context.TODO(), qSimpleDoguVersion).Return(nil, assert.AnError) + qDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, + Version: version, + } + remoteRepoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(nil, assert.AnError) + + sut := &DoguDescriptorRepository{remoteRepoMock, localRepoMock} + + // when + actual, err := sut.GetDogu(context.TODO(), qDoguVersion) + + // then + require.Error(t, err) + assert.Nil(t, actual) + assert.ErrorContains(t, err, "failed to get dogu \"testing/my-dogu\" with version \"1.2.3\"") + assert.ErrorIs(t, err, assert.AnError) + expectedErr := &domainservice.InternalError{} + assert.ErrorAs(t, err, &expectedErr) + }) + t.Run("should return dogu on remote hit", func(t *testing.T) { + // given + expectedDogu := core.Dogu{Name: "testing/my-dogu", Version: "1.2.3"} + + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + version, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + // no local hit + qSimpleDoguVersion := cescommons.SimpleNameVersion{Name: "my-dogu", Version: version} + localRepoMock.EXPECT().Get(context.TODO(), qSimpleDoguVersion).Return(nil, assert.AnError) + qDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, + Version: version, + } + remoteRepoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(&expectedDogu, nil) + localRepoMock.EXPECT().Add(context.TODO(), qDoguVersion.Name.SimpleName, &expectedDogu).Return(nil) + + sut := &DoguDescriptorRepository{remoteRepoMock, localRepoMock} + + // when + actual, err := sut.GetDogu(context.TODO(), qDoguVersion) + + // then + require.NoError(t, err) + assert.Equal(t, &expectedDogu, actual) + }) + t.Run("should return no error on local dogu description addition error", func(t *testing.T) { + // given + expectedDogu := core.Dogu{Name: "testing/my-dogu", Version: "1.2.3"} + + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + version, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + // no local hit + qSimpleDoguVersion := cescommons.SimpleNameVersion{Name: "my-dogu", Version: version} + localRepoMock.EXPECT().Get(context.TODO(), qSimpleDoguVersion).Return(nil, assert.AnError) + qDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, + Version: version, + } + remoteRepoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(&expectedDogu, nil) + localRepoMock.EXPECT().Add(context.TODO(), qDoguVersion.Name.SimpleName, &expectedDogu).Return(assert.AnError) + + sut := &DoguDescriptorRepository{remoteRepoMock, localRepoMock} + + // when + actual, err := sut.GetDogu(context.TODO(), qDoguVersion) + + // then + require.NoError(t, err) + assert.Equal(t, &expectedDogu, actual) + }) + t.Run("should return dogu on local hit", func(t *testing.T) { + // given + expectedDogu := core.Dogu{Name: "testing/my-dogu", Version: "1.2.3"} + + localRepoMock := newMockLocalDoguDescriptorRepository(t) + version, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + // no local hit + qSimpleDoguVersion := cescommons.SimpleNameVersion{Name: "my-dogu", Version: version} + localRepoMock.EXPECT().Get(context.TODO(), qSimpleDoguVersion).Return(&expectedDogu, nil) + + sut := &DoguDescriptorRepository{nil, localRepoMock} + + // when + qDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, + Version: version, + } + actual, err := sut.GetDogu(context.TODO(), qDoguVersion) + + // then + require.NoError(t, err) + assert.Equal(t, &expectedDogu, actual) + }) +} + +func TestRemote_GetDogus(t *testing.T) { + t.Run("should return collected errors on remote calls", func(t *testing.T) { + // given + remoteRepoMock := newMockRemoteDoguDescriptorRepository(t) + localRepoMock := newMockLocalDoguDescriptorRepository(t) + expectedDogu := core.Dogu{Name: "testing/good-dogu", Version: "0.1.2"} + goodVersion, err := core.ParseVersion("0.1.2") + require.NoError(t, err) + NotFoundVersion, err := core.ParseVersion("1.2.3") + require.NoError(t, err) + OtherVersion, err := core.ParseVersion("2.3.4") + require.NoError(t, err) + qGoodDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "good-dogu"}, + Version: goodVersion, + } + qNotFoundDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "not-found"}, + Version: NotFoundVersion, + } + qOtherErrorDoguVersion := cescommons.QualifiedVersion{ + Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "other-error"}, + Version: OtherVersion, + } + // no local hits + localRepoMock.EXPECT().Get(context.TODO(), mock.Anything).Return(nil, assert.AnError) + remoteRepoMock.EXPECT().Get(context.TODO(), qGoodDoguVersion).Return(&expectedDogu, nil) + notFoundError := fmt.Errorf("404 not found") + remoteRepoMock.EXPECT().Get(context.TODO(), qNotFoundDoguVersion).Return(nil, notFoundError) + remoteRepoMock.EXPECT().Get(context.TODO(), qOtherErrorDoguVersion).Return(nil, assert.AnError) + localRepoMock.EXPECT().Add(context.TODO(), qGoodDoguVersion.Name.SimpleName, &expectedDogu).Return(nil) + + sut := &DoguDescriptorRepository{remoteRepoMock, localRepoMock} + dogusToLoad := []cescommons.QualifiedVersion{ + qGoodDoguVersion, + qOtherErrorDoguVersion, + qNotFoundDoguVersion, + } + + expectedDogus := map[cescommons.QualifiedName]*core.Dogu{ + cescommons.QualifiedName{Namespace: "testing", SimpleName: "good-dogu"}: &expectedDogu, + cescommons.QualifiedName{Namespace: "testing", SimpleName: "not-found"}: nil, + cescommons.QualifiedName{Namespace: "testing", SimpleName: "other-error"}: nil, + } + + // when + actual, err := sut.GetDogus(context.TODO(), dogusToLoad) + + // then + require.Error(t, err) + + assert.ErrorContains(t, err, "dogu \"testing/not-found\" with version \"1.2.3\": 404 not found") + assert.ErrorIs(t, err, notFoundError) + + assert.ErrorContains(t, err, "failed to get dogu \"testing/other-error\" with version \"2.3.4\"") + assert.ErrorIs(t, err, assert.AnError) + expectedInternal := &domainservice.InternalError{} + assert.ErrorAs(t, err, &expectedInternal) + + assert.Equal(t, expectedDogus, actual) + }) +} diff --git a/pkg/adapter/doguregistry/interfaces.go b/pkg/adapter/doguregistry/interfaces.go index 1930cf6a..4a814c0c 100644 --- a/pkg/adapter/doguregistry/interfaces.go +++ b/pkg/adapter/doguregistry/interfaces.go @@ -1,7 +1,17 @@ package doguregistry -import cescommons "github.com/cloudogu/ces-commons-lib/dogu" +import ( + "context" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/cesapp-lib/core" +) type remoteDoguDescriptorRepository interface { cescommons.RemoteDoguDescriptorRepository } + +type localDoguDescriptorRepository interface { + Get(ctx context.Context, doguVersion cescommons.SimpleNameVersion) (*core.Dogu, error) + Add(ctx context.Context, name cescommons.SimpleName, dogu *core.Dogu) error +} diff --git a/pkg/adapter/doguregistry/mock_localDoguDescriptorRepository_test.go b/pkg/adapter/doguregistry/mock_localDoguDescriptorRepository_test.go new file mode 100644 index 00000000..63dc1faa --- /dev/null +++ b/pkg/adapter/doguregistry/mock_localDoguDescriptorRepository_test.go @@ -0,0 +1,146 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package doguregistry + +import ( + context "context" + + dogu "github.com/cloudogu/ces-commons-lib/dogu" + core "github.com/cloudogu/cesapp-lib/core" + + mock "github.com/stretchr/testify/mock" +) + +// mockLocalDoguDescriptorRepository is an autogenerated mock type for the localDoguDescriptorRepository type +type mockLocalDoguDescriptorRepository struct { + mock.Mock +} + +type mockLocalDoguDescriptorRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *mockLocalDoguDescriptorRepository) EXPECT() *mockLocalDoguDescriptorRepository_Expecter { + return &mockLocalDoguDescriptorRepository_Expecter{mock: &_m.Mock} +} + +// Add provides a mock function with given fields: ctx, name, _a2 +func (_m *mockLocalDoguDescriptorRepository) Add(ctx context.Context, name dogu.SimpleName, _a2 *core.Dogu) error { + ret := _m.Called(ctx, name, _a2) + + if len(ret) == 0 { + panic("no return value specified for Add") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, dogu.SimpleName, *core.Dogu) error); ok { + r0 = rf(ctx, name, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockLocalDoguDescriptorRepository_Add_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Add' +type mockLocalDoguDescriptorRepository_Add_Call struct { + *mock.Call +} + +// Add is a helper method to define mock.On call +// - ctx context.Context +// - name dogu.SimpleName +// - _a2 *core.Dogu +func (_e *mockLocalDoguDescriptorRepository_Expecter) Add(ctx interface{}, name interface{}, _a2 interface{}) *mockLocalDoguDescriptorRepository_Add_Call { + return &mockLocalDoguDescriptorRepository_Add_Call{Call: _e.mock.On("Add", ctx, name, _a2)} +} + +func (_c *mockLocalDoguDescriptorRepository_Add_Call) Run(run func(ctx context.Context, name dogu.SimpleName, _a2 *core.Dogu)) *mockLocalDoguDescriptorRepository_Add_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(dogu.SimpleName), args[2].(*core.Dogu)) + }) + return _c +} + +func (_c *mockLocalDoguDescriptorRepository_Add_Call) Return(_a0 error) *mockLocalDoguDescriptorRepository_Add_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockLocalDoguDescriptorRepository_Add_Call) RunAndReturn(run func(context.Context, dogu.SimpleName, *core.Dogu) error) *mockLocalDoguDescriptorRepository_Add_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: ctx, doguVersion +func (_m *mockLocalDoguDescriptorRepository) Get(ctx context.Context, doguVersion dogu.SimpleNameVersion) (*core.Dogu, error) { + ret := _m.Called(ctx, doguVersion) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 *core.Dogu + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, dogu.SimpleNameVersion) (*core.Dogu, error)); ok { + return rf(ctx, doguVersion) + } + if rf, ok := ret.Get(0).(func(context.Context, dogu.SimpleNameVersion) *core.Dogu); ok { + r0 = rf(ctx, doguVersion) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*core.Dogu) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, dogu.SimpleNameVersion) error); ok { + r1 = rf(ctx, doguVersion) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockLocalDoguDescriptorRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type mockLocalDoguDescriptorRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - doguVersion dogu.SimpleNameVersion +func (_e *mockLocalDoguDescriptorRepository_Expecter) Get(ctx interface{}, doguVersion interface{}) *mockLocalDoguDescriptorRepository_Get_Call { + return &mockLocalDoguDescriptorRepository_Get_Call{Call: _e.mock.On("Get", ctx, doguVersion)} +} + +func (_c *mockLocalDoguDescriptorRepository_Get_Call) Run(run func(ctx context.Context, doguVersion dogu.SimpleNameVersion)) *mockLocalDoguDescriptorRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(dogu.SimpleNameVersion)) + }) + return _c +} + +func (_c *mockLocalDoguDescriptorRepository_Get_Call) Return(_a0 *core.Dogu, _a1 error) *mockLocalDoguDescriptorRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockLocalDoguDescriptorRepository_Get_Call) RunAndReturn(run func(context.Context, dogu.SimpleNameVersion) (*core.Dogu, error)) *mockLocalDoguDescriptorRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// newMockLocalDoguDescriptorRepository creates a new instance of mockLocalDoguDescriptorRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockLocalDoguDescriptorRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *mockLocalDoguDescriptorRepository { + mock := &mockLocalDoguDescriptorRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/adapter/doguregistry/remote.go b/pkg/adapter/doguregistry/remote.go deleted file mode 100644 index db5a6819..00000000 --- a/pkg/adapter/doguregistry/remote.go +++ /dev/null @@ -1,55 +0,0 @@ -package doguregistry - -import ( - "context" - "errors" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" - "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" -) - -type Remote struct { - repository remoteDoguDescriptorRepository -} - -func NewRemote(repository remoteDoguDescriptorRepository) *Remote { - return &Remote{repository: repository} -} - -func (r *Remote) GetDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, error) { - // do not retry here. If any error happens, just reconcile later. We only do retries in application level. - // This makes the code way easier and non-blocking. - dogu, err := r.repository.Get(ctx, qualifiedDoguVersion) - if err != nil { - if cloudoguerrors.IsNotFoundError(err) { - return nil, domainservice.NewNotFoundError( - err, - "dogu %q with version %q could not be found", - qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, - ) - } else { - return nil, domainservice.NewInternalError( - err, - "failed to get dogu %q with version %q", - qualifiedDoguVersion.Name, qualifiedDoguVersion.Version.Raw, - ) - } - } - return dogu, nil -} - -func (r *Remote) GetDogus(ctx context.Context, dogusToLoad []cescommons.QualifiedVersion) (map[cescommons.QualifiedName]*core.Dogu, error) { - dogus := make(map[cescommons.QualifiedName]*core.Dogu) - - var errs []error - for _, doguRef := range dogusToLoad { - dogu, err := r.GetDogu(ctx, doguRef) - errs = append(errs, err) - - dogus[doguRef.Name] = dogu - } - - return dogus, errors.Join(errs...) -} diff --git a/pkg/adapter/doguregistry/remote_test.go b/pkg/adapter/doguregistry/remote_test.go deleted file mode 100644 index e6f623db..00000000 --- a/pkg/adapter/doguregistry/remote_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package doguregistry - -import ( - "context" - "fmt" - "testing" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - cloudoguerrors "github.com/cloudogu/ces-commons-lib/errors" - "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestNewRemote(t *testing.T) { - // given - repoMock := newMockRemoteDoguDescriptorRepository(t) - - // when - actual := NewRemote(repoMock) - - // then - assert.NotEmpty(t, actual) -} - -func TestRemote_GetDogu(t *testing.T) { - t.Run("should return not found error", func(t *testing.T) { - // given - repoMock := newMockRemoteDoguDescriptorRepository(t) - version, err := core.ParseVersion("1.2.3") - require.NoError(t, err) - qDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, - Version: version, - } - repoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(nil, cloudoguerrors.NewNotFoundError(cloudoguerrors.Error{})) - - sut := &Remote{repoMock} - - // when - actual, err := sut.GetDogu(context.TODO(), qDoguVersion) - - // then - require.Error(t, err) - assert.Nil(t, actual) - assert.ErrorContains(t, err, "dogu \"testing/my-dogu\" with version \"1.2.3\" could not be found") - expectedErr := &domainservice.NotFoundError{} - assert.ErrorAs(t, err, &expectedErr) - }) - t.Run("should return internal error", func(t *testing.T) { - // given - repoMock := newMockRemoteDoguDescriptorRepository(t) - - version, err := core.ParseVersion("1.2.3") - require.NoError(t, err) - qDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, - Version: version, - } - repoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(nil, assert.AnError) - - sut := &Remote{repoMock} - - // when - actual, err := sut.GetDogu(context.TODO(), qDoguVersion) - - // then - require.Error(t, err) - assert.Nil(t, actual) - assert.ErrorContains(t, err, "failed to get dogu \"testing/my-dogu\" with version \"1.2.3\"") - assert.ErrorIs(t, err, assert.AnError) - expectedErr := &domainservice.InternalError{} - assert.ErrorAs(t, err, &expectedErr) - }) - t.Run("should return dogu", func(t *testing.T) { - // given - expectedDogu := core.Dogu{Name: "testing/my-dogu", Version: "1.2.3"} - - repoMock := newMockRemoteDoguDescriptorRepository(t) - version, err := core.ParseVersion("1.2.3") - require.NoError(t, err) - qDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "my-dogu"}, - Version: version, - } - repoMock.EXPECT().Get(context.TODO(), qDoguVersion).Return(&expectedDogu, nil) - - sut := &Remote{repoMock} - - // when - actual, err := sut.GetDogu(context.TODO(), qDoguVersion) - - // then - require.NoError(t, err) - assert.Equal(t, &expectedDogu, actual) - }) -} - -func TestRemote_GetDogus(t *testing.T) { - t.Run("should return collected errors", func(t *testing.T) { - // given - repoMock := newMockRemoteDoguDescriptorRepository(t) - expectedDogu := core.Dogu{Name: "testing/good-dogu", Version: "0.1.2"} - goodVersion, err := core.ParseVersion("0.1.2") - require.NoError(t, err) - NotFoundVersion, err := core.ParseVersion("1.2.3") - require.NoError(t, err) - OtherVersion, err := core.ParseVersion("2.3.4") - require.NoError(t, err) - qGoodDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "good-dogu"}, - Version: goodVersion, - } - qNotFoundDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "not-found"}, - Version: NotFoundVersion, - } - qOtherErrorDoguVersion := cescommons.QualifiedVersion{ - Name: cescommons.QualifiedName{Namespace: "testing", SimpleName: "other-error"}, - Version: OtherVersion, - } - repoMock.EXPECT().Get(context.TODO(), qGoodDoguVersion).Return(&expectedDogu, nil) - notFoundError := fmt.Errorf("404 not found") - repoMock.EXPECT().Get(context.TODO(), qNotFoundDoguVersion).Return(nil, notFoundError) - repoMock.EXPECT().Get(context.TODO(), qOtherErrorDoguVersion).Return(nil, assert.AnError) - - sut := &Remote{repoMock} - dogusToLoad := []cescommons.QualifiedVersion{ - qGoodDoguVersion, - qOtherErrorDoguVersion, - qNotFoundDoguVersion, - } - - expectedDogus := map[cescommons.QualifiedName]*core.Dogu{ - cescommons.QualifiedName{Namespace: "testing", SimpleName: "good-dogu"}: &expectedDogu, - cescommons.QualifiedName{Namespace: "testing", SimpleName: "not-found"}: nil, - cescommons.QualifiedName{Namespace: "testing", SimpleName: "other-error"}: nil, - } - - // when - actual, err := sut.GetDogus(context.TODO(), dogusToLoad) - - // then - require.Error(t, err) - - assert.ErrorContains(t, err, "dogu \"testing/not-found\" with version \"1.2.3\": 404 not found") - assert.ErrorIs(t, err, notFoundError) - - assert.ErrorContains(t, err, "failed to get dogu \"testing/other-error\" with version \"2.3.4\"") - assert.ErrorIs(t, err, assert.AnError) - expectedInternal := &domainservice.InternalError{} - assert.ErrorAs(t, err, &expectedInternal) - - assert.Equal(t, expectedDogus, actual) - }) -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 6e6eb65f..b4737bcb 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -65,9 +65,9 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) return nil, err } - conditions := &blueprintCR.Status.Conditions + conditions := blueprintCR.Status.Conditions if conditions == nil { - conditions = &[]domain.Condition{} + conditions = []domain.Condition{} } blueprintSpec := &domain.BlueprintSpec{ @@ -126,7 +126,7 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.Blueprin Status: v2.BlueprintStatus{ EffectiveBlueprint: effectiveBlueprint, StateDiff: serializerv2.ConvertToStateDiffDTO(spec.StateDiff), - Conditions: *spec.Conditions, + Conditions: spec.Conditions, }, } diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index d1122aea..7c77ff76 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -2,14 +2,13 @@ package pkg import ( "fmt" - "time" adapterconfigk8s "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/config/kubernetes" v2 "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/blueprintcr/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/sensitiveconfigref" + "github.com/cloudogu/k8s-registry-lib/dogu" "github.com/cloudogu/k8s-registry-lib/repository" remotedogudescriptor "github.com/cloudogu/remote-dogu-descriptor-lib/repository" - "github.com/patrickmn/go-cache" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" @@ -40,13 +39,6 @@ var blueprintOperatorName = common.QualifiedComponentName{ SimpleName: "k8s-blueprint-operator", } -var ( - // we will often load the same dogu descriptors as long as the blueprint will be applied. - // Therefore, the cache should live long enough, so that a complete installation can profit from it. - doguDescriptorCacheExpiration = 15 * time.Minute - doguDescriptorCacheCleanUpInterval = 15 * time.Minute -) - // Bootstrap creates the ApplicationContext and does all dependency injection of the whole application. func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, namespace string) (*ApplicationContext, error) { ecosystemClientSet, err := createEcosystemClientSet(restConfig) @@ -67,7 +59,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name eventRecorder, ) - remoteDoguRegistry, err := createRemoteDoguRegistry() + remoteDoguRegistry, err := createRemoteDoguRegistry(ecosystemClientSet, namespace) if err != nil { return nil, err } @@ -114,7 +106,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name }, nil } -func createRemoteDoguRegistry() (*doguregistry.Remote, error) { +func createRemoteDoguRegistry(clientSet *adapterk8s.ClientSet, namespace string) (*doguregistry.DoguDescriptorRepository, error) { remoteConfig, err := config.GetRemoteConfiguration() if err != nil { return nil, fmt.Errorf("failed to get remote dogu registry config: %w", err) @@ -126,12 +118,11 @@ func createRemoteDoguRegistry() (*doguregistry.Remote, error) { } doguRemoteRepository, err := remotedogudescriptor.NewRemoteDoguDescriptorRepository(remoteConfig, remoteCreds) + doguLocalRepository := dogu.NewLocalDoguDescriptorRepository(clientSet.CoreV1().ConfigMaps(namespace)) if err != nil { return nil, fmt.Errorf("failed to create new remote dogu repository: %w", err) } - goCache := cache.New(doguDescriptorCacheExpiration, doguDescriptorCacheCleanUpInterval) - registryCache := doguregistry.NewCache(doguRemoteRepository, goCache) - return doguregistry.NewRemote(registryCache), nil + return doguregistry.NewDoguDescriptorRepository(doguRemoteRepository, doguLocalRepository), nil } func createEcosystemClientSet(restConfig *rest.Config) (*adapterk8s.ClientSet, error) { From 371a5ca320a969b734558af8a8be16e9ca855f0e Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 5 Sep 2025 06:48:48 +0200 Subject: [PATCH 049/119] #121 prevent additional health checks, when unnecessary --- pkg/application/applyComponentsUseCase.go | 6 +- .../applyComponentsUseCase_test.go | 33 ++++++----- pkg/application/applyDogusUseCase.go | 6 +- pkg/application/applyDogusUseCase_test.go | 33 ++++++----- pkg/application/blueprintSpecChangeUseCase.go | 57 +++++++++++++------ .../blueprintSpecValidationUseCase_test.go | 9 +-- .../completeBlueprintUseCase_test.go | 10 ++-- .../ecosystemConfigUseCase_test.go | 42 +++++++------- .../ecosystemHealthUseCase_test.go | 20 +++---- pkg/application/interfaces.go | 4 +- pkg/application/selfUpgradeUseCase_test.go | 18 +++--- pkg/application/stateDiffUseCase_test.go | 10 ++-- 12 files changed, 140 insertions(+), 108 deletions(-) diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go index 4283d367..19ffbb15 100644 --- a/pkg/application/applyComponentsUseCase.go +++ b/pkg/application/applyComponentsUseCase.go @@ -28,15 +28,15 @@ func NewApplyComponentsUseCase( // The conditions in the blueprint will be set accordingly. // returns domainservice.ConflictError if there was a concurrent update to the blueprint or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. -func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { err := useCase.componentUseCase.ApplyComponentStates(ctx, blueprint) changed := blueprint.SetComponentsAppliedCondition(err) if changed { updateErr := useCase.repo.Update(ctx, blueprint) if updateErr != nil { - return fmt.Errorf("cannot update condition while applying components: %w", errors.Join(updateErr, err)) + return changed, fmt.Errorf("cannot update condition while applying components: %w", errors.Join(updateErr, err)) } } - return err + return changed, err } diff --git a/pkg/application/applyComponentsUseCase_test.go b/pkg/application/applyComponentsUseCase_test.go index 078c6ed3..4aa80a79 100644 --- a/pkg/application/applyComponentsUseCase_test.go +++ b/pkg/application/applyComponentsUseCase_test.go @@ -22,7 +22,7 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { }, }, }, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -31,10 +31,11 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - err := useCase.ApplyComponents(testCtx, blueprint) + changed, err := useCase.ApplyComponents(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, changed) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) }) @@ -42,7 +43,7 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { t.Run("no update without condition change", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: domain.StateDiff{}, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -51,17 +52,19 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil).Twice() useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - err := useCase.ApplyComponents(testCtx, blueprint) + changed, err := useCase.ApplyComponents(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) - err = useCase.ApplyComponents(testCtx, blueprint) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, changed) + changed, err = useCase.ApplyComponents(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.False(t, changed) }) t.Run("fail to apply components", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -70,15 +73,16 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - err := useCase.ApplyComponents(testCtx, blueprint) + changed, err := useCase.ApplyComponents(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, changed) }) t.Run("fail to update blueprint", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -87,9 +91,10 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - err := useCase.ApplyComponents(testCtx, blueprint) + changed, err := useCase.ApplyComponents(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.True(t, changed) }) } diff --git a/pkg/application/applyDogusUseCase.go b/pkg/application/applyDogusUseCase.go index a951cdfd..8634cee3 100644 --- a/pkg/application/applyDogusUseCase.go +++ b/pkg/application/applyDogusUseCase.go @@ -28,15 +28,15 @@ func NewApplyDogusUseCase( // The conditions in the blueprint will be set accordingly. // returns domainservice.ConflictError if there was a concurrent update to the blueprint or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. -func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { err := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) changed := blueprint.SetDogusAppliedCondition(err) if changed { updateErr := useCase.repo.Update(ctx, blueprint) if updateErr != nil { - return fmt.Errorf("cannot update condition while applying dogus: %w", errors.Join(updateErr, err)) + return changed, fmt.Errorf("cannot update condition while applying dogus: %w", errors.Join(updateErr, err)) } } - return err + return changed, err } diff --git a/pkg/application/applyDogusUseCase_test.go b/pkg/application/applyDogusUseCase_test.go index 59ac1a7b..4460629b 100644 --- a/pkg/application/applyDogusUseCase_test.go +++ b/pkg/application/applyDogusUseCase_test.go @@ -12,7 +12,7 @@ import ( func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { t.Run("ok", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ DoguDiffs: domain.DoguDiffs{ { @@ -31,17 +31,18 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) - err := useCase.ApplyDogus(testCtx, blueprint) + changed, err := useCase.ApplyDogus(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, changed) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}, blueprint.Events[0]) }) t.Run("no update without condition change", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -50,17 +51,19 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) - err := useCase.ApplyDogus(testCtx, blueprint) + changed, err := useCase.ApplyDogus(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) - err = useCase.ApplyDogus(testCtx, blueprint) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, changed) + changed, err = useCase.ApplyDogus(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.False(t, changed) }) t.Run("fail to apply dogus", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -69,15 +72,16 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) - err := useCase.ApplyDogus(testCtx, blueprint) + changed, err := useCase.ApplyDogus(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, changed) }) t.Run("fail to update blueprint", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -86,9 +90,10 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) - err := useCase.ApplyDogus(testCtx, blueprint) + changed, err := useCase.ApplyDogus(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.True(t, changed) }) } diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index ec933f4e..1c1d597d 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "sigs.k8s.io/controller-runtime/pkg/log" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" @@ -74,7 +75,28 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C logger.V(1).Info("handle blueprint") - err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) + err = useCase.prepareBlueprint(ctx, blueprint) + if err != nil { + return err + } + + if !blueprint.ShouldBeApplied() { + // just stop the loop here on dry run or early exit + return nil + } + + // === Apply from here on === + err = useCase.applyBlueprint(ctx, blueprint) + if err != nil { + return err + } + + logger.Info("blueprint successfully applied") + return nil +} + +func (useCase *BlueprintSpecChangeUseCase) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) if err != nil { return err } @@ -98,14 +120,11 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C if err != nil { return err } + return nil +} - if !blueprint.ShouldBeApplied() { - // just stop the loop here on dry run or early exit - return nil - } - - // === Apply from here on === - err = useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) +func (useCase *BlueprintSpecChangeUseCase) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) if err != nil { // could be a domain.AwaitSelfUpgradeError to trigger another reconcile return err @@ -114,30 +133,32 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C if err != nil { return err } - err = useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) + changedComponents, err := useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) if err != nil { return err } // check after applying components - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err + if changedComponents { + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } } - err = useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) + changedDogus, err := useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) if err != nil { return err } // check after installing or updating dogus - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err + if changedDogus { + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } } err = useCase.applyUseCase.CompleteBlueprint(ctx, blueprint) if err != nil { return err } - - logger.Info("blueprint successfully applied") return nil } diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index 8658a7a8..f0e4e05c 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -41,6 +41,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_ok(t *testing.T) { //then require.NoError(t, err) + assert.Nil(t, blueprint.Conditions, "should not set conditions") } func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing.T) { @@ -101,7 +102,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) ctx := context.Background() @@ -119,7 +120,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_ok(t *testing.T) // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionValid)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionValid)) } func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testing.T) { @@ -138,7 +139,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin Version: version, TargetState: domain.TargetStatePresent, }}}, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } invalidDependencyError := errors.New("invalid dependencies") invalidMountsError := errors.New("invalid mounts") @@ -150,7 +151,7 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin err := useCase.ValidateBlueprintSpecDynamically(ctx, blueprint) // then - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionValid)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionValid)) require.Error(t, err) var invalidError *domain.InvalidBlueprintError diff --git a/pkg/application/completeBlueprintUseCase_test.go b/pkg/application/completeBlueprintUseCase_test.go index f98bbebe..162c57a2 100644 --- a/pkg/application/completeBlueprintUseCase_test.go +++ b/pkg/application/completeBlueprintUseCase_test.go @@ -20,7 +20,7 @@ func TestNewApplyBlueprintSpecUseCase(t *testing.T) { func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { t.Run("ok", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -31,11 +31,11 @@ func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionCompleted)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionCompleted)) }) t.Run("no change if already completed", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) @@ -46,11 +46,11 @@ func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { require.NoError(t, err) err = useCase.CompleteBlueprint(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionCompleted)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionCompleted)) }) t.Run("repo error while saving", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } repoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 748ad6bd..4c4f6c02 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -109,7 +109,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { blueprintRepoMock := newMockBlueprintSpecRepository(t) blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -125,14 +125,14 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { err := sut.ApplyConfig(testCtx, blueprint) // then - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) require.NoError(t, err) }) t.Run("should return on mark apply config start error", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -155,13 +155,13 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { require.Error(t, err) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "cannot mark blueprint as applying config") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) }) t.Run("error applying dogu config", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -197,7 +197,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { require.Error(t, err) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply normal dogu config") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) @@ -211,7 +211,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ redmine: { @@ -247,7 +247,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply sensitive dogu config") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) @@ -264,7 +264,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, @@ -300,7 +300,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply global config") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) require.Len(t, blueprint.Events, 2) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) @@ -556,7 +556,7 @@ func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { t.Run("should set applied condition and event", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -569,7 +569,7 @@ func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) }) @@ -577,7 +577,7 @@ func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { t.Run("should return an error on update error", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -591,7 +591,7 @@ func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { // then assert.ErrorContains(t, err, "failed to mark ecosystem config applied") assert.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) }) @@ -601,7 +601,7 @@ func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { t.Run("should set condition and event apply config", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -615,7 +615,7 @@ func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { // then require.NoError(t, err) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) require.Len(t, blueprint.Events, 1) assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) }) @@ -623,7 +623,7 @@ func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { t.Run("should return an error on update error", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -645,7 +645,7 @@ func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { t.Run("should set applied condition and event", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -658,17 +658,17 @@ func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { // then require.Error(t, err) - condition := meta.FindStatusCondition(*blueprint.Conditions, domain.ConditionConfigApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, domain.ConditionConfigApplied) require.NotNil(t, condition) assert.Equal(t, err.Error(), condition.Message) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) assert.IsType(t, domain.ApplyEcosystemConfigFailedEvent{}, blueprint.Events[0]) }) t.Run("should return error on update error", func(t *testing.T) { // given spec := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/application/ecosystemHealthUseCase_test.go b/pkg/application/ecosystemHealthUseCase_test.go index 7aea1414..e14d5720 100644 --- a/pkg/application/ecosystemHealthUseCase_test.go +++ b/pkg/application/ecosystemHealthUseCase_test.go @@ -48,7 +48,7 @@ func TestNewEcosystemHealthUseCase(t *testing.T) { func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { t.Run("all healthy", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: false, IgnoreComponentHealth: false, @@ -73,12 +73,12 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { require.NoError(t, err) assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionEcosystemHealthy)) }) t.Run("unhealthy", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: false, IgnoreComponentHealth: false, @@ -106,12 +106,12 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { assert.ErrorContains(t, err, "2 dogu(s) are unhealthy: postfix, scm") assert.ErrorContains(t, err, "3 component(s) are unhealthy: k8s-dogu-operator, k8s-etcd, k8s-service-discovery") assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionEcosystemHealthy)) }) t.Run("error updating blueprint", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: false, IgnoreComponentHealth: false, @@ -141,7 +141,7 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { t.Run("error getting health", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: false, IgnoreComponentHealth: false, @@ -163,13 +163,13 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) assert.True(t, meta.IsStatusConditionPresentAndEqual( - *blueprint.Conditions, domain.ConditionEcosystemHealthy, metav1.ConditionUnknown, + blueprint.Conditions, domain.ConditionEcosystemHealthy, metav1.ConditionUnknown, )) }) t.Run("no update without health change", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: false, IgnoreComponentHealth: false, @@ -192,10 +192,10 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { _, err := useCase.CheckEcosystemHealth(testCtx, blueprint) assert.ErrorContains(t, err, "ecosystem is unhealthy") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionEcosystemHealthy)) _, err = useCase.CheckEcosystemHealth(testCtx, blueprint) //no repo.Update called again assert.ErrorContains(t, err, "ecosystem is unhealthy") - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionEcosystemHealthy)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionEcosystemHealthy)) }) } diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 2c4a925a..b2b0f9dc 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -27,11 +27,11 @@ type doguInstallationUseCase interface { } type applyDogusUseCase interface { - ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error + ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) } type applyComponentsUseCase interface { - ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error + ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) } type componentInstallationUseCase interface { diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go index 2eafe6f6..4e8a893f 100644 --- a/pkg/application/selfUpgradeUseCase_test.go +++ b/pkg/application/selfUpgradeUseCase_test.go @@ -74,7 +74,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: upgradeToV2StateDiff, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } component := &ecosystem.ComponentInstallation{ @@ -92,7 +92,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { var awaitError *domain.AwaitSelfUpgradeError assert.ErrorAs(t, err, &awaitError) assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) t.Run("apply upgrade with missing component cr", func(t *testing.T) { @@ -103,7 +103,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: InstallV2StateDiff, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } var nilComponent *ecosystem.ComponentInstallation @@ -117,7 +117,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { var awaitError *domain.AwaitSelfUpgradeError assert.ErrorAs(t, err, &awaitError) assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) t.Run("check if self-upgrade is done -> not yet", func(t *testing.T) { @@ -128,7 +128,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: NoActionV2StateDiff, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } component := &ecosystem.ComponentInstallation{ @@ -144,7 +144,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { var awaitError *domain.AwaitSelfUpgradeError assert.ErrorAs(t, err, &awaitError) assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) t.Run("check if self-upgrade is done -> done", func(t *testing.T) { @@ -155,7 +155,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ StateDiff: NoActionV2StateDiff, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } component := &ecosystem.ComponentInstallation{ @@ -169,7 +169,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { err := useCase.HandleSelfUpgrade(testCtx, blueprint) assert.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(*blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) + assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) }) t.Run("cannot load component", func(t *testing.T) { @@ -247,7 +247,7 @@ func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { blueprint := &domain.BlueprintSpec{ Id: blueprintId, StateDiff: NoActionV2StateDiff, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } component := &ecosystem.ComponentInstallation{ diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 6fd3b52c..58cb600e 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -284,7 +284,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -323,7 +323,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { @@ -443,7 +443,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Global: domain.GlobalConfig{ @@ -511,7 +511,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ @@ -599,7 +599,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ From 03b045bbf2fcba4dcb819e723102417548d20a5c Mon Sep 17 00:00:00 2001 From: Marco Bergen Date: Mon, 8 Sep 2025 10:21:30 +0000 Subject: [PATCH 050/119] [#125] ignore nginx dependency --- CHANGELOG.md | 2 + pkg/application/stateDiffUseCase_test.go | 216 ++---------------- pkg/domain/blueprintMask_test.go | 1 - pkg/domain/dogu_test.go | 10 +- .../validateDependenciesDomainUseCase.go | 29 +-- 5 files changed, 32 insertions(+), 226 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3614ca7..324c70bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- [#125] ignore nginx dependencies ## [v2.7.0] - 2025-07-17 ### Fixed diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 3a987197..714169f9 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -17,10 +17,8 @@ var ( nsOfficial = cescommons.Namespace("official") nsK8s = cescommons.Namespace("k8s") - postfix = cescommons.SimpleName("postfix") - ldap = cescommons.SimpleName("ldap") - nginxIngress = cescommons.SimpleName("nginx-ingress") - nginxStatic = cescommons.SimpleName("nginx-static") + postfix = cescommons.SimpleName("postfix") + ldap = cescommons.SimpleName("ldap") postfixQualifiedDoguName = cescommons.QualifiedName{ Namespace: nsOfficial, @@ -30,14 +28,6 @@ var ( Namespace: nsOfficial, SimpleName: ldap, } - nginxIngressQualifiedDoguName = cescommons.QualifiedName{ - Namespace: nsK8s, - SimpleName: nginxIngress, - } - nginxStaticQualifiedDoguName = cescommons.QualifiedName{ - Namespace: nsK8s, - SimpleName: nginxStatic, - } nilDoguNameList []cescommons.SimpleName ) @@ -323,15 +313,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Version: mustParseVersion(t, "1.2.3"), TargetState: domain.TargetStatePresent, }, - { - Name: nginxIngressQualifiedDoguName, - Version: mustParseVersion(t, "1.8.5"), - TargetState: domain.TargetStatePresent, - }, - { - Name: nginxStaticQualifiedDoguName, - TargetState: domain.TargetStateAbsent, - }, }, }, Status: domain.StatusPhaseValidated, @@ -344,9 +325,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "ldap": {Name: ldapQualifiedDoguName, Version: mustParseVersion(t, "1.1.1")}, - "nginx-ingress": {Name: nginxIngressQualifiedDoguName, Version: mustParseVersion(t, "1.8.5")}, - "nginx-static": {Name: nginxStaticQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, + "ldap": {Name: ldapQualifiedDoguName, Version: mustParseVersion(t, "1.1.1")}, } doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) @@ -394,33 +373,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, NeededActions: []domain.Action{domain.ActionUpgrade}, }, - { - DoguName: "nginx-ingress", - Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.5"), - InstallationState: domain.TargetStatePresent, - }, - Expected: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.5"), - InstallationState: domain.TargetStatePresent, - }, - NeededActions: nil, - }, - { - DoguName: "nginx-static", - Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.6"), - InstallationState: domain.TargetStatePresent, - }, - Expected: domain.DoguDiffState{ - Namespace: "k8s", - InstallationState: domain.TargetStateAbsent, - }, - NeededActions: []domain.Action{domain.ActionUninstall}, - }, } assert.ElementsMatch(t, expectedDoguDiffs, blueprint.StateDiff.DoguDiffs) }) @@ -448,9 +400,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) - installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "nginx-static": {Name: nginxStaticQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, - } + installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{} doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) @@ -495,20 +445,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ - nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, - }, - SensitiveConfig: domain.SensitiveDoguConfig{}, - }, - }, + Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{}, }, }, Status: domain.StatusPhaseValidated, @@ -519,9 +456,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) - installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "nginx-static": {Name: nginxStaticQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, - } + installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{} doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) @@ -532,18 +467,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ - "nginxKey1": "val1", - "nginxKey2": "val2", - }) - require.NoError(t, entryErr) + var nilDogus []cescommons.SimpleName doguConfigRepoMock.EXPECT(). - GetAllExisting(testCtx, []cescommons.SimpleName{ - nginxStatic, - }). - Return(map[cescommons.SimpleName]config.DoguConfig{ - nginxStatic: config.CreateDoguConfig(nginxStatic, doguConfigEntries), - }, nil) + GetAllExisting(testCtx, nilDogus). + Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) @@ -556,22 +483,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) - expectedConfigDiff := map[cescommons.SimpleName]domain.DoguConfigDiffs{ - nginxStatic: { - domain.DoguConfigEntryDiff{ - Key: nginxStaticConfigKeyNginxKey1, - Actual: domain.DoguConfigValueState{Value: "val1", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "nginxVal1", Exists: true}, - NeededAction: domain.ConfigActionSet, - }, - domain.DoguConfigEntryDiff{ - Key: nginxStaticConfigKeyNginxKey2, - Actual: domain.DoguConfigValueState{Value: "val2", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "", Exists: false}, - NeededAction: domain.ConfigActionRemove, - }, - }, - } + expectedConfigDiff := map[cescommons.SimpleName]domain.DoguConfigDiffs{} assert.Equal(t, expectedConfigDiff, blueprint.StateDiff.DoguConfigDiffs) }) t.Run("should succeed for sensitive dogu config diff", func(t *testing.T) { @@ -580,19 +492,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{ Config: domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ - nginxStatic: { - DoguName: nginxStatic, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - nginxStaticSensitiveConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, - }, - }, - }, - }, + Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{}, }, }, Status: domain.StatusPhaseValidated, @@ -603,9 +503,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) doguInstallRepoMock := newMockDoguInstallationRepository(t) - installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "nginx-static": {Name: nginxStaticQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, - } + installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{} doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) componentInstallRepoMock := newMockComponentInstallationRepository(t) componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) @@ -619,18 +517,10 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ - "nginxKey1": "val1", - "nginxKey2": "val2", - }) - require.NoError(t, entryErr) + sensitiveDoguConfigRepoMock.EXPECT(). - GetAllExisting(testCtx, []cescommons.SimpleName{ - nginxStatic, - }). - Return(map[cescommons.SimpleName]config.DoguConfig{ - nginxStatic: config.CreateDoguConfig(nginxStatic, doguConfigEntries), - }, nil) + GetAllExisting(testCtx, nilDoguNameList). + Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock) @@ -640,22 +530,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) - expectedConfigDiff := map[cescommons.SimpleName]domain.DoguConfigDiffs{ - nginxStatic: { - { - Key: nginxStaticSensitiveConfigKeyNginxKey1, - Actual: domain.DoguConfigValueState{Value: "val1", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "nginxVal1", Exists: true}, - NeededAction: domain.ConfigActionSet, - }, - { - Key: nginxStaticSensitiveConfigKeyNginxKey2, - Actual: domain.DoguConfigValueState{Value: "val2", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "", Exists: false}, - NeededAction: domain.ConfigActionRemove, - }, - }, - } + expectedConfigDiff := map[cescommons.SimpleName]domain.DoguConfigDiffs{} assert.Equal(t, expectedConfigDiff, blueprint.StateDiff.SensitiveDoguConfigDiffs) }) } @@ -679,27 +554,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { "globalKey2", }, }, - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ - nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, - }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - nginxStaticSensitiveConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, - }, - }, - }, - }, + Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{}, }, } @@ -719,14 +574,9 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - nginxStaticConfig := config.CreateDoguConfig(nginxStatic, map[config.Key]config.Value{ - "nginxKey1": "val1", - }) sensitiveDoguConfigRepoMock.EXPECT(). GetAllExisting(testCtx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()). - Return(map[cescommons.SimpleName]config.DoguConfig{ - nginxStatic: nginxStaticConfig, - }, nil) + Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock) @@ -737,11 +587,9 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { assert.NoError(t, err) assert.Equal(t, ecosystem.EcosystemState{ - GlobalConfig: globalConfig, - ConfigByDogu: map[cescommons.SimpleName]config.DoguConfig{}, - SensitiveConfigByDogu: map[cescommons.SimpleName]config.DoguConfig{ - nginxStatic: nginxStaticConfig, - }, + GlobalConfig: globalConfig, + ConfigByDogu: map[cescommons.SimpleName]config.DoguConfig{}, + SensitiveConfigByDogu: map[cescommons.SimpleName]config.DoguConfig{}, }, ecosystemState) }) t.Run("fail with internalError and notFoundError", func(t *testing.T) { @@ -756,27 +604,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { "globalKey2", }, }, - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ - nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, - }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - nginxStaticSensitiveConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, - }, - }, - }, - }, + Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{}, }, } diff --git a/pkg/domain/blueprintMask_test.go b/pkg/domain/blueprintMask_test.go index a61f737d..a1bd6960 100644 --- a/pkg/domain/blueprintMask_test.go +++ b/pkg/domain/blueprintMask_test.go @@ -12,7 +12,6 @@ var version3_2_1_0, _ = core.ParseVersion("3.2.1-0") var ( officialNamespace = cescommons.Namespace("official") k8sNamespace = cescommons.Namespace("k8s") - nginxStatic = cescommons.QualifiedName{Namespace: k8sNamespace, SimpleName: cescommons.SimpleName("nginx-static")} officialDogu1 = cescommons.QualifiedName{Namespace: officialNamespace, SimpleName: cescommons.SimpleName("dogu1")} officialDogu2 = cescommons.QualifiedName{Namespace: officialNamespace, SimpleName: cescommons.SimpleName("dogu2")} officialDogu3 = cescommons.QualifiedName{Namespace: officialNamespace, SimpleName: cescommons.SimpleName("dogu3")} diff --git a/pkg/domain/dogu_test.go b/pkg/domain/dogu_test.go index 56a16888..0e69a34e 100644 --- a/pkg/domain/dogu_test.go +++ b/pkg/domain/dogu_test.go @@ -79,7 +79,7 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { t.Run("additionalMounts ok", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + dogu := Dogu{Name: officialDogu1, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", @@ -95,7 +95,7 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { t.Run("unknown sourceType", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + dogu := Dogu{Name: officialDogu1, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: "unsupportedType", Name: "html-config", @@ -107,12 +107,12 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { err := dogu.validate() // then require.Error(t, err) - require.ErrorContains(t, err, "dogu is invalid: dogu additional mounts sourceType must be one of 'ConfigMap', 'Secret': k8s/nginx-static") + require.ErrorContains(t, err, "dogu is invalid: dogu additional mounts sourceType must be one of 'ConfigMap', 'Secret': official/dogu1") }) t.Run("subfolder is no relative path", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + dogu := Dogu{Name: officialDogu1, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", @@ -124,7 +124,7 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { err := dogu.validate() // then require.Error(t, err) - require.ErrorContains(t, err, "dogu is invalid: dogu additional mounts Subfolder must be a relative path : k8s/nginx-static") + require.ErrorContains(t, err, "dogu is invalid: dogu additional mounts Subfolder must be a relative path : official/dogu1") }) } diff --git a/pkg/domainservice/validateDependenciesDomainUseCase.go b/pkg/domainservice/validateDependenciesDomainUseCase.go index 13942864..394ff427 100644 --- a/pkg/domainservice/validateDependenciesDomainUseCase.go +++ b/pkg/domainservice/validateDependenciesDomainUseCase.go @@ -12,10 +12,8 @@ import ( ) const ( - nginxDependencyName = "nginx" - nginxStaticDependencyName = "nginx-static" - nginxIngressDependencyName = "nginx-ingress" - registratorDependencyName = "registrator" + nginxDependencyName = "nginx" + registratorDependencyName = "registrator" ) type ValidateDependenciesDomainUseCase struct { @@ -95,12 +93,8 @@ func (useCase *ValidateDependenciesDomainUseCase) checkDoguDependencies( } // Exception for the old nginx dependency from the single node Cloudogu EcoSystem. - // We only have to check if nginx-static and nginx-ingress are present. + // The nginx dependency was replaced by a the k8s-ces-gateway and k8s-ces-assets component if dependencyOfWantedDogu.Name == nginxDependencyName { - if !checkNginxIngressAndStatic(wantedDogus) { - problems = append(problems, fmt.Errorf("dogu has %q dependency but %q and %q are missing in the effective blueprint", nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName)) - } - logger.Info(fmt.Sprintf("dogu has dependency %q. %q and %q are available.", nginxDependencyName, nginxIngressDependencyName, nginxStaticDependencyName)) continue } @@ -112,23 +106,6 @@ func (useCase *ValidateDependenciesDomainUseCase) checkDoguDependencies( return err } -func checkNginxIngressAndStatic(wantedDogus []domain.Dogu) bool { - foundNginxIngress := isDoguInSlice(wantedDogus, nginxIngressDependencyName) - foundNginxStatic := isDoguInSlice(wantedDogus, nginxStaticDependencyName) - - return foundNginxIngress && foundNginxStatic -} - -func isDoguInSlice(dogus []domain.Dogu, name cescommons.SimpleName) bool { - for _, dogu := range dogus { - if dogu.Name.SimpleName == name { - return true - } - } - - return false -} - func checkDoguDependency( dependencyOfWantedDogu core.Dependency, wantedDogus []domain.Dogu, From 13be6bc2dd13d307f837a648d051f54652bf39a5 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 11 Sep 2025 14:59:05 +0200 Subject: [PATCH 051/119] #121 Update to newest CRD version --- go.mod | 2 +- go.sum | 8 +- .../v2/blueprintSpecCRRepository.go | 37 +- .../v2/blueprintSpecCRRepository_test.go | 47 +- .../v2/mock_blueprintInterface_test.go | 59 -- .../blueprintcr/v2/serializer/blueprint.go | 15 +- .../v2/serializer/blueprint_test.go | 205 +++---- .../blueprintcr/v2/serializer/component.go | 23 +- .../v2/serializer/componentDiff.go | 60 +- .../v2/serializer/componentDiff_test.go | 57 +- .../v2/serializer/component_test.go | 23 +- .../blueprintcr/v2/serializer/config.go | 165 +++--- .../blueprintcr/v2/serializer/config_test.go | 81 +-- .../blueprintcr/v2/serializer/dogu.go | 123 +++-- .../v2/serializer/doguConfigDiff.go | 16 +- .../v2/serializer/doguConfigDiff_test.go | 59 +- .../blueprintcr/v2/serializer/doguDiff.go | 83 +-- .../v2/serializer/doguDiff_test.go | 15 +- .../blueprintcr/v2/serializer/dogu_test.go | 141 ++--- .../v2/serializer/globalConfigDiff.go | 16 +- .../v2/serializer/sensitiveConfig.go | 80 ++- .../v2/serializer/sensitiveConfig_test.go | 49 +- .../blueprintcr/v2/serializer/stateDiff.go | 54 +- .../v2/serializer/stateDiff_test.go | 517 ++++++++---------- .../blueprintcr/v2/serializer/targetState.go | 42 -- .../v2/serializer/targetState_test.go | 18 - .../dogucr/doguInstallationRepo_test.go | 9 +- .../kubernetes/dogucr/doguSerializer.go | 24 +- .../kubernetes/dogucr/doguSerializer_test.go | 53 +- .../blueprintSpecValidationUseCase_test.go | 6 +- .../doguInstallationUseCase_test.go | 93 ++-- pkg/application/ecosystemConfigUseCase.go | 12 +- .../ecosystemConfigUseCase_test.go | 14 +- .../mock_applyComponentsUseCase_test.go | 26 +- .../mock_applyDogusUseCase_test.go | 26 +- pkg/application/stateDiffUseCase_test.go | 159 ++---- pkg/domain/blueprint.go | 3 +- pkg/domain/blueprintMask_test.go | 13 +- pkg/domain/blueprintSpec.go | 17 +- pkg/domain/blueprintSpec_test.go | 62 ++- pkg/domain/blueprint_test.go | 55 +- pkg/domain/common/configNames.go | 1 + pkg/domain/component.go | 11 +- pkg/domain/component_test.go | 17 +- pkg/domain/dogu.go | 22 +- pkg/domain/dogu_test.go | 39 +- pkg/domain/ecosystem/doguInstallation.go | 45 +- pkg/domain/ecosystem/doguInstallation_test.go | 38 +- pkg/domain/ecosystem/reverseProxyConfig.go | 4 +- pkg/domain/ecosystem/volumeSize.go | 13 +- pkg/domain/effectiveBlueprint.go | 9 +- pkg/domain/maskDogu.go | 16 +- pkg/domain/maskDogu_test.go | 18 +- pkg/domain/stateDiffComponent.go | 48 +- pkg/domain/stateDiffComponent_test.go | 153 +++--- pkg/domain/stateDiffConfig.go | 6 +- pkg/domain/stateDiffConfig_test.go | 105 ++-- pkg/domain/stateDiffDogu.go | 58 +- pkg/domain/stateDiffDoguConfig.go | 14 +- pkg/domain/stateDiffDoguConfig_test.go | 8 +- pkg/domain/stateDiffDogu_test.go | 451 +++++++-------- pkg/domain/stateDiffGlobalConfig.go | 12 +- pkg/domain/stateDiffGlobalConfig_test.go | 13 +- pkg/domain/targetState.go | 27 - pkg/domain/targetState_test.go | 31 -- pkg/domainservice/util.go | 7 +- ...idateAdditionalMountsDomainUseCase_test.go | 33 +- .../validateDependenciesDomainUseCase.go | 12 +- .../validateDependenciesDomainUseCase_test.go | 49 +- 69 files changed, 1859 insertions(+), 1968 deletions(-) delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState.go delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState_test.go delete mode 100644 pkg/domain/targetState.go delete mode 100644 pkg/domain/targetState_test.go diff --git a/go.mod b/go.mod index a1e3d0c4..5acfaa74 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index 52ca4ea0..a11c9b55 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,12 @@ github.com/cloudogu/ces-commons-lib v0.2.0 h1:yOEZWFl4W9N3J/6fok4svE3UufK5GQQtyx github.com/cloudogu/ces-commons-lib v0.2.0/go.mod h1:4rvR2RTDDaz5a6OZ1fW27G0MOnl5I3ackeiHxt4gn3o= github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSAaYi7U= github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a h1:aj5qH5Ejn+TstaHjt+8Y6vigIY5uVl59IWPVzaFlczU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250820104829-95faf37d626a/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250905050828-1e362fb757b4 h1:0SBU7WSD/NWJAsVCV2+R0z/xXdM3E3tMjSVvgbY0Wsk= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250905050828-1e362fb757b4/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017 h1:TNqkUkQNCBhb7V1KkQvIFS05LLELkFPQBhsjyB1dBAs= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f h1:IUUD93cn1uF2T5LyE5+jLy2S7xvM98JCbr5ubpTE05g= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index b4737bcb..85b89767 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -55,14 +55,18 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) } } - effectiveBlueprint, err := serializerv2.ConvertToEffectiveBlueprintDomain(blueprintCR.Status.EffectiveBlueprint) - if err != nil { - return nil, err - } + var effectiveBlueprint domain.EffectiveBlueprint + var stateDiff domain.StateDiff + if blueprintCR.Status != nil { + effectiveBlueprint, err = serializerv2.ConvertToEffectiveBlueprintDomain(blueprintCR.Status.EffectiveBlueprint) + if err != nil { + return nil, err + } - stateDiff, err := serializerv2.ConvertToStateDiffDomain(blueprintCR.Status.StateDiff) - if err != nil { - return nil, err + stateDiff, err = serializerv2.ConvertToStateDiffDomain(blueprintCR.Status.StateDiff) + if err != nil { + return nil, err + } } conditions := blueprintCR.Status.Conditions @@ -76,10 +80,10 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) StateDiff: stateDiff, Conditions: conditions, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: blueprintCR.Spec.IgnoreDoguHealth, - IgnoreComponentHealth: blueprintCR.Spec.IgnoreComponentHealth, - AllowDoguNamespaceSwitch: blueprintCR.Spec.AllowDoguNamespaceSwitch, - DryRun: blueprintCR.Spec.DryRun, + IgnoreDoguHealth: boolPtrToValue(blueprintCR.Spec.IgnoreDoguHealth), + IgnoreComponentHealth: boolPtrToValue(blueprintCR.Spec.IgnoreComponentHealth), + AllowDoguNamespaceSwitch: boolPtrToValue(blueprintCR.Spec.AllowDoguNamespaceSwitch), + DryRun: boolPtrToValue(blueprintCR.Spec.Stopped), }, } @@ -106,6 +110,13 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) return blueprintSpec, nil } +func boolPtrToValue(boolPtr *bool) bool { + if boolPtr != nil { + return *boolPtr + } + return false +} + // Update persists changes in the blueprint to the corresponding blueprint CR. func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("blueprintSpecRepo.Update") @@ -123,8 +134,8 @@ func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.Blueprin ResourceVersion: persistenceContext.resourceVersion, CreationTimestamp: metav1.Time{}, }, - Status: v2.BlueprintStatus{ - EffectiveBlueprint: effectiveBlueprint, + Status: &v2.BlueprintStatus{ + EffectiveBlueprint: &effectiveBlueprint, StateDiff: serializerv2.ConvertToStateDiffDTO(spec.StateDiff), Conditions: spec.Conditions, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index bd21f784..cc8f3390 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -29,6 +29,7 @@ var ( Reason: "Completed", Message: "test", } + trueVar = true ) func Test_blueprintSpecRepo_GetById(t *testing.T) { @@ -43,14 +44,14 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { cr := &bpv2.Blueprint{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ResourceVersion: "abc"}, - Spec: bpv2.BlueprintSpec{ + Spec: &bpv2.BlueprintSpec{ Blueprint: bpv2.BlueprintManifest{}, - BlueprintMask: bpv2.BlueprintMask{}, - AllowDoguNamespaceSwitch: true, - IgnoreDoguHealth: true, - DryRun: true, + BlueprintMask: &bpv2.BlueprintMask{}, + AllowDoguNamespaceSwitch: &trueVar, + IgnoreDoguHealth: &trueVar, + Stopped: &trueVar, }, - Status: bpv2.BlueprintStatus{ + Status: &bpv2.BlueprintStatus{ Conditions: []metav1.Condition{testCondition}, }, } @@ -72,7 +73,7 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { }, StateDiff: domain.StateDiff{}, PersistenceContext: persistenceContext, - Conditions: &[]domain.Condition{testCondition}, + Conditions: []domain.Condition{testCondition}, }, spec) }) @@ -84,19 +85,19 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { cr := &bpv2.Blueprint{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ResourceVersion: "abc"}, - Spec: bpv2.BlueprintSpec{ + Spec: &bpv2.BlueprintSpec{ Blueprint: bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{ {Name: "invalid"}, }, }, - BlueprintMask: bpv2.BlueprintMask{ + BlueprintMask: &bpv2.BlueprintMask{ Dogus: []bpv2.MaskDogu{ {Name: "invalid"}, }, }, }, - Status: bpv2.BlueprintStatus{}, + Status: &bpv2.BlueprintStatus{}, } eventRecorderMock.EXPECT().Event(cr, "Warning", "BlueprintSpecInvalid", "cannot deserialize blueprint: cannot convert blueprint dogus: dogu name needs to be in the form 'namespace/dogu' but is 'invalid'") eventRecorderMock.EXPECT().Event(cr, "Warning", "BlueprintSpecInvalid", "cannot deserialize blueprint mask: cannot convert blueprint dogus: dogu name needs to be in the form 'namespace/dogu' but is 'invalid'") @@ -164,12 +165,12 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := bpv2.BlueprintStatus{ - EffectiveBlueprint: bpv2.BlueprintManifest{ + EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: bpv2.Config{}, + Config: &bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{testCondition}, } restClientMock.EXPECT(). @@ -186,7 +187,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, - Conditions: &[]domain.Condition{testCondition}, + Conditions: []domain.Condition{testCondition}, }) // then @@ -236,12 +237,12 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := bpv2.BlueprintStatus{ - EffectiveBlueprint: bpv2.BlueprintManifest{ + EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: bpv2.Config{}, + Config: &bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{}, } expectedError := k8sErrors.NewConflict( @@ -263,7 +264,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, }) // then @@ -279,12 +280,12 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := bpv2.BlueprintStatus{ - EffectiveBlueprint: bpv2.BlueprintManifest{ + EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: bpv2.Config{}, + Config: &bpv2.Config{}, }, - StateDiff: bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{}, } expectedError := fmt.Errorf("test-error") @@ -302,7 +303,7 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { Id: blueprintId, Events: nil, PersistenceContext: persistenceContext, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, }) // then @@ -345,7 +346,7 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { Id: blueprintId, Events: events, PersistenceContext: persistenceContext, - Conditions: &[]domain.Condition{}, + Conditions: []domain.Condition{}, } err := repo.Update(ctx, spec) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go index f03bbac1..47f0df5a 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go @@ -245,65 +245,6 @@ func (_c *mockBlueprintInterface_Get_Call) RunAndReturn(run func(context.Context return _c } -// List provides a mock function with given fields: ctx, opts -func (_m *mockBlueprintInterface) List(ctx context.Context, opts v1.ListOptions) (*apiv2.BlueprintList, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for List") - } - - var r0 *apiv2.BlueprintList - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) (*apiv2.BlueprintList, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) *apiv2.BlueprintList); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*apiv2.BlueprintList) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, v1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockBlueprintInterface_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' -type mockBlueprintInterface_List_Call struct { - *mock.Call -} - -// List is a helper method to define mock.On call -// - ctx context.Context -// - opts v1.ListOptions -func (_e *mockBlueprintInterface_Expecter) List(ctx interface{}, opts interface{}) *mockBlueprintInterface_List_Call { - return &mockBlueprintInterface_List_Call{Call: _e.mock.On("List", ctx, opts)} -} - -func (_c *mockBlueprintInterface_List_Call) Run(run func(ctx context.Context, opts v1.ListOptions)) *mockBlueprintInterface_List_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(v1.ListOptions)) - }) - return _c -} - -func (_c *mockBlueprintInterface_List_Call) Return(_a0 *apiv2.BlueprintList, _a1 error) *mockBlueprintInterface_List_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockBlueprintInterface_List_Call) RunAndReturn(run func(context.Context, v1.ListOptions) (*apiv2.BlueprintList, error)) *mockBlueprintInterface_List_Call { - _c.Call.Return(run) - return _c -} - // Patch provides a mock function with given fields: ctx, name, pt, data, opts, subresources func (_m *mockBlueprintInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (*apiv2.Blueprint, error) { _va := make([]interface{}, len(subresources)) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go index c138eee9..49ffbd1d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go @@ -3,6 +3,7 @@ package serializer import ( "errors" "fmt" + crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" ) @@ -26,14 +27,18 @@ func ConvertToBlueprintDomain(blueprint crd.BlueprintManifest) (domain.Blueprint Message: "cannot deserialize blueprint", } } + configDomain := ConvertToConfigDomain(blueprint.Config) return domain.Blueprint{ Dogus: convertedDogus, Components: convertedComponents, - Config: ConvertToConfigDomain(blueprint.Config), + Config: configDomain, }, nil } -func ConvertToEffectiveBlueprintDomain(blueprint crd.BlueprintManifest) (domain.EffectiveBlueprint, error) { +func ConvertToEffectiveBlueprintDomain(blueprint *crd.BlueprintManifest) (domain.EffectiveBlueprint, error) { + if blueprint == nil { + return domain.EffectiveBlueprint{}, nil + } convertedDogus, doguErr := ConvertDogus(blueprint.Dogus) convertedComponents, compErr := ConvertComponents(blueprint.Components) @@ -48,7 +53,11 @@ func ConvertToEffectiveBlueprintDomain(blueprint crd.BlueprintManifest) (domain. }, nil } -func ConvertToBlueprintMaskDomain(mask crd.BlueprintMask) (domain.BlueprintMask, error) { +func ConvertToBlueprintMaskDomain(mask *crd.BlueprintMask) (domain.BlueprintMask, error) { + if mask == nil { + return domain.BlueprintMask{}, nil + } + convertedDogus, err := ConvertMaskDogus(mask.Dogus) if err != nil { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index eec7dab0..86bbf085 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -2,10 +2,11 @@ package serializer import ( "fmt" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "testing" "github.com/Masterminds/semver/v3" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,28 +19,29 @@ import ( ) var ( - version3211, _ = core.ParseVersion("3.2.1-1") - version3212, _ = core.ParseVersion("3.2.1-2") - version1_2_3_3, _ = core.ParseVersion("1.2.3-3") + version3211, _ = core.ParseVersion("3.2.1-1") + version3212, _ = core.ParseVersion("3.2.1-2") + version1233, _ = core.ParseVersion("1.2.3-3") ) var ( - compVersion1233 = semver.MustParse("1.2.3-3") - compVersion3211 = semver.MustParse("3.2.1-1") - compVersion3212 = semver.MustParse("3.2.1-2") + compVersion1233 = semver.MustParse("1.2.3-3") + compVersion3211 = semver.MustParse("3.2.1-1") + compVersion3212 = semver.MustParse("3.2.1-2") + compVersion1233String = semver.MustParse("1.2.3-3").String() + compVersion3212String = semver.MustParse("3.2.1-2").String() ) -var emptyPlatformConfig = crd.PlatformConfig{ - ResourceConfig: crd.ResourceConfig{ - MinVolumeSize: "0", - }, -} +var ( + trueVar = true + falseVar = false +) func TestConvertToBlueprintDTO(t *testing.T) { t.Run("convert dogus", func(t *testing.T) { dogus := []domain.Dogu{ - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: version3211, TargetState: domain.TargetStateAbsent}, - {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: version3212, TargetState: domain.TargetStatePresent}, + {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: &version3211, Absent: true}, + {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: &version3212, Absent: false}, } blueprint := domain.EffectiveBlueprint{ Dogus: dogus, @@ -50,20 +52,20 @@ func TestConvertToBlueprintDTO(t *testing.T) { //then convertedDogus := []crd.Dogu{ - {Name: "official/dogu1", PlatformConfig: emptyPlatformConfig, Version: version3211.Raw, Absent: true}, - {Name: "premium/dogu3", PlatformConfig: emptyPlatformConfig, Version: version3212.Raw, Absent: false}, + {Name: "official/dogu1", Version: &version3211.Raw, Absent: &trueVar}, + {Name: "premium/dogu3", Version: &version3212.Raw, Absent: &falseVar}, } - - assert.Equal(t, crd.BlueprintManifest{ + expectedManifest := crd.BlueprintManifest{ Dogus: convertedDogus, Components: []crd.Component{}, - }, blueprintV2) + } + assert.Empty(t, cmp.Diff(expectedManifest, blueprintV2)) }) t.Run("convert components", func(t *testing.T) { components := []domain.Component{ - {Name: common.QualifiedComponentName{SimpleName: "component1", Namespace: "k8s"}, Version: nil, TargetState: domain.TargetStateAbsent}, - {Name: common.QualifiedComponentName{SimpleName: "component3", Namespace: "k8s-testing"}, Version: compVersion3212, TargetState: domain.TargetStatePresent}, + {Name: common.QualifiedComponentName{SimpleName: "component1", Namespace: "k8s"}, Version: nil, Absent: true}, + {Name: common.QualifiedComponentName{SimpleName: "component3", Namespace: "k8s-testing"}, Version: compVersion3212, Absent: false}, } blueprint := domain.EffectiveBlueprint{ Components: components, @@ -74,19 +76,21 @@ func TestConvertToBlueprintDTO(t *testing.T) { //then convertedComponents := []crd.Component{ - {Name: "k8s/component1", Absent: true}, - {Name: "k8s-testing/component3", Version: compVersion3212.String(), Absent: false}, + {Name: "k8s/component1", Absent: &trueVar}, + {Name: "k8s-testing/component3", Version: &compVersion3212String, Absent: &falseVar}, } - assert.Equal(t, crd.BlueprintManifest{ + expectedManifest := crd.BlueprintManifest{ Dogus: []crd.Dogu{}, Components: convertedComponents, - }, blueprintV2) + } + assert.Empty(t, cmp.Diff(expectedManifest, blueprintV2)) }) t.Run("convert config", func(t *testing.T) { + value42 := "42" blueprint := domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ "my-dogu": { Config: domain.DoguConfig{ @@ -94,7 +98,7 @@ func TestConvertToBlueprintDTO(t *testing.T) { { DoguName: "my-dogu", Key: "config", - }: "42", + }: common.DoguConfigValue(value42), }, }, SensitiveConfig: domain.SensitiveDoguConfig{ @@ -115,56 +119,58 @@ func TestConvertToBlueprintDTO(t *testing.T) { } blueprintV2 := ConvertToBlueprintDTO(blueprint) - assert.Equal(t, crd.BlueprintManifest{ + expectedManifest := crd.BlueprintManifest{ Dogus: []crd.Dogu{}, Components: []crd.Component{}, - Config: crd.Config{ - Dogus: map[string]crd.CombinedDoguConfig{ + Config: &crd.Config{ + Dogus: map[string][]crd.ConfigEntry{ "my-dogu": { - Config: &crd.DoguConfig{ - Present: map[string]string{ - "config": "42", - }, + crd.ConfigEntry{ + Key: "config", + Value: &value42, }, - SensitiveConfig: &crd.SensitiveDoguConfig{ - Present: []crd.SensitiveConfigEntry{ - { - Key: "sensitive-config", - SecretName: "mySecret", - SecretKey: "myKey", - }, + crd.ConfigEntry{ + Key: "sensitive-config", + SecretRef: &crd.SecretReference{ + Name: "mySecret", + Key: "myKey", }, }, }, }, - Global: crd.GlobalConfig{ - Absent: []string{"test/key"}, + Global: []crd.ConfigEntry{ + { + Key: "test/key", + Absent: &trueVar, + }, }, }, - }, blueprintV2) + } + assert.Empty(t, cmp.Diff(expectedManifest, blueprintV2)) }) } func TestConvertToEffectiveBlueprintDomain(t *testing.T) { //given + subfolder := "subfolder" convertedDogus := []crd.Dogu{ - {Name: "official/dogu1", Version: version3211.Raw, Absent: true}, - {Name: "official/dogu2", Absent: true}, - {Name: "premium/dogu3", Version: version3212.Raw, Absent: false}, - {Name: "premium/dogu4", Version: version1_2_3_3.Raw, Absent: false}, + {Name: "official/dogu1", Version: &version3211.Raw, Absent: &trueVar}, + {Name: "official/dogu2", Absent: &trueVar}, + {Name: "premium/dogu3", Version: &version3212.Raw, Absent: &falseVar}, + {Name: "premium/dogu4", Version: &version1233.Raw, Absent: &falseVar}, { Name: "premium/dogu5", - Version: version1_2_3_3.Raw, - Absent: false, - PlatformConfig: crd.PlatformConfig{ - ResourceConfig: crd.ResourceConfig{}, - ReverseProxyConfig: crd.ReverseProxyConfig{}, + Version: &version1233.Raw, + Absent: &falseVar, + PlatformConfig: &crd.PlatformConfig{ + ResourceConfig: nil, + ReverseProxyConfig: nil, AdditionalMountsConfig: []crd.AdditionalMount{ { SourceType: crd.DataSourceConfigMap, Name: "config", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, @@ -172,70 +178,74 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { } convertedComponents := []crd.Component{ - {Name: "k8s/component1", Version: version3211.Raw, Absent: true}, - {Name: "k8s/component2", Absent: true}, - {Name: "k8s-testing/component3", Version: version3212.Raw, Absent: false}, - {Name: "k8s-testing/component4", Version: version1_2_3_3.Raw, Absent: false}, + {Name: "k8s/component1", Version: &version3211.Raw, Absent: &trueVar}, + {Name: "k8s/component2", Absent: &trueVar}, + {Name: "k8s-testing/component3", Version: &version3212.Raw, Absent: &falseVar}, + {Name: "k8s-testing/component4", Version: &version1233.Raw, Absent: &falseVar}, } + value42 := "42" dto := crd.BlueprintManifest{ Dogus: convertedDogus, Components: convertedComponents, - Config: crd.Config{ - Dogus: map[string]crd.CombinedDoguConfig{ + Config: &crd.Config{ + Dogus: map[string][]crd.ConfigEntry{ "my-dogu": { - Config: &crd.DoguConfig{ - Present: map[string]string{ - "config": "42", - }, + crd.ConfigEntry{ + Key: "config", + Value: &value42, }, - SensitiveConfig: &crd.SensitiveDoguConfig{ - Present: []crd.SensitiveConfigEntry{ - { - Key: "sensitive-config", - SecretName: "mySecret", - SecretKey: "myKey", - }, + crd.ConfigEntry{ + Key: "sensitive-config", + Sensitive: &trueVar, + SecretRef: &crd.SecretReference{ + Name: "mySecret", + Key: "myKey", }, }, }, }, - Global: crd.GlobalConfig{Absent: []string{"test/key"}}, + Global: []crd.ConfigEntry{ + { + Key: "test/key", + Absent: &trueVar, + }, + }, }, } //when - blueprint, err := ConvertToEffectiveBlueprintDomain(dto) + blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) //then dogus := []domain.Dogu{ - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: version3211, TargetState: domain.TargetStateAbsent}, - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu2"}, TargetState: domain.TargetStateAbsent}, - {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: version3212, TargetState: domain.TargetStatePresent}, - {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu4"}, Version: version1_2_3_3}, + {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: &version3211, Absent: true}, + {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu2"}, Absent: true}, + {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: &version3212, Absent: false}, + {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu4"}, Version: &version1233}, { Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu5"}, - Version: version1_2_3_3, + Version: &version1233, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "config", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, } components := []domain.Component{ - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component1"}, Version: compVersion3211, TargetState: domain.TargetStateAbsent}, - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component2"}, TargetState: domain.TargetStateAbsent}, - {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component3"}, Version: compVersion3212, TargetState: domain.TargetStatePresent}, + {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component1"}, Version: compVersion3211, Absent: true}, + {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component2"}, Absent: true}, + {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component3"}, Version: compVersion3212, Absent: false}, {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component4"}, Version: compVersion1233}, } expected := domain.EffectiveBlueprint{ Dogus: dogus, Components: components, - Config: domain.Config{ + Config: &domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ "my-dogu": { DoguName: "my-dogu", @@ -244,7 +254,7 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { { DoguName: "my-dogu", Key: "config", - }: "42", + }: common.DoguConfigValue(value42), }, }, SensitiveConfig: domain.SensitiveDoguConfig{ @@ -265,7 +275,7 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { } require.NoError(t, err) - assert.Equal(t, expected, blueprint) + assert.Empty(t, cmp.Diff(expected, blueprint)) } func TestConvertToBlueprintDomain(t *testing.T) { @@ -278,11 +288,12 @@ func TestConvertToBlueprintDomain(t *testing.T) { }) t.Run("error converting", func(t *testing.T) { + versionUnparsable := "unparsable" given := crd.BlueprintManifest{ Dogus: []crd.Dogu{ { Name: "official/redmine", - Version: "unparsable", + Version: &versionUnparsable, }, }, } @@ -297,7 +308,7 @@ func TestConvertToBlueprintDomain(t *testing.T) { Dogus: []crd.Dogu{ { Name: "official/redmine", - Version: version1_2_3_3.Raw, + Version: &version1233.Raw, }, }, } @@ -307,9 +318,8 @@ func TestConvertToBlueprintDomain(t *testing.T) { assert.Equal(t, domain.Blueprint{ Dogus: []domain.Dogu{ { - Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "redmine"}, - Version: version1_2_3_3, - TargetState: domain.TargetStatePresent, + Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "redmine"}, + Version: &version1233, }, }, }, converted) @@ -320,7 +330,7 @@ func TestConvertToBlueprintDomain(t *testing.T) { Components: []crd.Component{ { Name: "k8s/loki", - Version: compVersion1233.Original(), + Version: &compVersion1233String, }, }, } @@ -334,8 +344,8 @@ func TestConvertToBlueprintDomain(t *testing.T) { Namespace: "k8s", SimpleName: "loki", }, - Version: compVersion1233, - TargetState: domain.TargetStatePresent, + Version: compVersion1233, + Absent: false, }, }, }, converted) @@ -365,7 +375,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { Dogus: []crd.MaskDogu{ { Name: "official/ldap", - Version: version1_2_3_3.Raw, + Version: &version1233.Raw, }, }, }}, @@ -376,8 +386,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { Namespace: "official", SimpleName: "ldap", }, - Version: version1_2_3_3, - TargetState: domain.TargetStatePresent, + Version: version1233, }, }, }, @@ -389,7 +398,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { Dogus: []crd.MaskDogu{ { Name: "invalid name", - Version: version1_2_3_3.Raw, + Version: &version1233.Raw, }, }, }}, @@ -401,7 +410,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ConvertToBlueprintMaskDomain(tt.args.mask) + got, err := ConvertToBlueprintMaskDomain(&tt.args.mask) if !tt.wantErr(t, err, fmt.Sprintf("ConvertToBlueprintMaskDomain(%v)", tt.args.mask)) { return } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go index 32e8d6cf..dd5774f1 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go @@ -3,6 +3,7 @@ package serializer import ( "errors" "fmt" + v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/Masterminds/semver/v3" @@ -20,15 +21,20 @@ func ConvertComponents(components []v2.Component) ([]domain.Component, error) { for _, component := range components { var version *semver.Version - if component.Version != "" { + if component.Version != nil && *component.Version != "" { var err error - version, err = semver.NewVersion(component.Version) + version, err = semver.NewVersion(*component.Version) if err != nil { errorList = append(errorList, fmt.Errorf("could not parse version of target component %q: %w", component.Name, err)) continue } } + absent := false + if component.Absent != nil { + absent = *component.Absent + } + name, err := common.QualifiedComponentNameFromString(component.Name) if err != nil { errorList = append(errorList, err) @@ -38,7 +44,7 @@ func ConvertComponents(components []v2.Component) ([]domain.Component, error) { convertedComponents = append(convertedComponents, domain.Component{ Name: name, Version: version, - TargetState: ToDomainTargetState(component.Absent), + Absent: absent, DeployConfig: ecosystem.DeployConfig(component.DeployConfig), }) } @@ -54,20 +60,19 @@ func ConvertComponents(components []v2.Component) ([]domain.Component, error) { // ConvertToComponentDTOs takes a slice of Component DTOs and returns a new slice with their domain equivalent. func ConvertToComponentDTOs(components []domain.Component) []v2.Component { converted := util.Map(components, func(component domain.Component) v2.Component { - isAbsent := ToSerializerAbsentState(component.TargetState) - // convert the distribution namespace back into the name field so the EffectiveBlueprint has the same syntax // as the original blueprint json from the Blueprint resource. joinedComponentName := component.Name.String() - version := "" - if !isAbsent && component.Version != nil { - version = component.Version.String() + var version *string + if !component.Absent && component.Version != nil { + versionString := component.Version.String() + version = &versionString } return v2.Component{ Name: joinedComponentName, Version: version, - Absent: isAbsent, + Absent: &component.Absent, DeployConfig: map[string]interface{}(component.DeployConfig), } }) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go index 02dd8484..970ea423 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go @@ -3,6 +3,7 @@ package serializer import ( "errors" "fmt" + "github.com/Masterminds/semver/v3" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -29,16 +30,16 @@ func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDi return crd.ComponentDiff{ Actual: crd.ComponentDiffState{ - Namespace: string(domainModel.Actual.Namespace), - Version: actualVersion, - InstallationState: domainModel.Actual.InstallationState.String(), - DeployConfig: crd.DeployConfig(domainModel.Actual.DeployConfig), + Namespace: string(domainModel.Actual.Namespace), + Version: &actualVersion, + Absent: domainModel.Actual.Absent, + DeployConfig: crd.DeployConfig(domainModel.Actual.DeployConfig), }, Expected: crd.ComponentDiffState{ - Namespace: string(domainModel.Expected.Namespace), - Version: expectedVersion, - InstallationState: domainModel.Expected.InstallationState.String(), - DeployConfig: crd.DeployConfig(domainModel.Expected.DeployConfig), + Namespace: string(domainModel.Expected.Namespace), + Version: &expectedVersion, + Absent: domainModel.Expected.Absent, + DeployConfig: crd.DeployConfig(domainModel.Expected.DeployConfig), }, NeededActions: componentActions, } @@ -47,42 +48,29 @@ func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDi func convertToComponentDiffDomain(componentName string, dto crd.ComponentDiff) (domain.ComponentDiff, error) { var actualVersion *semver.Version var actualVersionErr error - if dto.Actual.Version != "" { - actualVersion, actualVersionErr = semver.NewVersion(dto.Actual.Version) + if dto.Actual.Version != nil && *dto.Actual.Version != "" { + actualVersion, actualVersionErr = semver.NewVersion(*dto.Actual.Version) if actualVersionErr != nil { - actualVersionErr = fmt.Errorf("failed to parse actual version %q: %w", dto.Actual.Version, actualVersionErr) + actualVersionErr = fmt.Errorf("failed to parse actual version %q: %w", *dto.Actual.Version, actualVersionErr) } } var expectedVersion *semver.Version var expectedVersionErr error - if dto.Expected.Version != "" { - expectedVersion, expectedVersionErr = semver.NewVersion(dto.Expected.Version) + if dto.Expected.Version != nil && *dto.Expected.Version != "" { + expectedVersion, expectedVersionErr = semver.NewVersion(*dto.Expected.Version) if expectedVersionErr != nil { - expectedVersionErr = fmt.Errorf("failed to parse expected version %q: %w", dto.Expected.Version, expectedVersionErr) + expectedVersionErr = fmt.Errorf("failed to parse expected version %q: %w", *dto.Expected.Version, expectedVersionErr) } } - actualState, actualStateErr := ToOldDomainTargetState(dto.Actual.InstallationState) - if actualStateErr != nil { - actualStateErr = fmt.Errorf("failed to parse actual installation state %q: %w", dto.Actual.InstallationState, actualStateErr) - } - - expectedState, expectedStateErr := ToOldDomainTargetState(dto.Expected.InstallationState) - if expectedStateErr != nil { - expectedStateErr = fmt.Errorf("failed to parse expected installation state %q: %w", dto.Expected.InstallationState, expectedStateErr) - } - - actualDistributionNamespace := dto.Actual.Namespace - expectedDistributionNamespace := dto.Expected.Namespace - neededActions := dto.NeededActions componentActions := make([]domain.Action, 0, len(neededActions)) for _, action := range neededActions { componentActions = append(componentActions, domain.Action(action)) } - err := errors.Join(actualVersionErr, expectedVersionErr, actualStateErr, expectedStateErr) + err := errors.Join(actualVersionErr, expectedVersionErr) if err != nil { return domain.ComponentDiff{}, fmt.Errorf("failed to convert component diff dto %q to domain model: %w", componentName, err) } @@ -90,16 +78,16 @@ func convertToComponentDiffDomain(componentName string, dto crd.ComponentDiff) ( return domain.ComponentDiff{ Name: common.SimpleComponentName(componentName), Actual: domain.ComponentDiffState{ - Namespace: common.ComponentNamespace(actualDistributionNamespace), - Version: actualVersion, - InstallationState: actualState, - DeployConfig: ecosystem.DeployConfig(dto.Actual.DeployConfig), + Namespace: common.ComponentNamespace(dto.Actual.Namespace), + Version: actualVersion, + Absent: dto.Actual.Absent, + DeployConfig: ecosystem.DeployConfig(dto.Actual.DeployConfig), }, Expected: domain.ComponentDiffState{ - Namespace: common.ComponentNamespace(expectedDistributionNamespace), - Version: expectedVersion, - InstallationState: expectedState, - DeployConfig: ecosystem.DeployConfig(dto.Expected.DeployConfig), + Namespace: common.ComponentNamespace(dto.Expected.Namespace), + Version: expectedVersion, + Absent: dto.Expected.Absent, + DeployConfig: ecosystem.DeployConfig(dto.Expected.DeployConfig), }, NeededActions: componentActions, }, nil diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go index 9434b1e8..7f2ef099 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go @@ -1,15 +1,18 @@ package serializer import ( + "testing" + "github.com/Masterminds/semver/v3" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) -const testNamespace = "k8s" +var testNamespace = common.ComponentNamespace("k8s") +var testNamespaceString = "k8s" var testDeployConfig = map[string]interface{}{"key": "value", "key1": map[string]interface{}{"key": "value2"}} @@ -18,8 +21,8 @@ func Test_convertToComponentDiffDTO(t *testing.T) { // given domainDiff := domain.ComponentDiff{ Name: testComponentName, - Actual: domain.ComponentDiffState{Version: nil, InstallationState: domain.TargetStateAbsent}, - Expected: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent, Namespace: testNamespace, DeployConfig: testDeployConfig}, + Actual: domain.ComponentDiffState{Version: nil, Absent: true}, + Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Namespace: testNamespace, DeployConfig: testDeployConfig}, NeededActions: []domain.Action{domain.ActionInstall}} // when @@ -27,8 +30,8 @@ func Test_convertToComponentDiffDTO(t *testing.T) { // then expected := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Version: "", InstallationState: "absent"}, - Expected: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present", Namespace: testNamespace, DeployConfig: testDeployConfig}, + Actual: crd.ComponentDiffState{Version: nil, Absent: true}, + Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Namespace: testNamespaceString, DeployConfig: testDeployConfig}, NeededActions: []crd.ComponentAction{domain.ActionInstall}, } assert.Equal(t, expected, actual) @@ -37,8 +40,8 @@ func Test_convertToComponentDiffDTO(t *testing.T) { // given domainDiff := domain.ComponentDiff{ Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{Version: nil, InstallationState: domain.TargetStateAbsent}, + Actual: domain.ComponentDiffState{Version: testSemverVersionHigh}, + Expected: domain.ComponentDiffState{Version: nil, Absent: false}, NeededActions: []domain.Action{domain.ActionUninstall}} // when @@ -46,8 +49,8 @@ func Test_convertToComponentDiffDTO(t *testing.T) { // then expected := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{Version: "", InstallationState: "absent"}, + Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw}, + Expected: crd.ComponentDiffState{Version: nil, Absent: true}, NeededActions: []crd.ComponentAction{domain.ActionUninstall}, } assert.Equal(t, expected, actual) @@ -58,8 +61,8 @@ func Test_convertToComponentDiffDomain(t *testing.T) { t.Run("should copy model diff to DTO diff - absent", func(t *testing.T) { // given diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "", Version: "", InstallationState: "absent"}, - Expected: crd.ComponentDiffState{Namespace: "k8s", Version: testVersionHighRaw, InstallationState: "present", DeployConfig: testDeployConfig}, + Actual: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, + Expected: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &testSemverVersionHighRaw, DeployConfig: testDeployConfig}, NeededActions: []crd.ComponentAction{domain.ActionInstall}, } @@ -70,8 +73,8 @@ func Test_convertToComponentDiffDomain(t *testing.T) { require.NoError(t, err) expected := domain.ComponentDiff{ Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: "", Version: nil, InstallationState: domain.TargetStateAbsent}, - Expected: domain.ComponentDiffState{Namespace: "k8s", Version: testVersionHigh, InstallationState: domain.TargetStatePresent, DeployConfig: testDeployConfig}, + Actual: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, + Expected: domain.ComponentDiffState{Namespace: testNamespace, Version: testSemverVersionHigh, DeployConfig: testDeployConfig}, NeededActions: []domain.Action{domain.ActionInstall}, } assert.Equal(t, expected, actual) @@ -79,8 +82,8 @@ func Test_convertToComponentDiffDomain(t *testing.T) { t.Run("should copy model diff to DTO diff - present", func(t *testing.T) { // given diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "k8s", Version: testVersionHighRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{Namespace: "", Version: "", InstallationState: "absent"}, + Actual: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &testSemverVersionHighRaw}, + Expected: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, NeededActions: []crd.ComponentAction{domain.ActionUninstall}, } @@ -91,17 +94,18 @@ func Test_convertToComponentDiffDomain(t *testing.T) { require.NoError(t, err) expected := domain.ComponentDiff{ Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: "k8s", Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{Namespace: "", Version: nil, InstallationState: domain.TargetStateAbsent}, + Actual: domain.ComponentDiffState{Namespace: testNamespace, Version: testSemverVersionHigh}, + Expected: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, NeededActions: []domain.Action{domain.ActionUninstall}, } assert.Equal(t, expected, actual) }) - t.Run("should fail in all ways", func(t *testing.T) { + t.Run("should fail to parse version", func(t *testing.T) { // given + versionABC := "a-b-c" diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "", Version: "a-b-c", InstallationState: "☹"}, - Expected: crd.ComponentDiffState{Namespace: "", Version: "a-b-c", InstallationState: "☹"}, + Actual: crd.ComponentDiffState{Namespace: "", Version: &versionABC}, + Expected: crd.ComponentDiffState{Namespace: "", Version: &versionABC}, NeededActions: []crd.ComponentAction{domain.ActionUninstall}, } @@ -112,15 +116,14 @@ func Test_convertToComponentDiffDomain(t *testing.T) { require.Error(t, err) assert.ErrorContains(t, err, "failed to parse actual version") assert.ErrorContains(t, err, "failed to parse expected version") - assert.ErrorContains(t, err, "failed to parse actual installation state") - assert.ErrorContains(t, err, "failed to parse expected installation state") }) t.Run("should accept dev version", func(t *testing.T) { compVersion080dev := semver.MustParse("0.8.0-dev") // given + Version080String := compVersion080dev.String() diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "k8s", Version: compVersion080dev.String(), InstallationState: "present"}, - Expected: crd.ComponentDiffState{Namespace: "", Version: "", InstallationState: "absent"}, + Actual: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &Version080String}, + Expected: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, NeededActions: []crd.ComponentAction{domain.ActionUninstall}, } @@ -131,8 +134,8 @@ func Test_convertToComponentDiffDomain(t *testing.T) { require.NoError(t, err) expected := domain.ComponentDiff{ Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: "k8s", Version: compVersion080dev, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{Namespace: "", Version: nil, InstallationState: domain.TargetStateAbsent}, + Actual: domain.ComponentDiffState{Namespace: testNamespace, Version: compVersion080dev}, + Expected: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, NeededActions: []domain.Action{domain.ActionUninstall}, } assert.Equal(t, expected, actual) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go index a9074e33..97a14997 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go @@ -2,9 +2,10 @@ package serializer import ( "fmt" - bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "testing" + bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" + "github.com/stretchr/testify/assert" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -19,6 +20,8 @@ func TestConvertComponents(t *testing.T) { type args struct { components []bpv2.Component } + wrongVersion1 := "1." + version1 := "1.0.0" tests := []struct { name string args args @@ -39,31 +42,31 @@ func TestConvertComponents(t *testing.T) { }, { name: "normal component", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: version3211.Raw, Absent: false, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}}, - want: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211, TargetState: domain.TargetStatePresent, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}, + args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &version3211.Raw, Absent: &falseVar, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}}, + want: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}, wantErr: assert.NoError, }, { name: "absent component", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Absent: true}}}, - want: []domain.Component{{Name: k8sK8sDoguOperator, TargetState: domain.TargetStateAbsent}}, + args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Absent: &trueVar}}}, + want: []domain.Component{{Name: k8sK8sDoguOperator, Absent: true}}, wantErr: assert.NoError, }, { name: "unparsable version", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: "1."}}}, + args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &wrongVersion1}}}, want: nil, wantErr: assert.Error, }, { name: "invalid component name", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator/oh/no", Version: "1.0.0"}}}, + args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator/oh/no", Version: &version1}}}, want: nil, wantErr: assert.Error, }, { name: "does not contain distribution namespace", - args: args{components: []bpv2.Component{{Name: "k8s-dogu-operator", Version: version3211.Raw}}}, + args: args{components: []bpv2.Component{{Name: "k8s-dogu-operator", Version: &version3211.Raw}}}, want: nil, wantErr: assert.Error, }, @@ -100,8 +103,8 @@ func TestConvertToComponentDTOs(t *testing.T) { }, { name: "ok", - args: args{components: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211, TargetState: domain.TargetStatePresent}}}, - want: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: version3211.Raw, Absent: false}}, + args: args{components: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211}}}, + want: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &version3211.Raw, Absent: &falseVar}}, }, } for _, tt := range tests { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index c100b260..8b21d68b 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -9,24 +9,31 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) -func ConvertToConfigDTO(config domain.Config) v2.Config { - var dogus map[string]v2.CombinedDoguConfig +func ConvertToConfigDTO(config *domain.Config) *v2.Config { + if config == nil { + return nil + } + + var dogus map[string][]v2.ConfigEntry // we check for empty values to make good use of default values // this makes testing easier if len(config.Dogus) != 0 { - dogus = make(map[string]v2.CombinedDoguConfig, len(config.Dogus)) + dogus = make(map[string][]v2.ConfigEntry, len(config.Dogus)) for doguName, doguConfig := range config.Dogus { dogus[string(doguName)] = convertToCombinedDoguConfigDTO(doguConfig) } } - return v2.Config{ + return &v2.Config{ Dogus: dogus, Global: convertToGlobalConfigDTO(config.Global), } } -func ConvertToConfigDomain(config v2.Config) domain.Config { +func ConvertToConfigDomain(config *v2.Config) *domain.Config { + if config == nil { + return nil + } var dogus map[cescommons.SimpleName]domain.CombinedDoguConfig // we check for empty values to make good use of default values // this makes testing easier @@ -37,81 +44,75 @@ func ConvertToConfigDomain(config v2.Config) domain.Config { } } - return domain.Config{ + return &domain.Config{ Dogus: dogus, Global: convertToGlobalConfigDomain(config.Global), } } -func convertToCombinedDoguConfigDTO(config domain.CombinedDoguConfig) v2.CombinedDoguConfig { - return v2.CombinedDoguConfig{ - Config: convertToDoguConfigDTO(config.Config), - SensitiveConfig: convertToSensitiveDoguConfigDTO(config.SensitiveConfig), - } +func convertToCombinedDoguConfigDTO(config domain.CombinedDoguConfig) []v2.ConfigEntry { + var result []v2.ConfigEntry + result = append(result, convertToDoguConfigDTO(config.Config)...) + result = append(result, convertToSensitiveDoguConfigDTO(config.SensitiveConfig)...) + + return result } -func convertToCombinedDoguConfigDomain(doguName string, config v2.CombinedDoguConfig) domain.CombinedDoguConfig { +func convertToCombinedDoguConfigDomain(doguName string, config []v2.ConfigEntry) domain.CombinedDoguConfig { return domain.CombinedDoguConfig{ DoguName: cescommons.SimpleName(doguName), - Config: convertToDoguConfigDomain(doguName, config.Config), - SensitiveConfig: convertToSensitiveDoguConfigDomain(doguName, config.SensitiveConfig), + Config: convertToDoguConfigDomain(doguName, config), + SensitiveConfig: convertToSensitiveDoguConfigDomain(doguName, config), } } -func convertToDoguConfigDTO(config domain.DoguConfig) *v2.DoguConfig { +func convertToDoguConfigDTO(config domain.DoguConfig) []v2.ConfigEntry { // empty struct -> nil if len(config.Present) == 0 && len(config.Absent) == 0 { return nil } - var present map[string]string + result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) // we check for empty values to make good use of default values // this makes testing easier - if len(config.Present) != 0 { - present = make(map[string]string, len(config.Present)) - for key, value := range config.Present { - present[string(key.Key)] = string(value) - } + for key, value := range config.Present { + valueString := string(value) + result = append(result, v2.ConfigEntry{ + Key: string(key.Key), + Value: &valueString, + }) } - var absent []string - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Absent) != 0 { - absent = make([]string, len(config.Absent)) - for i, key := range config.Absent { - absent[i] = string(key.Key) - } + for _, key := range config.Absent { + truePtr := true + result = append(result, v2.ConfigEntry{ + Key: string(key.Key), + Absent: &truePtr, + }) } - return &v2.DoguConfig{ - Present: present, - Absent: absent, - } + return result } -func convertToDoguConfigDomain(doguName string, config *v2.DoguConfig) domain.DoguConfig { +func convertToDoguConfigDomain(doguName string, config []v2.ConfigEntry) domain.DoguConfig { if config == nil { return domain.DoguConfig{} } - var present map[common.DoguConfigKey]common.DoguConfigValue - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Present) != 0 { - present = make(map[common.DoguConfigKey]common.DoguConfigValue, len(config.Present)) - for key, value := range config.Present { - present[convertToDoguConfigKeyDomain(doguName, key)] = common.DoguConfigValue(value) + present := make(map[common.DoguConfigKey]common.DoguConfigValue, len(config)) + absent := make([]common.DoguConfigKey, len(config)) + + absentIndex := 0 + for _, configEntry := range config { + if configEntry.Sensitive != nil && *configEntry.Sensitive == true { + continue } - } - var absent []common.DoguConfigKey - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Absent) != 0 { - absent = make([]common.DoguConfigKey, len(config.Absent)) - for i, key := range config.Absent { - absent[i] = convertToDoguConfigKeyDomain(doguName, key) + if configEntry.Absent == nil || *configEntry.Absent == false && configEntry.Value != nil { + present[convertToDoguConfigKeyDomain(doguName, configEntry.Key)] = common.DoguConfigValue(*configEntry.Value) + } else { + absent[absentIndex] = convertToDoguConfigKeyDomain(doguName, configEntry.Key) + absentIndex++ } } @@ -128,51 +129,49 @@ func convertToDoguConfigKeyDomain(doguName, key string) common.DoguConfigKey { } } -func convertToGlobalConfigDTO(config domain.GlobalConfig) v2.GlobalConfig { - var present map[string]string - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Present) != 0 { - present = make(map[string]string, len(config.Present)) - for key, value := range config.Present { - present[string(key)] = string(value) - } +func convertToGlobalConfigDTO(config domain.GlobalConfig) []v2.ConfigEntry { + // empty struct -> nil + if len(config.Present) == 0 && len(config.Absent) == 0 { + return nil } - var absent []string + result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) // we check for empty values to make good use of default values // this makes testing easier - if len(config.Absent) != 0 { - absent = make([]string, len(config.Absent)) - for i, key := range config.Absent { - absent[i] = string(key) - } + for key, value := range config.Present { + valueString := string(value) + result = append(result, v2.ConfigEntry{ + Key: string(key), + Value: &valueString, + }) } - return v2.GlobalConfig{ - Present: present, - Absent: absent, + for _, key := range config.Absent { + truePtr := true + result = append(result, v2.ConfigEntry{ + Key: string(key), + Absent: &truePtr, + }) } + + return result } -func convertToGlobalConfigDomain(config v2.GlobalConfig) domain.GlobalConfig { - var present map[common.GlobalConfigKey]common.GlobalConfigValue - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Present) != 0 { - present = make(map[common.GlobalConfigKey]common.GlobalConfigValue, len(config.Present)) - for key, value := range config.Present { - present[common.GlobalConfigKey(key)] = common.GlobalConfigValue(value) - } +func convertToGlobalConfigDomain(config []v2.ConfigEntry) domain.GlobalConfig { + if config == nil { + return domain.GlobalConfig{} } - var absent []common.GlobalConfigKey - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Absent) != 0 { - absent = make([]common.GlobalConfigKey, len(config.Absent)) - for i, key := range config.Absent { - absent[i] = common.GlobalConfigKey(key) + present := make(map[common.GlobalConfigKey]common.GlobalConfigValue, len(config)) + absent := make([]common.GlobalConfigKey, len(config)) + + absentIndex := 0 + for _, configEntry := range config { + if configEntry.Absent != nil && *configEntry.Absent == false && configEntry.Value != nil { + present[common.GlobalConfigKey(configEntry.Key)] = common.GlobalConfigValue(*configEntry.Value) + } else { + absent[absentIndex] = common.GlobalConfigKey(configEntry.Key) + absentIndex++ } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go index 423c108b..7dabd448 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go @@ -1,18 +1,23 @@ package serializer import ( + "testing" + v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" - "testing" +) + +var ( + val1 = "val1" ) func Test_convertToDoguConfigDTO(t *testing.T) { tests := []struct { name string config domain.DoguConfig - want *v2.DoguConfig + want []v2.ConfigEntry }{ { name: "nil config", @@ -31,13 +36,11 @@ func Test_convertToDoguConfigDTO(t *testing.T) { name: "convert present config", config: domain.DoguConfig{ Present: map[common.DoguConfigKey]common.DoguConfigValue{ - testDoguKey1: "val1", + testDoguKey1: common.DoguConfigValue(val1), }, }, - want: &v2.DoguConfig{ - Present: map[string]string{ - testDoguKey1.Key.String(): "val1", - }, + want: []v2.ConfigEntry{ + {Key: testDoguKey1.Key.String(), Value: &val1}, }, }, { @@ -47,10 +50,8 @@ func Test_convertToDoguConfigDTO(t *testing.T) { testDoguKey1, }, }, - want: &v2.DoguConfig{ - Absent: []string{ - testDoguKey1.Key.String(), - }, + want: []v2.ConfigEntry{ + {Key: testDoguKey1.Key.String(), Absent: &trueVar}, }, }, } @@ -64,7 +65,7 @@ func Test_convertToDoguConfigDTO(t *testing.T) { func Test_convertToDoguConfigDomain(t *testing.T) { type args struct { doguName string - config *v2.DoguConfig + config []v2.ConfigEntry } tests := []struct { name string @@ -82,7 +83,7 @@ func Test_convertToDoguConfigDomain(t *testing.T) { name: "nil config", args: args{ doguName: string(testDoguKey1.DoguName), - config: &v2.DoguConfig{}, + config: []v2.ConfigEntry{}, }, want: domain.DoguConfig{}, }, @@ -90,15 +91,16 @@ func Test_convertToDoguConfigDomain(t *testing.T) { name: "convert present config", args: args{ doguName: string(testDoguKey1.DoguName), - config: &v2.DoguConfig{ - Present: map[string]string{ - testDoguKey1.Key.String(): "val1", + config: []v2.ConfigEntry{ + { + Key: testDoguKey1.Key.String(), + Value: &val1, }, }, }, want: domain.DoguConfig{ Present: map[common.DoguConfigKey]common.DoguConfigValue{ - testDoguKey1: "val1", + testDoguKey1: common.DoguConfigValue(val1), }, }, }, @@ -106,9 +108,10 @@ func Test_convertToDoguConfigDomain(t *testing.T) { name: "convert absent config", args: args{ doguName: string(testDoguKey1.DoguName), - config: &v2.DoguConfig{ - Absent: []string{ - testDoguKey1.Key.String(), + config: []v2.ConfigEntry{ + { + Key: testDoguKey1.Key.String(), + Absent: &trueVar, }, }, }, @@ -130,12 +133,12 @@ func Test_convertToGlobalConfigDTO(t *testing.T) { tests := []struct { name string config domain.GlobalConfig - want v2.GlobalConfig + want []v2.ConfigEntry }{ { name: "empty", config: domain.GlobalConfig{}, - want: v2.GlobalConfig{}, + want: nil, }, { name: "convert present", @@ -144,9 +147,10 @@ func Test_convertToGlobalConfigDTO(t *testing.T) { "test": "val1", }, }, - want: v2.GlobalConfig{ - Present: map[string]string{ - "test": "val1", + want: []v2.ConfigEntry{ + { + Key: "test", + Value: &val1, }, }, }, @@ -157,9 +161,10 @@ func Test_convertToGlobalConfigDTO(t *testing.T) { "test", }, }, - want: v2.GlobalConfig{ - Absent: []string{ - "test", + want: []v2.ConfigEntry{ + { + Key: "test", + Absent: &trueVar, }, }, }, @@ -174,19 +179,20 @@ func Test_convertToGlobalConfigDTO(t *testing.T) { func Test_convertToGlobalConfigDomain(t *testing.T) { tests := []struct { name string - config v2.GlobalConfig + config []v2.ConfigEntry want domain.GlobalConfig }{ { - name: "empty", - config: v2.GlobalConfig{}, + name: "nil", + config: nil, want: domain.GlobalConfig{}, }, { name: "convert present", - config: v2.GlobalConfig{ - Present: map[string]string{ - "test": "val1", + config: []v2.ConfigEntry{ + { + Key: "test", + Value: &val1, }, }, want: domain.GlobalConfig{ @@ -197,9 +203,10 @@ func Test_convertToGlobalConfigDomain(t *testing.T) { }, { name: "convert present", - config: v2.GlobalConfig{ - Absent: []string{ - "test", + config: []v2.ConfigEntry{ + { + Key: "test", + Absent: &trueVar, }, }, want: domain.GlobalConfig{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index ec1cf625..72ad27c9 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -3,9 +3,11 @@ package serializer import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" + "k8s.io/apimachinery/pkg/api/resource" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -23,40 +25,66 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { continue } - var version core.Version - if dogu.Version != "" { - version, err = core.ParseVersion(dogu.Version) + var version *core.Version + if dogu.Version != nil && *dogu.Version != "" { + coreVersion, err := core.ParseVersion(*dogu.Version) + version = &coreVersion if err != nil { errorList = append(errorList, fmt.Errorf("could not parse version of target dogu %q: %w", dogu.Name, err)) continue } } - minVolumeSizeStr := dogu.PlatformConfig.ResourceConfig.MinVolumeSize - minVolumeSize, minVolumeSizeErr := ecosystem.GetNonNilQuantityRef(minVolumeSizeStr) - if minVolumeSizeErr != nil { - errorList = append(errorList, fmt.Errorf("could not parse minimum volume size %q for dogu %q", minVolumeSizeStr, dogu.Name)) - continue + absent := false + if dogu.Absent != nil { + absent = *dogu.Absent } - maxBodySizeStr := dogu.PlatformConfig.ReverseProxyConfig.MaxBodySize - maxBodySize, maxBodySizeErr := ecosystem.GetQuantityReference(maxBodySizeStr) - if maxBodySizeErr != nil { - errorList = append(errorList, fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", maxBodySizeStr, dogu.Name)) - continue + var minVolumeSize, maxBodySize *resource.Quantity + var reverseProxyConfig *ecosystem.ReverseProxyConfig + var rewriteTarget, additionalConfig *string + var additionalMounts []ecosystem.AdditionalMount + if dogu.PlatformConfig != nil { + if dogu.PlatformConfig.ResourceConfig != nil && dogu.PlatformConfig.ResourceConfig.MinVolumeSize != nil { + minVolumeSizeStr := dogu.PlatformConfig.ResourceConfig.MinVolumeSize + minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) + if err != nil { + errorList = append(errorList, fmt.Errorf("could not parse minimum volume size %q for dogu %q", *minVolumeSizeStr, dogu.Name)) + continue + } + } + + if dogu.PlatformConfig.ReverseProxyConfig != nil { + if dogu.PlatformConfig.ReverseProxyConfig.MaxBodySize != nil { + maxBodySizeStr := dogu.PlatformConfig.ReverseProxyConfig.MaxBodySize + maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) + if err != nil { + errorList = append(errorList, fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", *maxBodySizeStr, dogu.Name)) + continue + } + } + + rewriteTarget = dogu.PlatformConfig.ReverseProxyConfig.RewriteTarget + additionalConfig = dogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig + reverseProxyConfig = &ecosystem.ReverseProxyConfig{ + MaxBodySize: maxBodySize, + RewriteTarget: rewriteTarget, + AdditionalConfig: additionalConfig, + } + } + + if dogu.PlatformConfig.AdditionalMountsConfig != nil { + additionalMounts = convertAdditionalMountsFromDTOToDomain(dogu.PlatformConfig.AdditionalMountsConfig) + } } convertedDogus = append(convertedDogus, domain.Dogu{ - Name: name, - Version: version, - TargetState: ToDomainTargetState(dogu.Absent), - MinVolumeSize: *minVolumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - MaxBodySize: maxBodySize, - RewriteTarget: ecosystem.RewriteTarget(dogu.PlatformConfig.ReverseProxyConfig.RewriteTarget), - AdditionalConfig: ecosystem.AdditionalConfig(dogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig), - }, - AdditionalMounts: convertAdditionalMountsFromDTOToDomain(dogu.PlatformConfig.AdditionalMountsConfig), + Name: name, + Version: version, + Absent: absent, + MinVolumeSize: minVolumeSize, + ReverseProxyConfig: reverseProxyConfig, + AdditionalMounts: additionalMounts, }) } @@ -80,18 +108,23 @@ func ConvertMaskDogus(dogus []bpv2.MaskDogu) ([]domain.MaskDogu, error) { } var version core.Version - if dogu.Version != "" { - version, err = core.ParseVersion(dogu.Version) + if dogu.Version != nil && *dogu.Version != "" { + version, err = core.ParseVersion(*dogu.Version) if err != nil { errorList = append(errorList, fmt.Errorf("could not parse version of mask dogu %q: %w", dogu.Name, err)) continue } } + absent := false + if dogu.Absent != nil { + absent = *dogu.Absent + } + convertedDogus = append(convertedDogus, domain.MaskDogu{ - Name: name, - Version: version, - TargetState: ToDomainTargetState(dogu.Absent), + Name: name, + Version: version, + Absent: absent, }) } @@ -119,39 +152,49 @@ func convertAdditionalMountsFromDTOToDomain(mounts []bpv2.AdditionalMount) []eco func ConvertToDoguDTOs(dogus []domain.Dogu) []bpv2.Dogu { converted := util.Map(dogus, func(dogu domain.Dogu) bpv2.Dogu { + var version *string + if dogu.Version != nil { + version = &dogu.Version.Raw + } return bpv2.Dogu{ Name: dogu.Name.String(), - Version: dogu.Version.Raw, - Absent: ToSerializerAbsentState(dogu.TargetState), + Version: version, + Absent: &dogu.Absent, PlatformConfig: convertPlatformConfigDTO(dogu), } }) return converted } -func convertPlatformConfigDTO(dogu domain.Dogu) bpv2.PlatformConfig { +func convertPlatformConfigDTO(dogu domain.Dogu) *bpv2.PlatformConfig { + if dogu.ReverseProxyConfig == nil && dogu.MinVolumeSize == nil && len(dogu.AdditionalMounts) == 0 { + return nil + } + config := bpv2.PlatformConfig{} config.ResourceConfig = convertResourceConfigDTO(dogu) config.ReverseProxyConfig = convertReverseProxyConfigDTO(dogu) config.AdditionalMountsConfig = convertAdditionalMountsConfig(dogu) - return config + return &config } -func convertReverseProxyConfigDTO(dogu domain.Dogu) bpv2.ReverseProxyConfig { +func convertReverseProxyConfigDTO(dogu domain.Dogu) *bpv2.ReverseProxyConfig { config := bpv2.ReverseProxyConfig{} - config.RewriteTarget = string(dogu.ReverseProxyConfig.RewriteTarget) - config.AdditionalConfig = string(dogu.ReverseProxyConfig.AdditionalConfig) - config.MaxBodySize = ecosystem.GetQuantityString(dogu.ReverseProxyConfig.MaxBodySize) + if dogu.ReverseProxyConfig != nil { + config.RewriteTarget = dogu.ReverseProxyConfig.RewriteTarget + config.AdditionalConfig = dogu.ReverseProxyConfig.AdditionalConfig + config.MaxBodySize = ecosystem.GetQuantityString(dogu.ReverseProxyConfig.MaxBodySize) + } - return config + return &config } -func convertResourceConfigDTO(dogu domain.Dogu) bpv2.ResourceConfig { +func convertResourceConfigDTO(dogu domain.Dogu) *bpv2.ResourceConfig { config := bpv2.ResourceConfig{} - config.MinVolumeSize = ecosystem.GetQuantityString(&dogu.MinVolumeSize) + config.MinVolumeSize = ecosystem.GetQuantityString(dogu.MinVolumeSize) - return config + return &config } func convertAdditionalMountsConfig(dogu domain.Dogu) []bpv2.AdditionalMount { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go index 80663772..49061aa4 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go @@ -8,7 +8,7 @@ import ( "github.com/cloudogu/k8s-registry-lib/config" ) -func convertToDoguConfigDiffsDomain(doguName string, dtoDiffs crd.DoguConfigDiff) domain.DoguConfigDiffs { +func convertToDoguConfigDiffsDomain(doguName string, dtoDiffs crd.ConfigDiff) domain.DoguConfigDiffs { var doguConfigDiff domain.DoguConfigDiffs for _, entryDiff := range dtoDiffs { doguConfigDiff = append(doguConfigDiff, convertToDoguConfigEntryDiffDomain(doguName, entryDiff)) @@ -16,7 +16,7 @@ func convertToDoguConfigDiffsDomain(doguName string, dtoDiffs crd.DoguConfigDiff return doguConfigDiff } -func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.DoguConfigEntryDiff) domain.DoguConfigEntryDiff { +func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.ConfigEntryDiff) domain.DoguConfigEntryDiff { return domain.DoguConfigEntryDiff{ Key: common.DoguConfigKey{ DoguName: cescommons.SimpleName(doguName), @@ -34,26 +34,26 @@ func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.DoguConfigEntry } } -func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) []crd.DoguConfigEntryDiff { - var dtoDiffs []crd.DoguConfigEntryDiff +func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) crd.ConfigDiff { + var dtoDiffs []crd.ConfigEntryDiff for _, domainDiff := range domainDiffs { dtoDiffs = append(dtoDiffs, convertToDoguConfigEntryDiffDTO(domainDiff, isSensitive)) } return dtoDiffs } -func convertToDoguConfigEntryDiffDTO(domainModel domain.DoguConfigEntryDiff, isSensitive bool) crd.DoguConfigEntryDiff { - actual := crd.DoguConfigValueState{ +func convertToDoguConfigEntryDiffDTO(domainModel domain.DoguConfigEntryDiff, isSensitive bool) crd.ConfigEntryDiff { + actual := crd.ConfigValueState{ Exists: domainModel.Actual.Exists, } - expected := crd.DoguConfigValueState{ + expected := crd.ConfigValueState{ Exists: domainModel.Expected.Exists, } if !isSensitive { actual.Value = domainModel.Actual.Value expected.Value = domainModel.Expected.Value } - return crd.DoguConfigEntryDiff{ + return crd.ConfigEntryDiff{ Key: string(domainModel.Key.Key), Actual: actual, Expected: expected, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go index a41b1d7b..802a9d1d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go @@ -1,18 +1,25 @@ package serializer import ( + "testing" + crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" - "testing" +) + +var ( + limit512 = "512m" + limit1024 = "1024m" ) func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { + tests := []struct { name string domainModel domain.DoguConfigDiffs - want []crd.DoguConfigEntryDiff + want crd.ConfigDiff isSensitive bool }{ { @@ -29,11 +36,11 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { Key: "container_config/memory_limit", }, Actual: domain.DoguConfigValueState{ - Value: "512m", + Value: &limit512, Exists: true, }, Expected: domain.DoguConfigValueState{ - Value: "1024m", + Value: &limit1024, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -47,32 +54,32 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { Exists: false, }, Expected: domain.DoguConfigValueState{ - Value: "512m", + Value: &limit512, Exists: true, }, NeededAction: domain.ConfigActionSet, }, }, - want: []crd.DoguConfigEntryDiff{ + want: crd.ConfigDiff{ { Key: "container_config/memory_limit", - Actual: crd.DoguConfigValueState{ - Value: "512m", + Actual: crd.ConfigValueState{ + Value: &limit512, Exists: true, }, - Expected: crd.DoguConfigValueState{ - Value: "1024m", + Expected: crd.ConfigValueState{ + Value: &limit1024, Exists: true, }, NeededAction: "set", }, { Key: "container_config/swap_limit", - Actual: crd.DoguConfigValueState{ + Actual: crd.ConfigValueState{ Exists: false, }, - Expected: crd.DoguConfigValueState{ - Value: "512m", + Expected: crd.ConfigValueState{ + Value: &limit512, Exists: true, }, NeededAction: "set", @@ -90,36 +97,36 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { func Test_convertToDoguConfigDiffsDomain(t *testing.T) { tests := []struct { name string - dto crd.DoguConfigDiff + dto crd.ConfigDiff want domain.DoguConfigDiffs }{ { name: "should exit early if slices are empty", - dto: crd.DoguConfigDiff{}, + dto: crd.ConfigDiff{}, want: nil, }, { name: "should convert multiple dogu config diffs", - dto: crd.DoguConfigDiff{ + dto: crd.ConfigDiff{ { Key: "container_config/memory_limit", - Actual: crd.DoguConfigValueState{ - Value: "512m", + Actual: crd.ConfigValueState{ + Value: &limit512, Exists: true, }, - Expected: crd.DoguConfigValueState{ - Value: "1024m", + Expected: crd.ConfigValueState{ + Value: &limit1024, Exists: true, }, NeededAction: "set", }, { Key: "container_config/swap_limit", - Actual: crd.DoguConfigValueState{ + Actual: crd.ConfigValueState{ Exists: false, }, - Expected: crd.DoguConfigValueState{ - Value: "512m", + Expected: crd.ConfigValueState{ + Value: &limit512, Exists: true, }, NeededAction: "set", @@ -132,11 +139,11 @@ func Test_convertToDoguConfigDiffsDomain(t *testing.T) { Key: "container_config/memory_limit", }, Actual: domain.DoguConfigValueState{ - Value: "512m", + Value: &limit512, Exists: true, }, Expected: domain.DoguConfigValueState{ - Value: "1024m", + Value: &limit1024, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -150,7 +157,7 @@ func Test_convertToDoguConfigDiffsDomain(t *testing.T) { Exists: false, }, Expected: domain.DoguConfigValueState{ - Value: "512m", + Value: &limit512, Exists: true, }, NeededAction: domain.ConfigActionSet, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index f44c214c..32179158 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -3,11 +3,13 @@ package serializer import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "k8s.io/apimachinery/pkg/api/resource" ) func convertToDoguDiffDTO(domainModel domain.DoguDiff) crd.DoguDiff { @@ -19,30 +21,30 @@ func convertToDoguDiffDTO(domainModel domain.DoguDiff) crd.DoguDiff { return crd.DoguDiff{ Actual: crd.DoguDiffState{ - Namespace: string(domainModel.Actual.Namespace), - Version: domainModel.Actual.Version.Raw, - InstallationState: domainModel.Actual.InstallationState.String(), - ResourceConfig: crd.ResourceConfig{ + Namespace: string(domainModel.Actual.Namespace), + Version: &domainModel.Actual.Version.Raw, + Absent: domainModel.Actual.Absent, + ResourceConfig: &crd.ResourceConfig{ MinVolumeSize: convertMinimumVolumeSizeToDTO(domainModel.Actual.MinVolumeSize), }, - ReverseProxyConfig: crd.ReverseProxyConfig{ + ReverseProxyConfig: &crd.ReverseProxyConfig{ MaxBodySize: ecosystem.GetQuantityString(domainModel.Actual.ReverseProxyConfig.MaxBodySize), - RewriteTarget: string(domainModel.Actual.ReverseProxyConfig.RewriteTarget), - AdditionalConfig: string(domainModel.Actual.ReverseProxyConfig.AdditionalConfig), + RewriteTarget: domainModel.Actual.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: domainModel.Actual.ReverseProxyConfig.AdditionalConfig, }, AdditionalMounts: convertAdditionalMountsToDoguDiffDTO(domainModel.Actual.AdditionalMounts), }, Expected: crd.DoguDiffState{ - Namespace: string(domainModel.Expected.Namespace), - Version: domainModel.Expected.Version.Raw, - InstallationState: domainModel.Expected.InstallationState.String(), - ResourceConfig: crd.ResourceConfig{ + Namespace: string(domainModel.Expected.Namespace), + Version: &domainModel.Expected.Version.Raw, + Absent: domainModel.Expected.Absent, + ResourceConfig: &crd.ResourceConfig{ MinVolumeSize: convertMinimumVolumeSizeToDTO(domainModel.Expected.MinVolumeSize), }, - ReverseProxyConfig: crd.ReverseProxyConfig{ + ReverseProxyConfig: &crd.ReverseProxyConfig{ MaxBodySize: ecosystem.GetQuantityString(domainModel.Expected.ReverseProxyConfig.MaxBodySize), - RewriteTarget: string(domainModel.Expected.ReverseProxyConfig.RewriteTarget), - AdditionalConfig: string(domainModel.Expected.ReverseProxyConfig.AdditionalConfig), + RewriteTarget: domainModel.Expected.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: domainModel.Expected.ReverseProxyConfig.AdditionalConfig, }, AdditionalMounts: convertAdditionalMountsToDoguDiffDTO(domainModel.Expected.AdditionalMounts), }, @@ -50,11 +52,12 @@ func convertToDoguDiffDTO(domainModel domain.DoguDiff) crd.DoguDiff { } } -func convertMinimumVolumeSizeToDTO(minVolSize ecosystem.VolumeSize) string { +func convertMinimumVolumeSizeToDTO(minVolSize *ecosystem.VolumeSize) *string { if minVolSize.IsZero() { - return "" + return nil } else { - return minVolSize.String() + s := minVolSize.String() + return &s } } @@ -111,38 +114,42 @@ func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, er var version core.Version var versionErr error - if dto.Version != "" { - version, versionErr = core.ParseVersion(dto.Version) + if dto.Version != nil && *dto.Version != "" { + version, versionErr = core.ParseVersion(*dto.Version) if versionErr != nil { - versionErr = fmt.Errorf("failed to parse version %q: %w", dto.Version, versionErr) + versionErr = fmt.Errorf("failed to parse version %q: %w", *dto.Version, versionErr) } } errorList = append(errorList, versionErr) - state, stateErr := ToOldDomainTargetState(dto.InstallationState) - if stateErr != nil { - errorList = append(errorList, fmt.Errorf("failed to parse installation state %q: %w", dto.InstallationState, stateErr)) - } - - minVolumeSize, volumeSizeErr := ecosystem.GetNonNilQuantityRef(dto.ResourceConfig.MinVolumeSize) - if volumeSizeErr != nil { - errorList = append(errorList, fmt.Errorf("failed to parse minimum volume size %q: %w", dto.ResourceConfig.MinVolumeSize, volumeSizeErr)) + var minVolumeSize, maxBodySize *resource.Quantity + var volumeSizeErr error + if dto.ResourceConfig != nil && dto.ResourceConfig.MinVolumeSize != nil { + minVolumeSizeStr := dto.ResourceConfig.MinVolumeSize + minVolumeSize, volumeSizeErr = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) + if volumeSizeErr != nil { + errorList = append(errorList, fmt.Errorf("failed to parse minimum volume size %q: %w", *minVolumeSizeStr, volumeSizeErr)) + } } - maxBodySize, bodySizeErr := ecosystem.GetQuantityReference(dto.ReverseProxyConfig.MaxBodySize) - if bodySizeErr != nil { - errorList = append(errorList, fmt.Errorf("failed to parse maximum proxy body size %q: %w", dto.ReverseProxyConfig.MaxBodySize, bodySizeErr)) + var bodySizeErr error + if dto.ReverseProxyConfig != nil && dto.ReverseProxyConfig.MaxBodySize != nil { + maxBodySizeStr := dto.ReverseProxyConfig.MaxBodySize + maxBodySize, bodySizeErr = ecosystem.GetQuantityReference(*maxBodySizeStr) + if bodySizeErr != nil { + errorList = append(errorList, fmt.Errorf("failed to parse maximum proxy body size %q: %w", *maxBodySizeStr, bodySizeErr)) + } } return domain.DoguDiffState{ - Namespace: cescommons.Namespace(dto.Namespace), - Version: version, - InstallationState: state, - MinVolumeSize: *minVolumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: cescommons.Namespace(dto.Namespace), + Version: &version, + Absent: dto.Absent, + MinVolumeSize: minVolumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: maxBodySize, - RewriteTarget: ecosystem.RewriteTarget(dto.ReverseProxyConfig.RewriteTarget), - AdditionalConfig: ecosystem.AdditionalConfig(dto.ReverseProxyConfig.AdditionalConfig), + RewriteTarget: dto.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: dto.ReverseProxyConfig.AdditionalConfig, }, AdditionalMounts: convertAdditionalMountsToDoguDiffDomain(dto.AdditionalMounts), }, errors.Join(errorList...) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index e9973067..01af15c6 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -1,26 +1,33 @@ package serializer import ( + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" - "testing" ) func Test_convertMinimumVolumeSizeToDTO(t *testing.T) { + volumeSize1g := resource.MustParse("1Gi") tests := []struct { name string - minVolSize ecosystem.VolumeSize + minVolSize *ecosystem.VolumeSize want string }{ + { + name: "nil", + minVolSize: nil, + want: "", + }, { name: "empty", - minVolSize: ecosystem.VolumeSize{}, + minVolSize: &ecosystem.VolumeSize{}, want: "", }, { name: "empty", - minVolSize: resource.MustParse("1Gi"), + minVolSize: &volumeSize1g, want: "1Gi", }, } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go index 39ae6c42..9a89f3cd 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go @@ -2,9 +2,10 @@ package serializer import ( "fmt" - bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "testing" + bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" @@ -14,12 +15,26 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" ) +var ( + wrongVersion = "1." + rewriteTarget = "/" + additionalConfig = "additional" + volumeSize = resource.MustParse("1Gi") + volumeSizeString = volumeSize.String() + proxyBodySize = resource.MustParse("1G") + proxyBodySizeString = proxyBodySize.String() + subfolder = "subfolder" + subfolder2 = "secsubfolder" +) + func TestConvertDogus(t *testing.T) { type args struct { dogus []bpv2.Dogu } - proxyBodySize := resource.MustParse("1G") - volumeSize := resource.MustParse("1Gi") + + wrongBodySize := "1GE" + wrongVolumeSize := "1GIE" + tests := []struct { name string args args @@ -40,61 +55,61 @@ func TestConvertDogus(t *testing.T) { }, { name: "normal dogu", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false}}, wantErr: assert.NoError, }, { name: "absent dogu", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Absent: true}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, TargetState: domain.TargetStateAbsent}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Absent: &trueVar}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Absent: true}}, wantErr: assert.NoError, }, { name: "dogu with max proxy body size", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false, PlatformConfig: bpv2.PlatformConfig{ReverseProxyConfig: bpv2.ReverseProxyConfig{MaxBodySize: "1G"}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &volumeSizeString}}}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize}}}, wantErr: assert.NoError, }, { name: "dogu with proxy rewrite target", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false, PlatformConfig: bpv2.PlatformConfig{ReverseProxyConfig: bpv2.ReverseProxyConfig{RewriteTarget: "/"}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent, ReverseProxyConfig: ecosystem.ReverseProxyConfig{RewriteTarget: "/"}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}, wantErr: assert.NoError, }, { name: "dogu with proxy additional config", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false, PlatformConfig: bpv2.PlatformConfig{ReverseProxyConfig: bpv2.ReverseProxyConfig{AdditionalConfig: "additional"}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent, ReverseProxyConfig: ecosystem.ReverseProxyConfig{AdditionalConfig: "additional"}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}, wantErr: assert.NoError, }, { name: "dogu with invalid proxy body size", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false, PlatformConfig: bpv2.PlatformConfig{ReverseProxyConfig: bpv2.ReverseProxyConfig{MaxBodySize: "1GE"}}}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &wrongBodySize}}}}}, want: nil, wantErr: assert.Error, }, { name: "dogu with min volume size", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, PlatformConfig: bpv2.PlatformConfig{ResourceConfig: bpv2.ResourceConfig{MinVolumeSize: "1Gi"}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent, MinVolumeSize: volumeSize}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, PlatformConfig: &bpv2.PlatformConfig{ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}}}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize}}, wantErr: assert.NoError, }, { name: "dogu with invalid volume size", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, PlatformConfig: bpv2.PlatformConfig{ResourceConfig: bpv2.ResourceConfig{MinVolumeSize: "1GIE"}}}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, PlatformConfig: &bpv2.PlatformConfig{ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &wrongVolumeSize}}}}}, want: nil, wantErr: assert.Error, }, { name: "no namespace", - args: args{dogus: []bpv2.Dogu{{Name: "postgres", Version: version3211.Raw}}}, + args: args{dogus: []bpv2.Dogu{{Name: "postgres", Version: &version3211.Raw}}}, want: nil, wantErr: assert.Error, }, { name: "unparsable version", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: "1."}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &wrongVersion}}}, want: nil, wantErr: assert.Error, }, @@ -102,40 +117,40 @@ func TestConvertDogus(t *testing.T) { name: "should convert additionalMounts", args: args{dogus: []bpv2.Dogu{{ Name: "official/postgres", - Version: version3211.Raw, - PlatformConfig: bpv2.PlatformConfig{ + Version: &version3211.Raw, + PlatformConfig: &bpv2.PlatformConfig{ AdditionalMountsConfig: []bpv2.AdditionalMount{ { SourceType: bpv2.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: bpv2.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, }}}, want: []domain.Dogu{{ - Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, - Version: version3211, - TargetState: domain.TargetStatePresent, + Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, + Version: &version3211, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }}}, wantErr: assert.NoError, @@ -144,15 +159,15 @@ func TestConvertDogus(t *testing.T) { name: "should return nil slice if dogu contains an nil slice", args: args{dogus: []bpv2.Dogu{{ Name: "official/postgres", - Version: version3211.Raw, - PlatformConfig: bpv2.PlatformConfig{ + Version: &version3211.Raw, + PlatformConfig: &bpv2.PlatformConfig{ AdditionalMountsConfig: nil, }, }}}, want: []domain.Dogu{{ Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, - Version: version3211, - TargetState: domain.TargetStatePresent, + Version: &version3211, + Absent: false, AdditionalMounts: nil, }}, wantErr: assert.NoError, @@ -193,25 +208,25 @@ func TestConvertMaskDogus(t *testing.T) { }, { name: "normal dogu", - args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false}}}, - want: []domain.MaskDogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent}}, + args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar}}}, + want: []domain.MaskDogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, Absent: false}}, wantErr: assert.NoError, }, { name: "absent dogu", - args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Absent: true}}}, - want: []domain.MaskDogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, TargetState: domain.TargetStateAbsent}}, + args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Absent: &trueVar}}}, + want: []domain.MaskDogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Absent: true}}, wantErr: assert.NoError, }, { name: "no namespace", - args: args{dogus: []bpv2.MaskDogu{{Name: "postgres", Version: version3211.Raw}}}, + args: args{dogus: []bpv2.MaskDogu{{Name: "postgres", Version: &version3211.Raw}}}, want: nil, wantErr: assert.Error, }, { name: "unparsable version", - args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Version: "1."}}}, + args: args{dogus: []bpv2.MaskDogu{{Name: "official/postgres", Version: &wrongVersion}}}, want: nil, wantErr: assert.Error, }, @@ -231,8 +246,6 @@ func TestConvertToDoguDTOs(t *testing.T) { type args struct { dogus []domain.Dogu } - bodySize := resource.MustParse("100M") - volumeSize := resource.MustParse("1G") tests := []struct { name string args args @@ -250,51 +263,51 @@ func TestConvertToDoguDTOs(t *testing.T) { }, { name: "ok", - args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: version3211, TargetState: domain.TargetStatePresent, MinVolumeSize: volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &bodySize, RewriteTarget: "/", AdditionalConfig: "additional"}}}}, - want: []bpv2.Dogu{{Name: "official/postgres", Version: version3211.Raw, Absent: false, PlatformConfig: bpv2.PlatformConfig{ResourceConfig: bpv2.ResourceConfig{MinVolumeSize: "1G"}, ReverseProxyConfig: bpv2.ReverseProxyConfig{MaxBodySize: "100M", RewriteTarget: "/", AdditionalConfig: "additional"}}}}, + args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, + want: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}, ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, }, { name: "additionalMountsConfig", args: args{dogus: []domain.Dogu{{ Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, - Version: version3211, - TargetState: domain.TargetStatePresent, - MinVolumeSize: volumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &bodySize, RewriteTarget: "/", AdditionalConfig: "additional"}, + Version: &version3211, + Absent: false, + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }}}, want: []bpv2.Dogu{{ Name: "official/postgres", - Version: version3211.Raw, - Absent: false, - PlatformConfig: bpv2.PlatformConfig{ - ResourceConfig: bpv2.ResourceConfig{MinVolumeSize: "1G"}, - ReverseProxyConfig: bpv2.ReverseProxyConfig{MaxBodySize: "100M", RewriteTarget: "/", AdditionalConfig: "additional"}, + Version: &version3211.Raw, + Absent: &falseVar, + PlatformConfig: &bpv2.PlatformConfig{ + ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}, + ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMountsConfig: []bpv2.AdditionalMount{ { SourceType: bpv2.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: bpv2.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }}}, @@ -303,19 +316,19 @@ func TestConvertToDoguDTOs(t *testing.T) { name: "should return nil slice if dogu contains an nil slice", args: args{dogus: []domain.Dogu{{ Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, - Version: version3211, - TargetState: domain.TargetStatePresent, - MinVolumeSize: volumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &bodySize, RewriteTarget: "/", AdditionalConfig: "additional"}, + Version: &version3211, + Absent: false, + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMounts: nil, }}}, want: []bpv2.Dogu{{ Name: "official/postgres", - Version: version3211.Raw, - Absent: false, - PlatformConfig: bpv2.PlatformConfig{ - ResourceConfig: bpv2.ResourceConfig{MinVolumeSize: "1G"}, - ReverseProxyConfig: bpv2.ReverseProxyConfig{MaxBodySize: "100M", RewriteTarget: "/", AdditionalConfig: "additional"}, + Version: &version3211.Raw, + Absent: &falseVar, + PlatformConfig: &bpv2.PlatformConfig{ + ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}, + ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMountsConfig: nil, }}}, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go index 2838bd0b..e58f6304 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go @@ -6,7 +6,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) -func convertToGlobalConfigDiffDomain(dto crd.GlobalConfigDiff) domain.GlobalConfigDiffs { +func convertToGlobalConfigDiffDomain(dto crd.ConfigDiff) domain.GlobalConfigDiffs { if len(dto) == 0 { return nil } @@ -18,7 +18,7 @@ func convertToGlobalConfigDiffDomain(dto crd.GlobalConfigDiff) domain.GlobalConf return globalConfigDiff } -func convertToGlobalConfigEntryDiffDomain(dto crd.GlobalConfigEntryDiff) domain.GlobalConfigEntryDiff { +func convertToGlobalConfigEntryDiffDomain(dto crd.ConfigEntryDiff) domain.GlobalConfigEntryDiff { return domain.GlobalConfigEntryDiff{ Key: common.GlobalConfigKey(dto.Key), Actual: domain.GlobalConfigValueState{ @@ -33,26 +33,26 @@ func convertToGlobalConfigEntryDiffDomain(dto crd.GlobalConfigEntryDiff) domain. } } -func convertToGlobalConfigDiffDTO(domainModel domain.GlobalConfigDiffs) crd.GlobalConfigDiff { +func convertToGlobalConfigDiffDTO(domainModel domain.GlobalConfigDiffs) crd.ConfigDiff { if len(domainModel) == 0 { return nil } - globalConfigDiff := make(crd.GlobalConfigDiff, len(domainModel)) + globalConfigDiff := make(crd.ConfigDiff, len(domainModel)) for i, entryDiff := range domainModel { globalConfigDiff[i] = convertToGlobalConfigEntryDiffDTO(entryDiff) } return globalConfigDiff } -func convertToGlobalConfigEntryDiffDTO(domainModel domain.GlobalConfigEntryDiff) crd.GlobalConfigEntryDiff { - return crd.GlobalConfigEntryDiff{ +func convertToGlobalConfigEntryDiffDTO(domainModel domain.GlobalConfigEntryDiff) crd.ConfigEntryDiff { + return crd.ConfigEntryDiff{ Key: string(domainModel.Key), - Actual: crd.GlobalConfigValueState{ + Actual: crd.ConfigValueState{ Value: domainModel.Actual.Value, Exists: domainModel.Actual.Exists, }, - Expected: crd.GlobalConfigValueState{ + Expected: crd.ConfigValueState{ Value: domainModel.Expected.Value, Exists: domainModel.Expected.Exists, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go index 5677d02c..f505c287 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go @@ -9,72 +9,60 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) -func convertToSensitiveDoguConfigDTO(config domain.SensitiveDoguConfig) *v2.SensitiveDoguConfig { +func convertToSensitiveDoguConfigDTO(config domain.SensitiveDoguConfig) []v2.ConfigEntry { // empty struct -> nil if len(config.Absent) == 0 && len(config.Present) == 0 { return nil } - var present []v2.SensitiveConfigEntry + result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) // we check for empty values to make good use of default values // this makes testing easier - if len(config.Present) != 0 { - present = make([]v2.SensitiveConfigEntry, len(config.Present)) - index := 0 - for key, value := range config.Present { - present[index] = v2.SensitiveConfigEntry{ - Key: string(key.Key), - SecretName: value.SecretName, - SecretKey: value.SecretKey, - } - index += 1 - } + for key, value := range config.Present { + result = append(result, v2.ConfigEntry{ + Key: string(key.Key), + SecretRef: &v2.SecretReference{ + Name: value.SecretName, + Key: value.SecretKey, + }, + }) } - var absent []string - // we check for empty values to make good use of default values - // this makes testing easier - if len(config.Absent) != 0 { - absent = make([]string, len(config.Absent)) - for i, key := range config.Absent { - absent[i] = string(key.Key) - } + for _, key := range config.Absent { + truePtr := true + result = append(result, v2.ConfigEntry{ + Key: string(key.Key), + Absent: &truePtr, + }) } - return &v2.SensitiveDoguConfig{ - Present: present, - Absent: absent, - } + return result } -func convertToSensitiveDoguConfigDomain(doguName string, doguConfig *v2.SensitiveDoguConfig) domain.SensitiveDoguConfig { +func convertToSensitiveDoguConfigDomain(doguName string, doguConfig []v2.ConfigEntry) domain.SensitiveDoguConfig { if doguConfig == nil { return domain.SensitiveDoguConfig{} } - var present map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef - // we check for empty values to make good use of default values - // this makes testing easier - if len(doguConfig.Present) != 0 { - present = make(map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef, len(doguConfig.Present)) - for _, value := range doguConfig.Present { - present[convertToSensitiveDoguConfigKeyDomain(doguName, value.Key)] = domain.SensitiveValueRef{ - SecretName: value.SecretName, - SecretKey: value.SecretKey, - } - } - } + present := make(map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef, len(doguConfig)) + absent := make([]common.SensitiveDoguConfigKey, len(doguConfig)) - var absent []common.SensitiveDoguConfigKey - // we check for empty values to make good use of default values - // this makes testing easier - if len(doguConfig.Absent) != 0 { - absent = make([]common.SensitiveDoguConfigKey, len(doguConfig.Absent)) - for i, key := range doguConfig.Absent { - absent[i] = common.SensitiveDoguConfigKey{ + absentIndex := 0 + for _, configEntry := range doguConfig { + if configEntry.Absent == nil || !*configEntry.Absent { + if configEntry.Sensitive != nil && !*configEntry.Sensitive || configEntry.SecretRef == nil { + continue + } + present[convertToSensitiveDoguConfigKeyDomain(doguName, configEntry.Key)] = domain.SensitiveValueRef{ + SecretName: configEntry.SecretRef.Name, + SecretKey: configEntry.SecretRef.Key, + } + } else { + absent[absentIndex] = common.SensitiveDoguConfigKey{ DoguName: cescommons.SimpleName(doguName), - Key: config.Key(key), + Key: config.Key(configEntry.Key), } + absentIndex++ } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go index 1d870a6b..48635af4 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go @@ -1,11 +1,12 @@ package serializer import ( + "testing" + v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" - "testing" ) func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { @@ -13,7 +14,7 @@ func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { tests := []struct { name string config domain.SensitiveDoguConfig - want *v2.SensitiveDoguConfig + want []v2.ConfigEntry }{ { name: "empty struct to nil", @@ -38,13 +39,14 @@ func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { }, }, }, - want: &v2.SensitiveDoguConfig{ - Present: []v2.SensitiveConfigEntry{ - { - SecretName: "mySecret", - SecretKey: "myKey", - Key: string(testDoguKey1.Key), + want: []v2.ConfigEntry{ + { + Key: string(testDoguKey1.Key), + SecretRef: &v2.SecretReference{ + Name: "mySecret", + Key: "myKey", }, + Sensitive: &trueVar, }, }, }, @@ -55,9 +57,10 @@ func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { testDoguKey1, }, }, - want: &v2.SensitiveDoguConfig{ - Absent: []string{ - string(testDoguKey1.Key), + want: []v2.ConfigEntry{ + { + Key: string(testDoguKey1.Key), + Absent: &trueVar, }, }, }, @@ -72,7 +75,7 @@ func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { type args struct { doguName string - doguConfig *v2.SensitiveDoguConfig + doguConfig []v2.ConfigEntry } tests := []struct { name string @@ -91,7 +94,7 @@ func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { name: "empty", args: args{ doguName: string(testDoguKey1.DoguName), - doguConfig: &v2.SensitiveDoguConfig{}, + doguConfig: []v2.ConfigEntry{}, }, want: domain.SensitiveDoguConfig{}, }, @@ -99,13 +102,14 @@ func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { name: "convert present config", args: args{ doguName: string(testDoguKey1.DoguName), - doguConfig: &v2.SensitiveDoguConfig{ - Present: []v2.SensitiveConfigEntry{ - { - SecretName: "mySecret", - SecretKey: "myKey", - Key: string(testDoguKey1.Key), + doguConfig: []v2.ConfigEntry{ + { + Key: string(testDoguKey1.Key), + SecretRef: &v2.SecretReference{ + Name: "mySecret", + Key: "myKey", }, + Sensitive: &trueVar, }, }, }, @@ -122,9 +126,10 @@ func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { name: "convert present config", args: args{ doguName: string(testDoguKey1.DoguName), - doguConfig: &v2.SensitiveDoguConfig{ - Absent: []string{ - string(testDoguKey1.Key), + doguConfig: []v2.ConfigEntry{ + { + Key: string(testDoguKey1.Key), + Absent: &trueVar, }, }, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go index 7c78b947..9efc5ce0 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go @@ -3,13 +3,13 @@ package serializer import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "slices" ) -func ConvertToStateDiffDTO(domainModel domain.StateDiff) crd.StateDiff { +func ConvertToStateDiffDTO(domainModel domain.StateDiff) *crd.StateDiff { doguDiffs := make(map[string]crd.DoguDiff, len(domainModel.DoguDiffs)) for _, doguDiff := range domainModel.DoguDiffs { doguDiffs[string(doguDiff.DoguName)] = convertToDoguDiffDTO(doguDiff) @@ -20,43 +20,31 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) crd.StateDiff { componentDiffs[string(componentDiff.Name)] = convertToComponentDiffDTO(componentDiff) } - var dogus []cescommons.SimpleName - var combinedConfigDiffs map[string]crd.CombinedDoguConfigDiff - var doguConfigDiffByDogu map[cescommons.SimpleName]crd.DoguConfigDiff - var sensitiveDoguConfigDiff map[cescommons.SimpleName]crd.SensitiveDoguConfigDiff + var doguConfigDiffs map[string]crd.ConfigDiff if len(domainModel.DoguConfigDiffs) != 0 || len(domainModel.SensitiveDoguConfigDiffs) != 0 { - combinedConfigDiffs = make(map[string]crd.CombinedDoguConfigDiff) - doguConfigDiffByDogu = make(map[cescommons.SimpleName]crd.DoguConfigDiff) + doguConfigDiffs = make(map[string]crd.ConfigDiff) for doguName, doguConfigDiff := range domainModel.DoguConfigDiffs { - doguConfigDiffByDogu[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, false) - dogus = append(dogus, doguName) + doguConfigDiffs[doguName.String()] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, false) } - sensitiveDoguConfigDiff = make(map[cescommons.SimpleName]crd.SensitiveDoguConfigDiff) for doguName, doguConfigDiff := range domainModel.SensitiveDoguConfigDiffs { - sensitiveDoguConfigDiff[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, true) - dogus = append(dogus, doguName) - } - - // remove duplicates, so we have a complete list of all dogus with config - dogus = slices.Compact(dogus) - for _, doguName := range dogus { - combinedConfigDiffs[string(doguName)] = crd.CombinedDoguConfigDiff{ - DoguConfigDiff: doguConfigDiffByDogu[doguName], - SensitiveDoguConfigDiff: sensitiveDoguConfigDiff[doguName], - } + doguConfigDiffs[doguName.String()] = append(doguConfigDiffs[doguName.String()], convertToDoguConfigEntryDiffsDTO(doguConfigDiff, true)...) } } - return crd.StateDiff{ + return &crd.StateDiff{ DoguDiffs: doguDiffs, ComponentDiffs: componentDiffs, - DoguConfigDiffs: combinedConfigDiffs, + DoguConfigDiffs: doguConfigDiffs, GlobalConfigDiff: convertToGlobalConfigDiffDTO(domainModel.GlobalConfigDiffs), } } -func ConvertToStateDiffDomain(dto crd.StateDiff) (domain.StateDiff, error) { +func ConvertToStateDiffDomain(dto *crd.StateDiff) (domain.StateDiff, error) { + if dto == nil { + return domain.StateDiff{}, nil + } + var errs []error var doguDiffs []domain.DoguDiff @@ -83,17 +71,17 @@ func ConvertToStateDiffDomain(dto crd.StateDiff) (domain.StateDiff, error) { if len(dto.DoguConfigDiffs) != 0 { doguConfigDiffs = map[cescommons.SimpleName]domain.DoguConfigDiffs{} sensitiveDoguConfigDiffs = map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{} - for doguName, combinedConfigDiff := range dto.DoguConfigDiffs { - doguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, combinedConfigDiff.DoguConfigDiff) - sensitiveDoguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, combinedConfigDiff.SensitiveDoguConfigDiff) + for doguName, doguConfigDiff := range dto.DoguConfigDiffs { + // TODO: remove sensitive config diff from domain + doguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, doguConfigDiff) + sensitiveDoguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, doguConfigDiff) } } return domain.StateDiff{ - DoguDiffs: doguDiffs, - ComponentDiffs: componentDiffs, - DoguConfigDiffs: doguConfigDiffs, - SensitiveDoguConfigDiffs: sensitiveDoguConfigDiffs, - GlobalConfigDiffs: convertToGlobalConfigDiffDomain(dto.GlobalConfigDiff), + DoguDiffs: doguDiffs, + ComponentDiffs: componentDiffs, + DoguConfigDiffs: doguConfigDiffs, + GlobalConfigDiffs: convertToGlobalConfigDiffDomain(dto.GlobalConfigDiff), }, nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index 0a7f0c2e..a90040b9 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -2,6 +2,10 @@ package serializer import ( "cmp" + "reflect" + "slices" + "testing" + "github.com/Masterminds/semver/v3" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" @@ -10,22 +14,27 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" - "reflect" - "slices" - "testing" ) const testComponentName = "my-component" var ( - testVersionLowRaw = "1.2.3" - testVersionLow = semver.MustParse(testVersionLowRaw) - testVersionHighRaw = "2.3.4" - testVersionHigh = semver.MustParse(testVersionHighRaw) - testDogu = cescommons.SimpleName("testDogu") - testDogu2 = cescommons.SimpleName("testDogu2") - testDoguKey1 = common.DoguConfigKey{DoguName: testDogu, Key: "key1"} - testDoguKey2 = common.DoguConfigKey{DoguName: testDogu2, Key: "key2"} + testSemverVersionLowRaw = "1.2.3" + testSemverVersionLow = semver.MustParse(testSemverVersionLowRaw) + testSemverVersionHighRaw = "2.3.4" + testSemverVersionHigh = semver.MustParse(testSemverVersionHighRaw) + testCoreVersionLow = mustParseVersion("1.1.1-1") + testCoreVersionLowStr = testCoreVersionLow.String() + testCoreVersionHigh = mustParseVersion("1.2.3-1") + testCoreVersionHighStr = testCoreVersionHigh.String() + testDogu = cescommons.SimpleName("testDogu") + testDogu2 = cescommons.SimpleName("testDogu2") + testDoguKey1 = common.DoguConfigKey{DoguName: testDogu, Key: "key1"} + testDoguKey2 = common.DoguConfigKey{DoguName: testDogu2, Key: "key2"} + testFqdn1 = "ces1.example.com" + testFqdn2 = "ces2.example.com" + testSubfolderStr = "subfolder" + testSubfolderStr2 = "different_subfolder" ) func TestConvertToDTO(t *testing.T) { @@ -39,28 +48,28 @@ func TestConvertToDTO(t *testing.T) { domainModel: domain.StateDiff{DoguDiffs: []domain.DoguDiff{{ DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.1.1-1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionLow, + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.2.3-1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionHigh, + Absent: false, }, NeededActions: []domain.Action{domain.ActionUpgrade}, }}}, want: crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.1.1-1", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: true, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-1", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: true, }, NeededActions: []crd.DoguAction{"upgrade"}, }, @@ -72,26 +81,26 @@ func TestConvertToDTO(t *testing.T) { { DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - InstallationState: domain.TargetStateAbsent, + Namespace: "official", + Absent: true, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.2.3-1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionHigh, + Absent: false, }, NeededActions: []domain.Action{domain.ActionInstall}, }, { DoguName: "nginx-ingress", Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion("8.2.3-2"), - InstallationState: domain.TargetStatePresent, + Namespace: "k8s", + Version: &testCoreVersionLow, + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "k8s", - InstallationState: domain.TargetStateAbsent, + Namespace: "k8s", + Absent: true, }, NeededActions: []domain.Action{domain.ActionUninstall}, }, @@ -99,25 +108,25 @@ func TestConvertToDTO(t *testing.T) { want: crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - InstallationState: "absent", + Namespace: "official", + Absent: true, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-1", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, }, NeededActions: []crd.DoguAction{"install"}, }, "nginx-ingress": { Actual: crd.DoguDiffState{ - Namespace: "k8s", - Version: "8.2.3-2", - InstallationState: "present", + Namespace: "k8s", + Version: &testCoreVersionLowStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "k8s", - InstallationState: "absent", + Namespace: "k8s", + Absent: true, }, NeededActions: []crd.DoguAction{"uninstall"}, }, @@ -130,14 +139,14 @@ func TestConvertToDTO(t *testing.T) { ComponentDiffs: []domain.ComponentDiff{ { Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testVersionLow, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, + Actual: domain.ComponentDiffState{Version: testSemverVersionLow, Absent: false}, + Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, NeededActions: []domain.Action{domain.ActionUpgrade, domain.ActionSwitchComponentNamespace}, }, { Name: "my-component-2", - Actual: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{InstallationState: domain.TargetStateAbsent}, + Actual: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, + Expected: domain.ComponentDiffState{Absent: true}, NeededActions: []domain.Action{domain.ActionUninstall}, }, }}, @@ -145,13 +154,13 @@ func TestConvertToDTO(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{ testComponentName: { - Actual: crd.ComponentDiffState{Version: testVersionLowRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present"}, + Actual: crd.ComponentDiffState{Version: &testSemverVersionLowRaw, Absent: false}, + Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, NeededActions: []crd.ComponentAction{"upgrade", "component namespace switch"}, }, "my-component-2": { - Actual: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{InstallationState: "absent"}, + Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, + Expected: crd.ComponentDiffState{Absent: true}, NeededActions: []crd.ComponentAction{"uninstall"}, }, }}, @@ -171,7 +180,7 @@ func TestConvertToDTO(t *testing.T) { want: crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ + DoguConfigDiffs: map[string]crd.ConfigDiff{ "ldap": {}, "postfix": {}, }, @@ -183,11 +192,11 @@ func TestConvertToDTO(t *testing.T) { GlobalConfigDiffs: []domain.GlobalConfigEntryDiff{{ Key: "fqdn", Actual: domain.GlobalConfigValueState{ - Value: "ces1.example.com", + Value: &testFqdn1, Exists: true, }, Expected: domain.GlobalConfigValueState{ - Value: "ces2.example.com", + Value: &testFqdn2, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -196,14 +205,14 @@ func TestConvertToDTO(t *testing.T) { want: crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - GlobalConfigDiff: []crd.GlobalConfigEntryDiff{{ + GlobalConfigDiff: []crd.ConfigEntryDiff{{ Key: "fqdn", - Actual: crd.GlobalConfigValueState{ - Value: "ces1.example.com", + Actual: crd.ConfigValueState{ + Value: &testFqdn1, Exists: true, }, - Expected: crd.GlobalConfigValueState{ - Value: "ces2.example.com", + Expected: crd.ConfigValueState{ + Value: &testFqdn2, Exists: true, }, NeededAction: "set", @@ -215,28 +224,28 @@ func TestConvertToDTO(t *testing.T) { domainModel: domain.StateDiff{DoguDiffs: []domain.DoguDiff{{ DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.1.1-1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionLow, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &testSubfolderStr, }, }, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.2.3-1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionHigh, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "secret", Volume: "volume2", - Subfolder: "different_subfolder2", + Subfolder: &testSubfolderStr2, }, }, }, @@ -247,28 +256,28 @@ func TestConvertToDTO(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.1.1-1", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: false, AdditionalMounts: []crd.AdditionalMount{ { SourceType: crd.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &testSubfolderStr, }, }, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-1", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, AdditionalMounts: []crd.AdditionalMount{ { SourceType: crd.DataSourceConfigMap, Name: "secret", Volume: "volume2", - Subfolder: "different_subfolder2", + Subfolder: &testSubfolderStr2, }, }, }, @@ -298,6 +307,7 @@ func mustParseVersion(raw string) core.Version { } func TestConvertToDomainModel(t *testing.T) { + wrongVersionABCD := "a.b.c-d" tests := []struct { name string dto crd.StateDiff @@ -310,14 +320,14 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "a.b.c-d", - InstallationState: "present", + Namespace: "official", + Version: &wrongVersionABCD, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, }, NeededActions: []crd.DoguAction{"upgrade"}, }, @@ -335,14 +345,14 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "a.b.c-d", - InstallationState: "present", + Namespace: "official", + Version: &wrongVersionABCD, + Absent: false, }, NeededActions: []crd.DoguAction{"downgrade"}, }, @@ -354,70 +364,18 @@ func TestConvertToDomainModel(t *testing.T) { assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") }, }, - { - name: "fail to parse actual installation state of single dogu diff", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "invalid", - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "present", - }, - NeededActions: []crd.DoguAction{"install"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse installation state \"invalid\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, - { - name: "fail to parse expected installation state of single dogu diff", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "invalid", - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse installation state \"invalid\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, { name: "fail with multiple errors in single dogu diff", dto: crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "a.b.c-d", - InstallationState: "invalid", + Namespace: "official", + Version: &wrongVersionABCD, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "a.b.c-d", - InstallationState: "invalid", + Namespace: "official", + Version: &wrongVersionABCD, }, NeededActions: []crd.DoguAction{"none"}, }, @@ -426,7 +384,6 @@ func TestConvertToDomainModel(t *testing.T) { want: domain.StateDiff{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to parse installation state \"invalid\"") && assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") }, }, @@ -436,27 +393,27 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "postfix": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, }, NeededActions: []crd.DoguAction{"upgrade"}, }, "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "invalid", + Namespace: "official", + Version: &wrongVersionABCD, + Absent: false, }, NeededActions: []crd.DoguAction{"upgrade"}, }, @@ -474,27 +431,23 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "postfix": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "a.b.c-d", - InstallationState: "present", + Namespace: "official", + Version: &wrongVersionABCD, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, }, NeededActions: []crd.DoguAction{"none"}, }, "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "invalid", + Namespace: "official", + Version: &wrongVersionABCD, }, NeededActions: []crd.DoguAction{"upgrade"}, }, @@ -503,7 +456,7 @@ func TestConvertToDomainModel(t *testing.T) { want: domain.StateDiff{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to parse installation state \"invalid\"") && + assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") && assert.ErrorContains(t, err, "failed to convert dogu diff dto \"postfix\" to domain model") }, @@ -514,26 +467,26 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "postfix": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - Version: "2.3.4-5", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionHighStr, + Absent: false, }, NeededActions: []crd.DoguAction{"upgrade"}, }, "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - Version: "1.2.3-4", - InstallationState: "present", + Namespace: "official", + Version: &testCoreVersionLowStr, + Absent: false, }, Expected: crd.DoguDiffState{ - Namespace: "official", - InstallationState: "absent", + Namespace: "official", + Absent: true, }, NeededActions: []crd.DoguAction{"uninstall"}, }, @@ -543,26 +496,26 @@ func TestConvertToDomainModel(t *testing.T) { { DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.2.3-4"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionLow, + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "official", - InstallationState: domain.TargetStateAbsent, + Namespace: "official", + Absent: true, }, NeededActions: []domain.Action{domain.ActionUninstall}, }, { DoguName: "postfix", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("1.2.3-4"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionLow, + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion("2.3.4-5"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &testCoreVersionHigh, + Absent: false, }, NeededActions: []domain.Action{domain.ActionUpgrade}, }, @@ -574,14 +527,14 @@ func TestConvertToDomainModel(t *testing.T) { { name: "succeed for multiple dogu config diffs", dto: crd.StateDiff{ - DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ + DoguConfigDiffs: map[string]crd.ConfigDiff{ "ldap": { - DoguConfigDiff: crd.DoguConfigDiff{}, - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{}, + crd.ConfigEntryDiff{}, + crd.ConfigEntryDiff{}, }, "postfix": { - DoguConfigDiff: crd.DoguConfigDiff{}, - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{}, + crd.ConfigEntryDiff{}, + crd.ConfigEntryDiff{}, }, }, }, @@ -602,14 +555,14 @@ func TestConvertToDomainModel(t *testing.T) { { name: "succeed for global config diffs", dto: crd.StateDiff{ - GlobalConfigDiff: crd.GlobalConfigDiff{{ + GlobalConfigDiff: crd.ConfigDiff{{ Key: "fqdn", - Actual: crd.GlobalConfigValueState{ - Value: "ces1.example.com", + Actual: crd.ConfigValueState{ + Value: &testFqdn1, Exists: true, }, - Expected: crd.GlobalConfigValueState{ - Value: "ces2.example.com", + Expected: crd.ConfigValueState{ + Value: &testFqdn2, Exists: true, }, NeededAction: "set", @@ -619,11 +572,11 @@ func TestConvertToDomainModel(t *testing.T) { GlobalConfigDiffs: []domain.GlobalConfigEntryDiff{{ Key: "fqdn", Actual: domain.GlobalConfigValueState{ - Value: "ces1.example.com", + Value: &testFqdn1, Exists: true, }, Expected: domain.GlobalConfigValueState{ - Value: "ces2.example.com", + Value: &testFqdn2, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -638,13 +591,13 @@ func TestConvertToDomainModel(t *testing.T) { dto: crd.StateDiff{ ComponentDiffs: map[string]crd.ComponentDiff{ testComponentName: { - Actual: crd.ComponentDiffState{Version: testVersionLowRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present"}, + Actual: crd.ComponentDiffState{Version: &testSemverVersionLowRaw, Absent: false}, + Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, NeededActions: []crd.ComponentAction{"upgrade", "component namespace switch"}, }, "my-component-2": { - Actual: crd.ComponentDiffState{Version: testVersionHighRaw, InstallationState: "present"}, - Expected: crd.ComponentDiffState{InstallationState: "absent"}, + Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, + Expected: crd.ComponentDiffState{Absent: true}, NeededActions: []crd.ComponentAction{"uninstall"}, }, }, @@ -652,14 +605,14 @@ func TestConvertToDomainModel(t *testing.T) { want: domain.StateDiff{ComponentDiffs: []domain.ComponentDiff{ { Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testVersionLow, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, + Actual: domain.ComponentDiffState{Version: testSemverVersionLow, Absent: false}, + Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, NeededActions: []domain.Action{domain.ActionUpgrade, domain.ActionSwitchComponentNamespace}, }, { Name: "my-component-2", - Actual: domain.ComponentDiffState{Version: testVersionHigh, InstallationState: domain.TargetStatePresent}, - Expected: domain.ComponentDiffState{InstallationState: domain.TargetStateAbsent}, + Actual: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, + Expected: domain.ComponentDiffState{Absent: true}, NeededActions: []domain.Action{domain.ActionUninstall}, }, }}, @@ -671,23 +624,19 @@ func TestConvertToDomainModel(t *testing.T) { ComponentDiffs: map[string]crd.ComponentDiff{ testComponentName: { Actual: crd.ComponentDiffState{ - Version: "a.b.c-d", - InstallationState: "present", + Version: &wrongVersionABCD, }, Expected: crd.ComponentDiffState{ - Version: "2.3.4-5", - InstallationState: "present", + Version: &testCoreVersionHighStr, }, NeededActions: []crd.ComponentAction{"none"}, }, "my-component-2": { Actual: crd.ComponentDiffState{ - Version: "1.2.3-4", - InstallationState: "present", + Version: &testCoreVersionLowStr, }, Expected: crd.ComponentDiffState{ - Version: "2.3.4-5", - InstallationState: "invalid", + Version: &wrongVersionABCD, }, NeededActions: []crd.ComponentAction{"upgrade"}, }, @@ -696,7 +645,7 @@ func TestConvertToDomainModel(t *testing.T) { want: domain.StateDiff{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return assert.ErrorContains(t, err, "failed to parse actual version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to parse expected installation state \"invalid\"") && + assert.ErrorContains(t, err, "failed to parse actual version \"a.b.c-d\"") && assert.ErrorContains(t, err, "failed to convert component diff dto \"my-component\" to domain model") && assert.ErrorContains(t, err, "failed to convert component diff dto \"my-component-2\" to domain model") }, @@ -708,26 +657,26 @@ func TestConvertToDomainModel(t *testing.T) { DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ - Namespace: "official", - InstallationState: "present", + Namespace: "official", + Absent: false, AdditionalMounts: []crd.AdditionalMount{ { SourceType: crd.DataSourceConfigMap, Name: "config", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &testSubfolderStr, }, }, }, Expected: crd.DoguDiffState{ - Namespace: "official", - InstallationState: "present", + Namespace: "official", + Absent: false, AdditionalMounts: []crd.AdditionalMount{ { SourceType: crd.DataSourceConfigMap, Name: "config-different", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &testSubfolderStr, }, }, }, @@ -739,26 +688,26 @@ func TestConvertToDomainModel(t *testing.T) { { DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "config", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &testSubfolderStr, }, }, }, Expected: domain.DoguDiffState{ - Namespace: "official", - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "config-different", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &testSubfolderStr, }, }, }, @@ -772,7 +721,7 @@ func TestConvertToDomainModel(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ConvertToStateDiffDomain(tt.dto) + got, err := ConvertToStateDiffDomain(&tt.dto) tt.wantErr(t, err) // sort to avoid flaky tests slices.SortFunc(got.DoguDiffs, func(a, b domain.DoguDiff) int { @@ -788,6 +737,8 @@ func TestConvertToDomainModel(t *testing.T) { func TestConvertToStateDiffDTO(t *testing.T) { + value1 := "1" + value123 := "123" tests := []struct { name string model domain.StateDiff @@ -803,11 +754,11 @@ func TestConvertToStateDiffDTO(t *testing.T) { { Key: testDoguKey1, Actual: domain.DoguConfigValueState{ - Value: "1", + Value: &value1, Exists: true, }, Expected: domain.DoguConfigValueState{ - Value: "123", + Value: &value123, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -817,11 +768,11 @@ func TestConvertToStateDiffDTO(t *testing.T) { { Key: testDoguKey2, Actual: domain.DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: domain.DoguConfigValueState{ - Value: "123", + Value: &value123, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -833,39 +784,33 @@ func TestConvertToStateDiffDTO(t *testing.T) { want: crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ + DoguConfigDiffs: map[string]crd.ConfigDiff{ testDogu.String(): { - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff(nil), - DoguConfigDiff: crd.DoguConfigDiff{ - crd.DoguConfigEntryDiff{ - Key: testDoguKey1.Key.String(), - Actual: crd.DoguConfigValueState{ - Value: "1", - Exists: true, - }, - Expected: crd.DoguConfigValueState{ - Value: "123", - Exists: true, - }, - NeededAction: crd.ConfigAction("set"), + crd.ConfigEntryDiff{ + Key: testDoguKey1.Key.String(), + Actual: crd.ConfigValueState{ + Value: &value1, + Exists: true, + }, + Expected: crd.ConfigValueState{ + Value: &value123, + Exists: true, }, + NeededAction: crd.ConfigAction("set"), }, }, testDogu2.String(): { - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff(nil), - DoguConfigDiff: crd.DoguConfigDiff{ - crd.DoguConfigEntryDiff{ - Key: testDoguKey2.Key.String(), - Actual: crd.DoguConfigValueState{ - Value: "", - Exists: false, - }, - Expected: crd.DoguConfigValueState{ - Value: "123", - Exists: true, - }, - NeededAction: crd.ConfigAction("set"), + crd.ConfigEntryDiff{ + Key: testDoguKey2.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: crd.ConfigValueState{ + Value: &value123, + Exists: true, }, + NeededAction: crd.ConfigAction("set"), }, }, }, @@ -883,11 +828,11 @@ func TestConvertToStateDiffDTO(t *testing.T) { { Key: testDoguKey1, Actual: domain.DoguConfigValueState{ - Value: "1", + Value: &value1, Exists: true, }, Expected: domain.DoguConfigValueState{ - Value: "123", + Value: &value123, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -897,11 +842,11 @@ func TestConvertToStateDiffDTO(t *testing.T) { { Key: testDoguKey2, Actual: domain.DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: domain.DoguConfigValueState{ - Value: "123", + Value: &value123, Exists: true, }, NeededAction: domain.ConfigActionSet, @@ -910,42 +855,6 @@ func TestConvertToStateDiffDTO(t *testing.T) { }, GlobalConfigDiffs: nil, }, - want: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ - testDogu.String(): { - DoguConfigDiff: crd.DoguConfigDiff(nil), - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{ - crd.DoguConfigEntryDiff{ - Key: testDoguKey1.Key.String(), - Actual: crd.DoguConfigValueState{ - Exists: true, - }, - Expected: crd.DoguConfigValueState{ - Exists: true, - }, - NeededAction: crd.ConfigAction("set"), - }, - }, - }, - testDogu2.String(): { - SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{ - crd.DoguConfigEntryDiff{ - Key: testDoguKey2.Key.String(), - Actual: crd.DoguConfigValueState{ - Exists: false, - }, - Expected: crd.DoguConfigValueState{ - Exists: true, - }, - NeededAction: crd.ConfigAction("set"), - }, - }, - }, - }, - GlobalConfigDiff: nil, - }, }, } for _, tt := range tests { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState.go deleted file mode 100644 index f068a42e..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState.go +++ /dev/null @@ -1,42 +0,0 @@ -package serializer - -import ( - "fmt" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" -) - -// ToDomainTargetState maps a string to a domain.TargetState or returns an error if this is not possible. -func ToDomainTargetState(absent bool) domain.TargetState { - if absent { - return domain.TargetStateAbsent - } else { - return domain.TargetStatePresent - } -} - -// ToSerializerAbsentState maps a domain.TargetState to the absent state of dogus in the CR. -// If the state is not present, it will be interpreted as absent -func ToSerializerAbsentState(domainState domain.TargetState) bool { - return domainState != domain.TargetStatePresent -} - -//FIXME: remove old TargetState types, we need to change the domain.StateDiff for that -// we do this in #54968 if these changes on the blueprint CRD got merged, so we do not have to revert everything - -// ToID provides common mappings from strings to domain.TargetState, e.g. for dogus. -var ToID = map[string]domain.TargetState{ - "": domain.TargetStatePresent, - "present": domain.TargetStatePresent, - "absent": domain.TargetStateAbsent, -} - -// ToOldDomainTargetState maps a string to a domain.TargetState or returns an error if this is not possible. -func ToOldDomainTargetState(stateString string) (domain.TargetState, error) { - // Note that if the string is not found then it will be set to the zero value, which is 'Created'. - id := ToID[stateString] - var err error - if id == domain.TargetStatePresent && stateString != "present" && stateString != "" { - err = fmt.Errorf("unknown target state %q", stateString) - } - return id, err -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState_test.go deleted file mode 100644 index 447ac053..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/targetState_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package serializer - -import ( - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/stretchr/testify/assert" - "testing" -) - -func Test_toDomainTargetState(t *testing.T) { - assert.Equal(t, domain.TargetState(domain.TargetStatePresent), ToDomainTargetState(false)) - assert.Equal(t, domain.TargetState(domain.TargetStateAbsent), ToDomainTargetState(true)) -} - -func Test_ToSerializerAbsentState(t *testing.T) { - assert.False(t, ToSerializerAbsentState(domain.TargetStatePresent)) - assert.True(t, ToSerializerAbsentState(domain.TargetState(-1))) - assert.True(t, ToSerializerAbsentState(domain.TargetStateAbsent)) -} diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index c1af9829..645e97df 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -3,6 +3,8 @@ package dogucr import ( "context" "errors" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -16,7 +18,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "testing" ) var version3214, _ = core.ParseVersion("3.2.1-4") @@ -68,6 +69,8 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { // then require.NoError(t, err) quantity1 := resource.MustParse("1G") + rewriteTarget := "/" + additionalConfig := "snippet" assert.Equal(t, &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, @@ -78,8 +81,8 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { MinVolumeSize: quantity2, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, - RewriteTarget: "/", - AdditionalConfig: "snippet", + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, }, }, dogu) }) diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index 746a5aa1..71c08471 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -65,7 +66,7 @@ func parseAdditionalMounts(mounts []v2.DataMount) []ecosystem.AdditionalMount { SourceType: ecosystem.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: m.Subfolder, + Subfolder: &m.Subfolder, }) } return result @@ -87,8 +88,10 @@ func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) reverseProxyConfig.MaxBodySize = &quantity } - reverseProxyConfig.RewriteTarget = ecosystem.RewriteTarget(annotations[ecosystem.NginxIngressAnnotationRewriteTarget]) - reverseProxyConfig.AdditionalConfig = ecosystem.AdditionalConfig(annotations[ecosystem.NginxIngressAnnotationAdditionalConfig]) + rewriteTarget := annotations[ecosystem.NginxIngressAnnotationRewriteTarget] + reverseProxyConfig.RewriteTarget = &rewriteTarget + additionalConfig := annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] + reverseProxyConfig.AdditionalConfig = &additionalConfig return reverseProxyConfig, nil } @@ -133,11 +136,15 @@ func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { func toDoguCRAdditionalMounts(mounts []ecosystem.AdditionalMount) []v2.DataMount { var result []v2.DataMount for _, m := range mounts { + subfolder := "" + if m.Subfolder != nil { + subfolder = *m.Subfolder + } result = append(result, v2.DataMount{ SourceType: v2.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: m.Subfolder, + Subfolder: subfolder, }) } return result @@ -151,13 +158,14 @@ func getNginxIngressAnnotations(config ecosystem.ReverseProxyConfig) map[string] } rewriteTarget := config.RewriteTarget - if rewriteTarget != "" { - annotations[ecosystem.NginxIngressAnnotationRewriteTarget] = string(rewriteTarget) + if rewriteTarget != nil && *rewriteTarget != "" { + annotations[ecosystem.NginxIngressAnnotationRewriteTarget] = *rewriteTarget + } additionalConfig := config.AdditionalConfig - if additionalConfig != "" { - annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] = string(additionalConfig) + if additionalConfig != nil && *additionalConfig != "" { + annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] = *additionalConfig } // Use nil here to delete existing annotation from the cr. diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 63e284a4..e7e2bb89 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -2,26 +2,33 @@ package dogucr import ( "fmt" + "reflect" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" v2 "github.com/cloudogu/k8s-dogu-lib/v2/api/v2" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "reflect" - "testing" ) -var postgresDoguName = cescommons.QualifiedName{ - Namespace: cescommons.Namespace("official"), - SimpleName: cescommons.SimpleName("postgresql"), -} -var ldapDoguName = cescommons.QualifiedName{ - Namespace: "official", - SimpleName: "ldap", -} -var volSize25G = resource.MustParse("25G") -var defaultVolSize = resource.MustParse(v2.DefaultVolumeSize) +var ( + ldapDoguName = cescommons.QualifiedName{ + Namespace: "official", + SimpleName: "ldap", + } + volSize25G = resource.MustParse("25G") + defaultVolSize = resource.MustParse(v2.DefaultVolumeSize) + postgresDoguName = cescommons.QualifiedName{ + Namespace: cescommons.Namespace("official"), + SimpleName: cescommons.SimpleName("postgresql"), + } + subfolder = "subfolder" + subfolder2 = "subfolder2" + rewriteTarget = "/" + additionalConfig = "additional" +) func Test_parseDoguCR(t *testing.T) { type args struct { @@ -135,13 +142,13 @@ func Test_parseDoguCR(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -292,13 +299,13 @@ func Test_toDoguCR(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -448,7 +455,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { }, MinVolumeSize: quantity2, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: "subfolder"}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, }, want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", @@ -465,7 +472,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { AllowNamespaceSwitch: true, }, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: "subfolder"}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, }, want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"0\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", @@ -528,13 +535,13 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { annotations: v2.IngressAnnotations{ "nginx.ingress.kubernetes.io/proxy-body-size": "1G", "nginx.ingress.kubernetes.io/rewrite-target": "/", - "nginx.ingress.kubernetes.io/configuration-snippet": "snippet", + "nginx.ingress.kubernetes.io/configuration-snippet": "additional", }, }, want: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, - RewriteTarget: "/", - AdditionalConfig: "snippet", + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, }, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return err == nil @@ -580,8 +587,8 @@ func Test_getNginxIngressAnnotations1(t *testing.T) { name: "should parse config", args: args{config: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity, - RewriteTarget: "/", - AdditionalConfig: "additional", + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, }}, want: map[string]string{ "nginx.ingress.kubernetes.io/proxy-body-size": "1M", diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index f0e4e05c..d6837785 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -135,9 +135,9 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecDynamically_invalid(t *testin blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{Dogus: []domain.Dogu{{ - Name: redmineQualifiedDoguName, - Version: version, - TargetState: domain.TargetStatePresent, + Name: redmineQualifiedDoguName, + Version: &version, + Absent: false, }}}, Conditions: []domain.Condition{}, } diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 856ea564..96d2a133 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -23,6 +23,13 @@ var postgresqlQualifiedName = cescommons.QualifiedName{ SimpleName: "postgresql", } +var ( + rewriteTarget = "/" + additionalConfig = "additional" + subfolder = "subfolder" + subfolder2 = "secsubfolder" +) + func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action none", func(t *testing.T) { // given @@ -32,14 +39,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { err := sut.applyDoguState(testCtx, domain.DoguDiff{ DoguName: "postgresql", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: version3211, - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &version3211, + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: version3211, - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: &version3211, + Absent: false, }, NeededActions: []domain.Action{}, }, &ecosystem.DoguInstallation{ @@ -56,22 +63,22 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { bodySize := resource.MustParse("2G") config := ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, - RewriteTarget: "/", - AdditionalConfig: "additional", + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, } additionalMounts := []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, } doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT(). Create(testCtx, - ecosystem.InstallDogu(postgresqlQualifiedName, version3211, volumeSize, config, additionalMounts)). + ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, &config, additionalMounts)). Return(nil) sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) @@ -82,22 +89,22 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: version3211, - InstallationState: domain.TargetStateAbsent, + Namespace: "official", + Version: &version3211, + Absent: true, }, Expected: domain.DoguDiffState{ Namespace: "official", - Version: version3211, - InstallationState: domain.TargetStatePresent, - MinVolumeSize: volumeSize, - ReverseProxyConfig: config, + Version: &version3211, + Absent: false, + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &config, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, }, }, @@ -155,7 +162,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - Version: version3212, + Version: &version3212, }, NeededActions: []domain.Action{domain.ActionUpgrade}, }, @@ -183,7 +190,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - Version: version3211, + Version: &version3211, }, NeededActions: []domain.Action{domain.ActionDowngrade}, }, @@ -220,7 +227,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - MinVolumeSize: expectedVolumeSize, + MinVolumeSize: &expectedVolumeSize, }, NeededActions: []domain.Action{domain.ActionUpdateDoguResourceMinVolumeSize}, }, @@ -260,7 +267,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, }, }, @@ -275,8 +282,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { }) t.Run("action update proxy rewrite target", func(t *testing.T) { - target := ecosystem.RewriteTarget("") - expectedTarget := ecosystem.RewriteTarget("/") + expectedTarget := &rewriteTarget expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ @@ -287,7 +293,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - RewriteTarget: target, + RewriteTarget: nil, }, } @@ -302,7 +308,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ RewriteTarget: expectedTarget, }, }, @@ -317,8 +323,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { }) t.Run("action update proxy additional config", func(t *testing.T) { - additionalConfig := ecosystem.AdditionalConfig("") - expectedAdditionalConfig := ecosystem.AdditionalConfig("snippet") + expectedAdditionalConfig := &additionalConfig expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ @@ -329,7 +334,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - AdditionalConfig: additionalConfig, + AdditionalConfig: nil, }, } @@ -344,7 +349,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ AdditionalConfig: expectedAdditionalConfig, }, }, @@ -366,13 +371,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, } @@ -384,13 +389,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, } @@ -403,13 +408,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -433,10 +438,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedVolumeSize := resource.MustParse("3Gi") proxyBodySize := resource.MustParse("2G") expectedProxyBodySize := resource.MustParse("3G") - target := ecosystem.RewriteTarget("") - expectedTarget := ecosystem.RewriteTarget("/") - additionalConfig := ecosystem.AdditionalConfig("") - expectedAdditionalConfig := ecosystem.AdditionalConfig("snippet") + expectedTarget := &rewriteTarget + expectedAdditionalConfig := &additionalConfig expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, Version: version3212, @@ -454,8 +457,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { MinVolumeSize: volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - RewriteTarget: target, - AdditionalConfig: additionalConfig, + RewriteTarget: nil, + AdditionalConfig: nil, }, } @@ -470,9 +473,9 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - Version: version3212, - MinVolumeSize: expectedVolumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Version: &version3212, + MinVolumeSize: &expectedVolumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, RewriteTarget: expectedTarget, AdditionalConfig: expectedAdditionalConfig, diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 6d40630f..61183b11 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -74,7 +74,11 @@ func (useCase *EcosystemConfigUseCase) applyGlobalConfigDiffs(ctx context.Contex entryDiffsToSet := globalConfigDiffsByAction[domain.ConfigActionSet] for _, diff := range entryDiffsToSet { var err error - updatedEntries, err = updatedEntries.Set(diff.Key, common.GlobalConfigValue(diff.Expected.Value)) + val := "" + if diff.Expected.Value != nil { + val = *diff.Expected.Value + } + updatedEntries, err = updatedEntries.Set(diff.Key, common.GlobalConfigValue(val)) errs = append(errs, err) } @@ -183,7 +187,11 @@ func applyDiff(doguConfig config.DoguConfig, diffs []domain.DoguConfigEntryDiff) var err error switch diff.NeededAction { case domain.ConfigActionSet: - updatedEntries, err = updatedEntries.Set(diff.Key.Key, config.Value(diff.Expected.Value)) + val := "" + if diff.Expected.Value != nil { + val = *diff.Expected.Value + } + updatedEntries, err = updatedEntries.Set(diff.Key.Key, config.Value(val)) case domain.ConfigActionRemove: updatedEntries = updatedEntries.Delete(diff.Key.Key) } diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 4c4f6c02..24b09959 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -334,9 +334,9 @@ func TestEcosystemConfigUseCase_applyDoguConfigDiffs(t *testing.T) { "key1": "val1", "key2": "val2", }).Config - updatedConfig, err := updatedConfig.Set(diff1.Key.Key, config.Value(diff1.Expected.Value)) + updatedConfig, err := updatedConfig.Set(diff1.Key.Key, config.Value(*diff1.Expected.Value)) require.NoError(t, err) - updatedConfig, err = updatedConfig.Set(diff2.Key.Key, config.Value(diff2.Expected.Value)) + updatedConfig, err = updatedConfig.Set(diff2.Key.Key, config.Value(*diff2.Expected.Value)) require.NoError(t, err) doguConfigMock.EXPECT(). @@ -468,9 +468,9 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) - updatedEntries, err := globalConfig.Set(diff1.Key, common.GlobalConfigValue(diff1.Expected.Value)) + updatedEntries, err := globalConfig.Set(diff1.Key, common.GlobalConfigValue(*diff1.Expected.Value)) require.NoError(t, err) - updatedEntries, err = updatedEntries.Set(diff2.Key, common.GlobalConfigValue(diff2.Expected.Value)) + updatedEntries, err = updatedEntries.Set(diff2.Key, common.GlobalConfigValue(*diff2.Expected.Value)) require.NoError(t, err) globalConfigMock.EXPECT().Get(testCtx).Return(globalConfig, nil) @@ -713,7 +713,7 @@ func getSetDoguConfigEntryDiff(key, value string, doguName cescommons.SimpleName DoguName: doguName, }, Expected: domain.DoguConfigValueState{ - Value: value, + Value: &value, }, NeededAction: domain.ConfigActionSet, } @@ -736,7 +736,7 @@ func getSensitiveDoguConfigEntryDiffForAction(key, value string, doguName cescom DoguName: doguName, }, Expected: domain.DoguConfigValueState{ - Value: value, + Value: &value, }, NeededAction: action, } @@ -756,7 +756,7 @@ func getSetGlobalConfigEntryDiff(key, value string) domain.GlobalConfigEntryDiff return domain.GlobalConfigEntryDiff{ Key: common.GlobalConfigKey(key), Expected: domain.GlobalConfigValueState{ - Value: value, + Value: &value, }, NeededAction: domain.ConfigActionSet, } diff --git a/pkg/application/mock_applyComponentsUseCase_test.go b/pkg/application/mock_applyComponentsUseCase_test.go index f94ba2dc..cc06f1a6 100644 --- a/pkg/application/mock_applyComponentsUseCase_test.go +++ b/pkg/application/mock_applyComponentsUseCase_test.go @@ -23,21 +23,31 @@ func (_m *mockApplyComponentsUseCase) EXPECT() *mockApplyComponentsUseCase_Expec } // ApplyComponents provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (_m *mockApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyComponents") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) (bool, error)); ok { + return rf(ctx, blueprint) + } + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) bool); ok { r0 = rf(ctx, blueprint) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, *domain.BlueprintSpec) error); ok { + r1 = rf(ctx, blueprint) + } else { + r1 = ret.Error(1) } - return r0 + return r0, r1 } // mockApplyComponentsUseCase_ApplyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponents' @@ -59,12 +69,12 @@ func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Run(run func(ctx cont return _c } -func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Return(_a0 error) *mockApplyComponentsUseCase_ApplyComponents_Call { - _c.Call.Return(_a0) +func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Return(_a0 bool, _a1 error) *mockApplyComponentsUseCase_ApplyComponents_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyComponentsUseCase_ApplyComponents_Call { +func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) (bool, error)) *mockApplyComponentsUseCase_ApplyComponents_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/mock_applyDogusUseCase_test.go b/pkg/application/mock_applyDogusUseCase_test.go index f2ad0d11..151e7b47 100644 --- a/pkg/application/mock_applyDogusUseCase_test.go +++ b/pkg/application/mock_applyDogusUseCase_test.go @@ -23,21 +23,31 @@ func (_m *mockApplyDogusUseCase) EXPECT() *mockApplyDogusUseCase_Expecter { } // ApplyDogus provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (_m *mockApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { ret := _m.Called(ctx, blueprint) if len(ret) == 0 { panic("no return value specified for ApplyDogus") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) (bool, error)); ok { + return rf(ctx, blueprint) + } + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) bool); ok { r0 = rf(ctx, blueprint) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, *domain.BlueprintSpec) error); ok { + r1 = rf(ctx, blueprint) + } else { + r1 = ret.Error(1) } - return r0 + return r0, r1 } // mockApplyDogusUseCase_ApplyDogus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyDogus' @@ -59,12 +69,12 @@ func (_c *mockApplyDogusUseCase_ApplyDogus_Call) Run(run func(ctx context.Contex return _c } -func (_c *mockApplyDogusUseCase_ApplyDogus_Call) Return(_a0 error) *mockApplyDogusUseCase_ApplyDogus_Call { - _c.Call.Return(_a0) +func (_c *mockApplyDogusUseCase_ApplyDogus_Call) Return(_a0 bool, _a1 error) *mockApplyDogusUseCase_ApplyDogus_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *mockApplyDogusUseCase_ApplyDogus_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockApplyDogusUseCase_ApplyDogus_Call { +func (_c *mockApplyDogusUseCase_ApplyDogus_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) (bool, error)) *mockApplyDogusUseCase_ApplyDogus_Call { _c.Call.Return(run) return _c } diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 58cb600e..7724dffb 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -49,6 +49,8 @@ var ( nginxStaticConfigKeyNginxKey2 = common.DoguConfigKey{DoguName: "nginx-static", Key: "nginxKey2"} nginxStaticSensitiveConfigKeyNginxKey1 = nginxStaticConfigKeyNginxKey1 nginxStaticSensitiveConfigKeyNginxKey2 = nginxStaticConfigKeyNginxKey2 + val1 = "val1" + val2 = "val2" ) func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { @@ -231,55 +233,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { assert.ErrorContains(t, err, "could not determine state diff") assert.ErrorContains(t, err, "could not collect ecosystem state") }) - t.Run("should fail to determine state diff for blueprint", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Id: "testBlueprint1", - EffectiveBlueprint: domain.EffectiveBlueprint{ - Components: []domain.Component{ - { - Name: common.QualifiedComponentName{ - Namespace: "k8s", - SimpleName: "k8s-dogu-operator", - }, - Version: semVer3212, - // invalid TargetState to provoke special error - // delete this test, if we replace the target states with the absent flag - // Instead we should have a test with a forbidden diff action - TargetState: domain.TargetState(-10), - }, - }, - }, - } - - doguInstallRepoMock := newMockDoguInstallationRepository(t) - doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - - globalConfigRepoMock := newMockGlobalConfigRepository(t) - entries, _ := config.MapToEntries(map[string]any{}) - globalConfig := config.CreateGlobalConfig(entries) - globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) - - // when - err := sut.DetermineStateDiff(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to determine state diff") - }) + // TODO: Instead we should have a test with a forbidden diff action t.Run("should fail to update blueprint", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ @@ -327,23 +281,23 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { EffectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: postfixQualifiedDoguName, - Version: mustParseVersion(t, "2.9.0"), - TargetState: domain.TargetStatePresent, + Name: postfixQualifiedDoguName, + Version: mustParseVersionToPtr(t, "2.9.0"), + Absent: false, }, { - Name: ldapQualifiedDoguName, - Version: mustParseVersion(t, "1.2.3"), - TargetState: domain.TargetStatePresent, + Name: ldapQualifiedDoguName, + Version: mustParseVersionToPtr(t, "1.2.3"), + Absent: false, }, { - Name: nginxIngressQualifiedDoguName, - Version: mustParseVersion(t, "1.8.5"), - TargetState: domain.TargetStatePresent, + Name: nginxIngressQualifiedDoguName, + Version: mustParseVersionToPtr(t, "1.8.5"), + Absent: false, }, { - Name: nginxStaticQualifiedDoguName, - TargetState: domain.TargetStateAbsent, + Name: nginxStaticQualifiedDoguName, + Absent: true, }, }, }, @@ -387,52 +341,52 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { expectedDoguDiffs := []domain.DoguDiff{ { DoguName: "postfix", - Actual: domain.DoguDiffState{InstallationState: domain.TargetStateAbsent}, + Actual: domain.DoguDiffState{Absent: true}, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion(t, "2.9.0"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: mustParseVersionToPtr(t, "2.9.0"), + Absent: false, }, NeededActions: []domain.Action{domain.ActionInstall}, }, { DoguName: "ldap", Actual: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion(t, "1.1.1"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: mustParseVersionToPtr(t, "1.1.1"), + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersion(t, "1.2.3"), - InstallationState: domain.TargetStatePresent, + Namespace: "official", + Version: mustParseVersionToPtr(t, "1.2.3"), + Absent: false, }, NeededActions: []domain.Action{domain.ActionUpgrade}, }, { DoguName: "nginx-ingress", Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.5"), - InstallationState: domain.TargetStatePresent, + Namespace: "k8s", + Version: mustParseVersionToPtr(t, "1.8.5"), + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.5"), - InstallationState: domain.TargetStatePresent, + Namespace: "k8s", + Version: mustParseVersionToPtr(t, "1.8.5"), + Absent: false, }, NeededActions: nil, }, { DoguName: "nginx-static", Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersion(t, "1.8.6"), - InstallationState: domain.TargetStatePresent, + Namespace: "k8s", + Version: mustParseVersionToPtr(t, "1.8.6"), + Absent: false, }, Expected: domain.DoguDiffState{ - Namespace: "k8s", - InstallationState: domain.TargetStateAbsent, + Namespace: "k8s", + Absent: true, }, NeededActions: []domain.Action{domain.ActionUninstall}, }, @@ -445,7 +399,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Global: domain.GlobalConfig{ Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ "globalKey1": "globalValue", @@ -494,14 +448,14 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { expectedConfigDiff := []domain.GlobalConfigEntryDiff{ { Key: "globalKey1", - Actual: domain.GlobalConfigValueState{Value: "", Exists: false}, - Expected: domain.GlobalConfigValueState{Value: "globalValue", Exists: true}, + Actual: domain.GlobalConfigValueState{Value: nil, Exists: false}, + Expected: domain.GlobalConfigValueState{Value: &val1, Exists: true}, NeededAction: domain.ConfigActionSet, }, { Key: "globalKey2", - Actual: domain.GlobalConfigValueState{Value: "", Exists: false}, - Expected: domain.GlobalConfigValueState{Value: "", Exists: false}, + Actual: domain.GlobalConfigValueState{Value: nil, Exists: false}, + Expected: domain.GlobalConfigValueState{Value: nil, Exists: false}, NeededAction: domain.ConfigActionNone, }, } @@ -513,7 +467,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ nginxStaticQualifiedDoguName.SimpleName: { DoguName: nginxStaticQualifiedDoguName.SimpleName, @@ -550,7 +504,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguConfigRepoMock := newMockDoguConfigRepository(t) doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ - "nginxKey1": "val1", + "nginxKey1": val1, "nginxKey2": "val2", }) require.NoError(t, entryErr) @@ -581,14 +535,14 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { nginxStatic: { domain.DoguConfigEntryDiff{ Key: nginxStaticConfigKeyNginxKey1, - Actual: domain.DoguConfigValueState{Value: "val1", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "nginxVal1", Exists: true}, + Actual: domain.DoguConfigValueState{Value: &val1, Exists: true}, + Expected: domain.DoguConfigValueState{Value: &val2, Exists: true}, NeededAction: domain.ConfigActionSet, }, domain.DoguConfigEntryDiff{ Key: nginxStaticConfigKeyNginxKey2, - Actual: domain.DoguConfigValueState{Value: "val2", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "", Exists: false}, + Actual: domain.DoguConfigValueState{Value: &val2, Exists: true}, + Expected: domain.DoguConfigValueState{Value: nil, Exists: false}, NeededAction: domain.ConfigActionRemove, }, }, @@ -601,7 +555,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ nginxStatic: { DoguName: nginxStatic, @@ -643,7 +597,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ - "nginxKey1": "val1", + "nginxKey1": val1, "nginxKey2": "val2", }) require.NoError(t, entryErr) @@ -676,14 +630,14 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { nginxStatic: { { Key: nginxStaticSensitiveConfigKeyNginxKey1, - Actual: domain.DoguConfigValueState{Value: "val1", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "nginxVal1", Exists: true}, + Actual: domain.DoguConfigValueState{Value: &val1, Exists: true}, + Expected: domain.DoguConfigValueState{Value: &val2, Exists: true}, NeededAction: domain.ConfigActionSet, }, { Key: nginxStaticSensitiveConfigKeyNginxKey2, - Actual: domain.DoguConfigValueState{Value: "val2", Exists: true}, - Expected: domain.DoguConfigValueState{Value: "", Exists: false}, + Actual: domain.DoguConfigValueState{Value: &val2, Exists: true}, + Expected: domain.DoguConfigValueState{Value: nil, Exists: false}, NeededAction: domain.ConfigActionRemove, }, }, @@ -698,11 +652,16 @@ func mustParseVersion(t *testing.T, raw string) core.Version { return version } +func mustParseVersionToPtr(t *testing.T, raw string) *core.Version { + version := mustParseVersion(t, raw) + return &version +} + func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { t.Run("all ok", func(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Global: domain.GlobalConfig{ Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ "globalKey1": "globalValue", @@ -783,7 +742,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { t.Run("fail with internalError and notFoundError", func(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ - Config: domain.Config{ + Config: &domain.Config{ Global: domain.GlobalConfig{ Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ "globalKey1": "globalValue", diff --git a/pkg/domain/blueprint.go b/pkg/domain/blueprint.go index 1edf7b12..54bbc088 100644 --- a/pkg/domain/blueprint.go +++ b/pkg/domain/blueprint.go @@ -3,6 +3,7 @@ package domain import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" @@ -22,7 +23,7 @@ type Blueprint struct { // this blueprint was applied. Optional. Components []Component // Config contains all config entries to set via blueprint. - Config Config + Config *Config } // Validate checks the structure and data of the blueprint statically and returns an error if there are any problems diff --git a/pkg/domain/blueprintMask_test.go b/pkg/domain/blueprintMask_test.go index a61f737d..9f0878c1 100644 --- a/pkg/domain/blueprintMask_test.go +++ b/pkg/domain/blueprintMask_test.go @@ -1,10 +1,11 @@ package domain import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/stretchr/testify/require" - "testing" ) var version3_2_1_0, _ = core.ParseVersion("3.2.1-0") @@ -20,9 +21,9 @@ var ( func Test_Validate(t *testing.T) { dogus := []MaskDogu{ - {Name: officialDogu1, Version: version3_2_1_0, TargetState: TargetStateAbsent}, - {Name: officialDogu2, TargetState: TargetStateAbsent}, - {Name: officialDogu3, Version: version3_2_1_0, TargetState: TargetStatePresent}, + {Name: officialDogu1, Version: version3_2_1_0, Absent: true}, + {Name: officialDogu2, Absent: true}, + {Name: officialDogu3, Version: version3_2_1_0, Absent: false}, } blueprintMask := BlueprintMask{ Dogus: dogus, @@ -35,8 +36,8 @@ func Test_Validate(t *testing.T) { func Test_ValidateWithDuplicatedDoguNames(t *testing.T) { dogus := []MaskDogu{ - {Name: officialDogu1, TargetState: TargetStatePresent}, - {Name: officialDogu1, TargetState: TargetStateAbsent}, + {Name: officialDogu1, Absent: false}, + {Name: officialDogu1, Absent: true}, } blueprintMask := BlueprintMask{Dogus: dogus} diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 20243813..99db5e67 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -102,7 +102,7 @@ func (spec *BlueprintSpec) validateMaskAgainstBlueprint() error { if !found { errorList = append(errorList, fmt.Errorf("dogu %q is missing in the blueprint", doguMask.Name)) } - if doguMask.TargetState == TargetStatePresent && dogu.TargetState == TargetStateAbsent { + if !doguMask.Absent && dogu.Absent { errorList = append(errorList, fmt.Errorf("absent dogu %q cannot be present in blueprint mask", dogu.Name.SimpleName)) } if !spec.Config.AllowDoguNamespaceSwitch && dogu.Name.Namespace != doguMask.Name.Namespace { @@ -191,7 +191,7 @@ func (spec *BlueprintSpec) calculateEffectiveDogu(dogu Dogu) (Dogu, error) { effectiveDogu := Dogu{ Name: dogu.Name, Version: dogu.Version, - TargetState: dogu.TargetState, + Absent: dogu.Absent, MinVolumeSize: dogu.MinVolumeSize, ReverseProxyConfig: dogu.ReverseProxyConfig, AdditionalMounts: dogu.AdditionalMounts, @@ -200,7 +200,7 @@ func (spec *BlueprintSpec) calculateEffectiveDogu(dogu Dogu) (Dogu, error) { if noMaskDoguErr == nil { emptyVersion := core.Version{} if maskDogu.Version != emptyVersion { - effectiveDogu.Version = maskDogu.Version + effectiveDogu.Version = &maskDogu.Version } if maskDogu.Name.Namespace != dogu.Name.Namespace { if spec.Config.AllowDoguNamespaceSwitch { @@ -210,23 +210,26 @@ func (spec *BlueprintSpec) calculateEffectiveDogu(dogu Dogu) (Dogu, error) { "changing the dogu namespace is forbidden by default and can be allowed by a flag: %q -> %q", dogu.Name, maskDogu.Name) } } - effectiveDogu.TargetState = maskDogu.TargetState + effectiveDogu.Absent = maskDogu.Absent } return effectiveDogu, nil } // It is not allowed to have config without the corresponding dogu, so this will clean up the unnecessary config. -func (spec *BlueprintSpec) removeConfigForMaskedDogus() Config { +func (spec *BlueprintSpec) removeConfigForMaskedDogus() *Config { + if spec.Blueprint.Config == nil { + return nil + } effectiveDoguConfig := maps.Clone(spec.Blueprint.Config.Dogus) for _, dogu := range spec.BlueprintMask.Dogus { - if dogu.TargetState == TargetStateAbsent { + if dogu.Absent { delete(effectiveDoguConfig, dogu.Name.SimpleName) } } - return Config{ + return &Config{ Dogus: effectiveDoguConfig, Global: spec.Blueprint.Config.Global, } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 6bdbb428..b515e96e 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -18,6 +18,7 @@ import ( var version3211, _ = core.ParseVersion("3.2.1-1") var version3212, _ = core.ParseVersion("3.2.1-2") var version3213, _ = core.ParseVersion("3.2.1-3") +var subfolder = "subfolder" const ( testDistributionNamespace = "k8s" @@ -57,9 +58,10 @@ func Test_BlueprintSpec_Validate_emptyID(t *testing.T) { } func Test_BlueprintSpec_Validate_combineErrors(t *testing.T) { + name, _ := cescommons.QualifiedNameFromString("/noNamespace") spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: []Dogu{{Version: core.Version{}, TargetState: TargetStatePresent}}}, - BlueprintMask: BlueprintMask{Dogus: []MaskDogu{{TargetState: 666}}}, + Blueprint: Blueprint{Dogus: []Dogu{{Version: &core.Version{}, Absent: false}}}, + BlueprintMask: BlueprintMask{Dogus: []MaskDogu{{Name: name}}}, } err := spec.ValidateStatically() @@ -109,8 +111,8 @@ func Test_BlueprintSpec_validateMaskAgainstBlueprint(t *testing.T) { }) t.Run("absent dogus cannot be present in blueprint mask", func(t *testing.T) { spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: []Dogu{{Name: officialNexus, TargetState: TargetStateAbsent}}}, - BlueprintMask: BlueprintMask{Dogus: []MaskDogu{{Name: officialNexus, TargetState: TargetStatePresent}}}, + Blueprint: Blueprint{Dogus: []Dogu{{Name: officialNexus, Absent: true}}}, + BlueprintMask: BlueprintMask{Dogus: []MaskDogu{{Name: officialNexus, Absent: false}}}, } err := spec.validateMaskAgainstBlueprint() @@ -123,9 +125,9 @@ func Test_BlueprintSpec_validateMaskAgainstBlueprint(t *testing.T) { func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { t.Run("no mask", func(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3211, TargetState: TargetStatePresent}, - {Name: officialDogu2, Version: version3212, TargetState: TargetStatePresent}, - {Name: officialDogu3, Version: version3213, TargetState: TargetStateAbsent}, + {Name: officialDogu1, Version: &version3211, Absent: false}, + {Name: officialDogu2, Version: &version3212, Absent: false}, + {Name: officialDogu3, Version: &version3213, Absent: true}, } spec := BlueprintSpec{ @@ -140,13 +142,13 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { t.Run("change version", func(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3211, TargetState: TargetStatePresent}, - {Name: officialDogu2, Version: version3212, TargetState: TargetStatePresent}, + {Name: officialDogu1, Version: &version3211, Absent: false}, + {Name: officialDogu2, Version: &version3212, Absent: false}, } maskedDogus := []MaskDogu{ - {Name: officialDogu1, Version: version3212, TargetState: TargetStatePresent}, - {Name: officialDogu2, Version: version3211, TargetState: TargetStatePresent}, + {Name: officialDogu1, Version: version3212, Absent: false}, + {Name: officialDogu2, Version: version3211, Absent: false}, } spec := BlueprintSpec{ @@ -157,19 +159,19 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { require.Nil(t, err) require.Equal(t, 2, len(spec.EffectiveBlueprint.Dogus), "effective blueprint should contain the elements from the mask") - assert.Equal(t, Dogu{Name: officialDogu1, Version: version3212, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[0]) - assert.Equal(t, Dogu{Name: officialDogu2, Version: version3211, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[1]) + assert.Equal(t, Dogu{Name: officialDogu1, Version: &version3212, Absent: false}, spec.EffectiveBlueprint.Dogus[0]) + assert.Equal(t, Dogu{Name: officialDogu2, Version: &version3211, Absent: false}, spec.EffectiveBlueprint.Dogus[1]) }) t.Run("make dogu absent", func(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3211, TargetState: TargetStatePresent}, - {Name: officialDogu2, Version: version3212, TargetState: TargetStatePresent}, + {Name: officialDogu1, Version: &version3211, Absent: false}, + {Name: officialDogu2, Version: &version3212, Absent: false}, } maskedDogus := []MaskDogu{ - {Name: officialDogu1, Version: version3211, TargetState: TargetStateAbsent}, - {Name: officialDogu2, TargetState: TargetStateAbsent}, + {Name: officialDogu1, Version: version3211, Absent: true}, + {Name: officialDogu2, Absent: true}, } config := Config{ @@ -179,25 +181,25 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: dogus, Config: config}, + Blueprint: Blueprint{Dogus: dogus, Config: &config}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, } err := spec.CalculateEffectiveBlueprint() require.Nil(t, err) require.Equal(t, 2, len(spec.EffectiveBlueprint.Dogus), "effective blueprint should contain the elements from the mask") - assert.Equal(t, Dogu{Name: officialDogu1, Version: version3211, TargetState: TargetStateAbsent}, spec.EffectiveBlueprint.Dogus[0]) - assert.Equal(t, Dogu{Name: officialDogu2, Version: version3212, TargetState: TargetStateAbsent}, spec.EffectiveBlueprint.Dogus[1]) + assert.Equal(t, Dogu{Name: officialDogu1, Version: &version3211, Absent: true}, spec.EffectiveBlueprint.Dogus[0]) + assert.Equal(t, Dogu{Name: officialDogu2, Version: &version3212, Absent: true}, spec.EffectiveBlueprint.Dogus[1]) assert.NotContains(t, spec.EffectiveBlueprint.Config.Dogus, officialDogu1.SimpleName) }) t.Run("change dogu namespace", func(t *testing.T) { dogus := []Dogu{ - {Name: officialNexus, Version: version3211, TargetState: TargetStatePresent}, + {Name: officialNexus, Version: &version3211, Absent: false}, } maskedDogus := []MaskDogu{ - {Name: premiumNexus, Version: version3211, TargetState: TargetStatePresent}, + {Name: premiumNexus, Version: version3211, Absent: false}, } spec := BlueprintSpec{ @@ -213,11 +215,11 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { t.Run("change dogu namespace with flag", func(t *testing.T) { dogus := []Dogu{ - {Name: officialNexus, Version: version3211, TargetState: TargetStatePresent}, + {Name: officialNexus, Version: &version3211, Absent: false}, } maskedDogus := []MaskDogu{ - {Name: premiumNexus, Version: version3211, TargetState: TargetStatePresent}, + {Name: premiumNexus, Version: version3211, Absent: false}, } spec := BlueprintSpec{ @@ -229,7 +231,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { require.NoError(t, err, "with the feature flag namespace changes should be allowed") require.Equal(t, 1, len(spec.EffectiveBlueprint.Dogus), "effective blueprint should contain the elements from the mask") - assert.Equal(t, Dogu{Name: premiumNexus, Version: version3211, TargetState: TargetStatePresent}, spec.EffectiveBlueprint.Dogus[0]) + assert.Equal(t, Dogu{Name: premiumNexus, Version: &version3211, Absent: false}, spec.EffectiveBlueprint.Dogus[0]) }) t.Run("validate only config for dogus in blueprint", func(t *testing.T) { @@ -240,7 +242,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } spec := BlueprintSpec{ - Blueprint: Blueprint{Config: config}, + Blueprint: Blueprint{Config: &config}, } err := spec.CalculateEffectiveBlueprint() @@ -252,11 +254,11 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { t.Run("add additionalMounts", func(t *testing.T) { dogus := []Dogu{ { - Name: k8sNginxStatic, - Version: version3211, - TargetState: TargetStatePresent, + Name: k8sNginxStatic, + Version: &version3211, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", Subfolder: "test"}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", Subfolder: &subfolder}, }, }, } diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index ce08d447..0141652c 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -1,11 +1,14 @@ package domain import ( + "testing" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/require" - "testing" + "k8s.io/apimachinery/pkg/api/resource" "github.com/stretchr/testify/assert" ) @@ -25,16 +28,16 @@ var ( func Test_validate_ok(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3_2_1_0, TargetState: TargetStateAbsent}, - {Name: officialDogu2, TargetState: TargetStateAbsent}, - {Name: officialDogu3, Version: version3_2_1_0, TargetState: TargetStatePresent}, - {Name: officialNexus, Version: version3213}, + {Name: officialDogu1, Version: &version3_2_1_0, Absent: true}, + {Name: officialDogu2, Absent: true}, + {Name: officialDogu3, Version: &version3_2_1_0, Absent: false}, + {Name: officialNexus, Version: &version3213}, } components := []Component{ - {Name: testComponentName1, Version: compVersion3210, TargetState: TargetStateAbsent}, - {Name: testComponentName2, TargetState: TargetStateAbsent}, - {Name: testComponentName3, Version: compVersion3212, TargetState: TargetStatePresent}, + {Name: testComponentName1, Version: compVersion3210, Absent: true}, + {Name: testComponentName2, Absent: true}, + {Name: testComponentName3, Version: compVersion3212, Absent: false}, {Name: testComponentName4, Version: compVersion3213}, } blueprint := Blueprint{Dogus: dogus, Components: components} @@ -45,8 +48,9 @@ func Test_validate_ok(t *testing.T) { } func Test_validate_multipleErrors(t *testing.T) { + dogus := []Dogu{ - {Version: version3212, TargetState: 666}, + {Version: nil}, } components := []Component{ {Version: compVersion3212}, @@ -55,7 +59,7 @@ func Test_validate_multipleErrors(t *testing.T) { blueprint := Blueprint{ Dogus: dogus, Components: components, - Config: Config{ + Config: &Config{ Global: GlobalConfig{ Present: nil, Absent: []common.GlobalConfigKey{ @@ -70,7 +74,7 @@ func Test_validate_multipleErrors(t *testing.T) { require.Error(t, err) assert.ErrorContains(t, err, "blueprint is invalid") assert.ErrorContains(t, err, "dogu is invalid") - assert.ErrorContains(t, err, "dogu target state is invalid") + assert.ErrorContains(t, err, "dogu version must not be empty") assert.ErrorContains(t, err, "component name must not be empty") assert.ErrorContains(t, err, `namespace of component "" must not be empty`) assert.ErrorContains(t, err, `key for absent global config should not be empty`) @@ -78,12 +82,12 @@ func Test_validate_multipleErrors(t *testing.T) { func Test_validateDogus_ok(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3_2_1_4, TargetState: TargetStateAbsent}, + {Name: officialDogu1, Version: &version3_2_1_4, Absent: true}, //versionIsOptionalForStateAbsent - {Name: officialDogu2, TargetState: TargetStateAbsent}, - {Name: officialDogu3, Version: version3212, TargetState: TargetStatePresent}, + {Name: officialDogu2, Absent: true}, + {Name: officialDogu3, Version: &version3212, Absent: false}, //StateDefaultsToPresent - {Name: officialNexus, Version: version3212}, + {Name: officialNexus, Version: &version3212}, } blueprint := Blueprint{Dogus: dogus} @@ -93,9 +97,10 @@ func Test_validateDogus_ok(t *testing.T) { } func Test_validateDogus_multipleErrors(t *testing.T) { + wrongBodySize := resource.MustParse("1Ki") dogus := []Dogu{ {Name: officialDogu1}, - {Name: officialDogu2, TargetState: 666}, + {Name: officialDogu2, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &wrongBodySize}}, } blueprint := Blueprint{Dogus: dogus} @@ -113,15 +118,15 @@ func Test_validateComponents_ok(t *testing.T) { Namespace: "k8s", SimpleName: "absent-component", }, - TargetState: TargetStateAbsent, + Absent: true, }, { Name: common.QualifiedComponentName{ SimpleName: "present-component", Namespace: "k8s", }, - Version: compVersion3212, - TargetState: TargetStatePresent, + Version: compVersion3212, + Absent: false, }, } blueprint := Blueprint{Components: components} @@ -146,10 +151,10 @@ func Test_validateComponents_multipleErrors(t *testing.T) { func Test_validateDoguUniqueness(t *testing.T) { dogus := []Dogu{ - {Name: officialDogu1, Version: version3_2_1_0, TargetState: TargetStatePresent}, - {Name: officialDogu1, Version: version3213}, - {Name: officialDogu2, Version: version3213}, - {Name: officialDogu2, Version: version3213}, + {Name: officialDogu1, Version: &version3_2_1_0, Absent: false}, + {Name: officialDogu1, Version: &version3213}, + {Name: officialDogu2, Version: &version3213}, + {Name: officialDogu2, Version: &version3213}, } blueprint := Blueprint{Dogus: dogus} @@ -169,8 +174,8 @@ func Test_validateComponentUniqueness(t *testing.T) { Namespace: "present", SimpleName: "component1", }, - Version: compVersion3210, - TargetState: TargetStatePresent, + Version: compVersion3210, + Absent: false, }, { Name: common.QualifiedComponentName{ diff --git a/pkg/domain/common/configNames.go b/pkg/domain/common/configNames.go index c5b3465c..9c4d2bcf 100644 --- a/pkg/domain/common/configNames.go +++ b/pkg/domain/common/configNames.go @@ -3,6 +3,7 @@ package common import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-registry-lib/config" ) diff --git a/pkg/domain/component.go b/pkg/domain/component.go index a2f4c9dc..7a56f55e 100644 --- a/pkg/domain/component.go +++ b/pkg/domain/component.go @@ -3,6 +3,7 @@ package domain import ( "errors" "fmt" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -16,8 +17,8 @@ type Component struct { // Version defines the version of the package that is to be installed. Must not be empty if the targetState is // "present"; otherwise it is optional and is not going to be interpreted. Version *semver.Version - // TargetState defines a state of installation of this package. Optional field, but defaults to "TargetStatePresent" - TargetState TargetState + // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. + Absent bool // DeployConfig defines generic properties for the component. This field is optional. DeployConfig ecosystem.DeployConfig } @@ -27,10 +28,8 @@ func (component *Component) Validate() error { nameError := component.Name.Validate() var versionErr error - if component.TargetState == TargetStatePresent { - if component.Version == nil { - versionErr = fmt.Errorf("version of component %q must not be empty", component.Name) - } + if !component.Absent && component.Version == nil { + versionErr = fmt.Errorf("version of component %q must not be empty", component.Name) } return errors.Join(versionErr, nameError) diff --git a/pkg/domain/component_test.go b/pkg/domain/component_test.go index 61826a5e..53901ed8 100644 --- a/pkg/domain/component_test.go +++ b/pkg/domain/component_test.go @@ -1,12 +1,13 @@ package domain import ( + "testing" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) var ( @@ -16,7 +17,7 @@ var ( func TestComponent_Validate(t *testing.T) { t.Run("errorOnMissingComponentVersion", func(t *testing.T) { - component := Component{Name: testComponentName, TargetState: TargetStatePresent} + component := Component{Name: testComponentName, Absent: false} err := component.Validate() @@ -25,7 +26,7 @@ func TestComponent_Validate(t *testing.T) { }) t.Run("errorOnEmptyComponentVersion", func(t *testing.T) { - component := Component{Name: testComponentName, Version: nil, TargetState: TargetStatePresent} + component := Component{Name: testComponentName, Version: nil, Absent: false} err := component.Validate() @@ -34,7 +35,7 @@ func TestComponent_Validate(t *testing.T) { }) t.Run("errorOnMissingComponentName", func(t *testing.T) { - component := Component{Version: compVersion123, TargetState: TargetStatePresent} + component := Component{Version: compVersion123, Absent: false} err := component.Validate() @@ -43,7 +44,7 @@ func TestComponent_Validate(t *testing.T) { }) t.Run("errorOnEmptyComponentNamespace", func(t *testing.T) { - component := Component{Name: common.QualifiedComponentName{Namespace: "", SimpleName: "test"}, Version: compVersion123, TargetState: TargetStatePresent} + component := Component{Name: common.QualifiedComponentName{Namespace: "", SimpleName: "test"}, Version: compVersion123, Absent: false} err := component.Validate() require.Error(t, err) @@ -51,7 +52,7 @@ func TestComponent_Validate(t *testing.T) { }) t.Run("errorOnEmptyComponentName", func(t *testing.T) { - component := Component{Name: common.QualifiedComponentName{Namespace: "k8s"}, Version: compVersion123, TargetState: TargetStatePresent} + component := Component{Name: common.QualifiedComponentName{Namespace: "k8s"}, Version: compVersion123, Absent: false} err := component.Validate() @@ -65,11 +66,11 @@ func TestComponent_Validate(t *testing.T) { err := component.Validate() require.NoError(t, err) - assert.Equal(t, TargetState(TargetStatePresent), component.TargetState) + assert.False(t, component.Absent) }) t.Run("missingComponentVersionOkayForAbsent", func(t *testing.T) { - component := Component{Name: testComponentName, TargetState: TargetStateAbsent} + component := Component{Name: testComponentName, Absent: true} err := component.Validate() diff --git a/pkg/domain/dogu.go b/pkg/domain/dogu.go index bdeab4ee..8186a695 100644 --- a/pkg/domain/dogu.go +++ b/pkg/domain/dogu.go @@ -3,12 +3,12 @@ package domain import ( "errors" "fmt" + "strings" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "k8s.io/apimachinery/pkg/api/resource" - "slices" - "strings" ) // Dogu defines a Dogu, its version, and the installation state in which it is supposed to be after a blueprint @@ -18,14 +18,14 @@ type Dogu struct { Name cescommons.QualifiedName // Version defines the version of the dogu that is to be installed. Must not be empty if the targetState is "present"; // otherwise it is optional and is not going to be interpreted. - Version core.Version - // TargetState defines a state of installation of this dogu. Optional field, but defaults to "TargetStatePresent" - TargetState TargetState + Version *core.Version + // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. + Absent bool // MinVolumeSize is the minimum storage of the dogu. 0 indicates that the default size should be set. // Reducing this value below the actual volume size has no impact as we do not support downsizing. - MinVolumeSize ecosystem.VolumeSize + MinVolumeSize *ecosystem.VolumeSize // ReverseProxyConfig defines configuration for the ecosystem reverse proxy. This field is optional. - ReverseProxyConfig ecosystem.ReverseProxyConfig + ReverseProxyConfig *ecosystem.ReverseProxyConfig // AdditionalMounts provides the possibility to mount additional data into the dogu. AdditionalMounts []ecosystem.AdditionalMount } @@ -33,11 +33,9 @@ type Dogu struct { // validate checks if the Dogu is semantically correct. func (dogu Dogu) validate() error { var errorList []error - if !slices.Contains(PossibleTargetStates, dogu.TargetState) { - errorList = append(errorList, fmt.Errorf("dogu target state is invalid: %s", dogu.Name)) - } + emptyVersion := core.Version{} - if dogu.TargetState != TargetStateAbsent && dogu.Version == emptyVersion { + if !dogu.Absent && (dogu.Version == nil || *dogu.Version == emptyVersion) { errorList = append(errorList, fmt.Errorf("dogu version must not be empty: %s", dogu.Name)) } // minVolumeSize is already checked while unmarshalling json/yaml @@ -57,7 +55,7 @@ func (dogu Dogu) validate() error { dogu.Name, )) } - if strings.HasPrefix(mount.Subfolder, "/") { + if mount.Subfolder != nil && strings.HasPrefix(*mount.Subfolder, "/") { errorList = append(errorList, fmt.Errorf("dogu additional mounts Subfolder must be a relative path : %s", dogu.Name)) } } diff --git a/pkg/domain/dogu_test.go b/pkg/domain/dogu_test.go index 56a16888..338924cf 100644 --- a/pkg/domain/dogu_test.go +++ b/pkg/domain/dogu_test.go @@ -1,15 +1,16 @@ package domain import ( + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" - "testing" ) func Test_TargetDogu_validate_errorOnMissingVersionForPresentDogu(t *testing.T) { - dogu := Dogu{Name: officialDogu1, TargetState: TargetStatePresent} + dogu := Dogu{Name: officialDogu1, Absent: false} err := dogu.validate() @@ -18,7 +19,7 @@ func Test_TargetDogu_validate_errorOnMissingVersionForPresentDogu(t *testing.T) } func Test_TargetDogu_validate_missingVersionOkayForAbsentDogu(t *testing.T) { - dogu := Dogu{Name: officialDogu1, TargetState: TargetStateAbsent} + dogu := Dogu{Name: officialDogu1, Absent: true} err := dogu.validate() @@ -26,28 +27,19 @@ func Test_TargetDogu_validate_missingVersionOkayForAbsentDogu(t *testing.T) { } func Test_TargetDogu_validate_defaultToPresentState(t *testing.T) { - dogu := Dogu{Name: officialDogu1, Version: version123} + dogu := Dogu{Name: officialDogu1, Version: &version123} err := dogu.validate() require.Nil(t, err) - assert.Equal(t, TargetState(TargetStatePresent), dogu.TargetState) -} - -func Test_TargetDogu_validate_errorOnUnknownTargetState(t *testing.T) { - dogu := Dogu{Name: officialDogu1, TargetState: -1} - - err := dogu.validate() - - require.Error(t, err) - require.ErrorContains(t, err, "dogu target state is invalid: official/dogu1") + assert.False(t, dogu.Absent) } func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { t.Run("error on invalid proxy body size format", func(t *testing.T) { // given parse := resource.MustParse("1Mi") - dogu := Dogu{Name: officialDogu1, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &parse}} + dogu := Dogu{Name: officialDogu1, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &parse}} // when err := dogu.validate() // then @@ -57,7 +49,7 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { t.Run("no error on empty quantity", func(t *testing.T) { // given - dogu := Dogu{Name: officialDogu1, Version: version123} + dogu := Dogu{Name: officialDogu1, Version: &version123} // when err := dogu.validate() // then @@ -67,7 +59,7 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { t.Run("no error on zero size quantity", func(t *testing.T) { // given zeroQuantity := resource.MustParse("0") - dogu := Dogu{Name: officialDogu1, Version: version123, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &zeroQuantity}} + dogu := Dogu{Name: officialDogu1, Version: &version123, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &zeroQuantity}} // when err := dogu.validate() // then @@ -79,12 +71,12 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { t.Run("additionalMounts ok", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + dogu := Dogu{Name: nginxStatic, Version: &version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", - Subfolder: "test", + Subfolder: &subfolder, }, }} // when @@ -95,12 +87,12 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { t.Run("unknown sourceType", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + dogu := Dogu{Name: nginxStatic, Version: &version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: "unsupportedType", Name: "html-config", Volume: "customhtml", - Subfolder: "test", + Subfolder: &subfolder, }, }} // when @@ -112,12 +104,13 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { t.Run("subfolder is no relative path", func(t *testing.T) { // given - dogu := Dogu{Name: nginxStatic, Version: version123, AdditionalMounts: []ecosystem.AdditionalMount{ + absoluteSubfolder := "/test" + dogu := Dogu{Name: nginxStatic, Version: &version123, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", - Subfolder: "/test", + Subfolder: &absoluteSubfolder, }, }} // when diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 6b75851f..7ce593c8 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -2,6 +2,7 @@ package ecosystem import ( "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" ) @@ -85,22 +86,37 @@ type AdditionalMount struct { Volume string // Subfolder defines a subfolder in which the data should be put within the volume. // +optional - Subfolder string + Subfolder *string } // InstallDogu is a factory for new DoguInstallation's. func InstallDogu( name cescommons.QualifiedName, - version core.Version, - minVolumeSize VolumeSize, - reverseProxyConfig ReverseProxyConfig, + version *core.Version, + minVolumeSize *VolumeSize, + reverseProxyConfig *ReverseProxyConfig, additionalMounts []AdditionalMount) *DoguInstallation { + + doguVersion := core.Version{} + if version != nil { + doguVersion = *version + } + + doguVolumeSize := VolumeSize{} + if minVolumeSize != nil { + doguVolumeSize = *minVolumeSize + } + + doguReverseProxyConfig := ReverseProxyConfig{} + if reverseProxyConfig != nil { + doguReverseProxyConfig = *reverseProxyConfig + } return &DoguInstallation{ Name: name, - Version: version, + Version: doguVersion, UpgradeConfig: UpgradeConfig{AllowNamespaceSwitch: false}, - MinVolumeSize: minVolumeSize, - ReverseProxyConfig: reverseProxyConfig, + MinVolumeSize: doguVolumeSize, + ReverseProxyConfig: doguReverseProxyConfig, AdditionalMounts: additionalMounts, } } @@ -109,8 +125,12 @@ func (dogu *DoguInstallation) IsHealthy() bool { return dogu.Health == AvailableHealthStatus } -func (dogu *DoguInstallation) Upgrade(newVersion core.Version) { - dogu.Version = newVersion +func (dogu *DoguInstallation) Upgrade(newVersion *core.Version) { + dogu.Version = core.Version{} + if newVersion != nil { + dogu.Version = *newVersion + } + dogu.UpgradeConfig.AllowNamespaceSwitch = false } @@ -127,8 +147,11 @@ func (dogu *DoguInstallation) UpdateProxyBodySize(value *BodySize) { dogu.ReverseProxyConfig.MaxBodySize = value } -func (dogu *DoguInstallation) UpdateMinVolumeSize(size VolumeSize) { - dogu.MinVolumeSize = size +func (dogu *DoguInstallation) UpdateMinVolumeSize(size *VolumeSize) { + dogu.MinVolumeSize = VolumeSize{} + if size != nil { + dogu.MinVolumeSize = *size + } } func (dogu *DoguInstallation) UpdateProxyRewriteTarget(value RewriteTarget) { diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index c90f131f..ec611a3e 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -1,31 +1,37 @@ package ecosystem import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" - "testing" ) -var version1231, _ = core.ParseVersion("1.2.3-1") -var version1232, _ = core.ParseVersion("1.2.3-2") +var ( + version1231, _ = core.ParseVersion("1.2.3-1") + version1232, _ = core.ParseVersion("1.2.3-2") + rewriteTarget = "/" + additionalConfig = "additional" + subfolder = "different_subfolder" +) func TestInstallDogu(t *testing.T) { volumeSize := resource.MustParse("1Gi") proxyBodySize := resource.MustParse("1G") dogu := InstallDogu( postgresqlQualifiedName, - version1231, - volumeSize, - ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: "/", AdditionalConfig: "additional"}, + &version1231, + &volumeSize, + &ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, []AdditionalMount{ { SourceType: DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, }, ) @@ -36,15 +42,15 @@ func TestInstallDogu(t *testing.T) { MinVolumeSize: volumeSize, ReverseProxyConfig: ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - RewriteTarget: "/", - AdditionalConfig: "additional", + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, }, AdditionalMounts: []AdditionalMount{ { SourceType: DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, }, }, dogu) @@ -80,7 +86,7 @@ func TestDoguInstallation_Upgrade(t *testing.T) { Version: version1231, } - dogu.Upgrade(version1232) + dogu.Upgrade(&version1232) assert.Equal(t, &DoguInstallation{ Name: postgresqlQualifiedName, @@ -139,10 +145,10 @@ func TestDoguInstallation_UpdateProxyRewriteTarget(t *testing.T) { dogu := DoguInstallation{} // when - dogu.UpdateProxyRewriteTarget("/") + dogu.UpdateProxyRewriteTarget(&rewriteTarget) // then - assert.Equal(t, RewriteTarget("/"), dogu.ReverseProxyConfig.RewriteTarget) + assert.Equal(t, RewriteTarget(&rewriteTarget), dogu.ReverseProxyConfig.RewriteTarget) }) } @@ -152,10 +158,10 @@ func TestDoguInstallation_UpdateProxyAdditionalConfig(t *testing.T) { dogu := DoguInstallation{} // when - dogu.UpdateProxyAdditionalConfig("config") + dogu.UpdateProxyAdditionalConfig(&additionalConfig) // then - assert.Equal(t, AdditionalConfig("config"), dogu.ReverseProxyConfig.AdditionalConfig) + assert.Equal(t, AdditionalConfig(&additionalConfig), dogu.ReverseProxyConfig.AdditionalConfig) }) } @@ -166,7 +172,7 @@ func TestDoguInstallation_UpdateMinVolumeSize(t *testing.T) { dogu := DoguInstallation{} // when - dogu.UpdateMinVolumeSize(volumeSize) + dogu.UpdateMinVolumeSize(&volumeSize) // then assert.Equal(t, volumeSize, dogu.MinVolumeSize) diff --git a/pkg/domain/ecosystem/reverseProxyConfig.go b/pkg/domain/ecosystem/reverseProxyConfig.go index 16dc706f..45c49aba 100644 --- a/pkg/domain/ecosystem/reverseProxyConfig.go +++ b/pkg/domain/ecosystem/reverseProxyConfig.go @@ -5,5 +5,5 @@ import ( ) type BodySize = resource.Quantity -type RewriteTarget string -type AdditionalConfig string +type RewriteTarget *string +type AdditionalConfig *string diff --git a/pkg/domain/ecosystem/volumeSize.go b/pkg/domain/ecosystem/volumeSize.go index 25d1e01b..c478f142 100644 --- a/pkg/domain/ecosystem/volumeSize.go +++ b/pkg/domain/ecosystem/volumeSize.go @@ -5,16 +5,15 @@ import "k8s.io/apimachinery/pkg/api/resource" type VolumeSize = resource.Quantity func GetQuantityReference(quantityStr string) (*resource.Quantity, error) { - var quantityPtr *resource.Quantity var quantityValue resource.Quantity var err error if quantityStr != "" && quantityStr != "" { quantityValue, err = resource.ParseQuantity(quantityStr) if err == nil { - quantityPtr = &quantityValue + return &quantityValue, nil } } - return quantityPtr, err + return nil, err } func GetNonNilQuantityRef(quantityStr string) (*resource.Quantity, error) { @@ -25,10 +24,10 @@ func GetNonNilQuantityRef(quantityStr string) (*resource.Quantity, error) { return quantityPtr, err } -func GetQuantityString(quantity *resource.Quantity) string { +func GetQuantityString(quantity *resource.Quantity) *string { if quantity == nil { - return "" + return nil } - - return quantity.String() + quantityStr := quantity.String() + return &quantityStr } diff --git a/pkg/domain/effectiveBlueprint.go b/pkg/domain/effectiveBlueprint.go index 18bfb60c..1a8f7aac 100644 --- a/pkg/domain/effectiveBlueprint.go +++ b/pkg/domain/effectiveBlueprint.go @@ -3,9 +3,10 @@ package domain import ( "errors" "fmt" + "slices" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" - "slices" ) // EffectiveBlueprint describes what the wanted state after evaluating the blueprint and the blueprintMask is. @@ -17,15 +18,15 @@ type EffectiveBlueprint struct { // Components contains a set of exact components versions which should be present or absent in the CES instance after which // this blueprint was applied. Optional. Components []Component - // Config contains all config entries to set via blueprint. - Config Config + // Config contains all config entries to set via blueprint. Optional. + Config *Config } // GetWantedDogus returns a list of all dogus which should be installed func (effectiveBlueprint *EffectiveBlueprint) GetWantedDogus() []Dogu { var wantedDogus []Dogu for _, dogu := range effectiveBlueprint.Dogus { - if dogu.TargetState == TargetStatePresent { + if !dogu.Absent { wantedDogus = append(wantedDogus, dogu) } } diff --git a/pkg/domain/maskDogu.go b/pkg/domain/maskDogu.go index 2015079a..d1054508 100644 --- a/pkg/domain/maskDogu.go +++ b/pkg/domain/maskDogu.go @@ -1,11 +1,8 @@ package domain import ( - "errors" - "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" - "slices" ) // MaskDogu defines a Dogu, its version, and the installation state in which it is supposed to be after a blueprint @@ -16,17 +13,10 @@ type MaskDogu struct { // Version defines the version of the dogu that is to be installed. This version is optional and overrides // the version of the dogu from the blueprint. Version core.Version - // TargetState defines a state of installation of this dogu. Optional field, but defaults to "TargetStatePresent" - TargetState TargetState + // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. + Absent bool } func (dogu MaskDogu) validate() error { - var errorList []error - errorList = append(errorList, dogu.Name.Validate()) - - if !slices.Contains(PossibleTargetStates, dogu.TargetState) { - errorList = append(errorList, fmt.Errorf("dogu mask is invalid: dogu target state is invalid: %s", dogu.Name)) - } - - return errors.Join(errorList...) + return dogu.Name.Validate() } diff --git a/pkg/domain/maskDogu_test.go b/pkg/domain/maskDogu_test.go index 3591cdd9..ef402d76 100644 --- a/pkg/domain/maskDogu_test.go +++ b/pkg/domain/maskDogu_test.go @@ -1,15 +1,16 @@ package domain import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) func Test_MaskTargetDogu_validate_noErrorOnMissingVersionForPresentDogu(t *testing.T) { - dogu := MaskDogu{Name: officialDogu1, TargetState: TargetStatePresent} + dogu := MaskDogu{Name: officialDogu1, Absent: false} err := dogu.validate() @@ -17,7 +18,7 @@ func Test_MaskTargetDogu_validate_noErrorOnMissingVersionForPresentDogu(t *testi } func Test_MaskTargetDogu_validate_missingVersionOkayForAbsentDogu(t *testing.T) { - dogu := MaskDogu{Name: officialDogu1, TargetState: TargetStateAbsent} + dogu := MaskDogu{Name: officialDogu1, Absent: true} err := dogu.validate() @@ -31,7 +32,7 @@ func Test_MaskTargetDogu_validate_defaultToPresentState(t *testing.T) { err := dogu.validate() require.Nil(t, err) - assert.Equal(t, TargetState(TargetStatePresent), dogu.TargetState) + assert.False(t, dogu.Absent) } func Test_MaskTargetDogu_validate_errorOnMissingNameForDogu(t *testing.T) { @@ -42,12 +43,3 @@ func Test_MaskTargetDogu_validate_errorOnMissingNameForDogu(t *testing.T) { require.Error(t, err) require.ErrorContains(t, err, "dogu name must not be empty") } - -func Test_MaskTargetDogu_validate_errorOnUnknownTargetState(t *testing.T) { - dogu := MaskDogu{Name: officialDogu1, TargetState: -1} - - err := dogu.validate() - - require.Error(t, err) - require.ErrorContains(t, err, "dogu target state is invalid: official/dogu1") -} diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go index 6af33f41..0b077d09 100644 --- a/pkg/domain/stateDiffComponent.go +++ b/pkg/domain/stateDiffComponent.go @@ -2,11 +2,12 @@ package domain import ( "fmt" + "reflect" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "golang.org/x/exp/maps" - "reflect" ) // ComponentDiffs contains the differences for all expected Components to the current ecosystem.ComponentInstallations. @@ -51,8 +52,8 @@ type ComponentDiffState struct { Namespace common.ComponentNamespace // Version contains the component's version. Version *semver.Version - // InstallationState contains the component's target state. - InstallationState TargetState + // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. + Absent bool // DeployConfig contains generic properties for the component. DeployConfig ecosystem.DeployConfig } @@ -88,10 +89,10 @@ func (diff *ComponentDiff) String() string { // String returns a string representation of the ComponentDiffState. func (diff *ComponentDiffState) String() string { return fmt.Sprintf( - "{Namespace: %q, Version: %q, InstallationState: %q}", + "{Namespace: %q, Version: %q, InstallationState: %t}", diff.Namespace, diff.getSafeVersionString(), - diff.InstallationState, + diff.Absent, ) } @@ -139,15 +140,14 @@ func determineComponentDiff(blueprintComponent *Component, installedComponent *e if installedComponent == nil { actualState = ComponentDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, } } else { componentName = installedComponent.Name.SimpleName actualState = ComponentDiffState{ - Namespace: installedComponent.Name.Namespace, - Version: installedComponent.ExpectedVersion, - InstallationState: TargetStatePresent, - DeployConfig: installedComponent.DeployConfig, + Namespace: installedComponent.Name.Namespace, + Version: installedComponent.ExpectedVersion, + DeployConfig: installedComponent.DeployConfig, } } @@ -156,10 +156,10 @@ func determineComponentDiff(blueprintComponent *Component, installedComponent *e } else { componentName = blueprintComponent.Name.SimpleName expectedState = ComponentDiffState{ - Namespace: blueprintComponent.Name.Namespace, - Version: blueprintComponent.Version, - InstallationState: blueprintComponent.TargetState, - DeployConfig: blueprintComponent.DeployConfig, + Namespace: blueprintComponent.Name.Namespace, + Version: blueprintComponent.Version, + Absent: blueprintComponent.Absent, + DeployConfig: blueprintComponent.DeployConfig, } } @@ -186,7 +186,7 @@ func findComponentByName(components []Component, name common.SimpleComponentName } func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) ([]Action, error) { - if expected.InstallationState == actual.InstallationState { + if expected.Absent == actual.Absent { return decideOnEqualState(expected, actual) } @@ -196,13 +196,10 @@ func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) func decideOnEqualState(expected ComponentDiffState, actual ComponentDiffState) ([]Action, error) { var neededActions []Action - switch expected.InstallationState { - case TargetStatePresent: - return getActionsForEqualPresentState(expected, actual), nil - case TargetStateAbsent: + if expected.Absent { return neededActions, nil - default: - return nil, fmt.Errorf("component has unexpected target state %q", expected.InstallationState) + } else { + return getActionsForEqualPresentState(expected, actual), nil } } @@ -232,12 +229,9 @@ func getActionsForEqualPresentState(expected ComponentDiffState, actual Componen func decideOnDifferentState(expected ComponentDiffState) ([]Action, error) { // at this place, the actual state is always the opposite to the expected state so just follow the expected state. - switch expected.InstallationState { - case TargetStatePresent: - return []Action{ActionInstall}, nil - case TargetStateAbsent: + if expected.Absent { return []Action{ActionUninstall}, nil - default: - return nil, fmt.Errorf("component has unexpected installation state %q", expected.InstallationState) + } else { + return []Action{ActionInstall}, nil } } diff --git a/pkg/domain/stateDiffComponent_test.go b/pkg/domain/stateDiffComponent_test.go index 9188b06d..af9266fe 100644 --- a/pkg/domain/stateDiffComponent_test.go +++ b/pkg/domain/stateDiffComponent_test.go @@ -1,9 +1,10 @@ package domain import ( - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/go-logr/logr" "github.com/stretchr/testify/assert" @@ -38,91 +39,91 @@ func Test_determineComponentDiff(t *testing.T) { { name: "equal, no action", args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, TargetStatePresent, nil), + blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: mockComponentInstallation(compVersion3211), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), NeededActions: nil, }, }, { name: "install", args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, TargetStatePresent, nil), + blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: nil, }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState("", nil, TargetStateAbsent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), + Actual: mockComponentDiffState("", nil, true, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), NeededActions: []Action{ActionInstall}, }, }, { name: "uninstall", args: args{ - blueprintComponent: mockTargetComponent(nil, TargetStateAbsent, nil), + blueprintComponent: mockTargetComponent(nil, true, nil), installedComponent: mockComponentInstallation(compVersion3211), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, nil, TargetStateAbsent, nil), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, nil, true, nil), NeededActions: []Action{ActionUninstall}, }, }, { name: "upgrade", args: args{ - blueprintComponent: mockTargetComponent(compVersion3212, TargetStatePresent, nil), + blueprintComponent: mockTargetComponent(compVersion3212, false, nil), installedComponent: mockComponentInstallation(compVersion3211), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, TargetStatePresent, nil), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), NeededActions: []Action{ActionUpgrade}, }, }, { name: "update package config", args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, TargetStatePresent, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), + blueprintComponent: mockTargetComponent(compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), installedComponent: mockComponentInstallation(compVersion3211), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), NeededActions: []Action{ActionUpdateComponentDeployConfig}, }, }, { name: "update package config and upgrade", args: args{ - blueprintComponent: mockTargetComponent(compVersion3212, TargetStatePresent, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), + blueprintComponent: mockTargetComponent(compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), installedComponent: mockComponentInstallation(compVersion3211), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, TargetStatePresent, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), NeededActions: []Action{ActionUpdateComponentDeployConfig, ActionUpgrade}, }, }, { name: "downgrade", args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, TargetStatePresent, nil), + blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: mockComponentInstallation(compVersion3212), }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3212, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), NeededActions: []Action{ActionDowngrade}, }, }, @@ -134,8 +135,8 @@ func Test_determineComponentDiff(t *testing.T) { }, want: ComponentDiff{ Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, TargetStatePresent, nil), + Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), + Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), NeededActions: nil, }, }, @@ -147,8 +148,8 @@ func Test_determineComponentDiff(t *testing.T) { }, want: ComponentDiff{ Name: "", - Actual: ComponentDiffState{InstallationState: TargetStateAbsent}, - Expected: ComponentDiffState{InstallationState: TargetStateAbsent}, + Actual: ComponentDiffState{Absent: true}, + Expected: ComponentDiffState{Absent: true}, NeededActions: nil, }, }, @@ -164,12 +165,12 @@ func Test_determineComponentDiff(t *testing.T) { func TestComponentDiff_String(t *testing.T) { actual := ComponentDiffState{ - Version: compVersion3211, - InstallationState: TargetStatePresent, + Version: compVersion3211, + Absent: false, } expected := ComponentDiffState{ - Version: compVersion3212, - InstallationState: TargetStatePresent, + Version: compVersion3212, + Absent: false, } diff := &ComponentDiff{ Name: testComponentName.SimpleName, @@ -188,19 +189,19 @@ func TestComponentDiff_String(t *testing.T) { func TestComponentDiffState_String(t *testing.T) { diff := &ComponentDiffState{ - Namespace: "k8s", - Version: compVersion3211, - InstallationState: TargetStatePresent, + Namespace: "k8s", + Version: compVersion3211, + Absent: false, } assert.Equal(t, `{Namespace: "k8s", Version: "3.2.1-1", InstallationState: "present"}`, diff.String()) } -func mockTargetComponent(version *semver.Version, state TargetState, deployConfig ecosystem.DeployConfig) *Component { +func mockTargetComponent(version *semver.Version, absent bool, deployConfig ecosystem.DeployConfig) *Component { return &Component{ Name: testComponentName, Version: version, - TargetState: state, + Absent: absent, DeployConfig: deployConfig, } } @@ -212,12 +213,12 @@ func mockComponentInstallation(version *semver.Version) *ecosystem.ComponentInst } } -func mockComponentDiffState(namespace common.ComponentNamespace, version *semver.Version, state TargetState, deployConfig ecosystem.DeployConfig) ComponentDiffState { +func mockComponentDiffState(namespace common.ComponentNamespace, version *semver.Version, absent bool, deployConfig ecosystem.DeployConfig) ComponentDiffState { return ComponentDiffState{ - Namespace: namespace, - Version: version, - InstallationState: state, - DeployConfig: deployConfig, + Namespace: namespace, + Version: version, + Absent: absent, + DeployConfig: deployConfig, } } @@ -245,9 +246,9 @@ func Test_determineComponentDiffs(t *testing.T) { args: args{ blueprintComponents: []Component{ { - Name: testComponentName, - Version: compVersion3211, - TargetState: TargetStatePresent, + Name: testComponentName, + Version: compVersion3211, + Absent: true, }, }, installedComponents: nil, @@ -256,12 +257,12 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName.SimpleName, Actual: ComponentDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, }, Expected: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - InstallationState: TargetStatePresent, + Namespace: testComponentName.Namespace, + Version: compVersion3211, + Absent: false, }, NeededActions: []Action{ActionInstall}, }, @@ -282,14 +283,14 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName.SimpleName, Actual: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - InstallationState: TargetStatePresent, + Namespace: testComponentName.Namespace, + Version: compVersion3211, + Absent: false, }, Expected: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - InstallationState: TargetStatePresent, + Namespace: testComponentName.Namespace, + Version: compVersion3211, + Absent: false, }, NeededActions: nil, }, @@ -300,9 +301,9 @@ func Test_determineComponentDiffs(t *testing.T) { args: args{ blueprintComponents: []Component{ { - Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "my-component"}, - Version: compVersion3211, - TargetState: TargetStatePresent, + Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "my-component"}, + Version: compVersion3211, + Absent: true, }, }, installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ @@ -316,14 +317,14 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName.SimpleName, Actual: ComponentDiffState{ - Version: compVersion3211, - InstallationState: TargetStatePresent, - Namespace: testDistributionNamespace, + Version: compVersion3211, + Absent: false, + Namespace: testDistributionNamespace, }, Expected: ComponentDiffState{ - Version: compVersion3211, - InstallationState: TargetStatePresent, - Namespace: testChangeDistributionNamespace, + Version: compVersion3211, + Absent: false, + Namespace: testChangeDistributionNamespace, }, NeededActions: []Action{ActionSwitchComponentNamespace}, }, @@ -334,9 +335,9 @@ func Test_determineComponentDiffs(t *testing.T) { args: args{ blueprintComponents: []Component{ { - Name: testComponentName, - Version: compVersion3212, - TargetState: TargetStatePresent, + Name: testComponentName, + Version: compVersion3212, + Absent: true, }, }, installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ @@ -350,14 +351,14 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName.SimpleName, Actual: ComponentDiffState{ - Version: compVersion3211, - Namespace: testComponentName.Namespace, - InstallationState: TargetStatePresent, + Version: compVersion3211, + Namespace: testComponentName.Namespace, + Absent: false, }, Expected: ComponentDiffState{ - Version: compVersion3212, - Namespace: testComponentName.Namespace, - InstallationState: TargetStatePresent, + Version: compVersion3212, + Namespace: testComponentName.Namespace, + Absent: false, }, NeededActions: []Action{ActionUpgrade}, }, @@ -377,9 +378,8 @@ func TestComponentDiffState_getSafeVersionString(t *testing.T) { version1, _ := semver.NewVersion("1.0.0") type fields struct { - Namespace common.ComponentNamespace - Version *semver.Version - InstallationState TargetState + Namespace common.ComponentNamespace + Version *semver.Version } tests := []struct { name string @@ -400,9 +400,8 @@ func TestComponentDiffState_getSafeVersionString(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff := &ComponentDiffState{ - Namespace: tt.fields.Namespace, - Version: tt.fields.Version, - InstallationState: tt.fields.InstallationState, + Namespace: tt.fields.Namespace, + Version: tt.fields.Version, } assert.Equalf(t, tt.want, diff.getSafeVersionString(), "getSafeVersionString()") }) diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index f7b5ca3d..7a96fe80 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -28,7 +28,7 @@ func countByAction(diffsByDogu map[cescommons.SimpleName]DoguConfigDiffs) map[Co } func determineConfigDiffs( - blueprintConfig Config, + blueprintConfig *Config, globalConfig config.GlobalConfig, configByDogu map[cescommons.SimpleName]config.DoguConfig, SensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig, @@ -38,6 +38,10 @@ func determineConfigDiffs( map[cescommons.SimpleName]SensitiveDoguConfigDiffs, GlobalConfigDiffs, ) { + if blueprintConfig == nil { + return nil, nil, nil + } + return determineDogusConfigDiffs(blueprintConfig.Dogus, configByDogu), determineSensitiveDogusConfigDiffs(blueprintConfig.Dogus, SensitiveConfigByDogu, referencedSensitiveConfig), determineGlobalConfigDiffs(blueprintConfig.Global, globalConfig) diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index 4d2875eb..c782548d 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -1,12 +1,13 @@ package domain import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) var ( @@ -19,14 +20,30 @@ var ( sensitiveDogu1Key1 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key1"} sensitiveDogu1Key2 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key2"} sensitiveDogu1Key3 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key3"} + val1 = "value1" + val2 = "value2" + val3 = "value3" ) func Test_determineConfigDiff(t *testing.T) { + t.Run("nil", func(t *testing.T) { + dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( + nil, + config.CreateGlobalConfig(map[config.Key]config.Value{}), + map[cescommons.SimpleName]config.DoguConfig{}, + map[cescommons.SimpleName]config.DoguConfig{}, + map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, + ) + + assert.Nil(t, dogusConfigDiffs) + assert.Nil(t, sensitiveConfigDiffs) + assert.Nil(t, globalConfigDiff) + }) t.Run("empty", func(t *testing.T) { emptyConfig := Config{} dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - emptyConfig, + &emptyConfig, config.CreateGlobalConfig(map[config.Key]config.Value{}), map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, @@ -62,7 +79,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - givenConfig, + &givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, @@ -76,11 +93,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ Key: "key1", Actual: GlobalConfigValueState{ - Value: "value1", + Value: &val1, Exists: true, }, Expected: GlobalConfigValueState{ - Value: "value1", + Value: &val1, Exists: true, }, NeededAction: ConfigActionNone, @@ -88,11 +105,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ Key: "key2", Actual: GlobalConfigValueState{ - Value: "value2", + Value: &val2, Exists: true, }, Expected: GlobalConfigValueState{ - Value: "value2.2", + Value: &val3, Exists: true, }, NeededAction: ConfigActionSet, @@ -100,11 +117,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ Key: "key3", Actual: GlobalConfigValueState{ - Value: "value3", + Value: &val1, Exists: true, }, Expected: GlobalConfigValueState{ - Value: "", + Value: nil, Exists: false, }, NeededAction: ConfigActionRemove, @@ -112,11 +129,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ Key: "key4", Actual: GlobalConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: GlobalConfigValueState{ - Value: "", + Value: nil, Exists: false, }, NeededAction: ConfigActionNone, @@ -155,7 +172,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - givenConfig, + &givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{ dogu1: doguConfig, @@ -171,11 +188,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ Key: dogu1Key1, Actual: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, Expected: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, NeededAction: ConfigActionNone, @@ -183,11 +200,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ Key: dogu1Key2, Actual: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, Expected: DoguConfigValueState{ - Value: "updatedValue", + Value: &val2, Exists: true, }, NeededAction: ConfigActionSet, @@ -195,11 +212,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ Key: dogu1Key3, Actual: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, Expected: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, NeededAction: ConfigActionRemove, @@ -211,11 +228,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ Key: dogu1Key4, Actual: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, NeededAction: ConfigActionNone, @@ -259,7 +276,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - givenConfig, + &givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{ @@ -281,11 +298,11 @@ func Test_determineConfigDiff(t *testing.T) { { Key: sensitiveDogu1Key1, Actual: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, Expected: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, NeededAction: ConfigActionNone, @@ -293,11 +310,11 @@ func Test_determineConfigDiff(t *testing.T) { { Key: sensitiveDogu1Key2, Actual: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, Expected: DoguConfigValueState{ - Value: "updated value", + Value: &val2, Exists: true, }, NeededAction: ConfigActionSet, @@ -305,11 +322,11 @@ func Test_determineConfigDiff(t *testing.T) { { Key: sensitiveDogu1Key3, Actual: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, NeededAction: ConfigActionNone, @@ -348,7 +365,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, _ := determineConfigDiffs( - givenConfig, + &givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{ dogu1: doguConfig, @@ -369,11 +386,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Equal(t, sensitiveConfigDiffs["dogu1"][0], SensitiveDoguConfigEntryDiff{ Key: sensitiveDogu1Key1, Actual: DoguConfigValueState{ - Value: "", + Value: nil, Exists: false, }, Expected: DoguConfigValueState{ - Value: "value", + Value: &val1, Exists: true, }, NeededAction: ConfigActionSet, @@ -391,44 +408,44 @@ func Test_getNeededConfigAction(t *testing.T) { }{ { name: "action none, both do not exist", - expected: ConfigValueState{Value: "", Exists: false}, - actual: ConfigValueState{Value: "", Exists: false}, + expected: ConfigValueState{Value: nil, Exists: false}, + actual: ConfigValueState{Value: nil, Exists: false}, want: ConfigActionNone, }, { name: "action none, for some reason the values are different", - expected: ConfigValueState{Value: "1", Exists: false}, - actual: ConfigValueState{Value: "2", Exists: false}, + expected: ConfigValueState{Value: &val1, Exists: false}, + actual: ConfigValueState{Value: &val2, Exists: false}, want: ConfigActionNone, }, { name: "action none, equal values", - expected: ConfigValueState{Value: "1", Exists: true}, - actual: ConfigValueState{Value: "1", Exists: true}, + expected: ConfigValueState{Value: &val1, Exists: true}, + actual: ConfigValueState{Value: &val1, Exists: true}, want: ConfigActionNone, }, { name: "set new value", - expected: ConfigValueState{Value: "", Exists: true}, - actual: ConfigValueState{Value: "", Exists: false}, + expected: ConfigValueState{Value: nil, Exists: true}, + actual: ConfigValueState{Value: nil, Exists: false}, want: ConfigActionSet, }, { name: "update value", - expected: ConfigValueState{Value: "1", Exists: true}, - actual: ConfigValueState{Value: "2", Exists: true}, + expected: ConfigValueState{Value: &val1, Exists: true}, + actual: ConfigValueState{Value: &val2, Exists: true}, want: ConfigActionSet, }, { name: "remove value", - expected: ConfigValueState{Value: "", Exists: false}, - actual: ConfigValueState{Value: "", Exists: true}, + expected: ConfigValueState{Value: nil, Exists: false}, + actual: ConfigValueState{Value: nil, Exists: true}, want: ConfigActionRemove, }, { name: "remove value", - expected: ConfigValueState{Value: "", Exists: false}, - actual: ConfigValueState{Value: "value3", Exists: true}, + expected: ConfigValueState{Value: nil, Exists: false}, + actual: ConfigValueState{Value: &val3, Exists: true}, want: ConfigActionRemove, }, } diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index 427ef047..705ed6de 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -2,11 +2,12 @@ package domain import ( "fmt" + "slices" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "golang.org/x/exp/maps" - "slices" ) // DoguDiffs contains the Diff for all expected Dogus to the current ecosystem.DoguInstallations. @@ -32,10 +33,10 @@ type DoguDiff struct { // DoguDiffState contains all fields to make a diff for dogus in respect to another DoguDiffState. type DoguDiffState struct { Namespace cescommons.Namespace - Version core.Version - InstallationState TargetState - MinVolumeSize ecosystem.VolumeSize - ReverseProxyConfig ecosystem.ReverseProxyConfig + Version *core.Version + Absent bool + MinVolumeSize *ecosystem.VolumeSize + ReverseProxyConfig *ecosystem.ReverseProxyConfig AdditionalMounts []ecosystem.AdditionalMount } @@ -57,10 +58,11 @@ func (diff *DoguDiff) String() string { // String returns a string representation of the DoguDiffState. func (diff *DoguDiffState) String() string { return fmt.Sprintf( - "{Version: %q, Namespace: %q, InstallationState: %q}", + "{Version: %q, Namespace: %q, Absent: %t}", + //TODO NilPointer? diff.Version.Raw, diff.Namespace, - diff.InstallationState, + diff.Absent, ) } @@ -93,16 +95,15 @@ func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstall if installedDogu == nil { actualState = DoguDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, } } else { doguName = installedDogu.Name.SimpleName actualState = DoguDiffState{ Namespace: installedDogu.Name.Namespace, - Version: installedDogu.Version, - InstallationState: TargetStatePresent, - MinVolumeSize: installedDogu.MinVolumeSize, - ReverseProxyConfig: installedDogu.ReverseProxyConfig, + Version: &installedDogu.Version, + MinVolumeSize: &installedDogu.MinVolumeSize, + ReverseProxyConfig: &installedDogu.ReverseProxyConfig, AdditionalMounts: installedDogu.AdditionalMounts, } } @@ -114,7 +115,7 @@ func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstall expectedState = DoguDiffState{ Namespace: blueprintDogu.Name.Namespace, Version: blueprintDogu.Version, - InstallationState: blueprintDogu.TargetState, + Absent: blueprintDogu.Absent, MinVolumeSize: blueprintDogu.MinVolumeSize, ReverseProxyConfig: blueprintDogu.ReverseProxyConfig, AdditionalMounts: blueprintDogu.AdditionalMounts, @@ -130,25 +131,20 @@ func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstall } func getNeededDoguActions(expected DoguDiffState, actual DoguDiffState) []Action { - if expected.InstallationState == actual.InstallationState { - switch expected.InstallationState { - case TargetStatePresent: - // dogu should stay installed, but maybe it needs an upgrade, downgrade or a namespace switch? - return getActionsForPresentDoguDiffs(expected, actual) - case TargetStateAbsent: + if expected.Absent == actual.Absent { + if expected.Absent { return []Action{} + } else { + return getActionsForPresentDoguDiffs(expected, actual) } } else { // actual state is always the opposite - switch expected.InstallationState { - case TargetStatePresent: - return []Action{ActionInstall} - case TargetStateAbsent: + if expected.Absent { return []Action{ActionUninstall} + } else { + return []Action{ActionInstall} } } - // all cases should be handled above, but if new fields are added, this is a safe fallback for any bugs. - return []Action{} } func getActionsForPresentDoguDiffs(expected DoguDiffState, actual DoguDiffState) []Action { @@ -167,9 +163,9 @@ func getActionsForPresentDoguDiffs(expected DoguDiffState, actual DoguDiffState) if expected.ReverseProxyConfig.AdditionalConfig != actual.ReverseProxyConfig.AdditionalConfig { neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) } - if expected.Version.IsNewerThan(actual.Version) { + if actual.Version != nil && expected.Version.IsNewerThan(*actual.Version) { neededActions = append(neededActions, ActionUpgrade) - } else if actual.Version.IsNewerThan(expected.Version) { + } else if expected.Version != nil && actual.Version.IsNewerThan(*expected.Version) { // if downgrades are allowed is not important here. // Downgrades can be rejected later, so forcing downgrades via a flag can be implemented without changing this code here. neededActions = append(neededActions, ActionDowngrade) @@ -178,9 +174,11 @@ func getActionsForPresentDoguDiffs(expected DoguDiffState, actual DoguDiffState) return neededActions } -func appendActionForMinVolumeSize(actions []Action, expectedSize ecosystem.VolumeSize, actualSize ecosystem.VolumeSize) []Action { +func appendActionForMinVolumeSize(actions []Action, expectedSize *ecosystem.VolumeSize, actualSize *ecosystem.VolumeSize) []Action { // if expected > actual = update needed - if expectedSize.Cmp(actualSize) > 0 { + if expectedSize == nil { + return actions + } else if actualSize == nil || expectedSize.Cmp(*actualSize) > 0 { return append(actions, ActionUpdateDoguResourceMinVolumeSize) } return actions @@ -192,7 +190,7 @@ func appendActionForProxyBodySizes(actions []Action, expectedProxyBodySize *ecos } else if proxyBodySizeIdentityChanged(expectedProxyBodySize, actualProxyBodySize) { return append(actions, ActionUpdateDoguProxyBodySize) } else { - if expectedProxyBodySize.Cmp(*actualProxyBodySize) != 0 { + if expectedProxyBodySize != nil && actualProxyBodySize != nil && expectedProxyBodySize.Cmp(*actualProxyBodySize) != 0 { return append(actions, ActionUpdateDoguProxyBodySize) } } diff --git a/pkg/domain/stateDiffDoguConfig.go b/pkg/domain/stateDiffDoguConfig.go index e788fbde..88e5aa0c 100644 --- a/pkg/domain/stateDiffDoguConfig.go +++ b/pkg/domain/stateDiffDoguConfig.go @@ -21,7 +21,7 @@ func (diffs DoguConfigDiffs) HasChanges() bool { type DoguConfigValueState ConfigValueState type ConfigValueState struct { - Value string + Value *string Exists bool } type DoguConfigEntryDiff struct { @@ -34,17 +34,17 @@ type SensitiveDoguConfigEntryDiff = DoguConfigEntryDiff func newDoguConfigEntryDiff( key common.DoguConfigKey, - actualValue config.Value, + actualValue *config.Value, actualExists bool, - expectedValue common.DoguConfigValue, + expectedValue *common.DoguConfigValue, expectedExists bool, ) DoguConfigEntryDiff { actual := DoguConfigValueState{ - Value: string(actualValue), + Value: (*string)(actualValue), Exists: actualExists, } expected := DoguConfigValueState{ - Value: string(expectedValue), + Value: (*string)(expectedValue), Exists: expectedExists, } return DoguConfigEntryDiff{ @@ -63,12 +63,12 @@ func determineDoguConfigDiffs( // present entries for key, expectedValue := range wantedConfig.Present { actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualEntry, exists, expectedValue, true)) + doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, &actualEntry, exists, &expectedValue, true)) } // absent entries for _, key := range wantedConfig.Absent { actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualEntry, exists, "", false)) + doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, &actualEntry, exists, nil, false)) } return doguConfigDiff } diff --git a/pkg/domain/stateDiffDoguConfig_test.go b/pkg/domain/stateDiffDoguConfig_test.go index 21f9ad77..30f50a01 100644 --- a/pkg/domain/stateDiffDoguConfig_test.go +++ b/pkg/domain/stateDiffDoguConfig_test.go @@ -1,11 +1,13 @@ package domain import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestDoguConfigDiffs_HasChanges(t *testing.T) { + testVal := "test" tests := []struct { name string diffs DoguConfigDiffs @@ -36,7 +38,7 @@ func TestDoguConfigDiffs_HasChanges(t *testing.T) { Exists: false, }, Expected: DoguConfigValueState{ - Value: "test", + Value: &testVal, Exists: true, }, NeededAction: ConfigActionSet, @@ -50,7 +52,7 @@ func TestDoguConfigDiffs_HasChanges(t *testing.T) { SensitiveDoguConfigEntryDiff{ Key: dogu1Key1, Actual: DoguConfigValueState{ - Value: "test", + Value: &testVal, Exists: true, }, Expected: DoguConfigValueState{ diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index 26fc42ce..9cfbba40 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -1,11 +1,18 @@ package domain import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" - "testing" +) + +var ( + additionalConfig = "additional" + rewriteConfig = "/" + subfolder2 = "secsubfolder" ) func Test_determineDoguDiff(t *testing.T) { @@ -30,9 +37,9 @@ func Test_determineDoguDiff(t *testing.T) { name: "equal, no action", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3211, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3211, + Absent: false, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -42,14 +49,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: nil, }, @@ -58,21 +65,21 @@ func Test_determineDoguDiff(t *testing.T) { name: "install", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3211, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3211, + Absent: false, }, installedDogu: nil, }, want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: []Action{ActionInstall}, }, @@ -81,8 +88,8 @@ func Test_determineDoguDiff(t *testing.T) { name: "uninstall", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - TargetState: TargetStateAbsent, + Name: officialNexus, + Absent: true, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -92,13 +99,13 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStateAbsent, + Namespace: officialNamespace, + Absent: true, }, NeededActions: []Action{ActionUninstall}, }, @@ -107,9 +114,9 @@ func Test_determineDoguDiff(t *testing.T) { name: "namespace switch", args: args{ blueprintDogu: &Dogu{ - Name: premiumNexus, - Version: version3211, - TargetState: TargetStatePresent, + Name: premiumNexus, + Version: &version3211, + Absent: false, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -119,14 +126,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: "premium", - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: "premium", + Version: &version3211, + Absent: false, }, NeededActions: []Action{ActionSwitchDoguNamespace}, }, @@ -135,9 +142,9 @@ func Test_determineDoguDiff(t *testing.T) { name: "upgrade", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3212, + Absent: false, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -147,14 +154,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3212, + Absent: false, }, NeededActions: []Action{ActionUpgrade}, }, @@ -164,8 +171,8 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - TargetState: TargetStatePresent, - MinVolumeSize: quantity100M, + Absent: false, + MinVolumeSize: &quantity100M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -175,14 +182,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity100M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity100M, }, Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity10M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity10M, }, NeededActions: []Action{ActionUpdateDoguResourceMinVolumeSize}, }, @@ -192,8 +199,8 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - TargetState: TargetStatePresent, - MinVolumeSize: quantity100M, + Absent: false, + MinVolumeSize: &quantity100M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -203,14 +210,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity100M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity100M, }, Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity100M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity100M, }, NeededActions: nil, }, @@ -220,8 +227,8 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - TargetState: TargetStatePresent, - MinVolumeSize: quantity10M, + Absent: false, + MinVolumeSize: &quantity10M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -231,14 +238,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity10M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity10M, }, Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, - MinVolumeSize: quantity100M, + Namespace: officialNamespace, + Absent: false, + MinVolumeSize: &quantity100M, }, NeededActions: nil, }, @@ -247,15 +254,15 @@ func Test_determineDoguDiff(t *testing.T) { name: "multiple update actions", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Name: officialNexus, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - AdditionalConfig: "additional", - RewriteTarget: "/", + AdditionalConfig: &additionalConfig, + RewriteTarget: &rewriteConfig, }, - MinVolumeSize: volumeSize2, + MinVolumeSize: &volumeSize2, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -266,21 +273,21 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, - MinVolumeSize: volumeSize1, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, + MinVolumeSize: &volumeSize1, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - AdditionalConfig: "additional", - RewriteTarget: "/", + AdditionalConfig: &additionalConfig, + RewriteTarget: &rewriteConfig, }, - MinVolumeSize: volumeSize2, + MinVolumeSize: &volumeSize2, }, NeededActions: []Action{ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguProxyBodySize, ActionUpdateDoguProxyRewriteTarget, ActionUpdateDoguProxyAdditionalConfig, ActionUpgrade}, }, @@ -289,9 +296,9 @@ func Test_determineDoguDiff(t *testing.T) { name: "downgrade", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3211, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3211, + Absent: false, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -301,14 +308,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3212, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: []Action{ActionDowngrade}, }, @@ -325,14 +332,14 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: nil, }, @@ -346,10 +353,10 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "", Actual: DoguDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, }, Expected: DoguDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, }, NeededActions: []Action{}, }, @@ -358,10 +365,10 @@ func Test_determineDoguDiff(t *testing.T) { name: "update proxy body size", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Name: officialNexus, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -376,18 +383,18 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -398,10 +405,10 @@ func Test_determineDoguDiff(t *testing.T) { name: "update if proxy body size changed", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Name: officialNexus, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -416,18 +423,18 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity10MPtr, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -438,10 +445,10 @@ func Test_determineDoguDiff(t *testing.T) { name: "no update if body sizes are nil", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Name: officialNexus, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, @@ -456,18 +463,18 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + Namespace: officialNamespace, + Version: &version3212, + Absent: false, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, @@ -478,20 +485,20 @@ func Test_determineDoguDiff(t *testing.T) { name: "no action if additional mounts are equal", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - TargetState: TargetStatePresent, + Name: officialNexus, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -502,13 +509,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -516,38 +523,38 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder2, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -558,20 +565,20 @@ func Test_determineDoguDiff(t *testing.T) { name: "no action if additional mounts are equal but order is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - TargetState: TargetStatePresent, + Name: officialNexus, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, @@ -582,13 +589,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -596,38 +603,38 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, @@ -638,14 +645,14 @@ func Test_determineDoguDiff(t *testing.T) { name: "needs update action for additional mounts if the size is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - TargetState: TargetStatePresent, + Name: officialNexus, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, @@ -656,13 +663,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -670,32 +677,32 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, }, }, @@ -706,20 +713,20 @@ func Test_determineDoguDiff(t *testing.T) { name: "needs update action for additional mounts if an element is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - TargetState: TargetStatePresent, + Name: officialNexus, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -730,13 +737,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -744,38 +751,38 @@ func Test_determineDoguDiff(t *testing.T) { want: DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: "different_subfolder", + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: "secsubfolder", + Subfolder: &subfolder2, }, }, }, @@ -813,9 +820,9 @@ func Test_determineDoguDiffs(t *testing.T) { args: args{ blueprintDogus: []Dogu{ { - Name: officialNexus, - Version: version3211, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3211, + Absent: false, }, }, installedDogus: nil, @@ -824,12 +831,12 @@ func Test_determineDoguDiffs(t *testing.T) { { DoguName: "nexus", Actual: DoguDiffState{ - InstallationState: TargetStateAbsent, + Absent: true, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: []Action{ActionInstall}, }, @@ -850,14 +857,14 @@ func Test_determineDoguDiffs(t *testing.T) { { DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, NeededActions: nil, }, @@ -868,9 +875,9 @@ func Test_determineDoguDiffs(t *testing.T) { args: args{ blueprintDogus: []Dogu{ { - Name: officialNexus, - Version: version3212, - TargetState: TargetStatePresent, + Name: officialNexus, + Version: &version3212, + Absent: false, }, }, installedDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{ @@ -884,14 +891,14 @@ func Test_determineDoguDiffs(t *testing.T) { { DoguName: "nexus", Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3211, + Absent: false, }, Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: version3212, - InstallationState: TargetStatePresent, + Namespace: officialNamespace, + Version: &version3212, + Absent: false, }, NeededActions: []Action{ActionUpgrade}, }, @@ -907,14 +914,14 @@ func Test_determineDoguDiffs(t *testing.T) { func TestDoguDiff_String(t *testing.T) { actual := DoguDiffState{ - Namespace: "official", - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: "official", + Version: &version3211, + Absent: false, } expected := DoguDiffState{ - Namespace: "premium", - Version: version3212, - InstallationState: TargetStatePresent, + Namespace: "premium", + Version: &version3212, + Absent: false, } diff := &DoguDiff{ DoguName: "postgresql", @@ -932,9 +939,9 @@ func TestDoguDiff_String(t *testing.T) { } func TestDoguDiffState_String(t *testing.T) { diff := &DoguDiffState{ - Namespace: "official", - Version: version3211, - InstallationState: TargetStatePresent, + Namespace: "official", + Version: &version3211, + Absent: false, } assert.Equal(t, "{Version: \"3.2.1-1\", Namespace: \"official\", InstallationState: \"present\"}", diff.String()) diff --git a/pkg/domain/stateDiffGlobalConfig.go b/pkg/domain/stateDiffGlobalConfig.go index 37c62473..698f86e2 100644 --- a/pkg/domain/stateDiffGlobalConfig.go +++ b/pkg/domain/stateDiffGlobalConfig.go @@ -41,17 +41,17 @@ func (diffs GlobalConfigDiffs) countByAction() map[ConfigAction]int { func newGlobalConfigEntryDiff( key common.GlobalConfigKey, - actualValue common.GlobalConfigValue, + actualValue *common.GlobalConfigValue, actualExists bool, - expectedValue common.GlobalConfigValue, + expectedValue *common.GlobalConfigValue, expectedExists bool, ) GlobalConfigEntryDiff { actual := GlobalConfigValueState{ - Value: string(actualValue), + Value: (*string)(actualValue), Exists: actualExists, } expected := GlobalConfigValueState{ - Value: string(expectedValue), + Value: (*string)(expectedValue), Exists: expectedExists, } return GlobalConfigEntryDiff{ @@ -71,12 +71,12 @@ func determineGlobalConfigDiffs( // present entries for key, expectedValue := range config.Present { actualEntry, actualExists := actualConfig.Get(key) - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualEntry, actualExists, expectedValue, true)) + configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, &actualEntry, actualExists, &expectedValue, true)) } // absent entries for _, key := range config.Absent { actualEntry, actualExists := actualConfig.Get(key) - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualEntry, actualExists, "", false)) + configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, &actualEntry, actualExists, nil, false)) } return configDiffs } diff --git a/pkg/domain/stateDiffGlobalConfig_test.go b/pkg/domain/stateDiffGlobalConfig_test.go index 2b3276d2..4b14e24f 100644 --- a/pkg/domain/stateDiffGlobalConfig_test.go +++ b/pkg/domain/stateDiffGlobalConfig_test.go @@ -1,11 +1,14 @@ package domain import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestGlobalConfigDiffs_HasChanges(t *testing.T) { + valChanged := "changed" + valInitial := "initial" tests := []struct { name string diffs GlobalConfigDiffs @@ -21,8 +24,8 @@ func TestGlobalConfigDiffs_HasChanges(t *testing.T) { diffs: []GlobalConfigEntryDiff{ { Key: "testkey", - Actual: GlobalConfigValueState{Value: "changed", Exists: true}, - Expected: GlobalConfigValueState{Value: "initial", Exists: true}, + Actual: GlobalConfigValueState{Value: &valChanged, Exists: true}, + Expected: GlobalConfigValueState{Value: &valInitial, Exists: true}, NeededAction: ConfigActionSet, }, }, @@ -33,8 +36,8 @@ func TestGlobalConfigDiffs_HasChanges(t *testing.T) { diffs: []GlobalConfigEntryDiff{ { Key: "testkey", - Actual: GlobalConfigValueState{Value: "changed", Exists: true}, - Expected: GlobalConfigValueState{Value: "initial", Exists: true}, + Actual: GlobalConfigValueState{Value: &valChanged, Exists: true}, + Expected: GlobalConfigValueState{Value: &valInitial, Exists: true}, NeededAction: ConfigActionNone, }, }, diff --git a/pkg/domain/targetState.go b/pkg/domain/targetState.go deleted file mode 100644 index 70edc2c8..00000000 --- a/pkg/domain/targetState.go +++ /dev/null @@ -1,27 +0,0 @@ -package domain - -// TargetState defines an enum of values that determines a state of installation. -type TargetState int - -const ( - // TargetStatePresent is the default state. If selected the chosen item must be present after the blueprint was - // applied. - TargetStatePresent = iota - // TargetStateAbsent sets the state of the item to absent. If selected the chosen item must be absent after the - // blueprint was applied. - TargetStateAbsent -) - -var PossibleTargetStates = []TargetState{ - TargetStatePresent, TargetStateAbsent, -} - -// String returns a string representation of the given TargetState enum value. -func (state TargetState) String() string { - return toString[state] -} - -var toString = map[TargetState]string{ - TargetStatePresent: "present", - TargetStateAbsent: "absent", -} diff --git a/pkg/domain/targetState_test.go b/pkg/domain/targetState_test.go deleted file mode 100644 index 3d3ba131..00000000 --- a/pkg/domain/targetState_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package domain - -import ( - "testing" -) - -func TestTargetState_String(t *testing.T) { - tests := []struct { - name string - state TargetState - want string - }{ - { - "map present enum value to string", - TargetStatePresent, - "present", - }, - { - "map absent enum value to string", - TargetStateAbsent, - "absent", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.state.String(); got != tt.want { - t.Errorf("TargetState.String() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/pkg/domainservice/util.go b/pkg/domainservice/util.go index d1fffd50..a85a523c 100644 --- a/pkg/domainservice/util.go +++ b/pkg/domainservice/util.go @@ -3,6 +3,7 @@ package domainservice import ( "context" "errors" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -11,9 +12,13 @@ import ( func loadDoguSpecifications(ctx context.Context, remoteDoguRegistry RemoteDoguRegistry, wantedDogus []domain.Dogu) (map[cescommons.QualifiedName]*core.Dogu, error) { dogusToLoad := util.Map(wantedDogus, func(dogu domain.Dogu) cescommons.QualifiedVersion { + doguVersion := core.Version{} + if dogu.Version != nil { + doguVersion = *dogu.Version + } return cescommons.QualifiedVersion{ Name: dogu.Name, - Version: dogu.Version, + Version: doguVersion, } }) doguSpecsOfWantedDogus, err := remoteDoguRegistry.GetDogus(ctx, dogusToLoad) diff --git a/pkg/domainservice/validateAdditionalMountsDomainUseCase_test.go b/pkg/domainservice/validateAdditionalMountsDomainUseCase_test.go index d6011cc5..dfa074e0 100644 --- a/pkg/domainservice/validateAdditionalMountsDomainUseCase_test.go +++ b/pkg/domainservice/validateAdditionalMountsDomainUseCase_test.go @@ -2,13 +2,14 @@ package domainservice import ( _ "embed" + "testing" + "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) //go:embed testdata/k8s-nginx-static-1-26-3-2.json @@ -35,9 +36,9 @@ func TestValidateAdditionalMountsDomainUseCase_ValidateAdditionalMounts(t *testi blueprint := domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: k8sNginxStatic, - Version: version1_26_3_2, - TargetState: domain.TargetStatePresent, + Name: k8sNginxStatic, + Version: &version1_26_3_2, + Absent: false, }, }, } @@ -54,9 +55,9 @@ func TestValidateAdditionalMountsDomainUseCase_ValidateAdditionalMounts(t *testi blueprint := domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: k8sNginxStatic, - Version: version1_26_3_2, - TargetState: domain.TargetStatePresent, + Name: k8sNginxStatic, + Version: &version1_26_3_2, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -87,9 +88,9 @@ func TestValidateAdditionalMountsDomainUseCase_ValidateAdditionalMounts(t *testi blueprint := domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: k8sNginxStatic, - Version: version1_26_3_2, - TargetState: domain.TargetStatePresent, + Name: k8sNginxStatic, + Version: &version1_26_3_2, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -123,9 +124,9 @@ func TestValidateAdditionalMountsDomainUseCase_ValidateAdditionalMounts(t *testi blueprint := domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: k8sNginxStatic, - Version: version1_26_3_2, - TargetState: domain.TargetStatePresent, + Name: k8sNginxStatic, + Version: &version1_26_3_2, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -160,9 +161,9 @@ func TestValidateAdditionalMountsDomainUseCase_ValidateAdditionalMounts(t *testi blueprint := domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: k8sNginxStatic, - Version: version1_26_3_2, - TargetState: domain.TargetStatePresent, + Name: k8sNginxStatic, + Version: &version1_26_3_2, + Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, diff --git a/pkg/domainservice/validateDependenciesDomainUseCase.go b/pkg/domainservice/validateDependenciesDomainUseCase.go index 906a055c..6ea854db 100644 --- a/pkg/domainservice/validateDependenciesDomainUseCase.go +++ b/pkg/domainservice/validateDependenciesDomainUseCase.go @@ -38,9 +38,13 @@ func (useCase *ValidateDependenciesDomainUseCase) ValidateDependenciesForAllDogu logger := log.FromContext(ctx).WithName("ValidateDependenciesDomainUseCase.ValidateDependenciesForAllDogus") wantedDogus := effectiveBlueprint.GetWantedDogus() dogusToLoad := util.Map(wantedDogus, func(dogu domain.Dogu) cescommons.QualifiedVersion { + doguVersion := core.Version{} + if dogu.Version != nil { + doguVersion = *dogu.Version + } return cescommons.QualifiedVersion{ Name: dogu.Name, - Version: dogu.Version, + Version: doguVersion, } }) logger.V(2).Info("load dogu specifications...", "wantedDogus", wantedDogus) @@ -165,7 +169,11 @@ func checkDependencyVersion(doguInBlueprint domain.Dogu, expectedVersion string) if err != nil { return fmt.Errorf("failed to parse version comparator of version %s for dogu dependency %s: %w", expectedVersion, doguInBlueprint.Name, err) } - allows, err := comparator.Allows(doguInBlueprint.Version) + doguInBlueprintVersion := core.Version{} + if doguInBlueprint.Version != nil { + doguInBlueprintVersion = *doguInBlueprint.Version + } + allows, err := comparator.Allows(doguInBlueprintVersion) if err != nil { return fmt.Errorf("an error occurred when comparing the versions: %w", err) } diff --git a/pkg/domainservice/validateDependenciesDomainUseCase_test.go b/pkg/domainservice/validateDependenciesDomainUseCase_test.go index 9e8af9df..dfb84b98 100644 --- a/pkg/domainservice/validateDependenciesDomainUseCase_test.go +++ b/pkg/domainservice/validateDependenciesDomainUseCase_test.go @@ -2,13 +2,14 @@ package domainservice import ( "context" + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "testing" ) var ( @@ -49,14 +50,14 @@ func Test_checkDependencyVersion(t *testing.T) { args args wantErr bool }{ - {name: "exact version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_1}, expectedVersion: "2.0.0-1"}, wantErr: false}, - {name: "has lower version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_1}, expectedVersion: ">=2.0.0-2"}, wantErr: true}, - {name: "has higher version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_3}, expectedVersion: ">=2.0.0-2"}, wantErr: false}, - {name: "needs lower version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_3}, expectedVersion: "<=2.0.0-2"}, wantErr: true}, - {name: "needs higher version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_1}, expectedVersion: ">2.0.0-1"}, wantErr: true}, - {name: "no constraint", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_1}, expectedVersion: ""}, wantErr: false}, - {name: "not parsable expected version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: version2_0_0_1}, expectedVersion: "abc"}, wantErr: true}, - {name: "not parsable actual version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: core.Version{Raw: "abc"}}, expectedVersion: "2.0.0-1"}, wantErr: true}, + {name: "exact version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_1}, expectedVersion: "2.0.0-1"}, wantErr: false}, + {name: "has lower version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_1}, expectedVersion: ">=2.0.0-2"}, wantErr: true}, + {name: "has higher version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_3}, expectedVersion: ">=2.0.0-2"}, wantErr: false}, + {name: "needs lower version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_3}, expectedVersion: "<=2.0.0-2"}, wantErr: true}, + {name: "needs higher version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_1}, expectedVersion: ">2.0.0-1"}, wantErr: true}, + {name: "no constraint", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_1}, expectedVersion: ""}, wantErr: false}, + {name: "not parsable expected version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &version2_0_0_1}, expectedVersion: "abc"}, wantErr: true}, + {name: "not parsable actual version", args: args{doguInBlueprint: domain.Dogu{Name: officialNginx, Version: &core.Version{Raw: "abc"}}, expectedVersion: "2.0.0-1"}, wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -82,8 +83,8 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te args: args{ effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialRedmine, Version: version1_0_0_1}, - {Name: officialPostgres, Version: version1_0_0_1}, + {Name: officialRedmine, Version: &version1_0_0_1}, + {Name: officialPostgres, Version: &version1_0_0_1}, }, }, }, @@ -94,8 +95,8 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te args: args{ effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialRedmine, Version: version1_0_0_1}, - {Name: officialPostgres, Version: version1_0_0_1}, + {Name: officialRedmine, Version: &version1_0_0_1}, + {Name: officialPostgres, Version: &version1_0_0_1}, }, }, }, @@ -106,7 +107,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te args: args{ effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialRedmine, Version: version1_0_0_1}, + {Name: officialRedmine, Version: &version1_0_0_1}, }, }, }, @@ -118,7 +119,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te args: args{ effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialRedmine2, Version: version1_0_0_1}, + {Name: officialRedmine2, Version: &version1_0_0_1}, }, }, }, @@ -130,7 +131,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te args: args{ effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialK8sCesControl, Version: version1_0_0_1}, + {Name: officialK8sCesControl, Version: &version1_0_0_1}, }, }, }, @@ -140,7 +141,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te name: "missing nginx-static and nginx ingress on nginx dependency", args: args{effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialScm, Version: version1_0_0_1}, + {Name: officialScm, Version: &version1_0_0_1}, }, }}, wantErr: true, @@ -149,9 +150,9 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te name: "ok with nginx dependency", args: args{effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialPlantuml, Version: version1_0_0_1}, - {Name: k8sNginxStatic, Version: version1_0_0_1}, - {Name: k8sNginxIngress, Version: version1_0_0_1}, + {Name: officialPlantuml, Version: &version1_0_0_1}, + {Name: k8sNginxStatic, Version: &version1_0_0_1}, + {Name: k8sNginxIngress, Version: &version1_0_0_1}, }, }}, wantErr: false, @@ -160,7 +161,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus(t *te name: "registrator should be ignored", args: args{effectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: ldapMapper, Version: version1_0_0_1}, + {Name: ldapMapper, Version: &version1_0_0_1}, }, }}, wantErr: false, @@ -189,7 +190,7 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus_NotFo // when err := useCase.ValidateDependenciesForAllDogus(ctx, domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialUnknownDogu, Version: version1_0_0_1}, + {Name: officialUnknownDogu, Version: &version1_0_0_1}, }, }) // then @@ -218,8 +219,8 @@ func TestValidateDependenciesDomainUseCase_ValidateDependenciesForAllDogus_colle // when err := useCase.ValidateDependenciesForAllDogus(ctx, domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ - {Name: officialRedmine, Version: version1_0_0_1}, - {Name: helloworldBluespice, Version: version1_0_0_1}, + {Name: officialRedmine, Version: &version1_0_0_1}, + {Name: helloworldBluespice, Version: &version1_0_0_1}, }, }) // then From ecde0fdfffe049cd0e799c6cf5505590fe7fd714 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 11 Sep 2025 16:12:08 +0200 Subject: [PATCH 052/119] #121 Add unit test for spec change usecase --- pkg/application/blueprintSpecChangeUseCase.go | 4 +- .../blueprintSpecChangeUseCase_test.go | 755 +++++++++++++++++- 2 files changed, 755 insertions(+), 4 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 1c1d597d..aa35d7d9 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -6,8 +6,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) type BlueprintSpecChangeUseCase struct { @@ -24,7 +22,7 @@ type BlueprintSpecChangeUseCase struct { } func NewBlueprintSpecChangeUseCase( - repo domainservice.BlueprintSpecRepository, + repo blueprintSpecRepository, validation blueprintSpecValidationUseCase, effectiveBlueprint effectiveBlueprintUseCase, stateDiff stateDiffUseCase, diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 713403cf..7041bdbc 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -2,12 +2,765 @@ package application import ( "context" + "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "testing" ) var testCtx = context.Background() var testBlueprintId = "testBlueprint1" -func TestBlueprintSpecChangeUseCase_HandleChange(t *testing.T) { +func TestNewBlueprintSpecChangeUseCase(t *testing.T) { + // given + blueprintSpecRepositoryMock := newMockBlueprintSpecRepository(t) + validationUseCaseMock := newMockBlueprintSpecValidationUseCase(t) + effectiveUseCaseMock := newMockEffectiveBlueprintUseCase(t) + stateDiffUseCaseMock := newMockStateDiffUseCase(t) + completeUseCaseMock := newMockCompleteBlueprintUseCase(t) + ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) + selfUpgradeUseCaseMock := newMockSelfUpgradeUseCase(t) + applyComponentUseCaseMock := newMockApplyComponentsUseCase(t) + applyDoguUseCaseMock := newMockApplyDogusUseCase(t) + ecosystemHealthUseCaseMock := newMockEcosystemHealthUseCase(t) + // when + result := NewBlueprintSpecChangeUseCase( + blueprintSpecRepositoryMock, + validationUseCaseMock, + effectiveUseCaseMock, + stateDiffUseCaseMock, + completeUseCaseMock, + ecosystemConfigUseCaseMock, + selfUpgradeUseCaseMock, + applyComponentUseCaseMock, + applyDoguUseCaseMock, + ecosystemHealthUseCaseMock, + ) + + // then + require.NotNil(t, result) + assert.Equal(t, blueprintSpecRepositoryMock, result.repo) + assert.Equal(t, validationUseCaseMock, result.validation) + assert.Equal(t, effectiveUseCaseMock, result.effectiveBlueprint) + assert.Equal(t, stateDiffUseCaseMock, result.stateDiff) + assert.Equal(t, ecosystemConfigUseCaseMock, result.ecosystemConfigUseCase) + assert.Equal(t, selfUpgradeUseCaseMock, result.selfUpgradeUseCase) + assert.Equal(t, applyComponentUseCaseMock, result.applyComponentUseCase) + assert.Equal(t, applyDoguUseCaseMock, result.applyDogusUseCase) + assert.Equal(t, ecosystemHealthUseCaseMock, result.healthUseCase) + assert.Equal(t, completeUseCaseMock, result.applyUseCase) +} + +func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { + testBlueprintSpec := &domain.BlueprintSpec{ + Id: testBlueprintId, + StateDiff: domain.StateDiff{DoguDiffs: domain.DoguDiffs{{NeededActions: []domain.Action{domain.ActionInstall}}}}, + } + testDryRunBlueprintSpec := &domain.BlueprintSpec{ + Id: testBlueprintId, + Config: domain.BlueprintConfiguration{DryRun: true}, + } + + type fields struct { + repo func(t *testing.T) blueprintSpecRepository + validation func(t *testing.T) blueprintSpecValidationUseCase + effectiveBlueprint func(t *testing.T) effectiveBlueprintUseCase + stateDiff func(t *testing.T) stateDiffUseCase + applyUseCase func(t *testing.T) completeBlueprintUseCase + ecosystemConfigUseCase func(t *testing.T) ecosystemConfigUseCase + selfUpgradeUseCase func(t *testing.T) selfUpgradeUseCase + applyComponentUseCase func(t *testing.T) applyComponentsUseCase + applyDogusUseCase func(t *testing.T) applyDogusUseCase + healthUseCase func(t *testing.T) ecosystemHealthUseCase + } + type args struct { + givenCtx context.Context + blueprintId string + } + + testArgs := args{ + givenCtx: testCtx, + blueprintId: testBlueprintId, + } + + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + { + name: "should return error on error getting blueprint by id", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(nil, assert.AnError).Run(func(ctx context.Context, blueprintId string) { + logger, err := logr.FromContext(ctx) + require.NoError(t, err) + assert.NotNil(t, logger) + }) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.ErrorContains(t, err, "cannot load blueprint spec") + }, + }, + { + name: "should return error on error validating blueprint statically", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(assert.AnError) + + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error calculate effective blueprint", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error validating blueprint dynamically", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error determining state diff", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error checking ecosystem health", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return nil and do nothing on dry run", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testDryRunBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testDryRunBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testDryRunBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testDryRunBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testDryRunBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testDryRunBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + }, + args: testArgs, + wantErr: assert.NoError, + }, + { + name: "should return error on error handle self upgrade", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error apply config", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error apply components", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error check health after component apply", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError).Times(1) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(true, nil) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error apply dogus", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyDogusUseCase: func(t *testing.T) applyDogusUseCase { + m := newMockApplyDogusUseCase(t) + m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error check health after dogu apply", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyDogusUseCase: func(t *testing.T) applyDogusUseCase { + m := newMockApplyDogusUseCase(t) + m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(true, nil) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return error on error complete blueprint", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyDogusUseCase: func(t *testing.T) applyDogusUseCase { + m := newMockApplyDogusUseCase(t) + m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyUseCase: func(t *testing.T) completeBlueprintUseCase { + m := newMockCompleteBlueprintUseCase(t) + m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, + { + name: "should return nil on success", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyDogusUseCase: func(t *testing.T) applyDogusUseCase { + m := newMockApplyDogusUseCase(t) + m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyUseCase: func(t *testing.T) completeBlueprintUseCase { + m := newMockCompleteBlueprintUseCase(t) + m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + }, + args: testArgs, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var repo blueprintSpecRepository + if tt.fields.repo != nil { + repo = tt.fields.repo(t) + } + + var validation blueprintSpecValidationUseCase + if tt.fields.validation != nil { + validation = tt.fields.validation(t) + } + + var effectiveBlueprint effectiveBlueprintUseCase + if tt.fields.effectiveBlueprint != nil { + effectiveBlueprint = tt.fields.effectiveBlueprint(t) + } + + var stateDiff stateDiffUseCase + if tt.fields.stateDiff != nil { + stateDiff = tt.fields.stateDiff(t) + } + + var applyUseCase completeBlueprintUseCase + if tt.fields.applyUseCase != nil { + applyUseCase = tt.fields.applyUseCase(t) + } + + var ecoConfigUseCase ecosystemConfigUseCase + if tt.fields.ecosystemConfigUseCase != nil { + ecoConfigUseCase = tt.fields.ecosystemConfigUseCase(t) + } + + var selfUpgrade selfUpgradeUseCase + if tt.fields.selfUpgradeUseCase != nil { + selfUpgrade = tt.fields.selfUpgradeUseCase(t) + } + + var applyComponentUseCase applyComponentsUseCase + if tt.fields.applyComponentUseCase != nil { + applyComponentUseCase = tt.fields.applyComponentUseCase(t) + } + + var applyDoguUseCase applyDogusUseCase + if tt.fields.applyDogusUseCase != nil { + applyDoguUseCase = tt.fields.applyDogusUseCase(t) + } + + var ecoHealthUseCase ecosystemHealthUseCase + if tt.fields.healthUseCase != nil { + ecoHealthUseCase = tt.fields.healthUseCase(t) + } + + useCase := &BlueprintSpecChangeUseCase{ + repo: repo, + validation: validation, + effectiveBlueprint: effectiveBlueprint, + stateDiff: stateDiff, + applyUseCase: applyUseCase, + ecosystemConfigUseCase: ecoConfigUseCase, + selfUpgradeUseCase: selfUpgrade, + applyComponentUseCase: applyComponentUseCase, + applyDogusUseCase: applyDoguUseCase, + healthUseCase: ecoHealthUseCase, + } + tt.wantErr(t, useCase.HandleUntilApplied(tt.args.givenCtx, tt.args.blueprintId), fmt.Sprintf("HandleUntilApplied(%v, %v)", tt.args.givenCtx, tt.args.blueprintId)) + }) + } } From ba7e13d682b33829d0988876d27c4df2b807348b Mon Sep 17 00:00:00 2001 From: Marco Bergen Date: Fri, 12 Sep 2025 08:15:58 +0000 Subject: [PATCH 053/119] [#125] replace annotation dependency with validate capabilities --- k8s/helm/Chart.yaml | 1 - k8s/helm/templates/validate.yaml | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 k8s/helm/templates/validate.yaml diff --git a/k8s/helm/Chart.yaml b/k8s/helm/Chart.yaml index c0518d4b..d162a010 100644 --- a/k8s/helm/Chart.yaml +++ b/k8s/helm/Chart.yaml @@ -27,5 +27,4 @@ annotations: # TODO: need to update Dogu-CRD and blueprint-CRD dependencies "k8s.cloudogu.com/ces-dependency/k8s-blueprint-operator-crd": ">=1.3.0-0" "k8s.cloudogu.com/ces-dependency/k8s-dogu-operator-crd": ">=2.8.0-0, <3.0.0-0" - "k8s.cloudogu.com/ces-dependency/k8s-component-operator-crd": "1.x.x-0" "k8s.cloudogu.com/ces-dependency/k8s-service-discovery": ">=0.15.0-0" diff --git a/k8s/helm/templates/validate.yaml b/k8s/helm/templates/validate.yaml new file mode 100644 index 00000000..9626893a --- /dev/null +++ b/k8s/helm/templates/validate.yaml @@ -0,0 +1,3 @@ +{{- if not (.Capabilities.APIVersions.Has "k8s.cloudogu.com/v1/Component") -}} + {{- fail "CRD k8s.cloudogu.com/v1/Component is not installed." -}} +{{- end -}} \ No newline at end of file From c2506bf51d582720075e6df7fd8da4f2f25371cd Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 12 Sep 2025 12:11:57 +0200 Subject: [PATCH 054/119] #121 Fix tests and nil pointer problems --- .../v2/blueprintSpecCRRepository_test.go | 12 +- .../v2/serializer/componentDiff.go | 13 +- .../v2/serializer/componentDiff_test.go | 2 +- .../blueprintcr/v2/serializer/config.go | 4 +- .../blueprintcr/v2/serializer/config_test.go | 4 +- .../blueprintcr/v2/serializer/doguDiff.go | 119 ++++---- .../v2/serializer/doguDiff_test.go | 11 +- .../v2/serializer/sensitiveConfig_test.go | 2 +- .../v2/serializer/stateDiff_test.go | 62 ++++- .../dogucr/doguInstallationRepo_test.go | 8 +- .../kubernetes/dogucr/doguSerializer.go | 53 +++- .../kubernetes/dogucr/doguSerializer_test.go | 26 +- .../blueprintSpecValidationUseCase_test.go | 2 - .../doguInstallationUseCase_test.go | 24 +- pkg/application/stateDiffUseCase.go | 39 ++- pkg/application/stateDiffUseCase_test.go | 158 ++++------- pkg/domain/blueprint.go | 9 +- pkg/domain/blueprintSpec_test.go | 14 +- pkg/domain/blueprint_test.go | 2 +- pkg/domain/dogu.go | 8 +- pkg/domain/ecosystem/doguInstallation.go | 35 +-- pkg/domain/ecosystem/doguInstallation_test.go | 6 +- pkg/domain/ecosystem/volumeSize_test.go | 10 +- pkg/domain/effectiveBlueprint.go | 4 + pkg/domain/stateDiffComponent.go | 2 +- pkg/domain/stateDiffComponent_test.go | 12 +- pkg/domain/stateDiffConfig.go | 2 +- pkg/domain/stateDiffConfig_test.go | 262 ++++++++++-------- pkg/domain/stateDiffDogu.go | 68 ++++- pkg/domain/stateDiffDoguConfig.go | 26 +- pkg/domain/stateDiffDogu_test.go | 109 ++++---- pkg/domain/stateDiffGlobalConfig.go | 12 +- 32 files changed, 636 insertions(+), 484 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index cc8f3390..0c5549cf 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -164,11 +164,11 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { restClientMock := newMockBlueprintInterface(t) eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) - expectedStatus := bpv2.BlueprintStatus{ + expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: &bpv2.Config{}, + Config: nil, }, StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{testCondition}, @@ -236,11 +236,11 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { restClientMock := newMockBlueprintInterface(t) eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) - expectedStatus := bpv2.BlueprintStatus{ + expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: &bpv2.Config{}, + Config: nil, }, StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{}, @@ -279,11 +279,11 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { restClientMock := newMockBlueprintInterface(t) eventRecorderMock := newMockEventRecorder(t) repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) - expectedStatus := bpv2.BlueprintStatus{ + expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ Dogus: []bpv2.Dogu{}, Components: []bpv2.Component{}, - Config: &bpv2.Config{}, + Config: nil, }, StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, Conditions: []metav1.Condition{}, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go index 970ea423..65dc435d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go @@ -12,14 +12,15 @@ import ( ) func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDiff { - actualVersion := "" - expectedVersion := "" + var actualVersion, expectedVersion *string if domainModel.Actual.Version != nil { - actualVersion = domainModel.Actual.Version.String() + actualVersionString := domainModel.Actual.Version.String() + actualVersion = &actualVersionString } if domainModel.Expected.Version != nil { - expectedVersion = domainModel.Expected.Version.String() + expectedVersionString := domainModel.Expected.Version.String() + expectedVersion = &expectedVersionString } neededActions := domainModel.NeededActions @@ -31,13 +32,13 @@ func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDi return crd.ComponentDiff{ Actual: crd.ComponentDiffState{ Namespace: string(domainModel.Actual.Namespace), - Version: &actualVersion, + Version: actualVersion, Absent: domainModel.Actual.Absent, DeployConfig: crd.DeployConfig(domainModel.Actual.DeployConfig), }, Expected: crd.ComponentDiffState{ Namespace: string(domainModel.Expected.Namespace), - Version: &expectedVersion, + Version: expectedVersion, Absent: domainModel.Expected.Absent, DeployConfig: crd.DeployConfig(domainModel.Expected.DeployConfig), }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go index 7f2ef099..3baef6f8 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go @@ -50,7 +50,7 @@ func Test_convertToComponentDiffDTO(t *testing.T) { // then expected := crd.ComponentDiff{ Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw}, - Expected: crd.ComponentDiffState{Version: nil, Absent: true}, + Expected: crd.ComponentDiffState{Version: nil, Absent: false}, NeededActions: []crd.ComponentAction{domain.ActionUninstall}, } assert.Equal(t, expected, actual) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 8b21d68b..b1f10535 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -95,7 +95,7 @@ func convertToDoguConfigDTO(config domain.DoguConfig) []v2.ConfigEntry { } func convertToDoguConfigDomain(doguName string, config []v2.ConfigEntry) domain.DoguConfig { - if config == nil { + if config == nil || len(config) == 0 { return domain.DoguConfig{} } @@ -167,7 +167,7 @@ func convertToGlobalConfigDomain(config []v2.ConfigEntry) domain.GlobalConfig { absentIndex := 0 for _, configEntry := range config { - if configEntry.Absent != nil && *configEntry.Absent == false && configEntry.Value != nil { + if (configEntry.Absent == nil || !*configEntry.Absent) && configEntry.Value != nil { present[common.GlobalConfigKey(configEntry.Key)] = common.GlobalConfigValue(*configEntry.Value) } else { absent[absentIndex] = common.GlobalConfigKey(configEntry.Key) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go index 7dabd448..f0bd7333 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go @@ -80,7 +80,7 @@ func Test_convertToDoguConfigDomain(t *testing.T) { want: domain.DoguConfig{}, }, { - name: "nil config", + name: "empty config", args: args{ doguName: string(testDoguKey1.DoguName), config: []v2.ConfigEntry{}, @@ -202,7 +202,7 @@ func Test_convertToGlobalConfigDomain(t *testing.T) { }, }, { - name: "convert present", + name: "convert absent", config: []v2.ConfigEntry{ { Key: "test", diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index 32179158..36065240 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -20,40 +20,45 @@ func convertToDoguDiffDTO(domainModel domain.DoguDiff) crd.DoguDiff { } return crd.DoguDiff{ - Actual: crd.DoguDiffState{ - Namespace: string(domainModel.Actual.Namespace), - Version: &domainModel.Actual.Version.Raw, - Absent: domainModel.Actual.Absent, - ResourceConfig: &crd.ResourceConfig{ - MinVolumeSize: convertMinimumVolumeSizeToDTO(domainModel.Actual.MinVolumeSize), - }, - ReverseProxyConfig: &crd.ReverseProxyConfig{ - MaxBodySize: ecosystem.GetQuantityString(domainModel.Actual.ReverseProxyConfig.MaxBodySize), - RewriteTarget: domainModel.Actual.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: domainModel.Actual.ReverseProxyConfig.AdditionalConfig, - }, - AdditionalMounts: convertAdditionalMountsToDoguDiffDTO(domainModel.Actual.AdditionalMounts), - }, - Expected: crd.DoguDiffState{ - Namespace: string(domainModel.Expected.Namespace), - Version: &domainModel.Expected.Version.Raw, - Absent: domainModel.Expected.Absent, - ResourceConfig: &crd.ResourceConfig{ - MinVolumeSize: convertMinimumVolumeSizeToDTO(domainModel.Expected.MinVolumeSize), - }, - ReverseProxyConfig: &crd.ReverseProxyConfig{ - MaxBodySize: ecosystem.GetQuantityString(domainModel.Expected.ReverseProxyConfig.MaxBodySize), - RewriteTarget: domainModel.Expected.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: domainModel.Expected.ReverseProxyConfig.AdditionalConfig, - }, - AdditionalMounts: convertAdditionalMountsToDoguDiffDTO(domainModel.Expected.AdditionalMounts), - }, + Actual: convertToDoguDiffStateDTO(domainModel.Actual), + Expected: convertToDoguDiffStateDTO(domainModel.Expected), NeededActions: doguActions, } } +func convertToDoguDiffStateDTO(domainModel domain.DoguDiffState) crd.DoguDiffState { + var version *string + if domainModel.Version != nil { + version = &domainModel.Version.Raw + } + + var reverseProxyConfig *crd.ReverseProxyConfig + if domainModel.ReverseProxyConfig != nil { + reverseProxyConfig = &crd.ReverseProxyConfig{ + RewriteTarget: domainModel.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: domainModel.ReverseProxyConfig.AdditionalConfig, + MaxBodySize: ecosystem.GetQuantityString(domainModel.ReverseProxyConfig.MaxBodySize), + } + } + + var resourceConfig *crd.ResourceConfig + if domainModel.MinVolumeSize != nil { + resourceConfig = &crd.ResourceConfig{ + MinVolumeSize: ecosystem.GetQuantityString(domainModel.MinVolumeSize), + } + } + return crd.DoguDiffState{ + Namespace: string(domainModel.Namespace), + Version: version, + Absent: domainModel.Absent, + ResourceConfig: resourceConfig, + ReverseProxyConfig: reverseProxyConfig, + AdditionalMounts: convertAdditionalMountsToDoguDiffDTO(domainModel.AdditionalMounts), + } +} + func convertMinimumVolumeSizeToDTO(minVolSize *ecosystem.VolumeSize) *string { - if minVolSize.IsZero() { + if minVolSize == nil || minVolSize.IsZero() { return nil } else { s := minVolSize.String() @@ -112,46 +117,50 @@ func convertToDoguDiffDomain(doguName string, dto crd.DoguDiff) (domain.DoguDiff func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, error) { var errorList []error - var version core.Version - var versionErr error + var version *core.Version + var err error if dto.Version != nil && *dto.Version != "" { - version, versionErr = core.ParseVersion(*dto.Version) - if versionErr != nil { - versionErr = fmt.Errorf("failed to parse version %q: %w", *dto.Version, versionErr) + var coreVersion core.Version + coreVersion, err = core.ParseVersion(*dto.Version) + version = &coreVersion + if err != nil { + err = fmt.Errorf("failed to parse version %q: %w", *dto.Version, err) } } - errorList = append(errorList, versionErr) + errorList = append(errorList, err) var minVolumeSize, maxBodySize *resource.Quantity - var volumeSizeErr error if dto.ResourceConfig != nil && dto.ResourceConfig.MinVolumeSize != nil { minVolumeSizeStr := dto.ResourceConfig.MinVolumeSize - minVolumeSize, volumeSizeErr = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) - if volumeSizeErr != nil { - errorList = append(errorList, fmt.Errorf("failed to parse minimum volume size %q: %w", *minVolumeSizeStr, volumeSizeErr)) + minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) + if err != nil { + errorList = append(errorList, fmt.Errorf("failed to parse minimum volume size %q: %w", *minVolumeSizeStr, err)) } } - var bodySizeErr error - if dto.ReverseProxyConfig != nil && dto.ReverseProxyConfig.MaxBodySize != nil { - maxBodySizeStr := dto.ReverseProxyConfig.MaxBodySize - maxBodySize, bodySizeErr = ecosystem.GetQuantityReference(*maxBodySizeStr) - if bodySizeErr != nil { - errorList = append(errorList, fmt.Errorf("failed to parse maximum proxy body size %q: %w", *maxBodySizeStr, bodySizeErr)) + var reverseProxyConfig *ecosystem.ReverseProxyConfig + if dto.ReverseProxyConfig != nil { + if dto.ReverseProxyConfig.MaxBodySize != nil { + maxBodySizeStr := dto.ReverseProxyConfig.MaxBodySize + maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) + if err != nil { + errorList = append(errorList, fmt.Errorf("failed to parse maximum proxy body size %q: %w", *maxBodySizeStr, err)) + } } - } - - return domain.DoguDiffState{ - Namespace: cescommons.Namespace(dto.Namespace), - Version: &version, - Absent: dto.Absent, - MinVolumeSize: minVolumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + reverseProxyConfig = &ecosystem.ReverseProxyConfig{ MaxBodySize: maxBodySize, RewriteTarget: dto.ReverseProxyConfig.RewriteTarget, AdditionalConfig: dto.ReverseProxyConfig.AdditionalConfig, - }, - AdditionalMounts: convertAdditionalMountsToDoguDiffDomain(dto.AdditionalMounts), + } + } + + return domain.DoguDiffState{ + Namespace: cescommons.Namespace(dto.Namespace), + Version: version, + Absent: dto.Absent, + MinVolumeSize: minVolumeSize, + ReverseProxyConfig: reverseProxyConfig, + AdditionalMounts: convertAdditionalMountsToDoguDiffDomain(dto.AdditionalMounts), }, errors.Join(errorList...) } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index 01af15c6..43072c8d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -10,25 +10,26 @@ import ( func Test_convertMinimumVolumeSizeToDTO(t *testing.T) { volumeSize1g := resource.MustParse("1Gi") + val1Gi := "1Gi" tests := []struct { name string minVolSize *ecosystem.VolumeSize - want string + want *string }{ { name: "nil", minVolSize: nil, - want: "", + want: nil, }, { name: "empty", minVolSize: &ecosystem.VolumeSize{}, - want: "", + want: nil, }, { - name: "empty", + name: "1Gi", minVolSize: &volumeSize1g, - want: "1Gi", + want: &val1Gi, }, } for _, tt := range tests { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go index 48635af4..6e003211 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go @@ -123,7 +123,7 @@ func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { }, }, { - name: "convert present config", + name: "convert absent config", args: args{ doguName: string(testDoguKey1.DoguName), doguConfig: []v2.ConfigEntry{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index a90040b9..523e0d35 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -13,6 +13,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + googlecmp "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" ) @@ -41,7 +42,7 @@ func TestConvertToDTO(t *testing.T) { tests := []struct { name string domainModel domain.StateDiff - want crd.StateDiff + want *crd.StateDiff }{ { name: "should convert single dogu diff", @@ -59,17 +60,17 @@ func TestConvertToDTO(t *testing.T) { }, NeededActions: []domain.Action{domain.ActionUpgrade}, }}}, - want: crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ + want: &crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ Namespace: "official", Version: &testCoreVersionLowStr, - Absent: true, + Absent: false, }, Expected: crd.DoguDiffState{ Namespace: "official", Version: &testCoreVersionHighStr, - Absent: true, + Absent: false, }, NeededActions: []crd.DoguAction{"upgrade"}, }, @@ -105,7 +106,7 @@ func TestConvertToDTO(t *testing.T) { NeededActions: []domain.Action{domain.ActionUninstall}, }, }}, - want: crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ + want: &crd.StateDiff{DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ Namespace: "official", @@ -150,7 +151,7 @@ func TestConvertToDTO(t *testing.T) { NeededActions: []domain.Action{domain.ActionUninstall}, }, }}, - want: crd.StateDiff{ + want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{ testComponentName: { @@ -177,7 +178,7 @@ func TestConvertToDTO(t *testing.T) { "postfix": {}, }, }, - want: crd.StateDiff{ + want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, DoguConfigDiffs: map[string]crd.ConfigDiff{ @@ -202,7 +203,7 @@ func TestConvertToDTO(t *testing.T) { NeededAction: domain.ConfigActionSet, }}, }, - want: crd.StateDiff{ + want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, GlobalConfigDiff: []crd.ConfigEntryDiff{{ @@ -251,7 +252,7 @@ func TestConvertToDTO(t *testing.T) { }, NeededActions: []domain.Action{domain.ActionUpgrade}, }}}, - want: crd.StateDiff{ + want: &crd.StateDiff{ ComponentDiffs: map[string]crd.ComponentDiff{}, DoguDiffs: map[string]crd.DoguDiff{ "ldap": { @@ -291,7 +292,7 @@ func TestConvertToDTO(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if got := ConvertToStateDiffDTO(tt.domainModel); !reflect.DeepEqual(got, tt.want) { t.Errorf("ConvertToStateDiffDTO() = %v, want %v", got, tt.want) - assert.Equal(t, tt.want, got) + assert.Empty(t, googlecmp.Diff(tt.want, got)) } }) } @@ -421,7 +422,7 @@ func TestConvertToDomainModel(t *testing.T) { }, want: domain.StateDiff{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse installation state \"invalid\"") && + return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") }, }, @@ -742,7 +743,7 @@ func TestConvertToStateDiffDTO(t *testing.T) { tests := []struct { name string model domain.StateDiff - want crd.StateDiff + want *crd.StateDiff }{ { name: "normal dogu config", @@ -781,7 +782,7 @@ func TestConvertToStateDiffDTO(t *testing.T) { }, GlobalConfigDiffs: nil, }, - want: crd.StateDiff{ + want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, DoguConfigDiffs: map[string]crd.ConfigDiff{ @@ -855,6 +856,41 @@ func TestConvertToStateDiffDTO(t *testing.T) { }, GlobalConfigDiffs: nil, }, + want: &crd.StateDiff{ + DoguDiffs: map[string]crd.DoguDiff{}, + ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguConfigDiffs: map[string]crd.ConfigDiff{ + testDogu.String(): { + crd.ConfigEntryDiff{ + Key: testDoguKey1.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + Expected: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), + }, + }, + testDogu2.String(): { + crd.ConfigEntryDiff{ + Key: testDoguKey2.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), + }, + }, + }, + GlobalConfigDiff: nil, + }, }, } for _, tt := range tests { diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index 645e97df..9d3f0670 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -78,8 +78,8 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{}, PersistenceContext: persistenceContext, - MinVolumeSize: quantity2, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + MinVolumeSize: &quantity2, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -219,13 +219,13 @@ func Test_doguInstallationRepo_GetAll(t *testing.T) { "postgresql": { Name: postgresDoguName, Version: version1231, - MinVolumeSize: volumeQuantity2, + MinVolumeSize: &volumeQuantity2, PersistenceContext: map[string]interface{}{"doguInstallationRepoContext": doguInstallationRepoContext{resourceVersion: ""}}, }, "ldap": { Name: ldapDoguName, Version: version3213, - MinVolumeSize: volumeQuantity3, + MinVolumeSize: &volumeQuantity3, PersistenceContext: map[string]interface{}{"doguInstallationRepoContext": doguInstallationRepoContext{resourceVersion: ""}}, }, } diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index 71c08471..114a157b 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -46,13 +46,14 @@ func parseDoguCR(cr *v2.Dogu) (*ecosystem.DoguInstallation, error) { persistenceContext[doguInstallationRepoContextKey] = doguInstallationRepoContext{ resourceVersion: cr.GetResourceVersion(), } + return &ecosystem.DoguInstallation{ Name: doguName, Version: version, Status: cr.Status.Status, Health: ecosystem.HealthStatus(cr.Status.Health), UpgradeConfig: ecosystem.UpgradeConfig{AllowNamespaceSwitch: cr.Spec.UpgradeConfig.AllowNamespaceSwitch}, - MinVolumeSize: minVolumeSize, + MinVolumeSize: &minVolumeSize, ReverseProxyConfig: reverseProxyConfigEntries, PersistenceContext: persistenceContext, AdditionalMounts: parseAdditionalMounts(cr.Spec.AdditionalMounts), @@ -72,31 +73,48 @@ func parseAdditionalMounts(mounts []v2.DataMount) []ecosystem.AdditionalMount { return result } -func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) (ecosystem.ReverseProxyConfig, error) { - reverseProxyConfig := ecosystem.ReverseProxyConfig{} +func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) (*ecosystem.ReverseProxyConfig, error) { + reverseProxyConfig := &ecosystem.ReverseProxyConfig{} - reverseProxyBodySize, ok := annotations[ecosystem.NginxIngressAnnotationBodySize] - if ok { + reverseProxyBodySize, bodySizeOk := annotations[ecosystem.NginxIngressAnnotationBodySize] + if bodySizeOk { // Sizes for Nginx can be specified in bytes, kilobytes (suffixes k and K) or megabytes (suffixes m and M), for example, “1024”, “8k”, “1m” in Decimal SI. // Since the actual dogu-operator and service-discovery just use this format we can expect that the values for the volume size in are safe to set in the doguinstallation. // Formats “1024”, “8k”, “1m” can be parsed by resource.Quantity // See: [Documentation](https://nginx.org/en/docs/syntax.html) quantity, err := resource.ParseQuantity(reverseProxyBodySize) if err != nil { - return ecosystem.ReverseProxyConfig{}, domainservice.NewInternalError(err, "failed to parse quantity %q", reverseProxyBodySize) + return nil, domainservice.NewInternalError(err, "failed to parse quantity %q", reverseProxyBodySize) } reverseProxyConfig.MaxBodySize = &quantity } - rewriteTarget := annotations[ecosystem.NginxIngressAnnotationRewriteTarget] - reverseProxyConfig.RewriteTarget = &rewriteTarget - additionalConfig := annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] - reverseProxyConfig.AdditionalConfig = &additionalConfig + var rewriteTarget, additionalConfig *string + if annotations[ecosystem.NginxIngressAnnotationRewriteTarget] != "" { + rewriteTargetString := annotations[ecosystem.NginxIngressAnnotationRewriteTarget] + rewriteTarget = &rewriteTargetString + reverseProxyConfig.RewriteTarget = rewriteTarget + } + + if annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] != "" { + additionalConfigString := annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] + additionalConfig = &additionalConfigString + reverseProxyConfig.AdditionalConfig = additionalConfig + } + + if reverseProxyConfig.IsEmpty() { + return nil, nil + } return reverseProxyConfig, nil } func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { + var minVolumeSize = ecosystem.VolumeSize{} + if dogu.MinVolumeSize != nil { + minVolumeSize = *dogu.MinVolumeSize + } + return &v2.Dogu{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -119,7 +137,7 @@ func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { // always set MinDataVolumeSize instead of the deprecated DataVolumeSize // the dogu-operator has a default of 2GiB if this field is 0 or not set // we just always set this value, if a new dogu CR is created via blueprint - MinDataVolumeSize: dogu.MinVolumeSize, + MinDataVolumeSize: minVolumeSize, }, SupportMode: false, UpgradeConfig: v2.UpgradeConfig{ @@ -150,7 +168,11 @@ func toDoguCRAdditionalMounts(mounts []ecosystem.AdditionalMount) []v2.DataMount return result } -func getNginxIngressAnnotations(config ecosystem.ReverseProxyConfig) map[string]string { +func getNginxIngressAnnotations(config *ecosystem.ReverseProxyConfig) map[string]string { + if config == nil { + return nil + } + annotations := v2.IngressAnnotations{} maxBodySize := config.MaxBodySize if maxBodySize != nil { @@ -205,6 +227,11 @@ type doguResourcesPatch struct { } func toDoguCRPatch(dogu *ecosystem.DoguInstallation) *doguCRPatch { + var minVolumeSize = ecosystem.VolumeSize{} + if dogu.MinVolumeSize != nil { + minVolumeSize = *dogu.MinVolumeSize + } + return &doguCRPatch{ Spec: doguSpecPatch{ Name: dogu.Name.String(), @@ -214,7 +241,7 @@ func toDoguCRPatch(dogu *ecosystem.DoguInstallation) *doguCRPatch { // the dogu-operator has a default of 2Gi if this field is 0 or not set // we just always set this value, if a new dogu CR is created via blueprint DataVolumeSize: "", - MinDataVolumeSize: dogu.MinVolumeSize, + MinDataVolumeSize: minVolumeSize, }, AdditionalIngressAnnotations: getNginxIngressAnnotations(dogu.ReverseProxyConfig), // always set this to false as a dogu cannot start in support mode diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index e7e2bb89..29a96da4 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -25,7 +25,7 @@ var ( SimpleName: cescommons.SimpleName("postgresql"), } subfolder = "subfolder" - subfolder2 = "subfolder2" + subfolder2 = "secsubfolder" rewriteTarget = "/" additionalConfig = "additional" ) @@ -75,7 +75,7 @@ func Test_parseDoguCR(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, }, - MinVolumeSize: defaultVolSize, + MinVolumeSize: &defaultVolSize, PersistenceContext: persistenceContext, }, wantErr: false, @@ -136,7 +136,7 @@ func Test_parseDoguCR(t *testing.T) { Name: postgresDoguName, Version: version3214, PersistenceContext: persistenceContext, - MinVolumeSize: defaultVolSize, + MinVolumeSize: &defaultVolSize, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -174,7 +174,7 @@ func Test_parseDoguCR(t *testing.T) { Name: postgresDoguName, Version: version3214, PersistenceContext: persistenceContext, - MinVolumeSize: volSize25G, + MinVolumeSize: &volSize25G, }, wantErr: false, }, @@ -199,7 +199,7 @@ func Test_parseDoguCR(t *testing.T) { Name: postgresDoguName, Version: version3214, PersistenceContext: persistenceContext, - MinVolumeSize: volSize25G, + MinVolumeSize: &volSize25G, }, wantErr: false, }, @@ -223,7 +223,7 @@ func Test_parseDoguCR(t *testing.T) { Name: postgresDoguName, Version: version3214, PersistenceContext: persistenceContext, - MinVolumeSize: volSize25G, + MinVolumeSize: &volSize25G, }, wantErr: false, }, @@ -355,7 +355,7 @@ func Test_toDoguCR(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, }, - MinVolumeSize: volSize25G, + MinVolumeSize: &volSize25G, }, want: &v2.Dogu{ TypeMeta: metav1.TypeMeta{}, @@ -453,7 +453,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, }, - MinVolumeSize: quantity2, + MinVolumeSize: &quantity2, AdditionalMounts: []ecosystem.AdditionalMount{ {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, @@ -513,7 +513,7 @@ func Test_getNginxIngressAnnotations(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, getNginxIngressAnnotations(tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) + assert.Equalf(t, tt.want, getNginxIngressAnnotations(&tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) }) } } @@ -526,7 +526,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { tests := []struct { name string args args - want ecosystem.ReverseProxyConfig + want *ecosystem.ReverseProxyConfig wantErr assert.ErrorAssertionFunc }{ { @@ -538,7 +538,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { "nginx.ingress.kubernetes.io/configuration-snippet": "additional", }, }, - want: ecosystem.ReverseProxyConfig{ + want: &ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -554,7 +554,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { "nginx.ingress.kubernetes.io/proxy-body-size": "1GG", }, }, - want: ecosystem.ReverseProxyConfig{}, + want: &ecosystem.ReverseProxyConfig{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { assert.Error(t, err) assert.ErrorContains(t, err, "failed to parse quantity \"1GG\"") @@ -599,7 +599,7 @@ func Test_getNginxIngressAnnotations1(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, getNginxIngressAnnotations(tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) + assert.Equalf(t, tt.want, getNginxIngressAnnotations(&tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) }) } } diff --git a/pkg/application/blueprintSpecValidationUseCase_test.go b/pkg/application/blueprintSpecValidationUseCase_test.go index d6837785..759f37fb 100644 --- a/pkg/application/blueprintSpecValidationUseCase_test.go +++ b/pkg/application/blueprintSpecValidationUseCase_test.go @@ -64,8 +64,6 @@ func TestBlueprintSpecUseCase_ValidateBlueprintSpecStatically_invalid(t *testing err := useCase.ValidateBlueprintSpecStatically(ctx, blueprint) //then - assert.Nil(t, blueprint.Conditions, "should not set conditions") - require.Error(t, err) var invalidError *domain.InvalidBlueprintError assert.ErrorAs(t, err, &invalidError, "error should be an InvalidBlueprintError") diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 96d2a133..9cb710f8 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -208,12 +208,12 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedVolumeSize := resource.MustParse("3Gi") expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - MinVolumeSize: expectedVolumeSize, + MinVolumeSize: &expectedVolumeSize, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - MinVolumeSize: volumeSize, + MinVolumeSize: &volumeSize, } doguRepoMock := newMockDoguInstallationRepository(t) @@ -244,14 +244,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedProxyBodySize := resource.MustParse("3G") expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, }, } @@ -285,14 +285,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedTarget := &rewriteTarget expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ RewriteTarget: expectedTarget, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ RewriteTarget: nil, }, } @@ -326,14 +326,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedAdditionalConfig := &additionalConfig expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ AdditionalConfig: expectedAdditionalConfig, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ AdditionalConfig: nil, }, } @@ -443,8 +443,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, Version: version3212, - MinVolumeSize: expectedVolumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + MinVolumeSize: &expectedVolumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, RewriteTarget: expectedTarget, AdditionalConfig: expectedAdditionalConfig, @@ -454,8 +454,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, Version: version3211, - MinVolumeSize: volumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: nil, AdditionalConfig: nil, diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index d927d112..e4e8c2b9 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -5,9 +5,12 @@ import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" + "github.com/cloudogu/k8s-registry-lib/config" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -55,17 +58,21 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri logger.V(2).Info("load referenced sensitive config") // load referenced config before collecting ecosystem state // if an error happens here, we save a lot of heavy work - referencedSensitiveConfig, err := useCase.sensitiveConfigRefReader.GetValues( - ctx, blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), - ) - if err != nil { - err = fmt.Errorf("%s: %w", REFERENCED_CONFIG_NOT_FOUND, err) - blueprint.MissingConfigReferences(err) - updateError := useCase.blueprintSpecRepo.Update(ctx, blueprint) - if updateError != nil { - return errors.Join(updateError, err) + var referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue + var err error + if blueprint.EffectiveBlueprint.Config != nil { + referencedSensitiveConfig, err = useCase.sensitiveConfigRefReader.GetValues( + ctx, blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), + ) + if err != nil { + err = fmt.Errorf("%s: %w", REFERENCED_CONFIG_NOT_FOUND, err) + blueprint.MissingConfigReferences(err) + updateError := useCase.blueprintSpecRepo.Update(ctx, blueprint) + if updateError != nil { + return errors.Join(updateError, err) + } + return err } - return err } logger.V(2).Info("collect ecosystem state for state diff") @@ -106,11 +113,15 @@ func (useCase *StateDiffUseCase) collectEcosystemState(ctx context.Context, effe logger.V(2).Info("collect needed global config") globalConfig, globalConfigErr := useCase.globalConfigRepo.Get(ctx) - logger.V(2).Info("collect needed dogu config") - configByDogu, doguConfigErr := useCase.doguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedConfig()) + var configByDogu, sensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig + var doguConfigErr, sensitiveConfigErr error + if effectiveBlueprint.Config != nil { + logger.V(2).Info("collect needed dogu config") + configByDogu, doguConfigErr = useCase.doguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedConfig()) - logger.V(2).Info("collect needed sensitive dogu config") - sensitiveConfigByDogu, sensitiveConfigErr := useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) + logger.V(2).Info("collect needed sensitive dogu config") + sensitiveConfigByDogu, sensitiveConfigErr = useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) + } joinedError := errors.Join(doguErr, componentErr, globalConfigErr, doguConfigErr, sensitiveConfigErr) if joinedError != nil { diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 7724dffb..bd8a27b6 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -51,6 +51,7 @@ var ( nginxStaticSensitiveConfigKeyNginxKey2 = nginxStaticConfigKeyNginxKey2 val1 = "val1" val2 = "val2" + val3 = "val3" ) func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { @@ -67,16 +68,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -101,16 +94,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -135,16 +119,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, domainservice.NewInternalError(assert.AnError, "internal error")) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -159,45 +134,12 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should fail to get dogu config", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - - doguInstallRepoMock := newMockDoguInstallationRepository(t) - doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - - globalConfigRepoMock := newMockGlobalConfigRepository(t) - entries, _ := config.MapToEntries(map[string]any{}) - globalConfig := config.CreateGlobalConfig(entries) - globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT(). - GetAllExisting(testCtx, nilDoguNameList). - Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) - - // when - err := sut.DetermineStateDiff(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - var internalError *domainservice.InternalError - assert.ErrorAs(t, err, &internalError) - assert.ErrorContains(t, err, "could not determine state diff") - assert.ErrorContains(t, err, "could not collect ecosystem state") - }) - t.Run("should fail to get sensitive dogu config", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} + blueprint := &domain.BlueprintSpec{ + Id: "testBlueprint1", + EffectiveBlueprint: domain.EffectiveBlueprint{ + Config: &domain.Config{}, + }, + } doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) @@ -210,11 +152,11 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) sensitiveDoguConfigRepoMock.EXPECT(). GetAllExisting(testCtx, nilDoguNameList). - Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) + Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) configRefReaderMock := newMockSensitiveConfigRefReader(t) configRefReaderMock.EXPECT(). GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). @@ -233,6 +175,44 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { assert.ErrorContains(t, err, "could not determine state diff") assert.ErrorContains(t, err, "could not collect ecosystem state") }) + //t.Run("should fail to get sensitive dogu config", func(t *testing.T) { + // // given + // blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} + // + // doguInstallRepoMock := newMockDoguInstallationRepository(t) + // doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) + // componentInstallRepoMock := newMockComponentInstallationRepository(t) + // componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) + // + // globalConfigRepoMock := newMockGlobalConfigRepository(t) + // entries, _ := config.MapToEntries(map[string]any{}) + // globalConfig := config.CreateGlobalConfig(entries) + // globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + // + // doguConfigRepoMock := newMockDoguConfigRepository(t) + // doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + // sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + // sensitiveDoguConfigRepoMock.EXPECT(). + // GetAllExisting(testCtx, nilDoguNameList). + // Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) + // configRefReaderMock := newMockSensitiveConfigRefReader(t) + // configRefReaderMock.EXPECT(). + // GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + // Return(map[common.DoguConfigKey]config.Value{}, nil) + // + // sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + // + // // when + // err := sut.DetermineStateDiff(testCtx, blueprint) + // + // // then + // require.Error(t, err) + // assert.ErrorIs(t, err, assert.AnError) + // var internalError *domainservice.InternalError + // assert.ErrorAs(t, err, &internalError) + // assert.ErrorContains(t, err, "could not determine state diff") + // assert.ErrorContains(t, err, "could not collect ecosystem state") + //}) // TODO: Instead we should have a test with a forbidden diff action t.Run("should fail to update blueprint", func(t *testing.T) { // given @@ -254,16 +234,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -322,16 +293,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -402,7 +364,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Config: &domain.Config{ Global: domain.GlobalConfig{ Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "globalKey1": "globalValue", + "globalKey1": common.GlobalConfigValue(val1), }, Absent: []common.GlobalConfigKey{ "globalKey2", @@ -473,7 +435,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { DoguName: nginxStaticQualifiedDoguName.SimpleName, Config: domain.DoguConfig{ Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", + nginxStaticConfigKeyNginxKey1: common.DoguConfigValue(val3), }, Absent: []common.DoguConfigKey{ nginxStaticConfigKeyNginxKey2, @@ -505,7 +467,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguConfigRepoMock := newMockDoguConfigRepository(t) doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ "nginxKey1": val1, - "nginxKey2": "val2", + "nginxKey2": val2, }) require.NoError(t, entryErr) doguConfigRepoMock.EXPECT(). @@ -536,7 +498,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { domain.DoguConfigEntryDiff{ Key: nginxStaticConfigKeyNginxKey1, Actual: domain.DoguConfigValueState{Value: &val1, Exists: true}, - Expected: domain.DoguConfigValueState{Value: &val2, Exists: true}, + Expected: domain.DoguConfigValueState{Value: &val3, Exists: true}, NeededAction: domain.ConfigActionSet, }, domain.DoguConfigEntryDiff{ @@ -564,7 +526,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { nginxStaticSensitiveConfigKeyNginxKey1: { SecretName: "nginx-conf", SecretKey: "nginxKey1", - }, //"nginxVal1" + }, // val3 }, Absent: []common.SensitiveDoguConfigKey{ nginxStaticSensitiveConfigKeyNginxKey2, @@ -598,7 +560,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) doguConfigEntries, entryErr := config.MapToEntries(map[string]any{ "nginxKey1": val1, - "nginxKey2": "val2", + "nginxKey2": val2, }) require.NoError(t, entryErr) sensitiveDoguConfigRepoMock.EXPECT(). @@ -615,7 +577,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprint.EffectiveBlueprint.Config.Dogus[nginxStatic].SensitiveConfig.Present, ). Return(map[common.DoguConfigKey]config.Value{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", + nginxStaticConfigKeyNginxKey1: config.Value(val3), }, nil) sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) @@ -631,7 +593,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { { Key: nginxStaticSensitiveConfigKeyNginxKey1, Actual: domain.DoguConfigValueState{Value: &val1, Exists: true}, - Expected: domain.DoguConfigValueState{Value: &val2, Exists: true}, + Expected: domain.DoguConfigValueState{Value: &val3, Exists: true}, NeededAction: domain.ConfigActionSet, }, { diff --git a/pkg/domain/blueprint.go b/pkg/domain/blueprint.go index 54bbc088..5aea5c59 100644 --- a/pkg/domain/blueprint.go +++ b/pkg/domain/blueprint.go @@ -33,7 +33,7 @@ func (blueprint *Blueprint) Validate() error { blueprint.validateDoguUniqueness(), blueprint.validateComponents(), blueprint.validateComponentUniqueness(), - blueprint.Config.validate(), + blueprint.validateConfig(), } err := errors.Join(errorList...) @@ -72,3 +72,10 @@ func (blueprint *Blueprint) validateComponentUniqueness() error { } return nil } + +func (blueprint *Blueprint) validateConfig() error { + if blueprint.Config == nil { + return nil + } + return blueprint.Config.validate() +} diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index b515e96e..cb2e1b61 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -303,10 +303,8 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then stateDiff := StateDiff{ - DoguDiffs: DoguDiffs{}, - ComponentDiffs: ComponentDiffs{}, - DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, + DoguDiffs: DoguDiffs{}, + ComponentDiffs: ComponentDiffs{}, } assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) @@ -315,12 +313,8 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) assert.Equal(t, newStateDiffComponentEvent(stateDiff.ComponentDiffs), spec.Events[1]) assert.Equal(t, GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: GlobalConfigDiffs(nil)}, spec.Events[2]) - assert.Equal(t, DoguConfigDiffDeterminedEvent{ - DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{}, - }, spec.Events[3]) - assert.Equal(t, SensitiveDoguConfigDiffDeterminedEvent{ - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, - }, spec.Events[4]) + assert.Equal(t, DoguConfigDiffDeterminedEvent{}, spec.Events[3]) + assert.Equal(t, SensitiveDoguConfigDiffDeterminedEvent{}, spec.Events[4]) assert.Equal(t, stateDiff, spec.StateDiff) }) diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index 0141652c..fcf8f6ca 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -107,7 +107,7 @@ func Test_validateDogus_multipleErrors(t *testing.T) { err := blueprint.validateDogus() require.Error(t, err) - assert.Contains(t, err.Error(), "dogu target state is invalid") + assert.Contains(t, err.Error(), "dogu proxy body size is not in Decimal SI (\"M\" or \"G\")") assert.Contains(t, err.Error(), "dogu version must not be empty") } diff --git a/pkg/domain/dogu.go b/pkg/domain/dogu.go index 8186a695..4e7f244f 100644 --- a/pkg/domain/dogu.go +++ b/pkg/domain/dogu.go @@ -41,9 +41,11 @@ func (dogu Dogu) validate() error { // minVolumeSize is already checked while unmarshalling json/yaml // Nginx only supports quantities in Decimal SI. This check can be removed if the dogu-operator implements an abstraction for the body size. - maxBodySize := dogu.ReverseProxyConfig.MaxBodySize - if maxBodySize != nil && !maxBodySize.IsZero() && maxBodySize.Format != resource.DecimalSI { - errorList = append(errorList, fmt.Errorf("dogu proxy body size is not in Decimal SI (\"M\" or \"G\"): %s", dogu.Name)) + if dogu.ReverseProxyConfig != nil { + maxBodySize := dogu.ReverseProxyConfig.MaxBodySize + if maxBodySize != nil && !maxBodySize.IsZero() && maxBodySize.Format != resource.DecimalSI { + errorList = append(errorList, fmt.Errorf("dogu proxy body size is not in Decimal SI (\"M\" or \"G\"): %s", dogu.Name)) + } } for _, mount := range dogu.AdditionalMounts { diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 7ce593c8..7803679e 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -25,9 +25,9 @@ type DoguInstallation struct { PersistenceContext map[string]interface{} // MinVolumeSize is the minimum storage of the dogu. This field is optional and can be nil to indicate that no // storage is needed. - MinVolumeSize VolumeSize + MinVolumeSize *VolumeSize // ReverseProxyConfig defines configuration for the ecosystem reverse proxy. This field is optional. - ReverseProxyConfig ReverseProxyConfig + ReverseProxyConfig *ReverseProxyConfig // AdditionalMounts provides the possibility to mount additional data into the dogu. AdditionalMounts []AdditionalMount } @@ -56,6 +56,10 @@ type ReverseProxyConfig struct { AdditionalConfig AdditionalConfig } +func (r *ReverseProxyConfig) IsEmpty() bool { + return r == nil || (r.MaxBodySize == nil && r.RewriteTarget == nil && r.AdditionalConfig == nil) +} + // UpgradeConfig contains configuration hints regarding aspects during the upgrade of dogus. type UpgradeConfig struct { // AllowNamespaceSwitch lets a dogu switch its dogu namespace during an upgrade. The dogu must be technically the @@ -102,21 +106,12 @@ func InstallDogu( doguVersion = *version } - doguVolumeSize := VolumeSize{} - if minVolumeSize != nil { - doguVolumeSize = *minVolumeSize - } - - doguReverseProxyConfig := ReverseProxyConfig{} - if reverseProxyConfig != nil { - doguReverseProxyConfig = *reverseProxyConfig - } return &DoguInstallation{ Name: name, Version: doguVersion, UpgradeConfig: UpgradeConfig{AllowNamespaceSwitch: false}, - MinVolumeSize: doguVolumeSize, - ReverseProxyConfig: doguReverseProxyConfig, + MinVolumeSize: minVolumeSize, + ReverseProxyConfig: reverseProxyConfig, AdditionalMounts: additionalMounts, } } @@ -144,21 +139,27 @@ func (dogu *DoguInstallation) SwitchNamespace(newNamespace cescommons.Namespace, } func (dogu *DoguInstallation) UpdateProxyBodySize(value *BodySize) { + if dogu.ReverseProxyConfig == nil { + dogu.ReverseProxyConfig = &ReverseProxyConfig{} + } dogu.ReverseProxyConfig.MaxBodySize = value } func (dogu *DoguInstallation) UpdateMinVolumeSize(size *VolumeSize) { - dogu.MinVolumeSize = VolumeSize{} - if size != nil { - dogu.MinVolumeSize = *size - } + dogu.MinVolumeSize = size } func (dogu *DoguInstallation) UpdateProxyRewriteTarget(value RewriteTarget) { + if dogu.ReverseProxyConfig == nil { + dogu.ReverseProxyConfig = &ReverseProxyConfig{} + } dogu.ReverseProxyConfig.RewriteTarget = value } func (dogu *DoguInstallation) UpdateProxyAdditionalConfig(value AdditionalConfig) { + if dogu.ReverseProxyConfig == nil { + dogu.ReverseProxyConfig = &ReverseProxyConfig{} + } dogu.ReverseProxyConfig.AdditionalConfig = value } diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index ec611a3e..ab8e0fda 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -39,8 +39,8 @@ func TestInstallDogu(t *testing.T) { Name: postgresqlQualifiedName, Version: version1231, UpgradeConfig: UpgradeConfig{AllowNamespaceSwitch: false}, - MinVolumeSize: volumeSize, - ReverseProxyConfig: ReverseProxyConfig{ + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -175,6 +175,6 @@ func TestDoguInstallation_UpdateMinVolumeSize(t *testing.T) { dogu.UpdateMinVolumeSize(&volumeSize) // then - assert.Equal(t, volumeSize, dogu.MinVolumeSize) + assert.Equal(t, &volumeSize, dogu.MinVolumeSize) }) } diff --git a/pkg/domain/ecosystem/volumeSize_test.go b/pkg/domain/ecosystem/volumeSize_test.go index aa676f1c..92957a89 100644 --- a/pkg/domain/ecosystem/volumeSize_test.go +++ b/pkg/domain/ecosystem/volumeSize_test.go @@ -2,9 +2,10 @@ package ecosystem import ( "fmt" + "testing" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/api/resource" - "testing" ) func TestGetQuantityReference(t *testing.T) { @@ -44,23 +45,24 @@ func TestGetQuantityReference(t *testing.T) { func TestGetQuantityString(t *testing.T) { twoGigaByte := resource.MustParse("2G") + twoGigaByteString := twoGigaByte.String() type args struct { quantity *resource.Quantity } tests := []struct { name string args args - want string + want *string }{ { name: "should return string if reference is not nil", args: args{quantity: &twoGigaByte}, - want: "2G", + want: &twoGigaByteString, }, { name: "should return empty string if reference is nil", args: args{quantity: nil}, - want: "", + want: nil, }, } for _, tt := range tests { diff --git a/pkg/domain/effectiveBlueprint.go b/pkg/domain/effectiveBlueprint.go index 1a8f7aac..5f650425 100644 --- a/pkg/domain/effectiveBlueprint.go +++ b/pkg/domain/effectiveBlueprint.go @@ -35,6 +35,10 @@ func (effectiveBlueprint *EffectiveBlueprint) GetWantedDogus() []Dogu { // validateOnlyConfigForDogusInBlueprint checks that there is only config for dogus to install in the blueprint func (effectiveBlueprint *EffectiveBlueprint) validateOnlyConfigForDogusInBlueprint() error { + if effectiveBlueprint.Config == nil { + return nil + } + wantedDogus := util.Map(effectiveBlueprint.GetWantedDogus(), func(dogu Dogu) cescommons.SimpleName { return dogu.Name.SimpleName }) diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go index 0b077d09..9e2235e3 100644 --- a/pkg/domain/stateDiffComponent.go +++ b/pkg/domain/stateDiffComponent.go @@ -89,7 +89,7 @@ func (diff *ComponentDiff) String() string { // String returns a string representation of the ComponentDiffState. func (diff *ComponentDiffState) String() string { return fmt.Sprintf( - "{Namespace: %q, Version: %q, InstallationState: %t}", + "{Namespace: %q, Version: %q, Absent: %t}", diff.Namespace, diff.getSafeVersionString(), diff.Absent, diff --git a/pkg/domain/stateDiffComponent_test.go b/pkg/domain/stateDiffComponent_test.go index af9266fe..350ae55d 100644 --- a/pkg/domain/stateDiffComponent_test.go +++ b/pkg/domain/stateDiffComponent_test.go @@ -181,8 +181,8 @@ func TestComponentDiff_String(t *testing.T) { assert.Equal(t, "{"+ "Name: \"my-component\", "+ - "Actual: {Namespace: \"\", Version: \"3.2.1-1\", InstallationState: \"present\"}, "+ - "Expected: {Namespace: \"\", Version: \"3.2.1-2\", InstallationState: \"present\"}, "+ + "Actual: {Namespace: \"\", Version: \"3.2.1-1\", Absent: false}, "+ + "Expected: {Namespace: \"\", Version: \"3.2.1-2\", Absent: false}, "+ "NeededActions: [\"install\"]"+ "}", diff.String()) } @@ -194,7 +194,7 @@ func TestComponentDiffState_String(t *testing.T) { Absent: false, } - assert.Equal(t, `{Namespace: "k8s", Version: "3.2.1-1", InstallationState: "present"}`, diff.String()) + assert.Equal(t, `{Namespace: "k8s", Version: "3.2.1-1", Absent: false}`, diff.String()) } func mockTargetComponent(version *semver.Version, absent bool, deployConfig ecosystem.DeployConfig) *Component { @@ -248,7 +248,7 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName, Version: compVersion3211, - Absent: true, + Absent: false, }, }, installedComponents: nil, @@ -303,7 +303,7 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "my-component"}, Version: compVersion3211, - Absent: true, + Absent: false, }, }, installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ @@ -337,7 +337,7 @@ func Test_determineComponentDiffs(t *testing.T) { { Name: testComponentName, Version: compVersion3212, - Absent: true, + Absent: false, }, }, installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index 7a96fe80..735526fd 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -90,7 +90,7 @@ func createDoguConfigFromReferencedValues( } func getNeededConfigAction(expected ConfigValueState, actual ConfigValueState) ConfigAction { - if expected == actual { + if expected.Equal(actual) { return ConfigActionNone } if !expected.Exists { diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index c782548d..87284732 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -6,6 +6,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-registry-lib/config" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -69,7 +70,7 @@ func Test_determineConfigDiff(t *testing.T) { Global: GlobalConfig{ Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ "key1": "value1", - "key2": "value2.2", + "key2": "value3", }, Absent: []common.GlobalConfigKey{ "key3", "key4", @@ -90,64 +91,81 @@ func Test_determineConfigDiff(t *testing.T) { assert.Equal(t, map[cescommons.SimpleName]DoguConfigDiffs{}, dogusConfigDiffs) assert.Equal(t, map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, sensitiveConfigDiffs) assert.Equal(t, 4, len(globalConfigDiff)) - assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ - Key: "key1", - Actual: GlobalConfigValueState{ - Value: &val1, - Exists: true, - }, - Expected: GlobalConfigValueState{ - Value: &val1, - Exists: true, - }, - NeededAction: ConfigActionNone, - }) - assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ - Key: "key2", - Actual: GlobalConfigValueState{ - Value: &val2, - Exists: true, - }, - Expected: GlobalConfigValueState{ - Value: &val3, - Exists: true, - }, - NeededAction: ConfigActionSet, - }) - assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ - Key: "key3", - Actual: GlobalConfigValueState{ - Value: &val1, - Exists: true, - }, - Expected: GlobalConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionRemove, - }) - assert.Contains(t, globalConfigDiff, GlobalConfigEntryDiff{ - Key: "key4", - Actual: GlobalConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: GlobalConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionNone, - }) + hitKeys := make(map[string]bool) + for _, diff := range globalConfigDiff { + if diff.Key == "key1" { + assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ + Key: "key1", + Actual: GlobalConfigValueState{ + Value: &val1, + Exists: true, + }, + Expected: GlobalConfigValueState{ + Value: &val1, + Exists: true, + }, + NeededAction: ConfigActionNone, + })) + hitKeys["key1"] = true + } + if diff.Key == "key2" { + assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ + Key: "key2", + Actual: GlobalConfigValueState{ + Value: &val2, + Exists: true, + }, + Expected: GlobalConfigValueState{ + Value: &val3, + Exists: true, + }, + NeededAction: ConfigActionSet, + })) + hitKeys["key2"] = true + } + if diff.Key == "key3" { + assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ + Key: "key3", + Actual: GlobalConfigValueState{ + Value: &val3, + Exists: true, + }, + Expected: GlobalConfigValueState{ + Value: nil, + Exists: false, + }, + NeededAction: ConfigActionRemove, + })) + hitKeys["key3"] = true + } + if diff.Key == "key4" { + assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ + Key: "key4", + Actual: GlobalConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: GlobalConfigValueState{ + Value: nil, + Exists: false, + }, + NeededAction: ConfigActionNone, + })) + hitKeys["key4"] = true + } + } + assert.Equal(t, 4, len(hitKeys)) }) + t.Run("all actions normal dogu config", func(t *testing.T) { //given ecosystem config globalConfigEntries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(globalConfigEntries) doguConfigEntries, _ := config.MapToEntries(map[string]any{ - "key1": "value", //action none - "key2": "value", //action set - "key3": "value", //action delete + "key1": "value1", //action none + "key2": "value1", //action set + "key3": "value1", //action delete //key4 -> absent, so action none }) doguConfig := config.CreateDoguConfig(dogu1, doguConfigEntries) @@ -159,8 +177,8 @@ func Test_determineConfigDiff(t *testing.T) { DoguName: "dogu1", Config: DoguConfig{ Present: map[common.DoguConfigKey]common.DoguConfigValue{ - dogu1Key1: "value", - dogu1Key2: "updatedValue", + dogu1Key1: "value1", + dogu1Key2: "value2", }, Absent: []common.DoguConfigKey{ dogu1Key3, dogu1Key4, @@ -185,58 +203,74 @@ func Test_determineConfigDiff(t *testing.T) { require.NotNil(t, dogusConfigDiffs["dogu1"]) assert.Equal(t, SensitiveDoguConfigDiffs(nil), sensitiveConfigDiffs["dogu1"]) assert.Equal(t, 4, len(dogusConfigDiffs["dogu1"])) - assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ - Key: dogu1Key1, - Actual: DoguConfigValueState{ - Value: &val1, - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: &val1, - Exists: true, - }, - NeededAction: ConfigActionNone, - }) - assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ - Key: dogu1Key2, - Actual: DoguConfigValueState{ - Value: &val1, - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: &val2, - Exists: true, - }, - NeededAction: ConfigActionSet, - }) - assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ - Key: dogu1Key3, - Actual: DoguConfigValueState{ - Value: &val1, - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionRemove, - }) - //domain.DoguConfigEntryDiff{Key:common.DoguConfigKey{DoguName:"dogu1", Key:"key3"}, - //Actual:domain.DoguConfigValueState{Value:"value", Exists:true}, - //Expected:domain.DoguConfigValueState{Value:"", Exists:false}, - //NeededAction:"set"} - assert.Contains(t, dogusConfigDiffs["dogu1"], DoguConfigEntryDiff{ - Key: dogu1Key4, - Actual: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionNone, - }) + hitKeys := make(map[common.DoguConfigKey]bool) + for _, diff := range dogusConfigDiffs["dogu1"] { + if diff.Key == dogu1Key1 { + assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ + Key: dogu1Key1, + Actual: DoguConfigValueState{ + Value: &val1, + Exists: true, + }, + Expected: DoguConfigValueState{ + Value: &val1, + Exists: true, + }, + NeededAction: ConfigActionNone, + })) + hitKeys[dogu1Key1] = true + } + if diff.Key == dogu1Key2 { + assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ + Key: dogu1Key2, + Actual: DoguConfigValueState{ + Value: &val1, + Exists: true, + }, + Expected: DoguConfigValueState{ + Value: &val2, + Exists: true, + }, + NeededAction: ConfigActionSet, + })) + hitKeys[dogu1Key2] = true + } + if diff.Key == dogu1Key3 { + assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ + Key: dogu1Key3, + Actual: DoguConfigValueState{ + Value: &val1, + Exists: true, + }, + Expected: DoguConfigValueState{ + Value: nil, + Exists: false, + }, + NeededAction: ConfigActionRemove, + })) + hitKeys[dogu1Key3] = true + } + //domain.DoguConfigEntryDiff{Key:common.DoguConfigKey{DoguName:"dogu1", Key:"key3"}, + //Actual:domain.DoguConfigValueState{Value:"value", Exists:true}, + //Expected:domain.DoguConfigValueState{Value:"", Exists:false}, + //NeededAction:"set"} + if diff.Key == dogu1Key4 { + assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ + Key: dogu1Key4, + Actual: DoguConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: DoguConfigValueState{ + Value: nil, + Exists: false, + }, + NeededAction: ConfigActionNone, + })) + hitKeys[dogu1Key4] = true + } + } + assert.Equal(t, 4, len(hitKeys)) }) t.Run("all actions for sensitive dogu config for present dogu", func(t *testing.T) { //given ecosystem config @@ -244,8 +278,8 @@ func Test_determineConfigDiff(t *testing.T) { globalConfig := config.CreateGlobalConfig(globalConfigEntries) sensitiveDoguConfigEntries, _ := config.MapToEntries(map[string]any{ - "key1": "value", //action none - "key2": "value", //action set + "key1": "value1", //action none + "key2": "value1", //action set //key3 absent, action none }) sensitiveDoguConfig := config.CreateDoguConfig(dogu1, sensitiveDoguConfigEntries) @@ -284,8 +318,8 @@ func Test_determineConfigDiff(t *testing.T) { }, //loaded referenced sensitive config map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - sensitiveDogu1Key1: "value", - sensitiveDogu1Key2: "updated value", + sensitiveDogu1Key1: "value1", + sensitiveDogu1Key2: "value2", }, ) //then @@ -294,7 +328,7 @@ func Test_determineConfigDiff(t *testing.T) { require.NotNil(t, sensitiveConfigDiffs["dogu1"]) assert.Equal(t, 3, len(sensitiveConfigDiffs["dogu1"])) - entriesDogu1 := []SensitiveDoguConfigEntryDiff{ + entriesDogu1 := SensitiveDoguConfigDiffs{ { Key: sensitiveDogu1Key1, Actual: DoguConfigValueState{ @@ -375,7 +409,7 @@ func Test_determineConfigDiff(t *testing.T) { }, //loaded referenced sensitive config map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - sensitiveDogu1Key1: "value", + sensitiveDogu1Key1: "value1", }, ) //then @@ -383,7 +417,7 @@ func Test_determineConfigDiff(t *testing.T) { require.NotNil(t, sensitiveConfigDiffs["dogu1"]) require.Equal(t, 1, len(sensitiveConfigDiffs["dogu1"])) - assert.Equal(t, sensitiveConfigDiffs["dogu1"][0], SensitiveDoguConfigEntryDiff{ + assert.Empty(t, cmp.Diff(sensitiveConfigDiffs["dogu1"][0], SensitiveDoguConfigEntryDiff{ Key: sensitiveDogu1Key1, Actual: DoguConfigValueState{ Value: nil, @@ -394,7 +428,7 @@ func Test_determineConfigDiff(t *testing.T) { Exists: true, }, NeededAction: ConfigActionSet, - }) + })) }) } diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index 705ed6de..b9b288c9 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -59,13 +59,20 @@ func (diff *DoguDiff) String() string { func (diff *DoguDiffState) String() string { return fmt.Sprintf( "{Version: %q, Namespace: %q, Absent: %t}", - //TODO NilPointer? - diff.Version.Raw, + diff.getSafeVersionString(), diff.Namespace, diff.Absent, ) } +func (diff *DoguDiffState) getSafeVersionString() string { + if diff.Version != nil { + return diff.Version.String() + } else { + return "" + } +} + // determineDoguDiffs creates DoguDiffs for all dogus in the blueprint and all installed dogus as well. // see determineDoguDiff for more information. func determineDoguDiffs(blueprintDogus []Dogu, installedDogus map[cescommons.SimpleName]*ecosystem.DoguInstallation) []DoguDiff { @@ -102,8 +109,8 @@ func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstall actualState = DoguDiffState{ Namespace: installedDogu.Name.Namespace, Version: &installedDogu.Version, - MinVolumeSize: &installedDogu.MinVolumeSize, - ReverseProxyConfig: &installedDogu.ReverseProxyConfig, + MinVolumeSize: installedDogu.MinVolumeSize, + ReverseProxyConfig: installedDogu.ReverseProxyConfig, AdditionalMounts: installedDogu.AdditionalMounts, } } @@ -154,16 +161,10 @@ func getActionsForPresentDoguDiffs(expected DoguDiffState, actual DoguDiffState) } neededActions = appendActionForMinVolumeSize(neededActions, expected.MinVolumeSize, actual.MinVolumeSize) - neededActions = appendActionForProxyBodySizes(neededActions, expected.ReverseProxyConfig.MaxBodySize, actual.ReverseProxyConfig.MaxBodySize) neededActions = appendActionForAdditionalMounts(neededActions, expected.AdditionalMounts, actual.AdditionalMounts) + neededActions = appendActionForReverseProxyConfig(neededActions, expected, actual) - if expected.ReverseProxyConfig.RewriteTarget != actual.ReverseProxyConfig.RewriteTarget { - neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) - } - if expected.ReverseProxyConfig.AdditionalConfig != actual.ReverseProxyConfig.AdditionalConfig { - neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) - } - if actual.Version != nil && expected.Version.IsNewerThan(*actual.Version) { + if expected.Version != nil && actual.Version != nil && expected.Version.IsNewerThan(*actual.Version) { neededActions = append(neededActions, ActionUpgrade) } else if expected.Version != nil && actual.Version.IsNewerThan(*expected.Version) { // if downgrades are allowed is not important here. @@ -174,6 +175,35 @@ func getActionsForPresentDoguDiffs(expected DoguDiffState, actual DoguDiffState) return neededActions } +func appendActionForReverseProxyConfig(neededActions []Action, expected DoguDiffState, actual DoguDiffState) []Action { + exp := expected.ReverseProxyConfig + act := actual.ReverseProxyConfig + + neededActions = appendActionForProxyBodySizes(neededActions, exp, act) + + switch { + case exp == nil && act == nil: + // both nil → nothing to do + + case exp == nil || act == nil: + // one nil → both fields need updating + neededActions = append(neededActions, + ActionUpdateDoguProxyRewriteTarget, + ActionUpdateDoguProxyAdditionalConfig, + ) + + default: + // both non-nil → compare fields + if exp.RewriteTarget != act.RewriteTarget { + neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) + } + if exp.AdditionalConfig != act.AdditionalConfig { + neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) + } + } + return neededActions +} + func appendActionForMinVolumeSize(actions []Action, expectedSize *ecosystem.VolumeSize, actualSize *ecosystem.VolumeSize) []Action { // if expected > actual = update needed if expectedSize == nil { @@ -184,7 +214,19 @@ func appendActionForMinVolumeSize(actions []Action, expectedSize *ecosystem.Volu return actions } -func appendActionForProxyBodySizes(actions []Action, expectedProxyBodySize *ecosystem.BodySize, actualProxyBodySize *ecosystem.BodySize) []Action { +func appendActionForProxyBodySizes( + actions []Action, + expectedReverseProxyConfig *ecosystem.ReverseProxyConfig, + actualReverseProxyConfig *ecosystem.ReverseProxyConfig, +) []Action { + var expectedProxyBodySize, actualProxyBodySize *ecosystem.BodySize + if actualReverseProxyConfig != nil { + actualProxyBodySize = actualReverseProxyConfig.MaxBodySize + } + if expectedReverseProxyConfig != nil { + expectedProxyBodySize = expectedReverseProxyConfig.MaxBodySize + } + if expectedProxyBodySize == nil && actualProxyBodySize == nil { return actions } else if proxyBodySizeIdentityChanged(expectedProxyBodySize, actualProxyBodySize) { diff --git a/pkg/domain/stateDiffDoguConfig.go b/pkg/domain/stateDiffDoguConfig.go index 88e5aa0c..7c7764d8 100644 --- a/pkg/domain/stateDiffDoguConfig.go +++ b/pkg/domain/stateDiffDoguConfig.go @@ -24,6 +24,20 @@ type ConfigValueState struct { Value *string Exists bool } + +func (a ConfigValueState) Equal(b ConfigValueState) bool { + if a.Exists != b.Exists { + return false + } + if a.Value == b.Value { // covers both nil and same address + return true + } + if a.Value == nil || b.Value == nil { + return false + } + return *a.Value == *b.Value +} + type DoguConfigEntryDiff struct { Key common.DoguConfigKey Actual DoguConfigValueState @@ -62,13 +76,21 @@ func determineDoguConfigDiffs( var doguConfigDiff []DoguConfigEntryDiff // present entries for key, expectedValue := range wantedConfig.Present { + var actualValue *config.Value actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, &actualEntry, exists, &expectedValue, true)) + if exists { + actualValue = &actualEntry + } + doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualValue, exists, &expectedValue, true)) } // absent entries for _, key := range wantedConfig.Absent { + var actualValue *config.Value actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, &actualEntry, exists, nil, false)) + if exists { + actualValue = &actualEntry + } + doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualValue, exists, nil, false)) } return doguConfigDiff } diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index 9cfbba40..db28df2d 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -13,6 +13,7 @@ var ( additionalConfig = "additional" rewriteConfig = "/" subfolder2 = "secsubfolder" + subfolder3 = "different_subfolder" ) func Test_determineDoguDiff(t *testing.T) { @@ -171,24 +172,25 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, - MinVolumeSize: quantity10M, + Version: version3212, + MinVolumeSize: &quantity10M, }, }, want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity10M, }, NeededActions: []Action{ActionUpdateDoguResourceMinVolumeSize}, @@ -199,24 +201,25 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, - MinVolumeSize: quantity100M, + Version: version3212, + MinVolumeSize: &quantity100M, }, }, want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, NeededActions: nil, @@ -227,24 +230,25 @@ func Test_determineDoguDiff(t *testing.T) { args: args{ blueprintDogu: &Dogu{ Name: officialNexus, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity10M, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, - MinVolumeSize: quantity100M, + Version: version3212, + MinVolumeSize: &quantity100M, }, }, want: DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity10M, }, Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, MinVolumeSize: &quantity100M, }, NeededActions: nil, @@ -256,7 +260,6 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, AdditionalConfig: &additionalConfig, @@ -267,7 +270,7 @@ func Test_determineDoguDiff(t *testing.T) { installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3211, - MinVolumeSize: volumeSize1, + MinVolumeSize: &volumeSize1, }, }, want: DoguDiff{ @@ -275,13 +278,11 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3211, - Absent: false, MinVolumeSize: &volumeSize1, }, Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, AdditionalConfig: &additionalConfig, @@ -298,7 +299,6 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3211, - Absent: false, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, @@ -310,12 +310,10 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, }, Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3211, - Absent: false, }, NeededActions: []Action{ActionDowngrade}, }, @@ -334,12 +332,10 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3211, - Absent: false, }, Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3211, - Absent: false, }, NeededActions: nil, }, @@ -367,7 +363,6 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, @@ -375,7 +370,7 @@ func Test_determineDoguDiff(t *testing.T) { installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, @@ -385,7 +380,6 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, @@ -393,7 +387,6 @@ func Test_determineDoguDiff(t *testing.T) { Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, @@ -407,7 +400,6 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, @@ -415,7 +407,7 @@ func Test_determineDoguDiff(t *testing.T) { installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity10MPtr, }, }, @@ -425,7 +417,6 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity10MPtr, }, @@ -433,7 +424,6 @@ func Test_determineDoguDiff(t *testing.T) { Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, @@ -447,7 +437,6 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, @@ -455,7 +444,7 @@ func Test_determineDoguDiff(t *testing.T) { installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, @@ -465,7 +454,6 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, @@ -473,7 +461,6 @@ func Test_determineDoguDiff(t *testing.T) { Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, @@ -485,8 +472,8 @@ func Test_determineDoguDiff(t *testing.T) { name: "no action if additional mounts are equal", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Absent: false, + Name: officialNexus, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -503,7 +490,8 @@ func Test_determineDoguDiff(t *testing.T) { }, }, installedDogu: &ecosystem.DoguInstallation{ - Name: officialNexus, + Name: officialNexus, + Version: version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -524,7 +512,7 @@ func Test_determineDoguDiff(t *testing.T) { DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -542,13 +530,13 @@ func Test_determineDoguDiff(t *testing.T) { }, Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder2, + Subfolder: &subfolder, }, { SourceType: ecosystem.DataSourceSecret, @@ -565,8 +553,8 @@ func Test_determineDoguDiff(t *testing.T) { name: "no action if additional mounts are equal but order is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Absent: false, + Name: officialNexus, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceSecret, @@ -583,7 +571,8 @@ func Test_determineDoguDiff(t *testing.T) { }, }, installedDogu: &ecosystem.DoguInstallation{ - Name: officialNexus, + Name: officialNexus, + Version: version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -604,7 +593,7 @@ func Test_determineDoguDiff(t *testing.T) { DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -622,7 +611,7 @@ func Test_determineDoguDiff(t *testing.T) { }, Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceSecret, @@ -645,8 +634,8 @@ func Test_determineDoguDiff(t *testing.T) { name: "needs update action for additional mounts if the size is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Absent: false, + Name: officialNexus, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -657,7 +646,8 @@ func Test_determineDoguDiff(t *testing.T) { }, }, installedDogu: &ecosystem.DoguInstallation{ - Name: officialNexus, + Name: officialNexus, + Version: version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -678,7 +668,7 @@ func Test_determineDoguDiff(t *testing.T) { DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -696,7 +686,7 @@ func Test_determineDoguDiff(t *testing.T) { }, Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -713,14 +703,14 @@ func Test_determineDoguDiff(t *testing.T) { name: "needs update action for additional mounts if an element is different", args: args{ blueprintDogu: &Dogu{ - Name: officialNexus, - Absent: false, + Name: officialNexus, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: &subfolder3, }, { SourceType: ecosystem.DataSourceSecret, @@ -731,7 +721,8 @@ func Test_determineDoguDiff(t *testing.T) { }, }, installedDogu: &ecosystem.DoguInstallation{ - Name: officialNexus, + Name: officialNexus, + Version: version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -752,7 +743,7 @@ func Test_determineDoguDiff(t *testing.T) { DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -770,13 +761,13 @@ func Test_determineDoguDiff(t *testing.T) { }, Expected: DoguDiffState{ Namespace: officialNamespace, - Absent: false, + Version: &version3212, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: &subfolder3, }, { SourceType: ecosystem.DataSourceSecret, @@ -932,8 +923,8 @@ func TestDoguDiff_String(t *testing.T) { assert.Equal(t, "{"+ "DoguName: \"postgresql\", "+ - "Actual: {Version: \"3.2.1-1\", Namespace: \"official\", InstallationState: \"present\"}, "+ - "Expected: {Version: \"3.2.1-2\", Namespace: \"premium\", InstallationState: \"present\"}, "+ + "Actual: {Version: \"3.2.1-1\", Namespace: \"official\", Absent: false}, "+ + "Expected: {Version: \"3.2.1-2\", Namespace: \"premium\", Absent: false}, "+ "NeededActions: [\"upgrade\" \"update resource minimum volume size\"]"+ "}", diff.String()) } @@ -944,5 +935,5 @@ func TestDoguDiffState_String(t *testing.T) { Absent: false, } - assert.Equal(t, "{Version: \"3.2.1-1\", Namespace: \"official\", InstallationState: \"present\"}", diff.String()) + assert.Equal(t, "{Version: \"3.2.1-1\", Namespace: \"official\", Absent: false}", diff.String()) } diff --git a/pkg/domain/stateDiffGlobalConfig.go b/pkg/domain/stateDiffGlobalConfig.go index 698f86e2..f98ab08f 100644 --- a/pkg/domain/stateDiffGlobalConfig.go +++ b/pkg/domain/stateDiffGlobalConfig.go @@ -70,13 +70,21 @@ func determineGlobalConfigDiffs( // present entries for key, expectedValue := range config.Present { + var actualValue *common.GlobalConfigValue actualEntry, actualExists := actualConfig.Get(key) - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, &actualEntry, actualExists, &expectedValue, true)) + if actualExists { + actualValue = &actualEntry + } + configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualValue, actualExists, &expectedValue, true)) } // absent entries for _, key := range config.Absent { + var actualValue *common.GlobalConfigValue actualEntry, actualExists := actualConfig.Get(key) - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, &actualEntry, actualExists, nil, false)) + if actualExists { + actualValue = &actualEntry + } + configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualValue, actualExists, nil, false)) } return configDiffs } From e499e6eb0fc01eb0a8167592668df668ee29a362 Mon Sep 17 00:00:00 2001 From: Marco Bergen Date: Fri, 12 Sep 2025 13:05:10 +0000 Subject: [PATCH 055/119] [#125] install component crd --- Jenkinsfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 95a40315..90412765 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,6 +15,8 @@ gpg = new Gpg(this, docker) goVersion = "1.24.3" Makefile makefile = new Makefile(this) +componentOperatorVersion=1.10.0 + // Configuration of repository repositoryOwner = "cloudogu" repositoryName = "k8s-blueprint-operator" @@ -109,6 +111,11 @@ node('docker') { } stage('Deploy Manager') { + withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'harborhelmchartpush', usernameVariable: 'HARBOR_USERNAME', passwordVariable: 'HARBOR_PASSWORD']]) { + k3d.helm("registry login ${registry} --username '${HARBOR_USERNAME}' --password '${HARBOR_PASSWORD}'") + k3d.helm("install k8s-component-operator-crd oci://${registry}/k8s/k8s-component-operator-crd --version ${componentOperatorVersion}") + k3d.helm("registry logout ${registry}") + } k3d.helm("install ${repositoryName} ${helmChartDir}") } From 166b3aa942e8376241de55d74c37c80c9103257e Mon Sep 17 00:00:00 2001 From: Marco Bergen Date: Fri, 12 Sep 2025 13:05:47 +0000 Subject: [PATCH 056/119] [#125] install component crd --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 90412765..ba8bfb2b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ gpg = new Gpg(this, docker) goVersion = "1.24.3" Makefile makefile = new Makefile(this) -componentOperatorVersion=1.10.0 +componentOperatorVersion="1.10.0" // Configuration of repository repositoryOwner = "cloudogu" From 9382d7e55617ab7bfe390b3557a372eefd0ed4b5 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 15 Sep 2025 08:35:25 +0200 Subject: [PATCH 057/119] #121 flatten config in domain --- go.mod | 2 +- go.sum | 4 + .../v2/serializer/blueprint_test.go | 73 ++- .../blueprintcr/v2/serializer/config.go | 170 +++--- .../blueprintcr/v2/serializer/config_test.go | 137 +++-- .../v2/serializer/doguConfigDiff.go | 2 +- .../v2/serializer/doguConfigDiff_test.go | 4 +- .../blueprintcr/v2/serializer/dogu_test.go | 2 +- .../v2/serializer/globalConfigDiff.go | 6 +- .../v2/serializer/sensitiveConfig.go | 80 --- .../v2/serializer/sensitiveConfig_test.go | 148 ----- .../blueprintcr/v2/serializer/stateDiff.go | 41 +- .../v2/serializer/stateDiff_test.go | 106 ++-- .../sensitiveconfigref/secretRefReader.go | 13 +- .../secretRefReader_test.go | 23 +- .../ecosystemConfigUseCase_test.go | 4 +- pkg/application/stateDiffUseCase.go | 2 +- pkg/application/stateDiffUseCase_test.go | 155 ++--- pkg/domain/blueprintSpec.go | 2 +- pkg/domain/blueprintSpec_test.go | 16 +- pkg/domain/blueprint_test.go | 10 +- pkg/domain/common/configNames.go | 15 - pkg/domain/config.go | 280 ++++----- pkg/domain/config_test.go | 547 +++++++++--------- pkg/domain/ecosystem/ecosystemConfig.go | 2 +- pkg/domain/ecosystem/ecosystemHealth_test.go | 3 +- pkg/domain/stateDiffConfig.go | 43 +- pkg/domain/stateDiffConfig_test.go | 193 +++--- pkg/domain/stateDiffDoguConfig.go | 27 +- pkg/domain/stateDiffGlobalConfig.go | 18 +- pkg/domainservice/adapterInterfaces.go | 4 +- 31 files changed, 909 insertions(+), 1223 deletions(-) delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go diff --git a/go.mod b/go.mod index 5acfaa74..b9dfe596 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index a11c9b55..1102f1fc 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,10 @@ github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017 h1:T github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f h1:IUUD93cn1uF2T5LyE5+jLy2S7xvM98JCbr5ubpTE05g= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323 h1:l16+dYDhYKFA7KoouycRrfgJqTfFCcqEqWeC/L2+sKs= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 h1:8H1XAdX77EP1Q55qS+qsuuEY+H0aIG8dNxKvuWbYozE= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index 86bbf085..4d6f176b 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -6,6 +6,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/cloudogu/k8s-registry-lib/config" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -91,30 +92,28 @@ func TestConvertToBlueprintDTO(t *testing.T) { value42 := "42" blueprint := domain.EffectiveBlueprint{ Config: &domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ "my-dogu": { - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - { - DoguName: "my-dogu", - Key: "config", - }: common.DoguConfigValue(value42), - }, + { + Key: "config", + Value: (*config.Value)(&value42), }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ - { - DoguName: "my-dogu", - Key: "sensitive-config", - }: { - SecretName: "mySecret", - SecretKey: "myKey", - }, + { + Key: "sensitive-config", + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "mySecret", + SecretKey: "myKey", }, }, }, }, - Global: domain.GlobalConfig{Absent: []common.GlobalConfigKey{"test/key"}}, + Global: domain.GlobalConfigEntries{ + { + Key: "test/key", + Absent: true, + }, + }, }, } blueprintV2 := ConvertToBlueprintDTO(blueprint) @@ -130,7 +129,8 @@ func TestConvertToBlueprintDTO(t *testing.T) { Value: &value42, }, crd.ConfigEntry{ - Key: "sensitive-config", + Key: "sensitive-config", + Sensitive: &trueVar, SecretRef: &crd.SecretReference{ Name: "mySecret", Key: "myKey", @@ -246,31 +246,28 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { Dogus: dogus, Components: components, Config: &domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ "my-dogu": { - DoguName: "my-dogu", - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - { - DoguName: "my-dogu", - Key: "config", - }: common.DoguConfigValue(value42), - }, + { + Key: "config", + Value: (*config.Value)(&value42), }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ - { - DoguName: "my-dogu", - Key: "sensitive-config", - }: { - SecretName: "mySecret", - SecretKey: "myKey", - }, + { + Key: "sensitive-config", + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "mySecret", + SecretKey: "myKey", }, }, }, }, - Global: domain.GlobalConfig{Absent: []common.GlobalConfigKey{"test/key"}}, + Global: domain.GlobalConfigEntries{ + { + Key: "test/key", + Absent: true, + }, + }, }, } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index b1f10535..015e98af 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -3,10 +3,9 @@ package serializer import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "github.com/cloudogu/k8s-registry-lib/config" + libconfig "github.com/cloudogu/k8s-registry-lib/config" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) func ConvertToConfigDTO(config *domain.Config) *v2.Config { @@ -20,7 +19,7 @@ func ConvertToConfigDTO(config *domain.Config) *v2.Config { if len(config.Dogus) != 0 { dogus = make(map[string][]v2.ConfigEntry, len(config.Dogus)) for doguName, doguConfig := range config.Dogus { - dogus[string(doguName)] = convertToCombinedDoguConfigDTO(doguConfig) + dogus[string(doguName)] = convertToDoguConfigDTO(doguConfig) } } @@ -34,13 +33,13 @@ func ConvertToConfigDomain(config *v2.Config) *domain.Config { if config == nil { return nil } - var dogus map[cescommons.SimpleName]domain.CombinedDoguConfig + var dogus map[cescommons.SimpleName]domain.DoguConfigEntries // we check for empty values to make good use of default values // this makes testing easier if len(config.Dogus) != 0 { - dogus = make(map[cescommons.SimpleName]domain.CombinedDoguConfig, len(config.Dogus)) + dogus = make(map[cescommons.SimpleName]domain.DoguConfigEntries, len(config.Dogus)) for doguName, doguConfig := range config.Dogus { - dogus[cescommons.SimpleName(doguName)] = convertToCombinedDoguConfigDomain(doguName, doguConfig) + dogus[cescommons.SimpleName(doguName)] = convertToDoguConfigEntriesDomain(doguConfig) } } @@ -50,133 +49,94 @@ func ConvertToConfigDomain(config *v2.Config) *domain.Config { } } -func convertToCombinedDoguConfigDTO(config domain.CombinedDoguConfig) []v2.ConfigEntry { - var result []v2.ConfigEntry - result = append(result, convertToDoguConfigDTO(config.Config)...) - result = append(result, convertToSensitiveDoguConfigDTO(config.SensitiveConfig)...) +func convertToDoguConfigDTO(config domain.DoguConfigEntries) []v2.ConfigEntry { + return convertToConfigEntriesDTO(domain.ConfigEntries(config)) +} - return result +func convertToDoguConfigEntriesDomain(config []v2.ConfigEntry) domain.DoguConfigEntries { + return domain.DoguConfigEntries(convertToConfigEntriesDomain(config)) } -func convertToCombinedDoguConfigDomain(doguName string, config []v2.ConfigEntry) domain.CombinedDoguConfig { - return domain.CombinedDoguConfig{ - DoguName: cescommons.SimpleName(doguName), - Config: convertToDoguConfigDomain(doguName, config), - SensitiveConfig: convertToSensitiveDoguConfigDomain(doguName, config), - } +func convertToGlobalConfigDTO(config domain.GlobalConfigEntries) []v2.ConfigEntry { + return convertToConfigEntriesDTO(domain.ConfigEntries(config)) } -func convertToDoguConfigDTO(config domain.DoguConfig) []v2.ConfigEntry { - // empty struct -> nil - if len(config.Present) == 0 && len(config.Absent) == 0 { +func convertToConfigEntriesDTO(config domain.ConfigEntries) []v2.ConfigEntry { + if config == nil || len(config) == 0 { return nil } - result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) - // we check for empty values to make good use of default values - // this makes testing easier - for key, value := range config.Present { - valueString := string(value) - result = append(result, v2.ConfigEntry{ - Key: string(key.Key), - Value: &valueString, - }) - } - - for _, key := range config.Absent { - truePtr := true - result = append(result, v2.ConfigEntry{ - Key: string(key.Key), - Absent: &truePtr, - }) - } + result := make([]v2.ConfigEntry, len(config)) - return result -} - -func convertToDoguConfigDomain(doguName string, config []v2.ConfigEntry) domain.DoguConfig { - if config == nil || len(config) == 0 { - return domain.DoguConfig{} - } + for i, domainEntry := range config { + var absent *bool + if domainEntry.Absent { + absent = &domainEntry.Absent + } - present := make(map[common.DoguConfigKey]common.DoguConfigValue, len(config)) - absent := make([]common.DoguConfigKey, len(config)) + var sensitive *bool + if domainEntry.Sensitive { + sensitive = &domainEntry.Sensitive + } - absentIndex := 0 - for _, configEntry := range config { - if configEntry.Sensitive != nil && *configEntry.Sensitive == true { - continue + var secretRef *v2.SecretReference + if domainEntry.SecretRef != nil { + secretRef = &v2.SecretReference{ + Name: domainEntry.SecretRef.SecretName, + Key: domainEntry.SecretRef.SecretKey, + } } - if configEntry.Absent == nil || *configEntry.Absent == false && configEntry.Value != nil { - present[convertToDoguConfigKeyDomain(doguName, configEntry.Key)] = common.DoguConfigValue(*configEntry.Value) - } else { - absent[absentIndex] = convertToDoguConfigKeyDomain(doguName, configEntry.Key) - absentIndex++ + result[i] = v2.ConfigEntry{ + Key: domainEntry.Key.String(), + Absent: absent, + Value: (*string)(domainEntry.Value), + Sensitive: sensitive, + SecretRef: secretRef, } } - return domain.DoguConfig{ - Present: present, - Absent: absent, - } + return result } -func convertToDoguConfigKeyDomain(doguName, key string) common.DoguConfigKey { - return common.DoguConfigKey{ - DoguName: cescommons.SimpleName(doguName), - Key: config.Key(key), - } +func convertToGlobalConfigDomain(config []v2.ConfigEntry) domain.GlobalConfigEntries { + return domain.GlobalConfigEntries(convertToConfigEntriesDomain(config)) } -func convertToGlobalConfigDTO(config domain.GlobalConfig) []v2.ConfigEntry { - // empty struct -> nil - if len(config.Present) == 0 && len(config.Absent) == 0 { +func convertToConfigEntriesDomain(config []v2.ConfigEntry) domain.ConfigEntries { + if config == nil || len(config) == 0 { return nil } - result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) - // we check for empty values to make good use of default values - // this makes testing easier - for key, value := range config.Present { - valueString := string(value) - result = append(result, v2.ConfigEntry{ - Key: string(key), - Value: &valueString, - }) - } - - for _, key := range config.Absent { - truePtr := true - result = append(result, v2.ConfigEntry{ - Key: string(key), - Absent: &truePtr, - }) - } + result := make([]domain.ConfigEntry, len(config)) - return result -} + for i, v2Entry := range config { + absent := false + if v2Entry.Absent != nil { + absent = *v2Entry.Absent + } -func convertToGlobalConfigDomain(config []v2.ConfigEntry) domain.GlobalConfig { - if config == nil { - return domain.GlobalConfig{} - } + sensitive := false + if v2Entry.Sensitive != nil { + sensitive = *v2Entry.Sensitive + } - present := make(map[common.GlobalConfigKey]common.GlobalConfigValue, len(config)) - absent := make([]common.GlobalConfigKey, len(config)) + var secretRef *domain.SensitiveValueRef + if v2Entry.SecretRef != nil { + secretRef = &domain.SensitiveValueRef{ + SecretName: v2Entry.SecretRef.Name, + SecretKey: v2Entry.SecretRef.Key, + } + } - absentIndex := 0 - for _, configEntry := range config { - if (configEntry.Absent == nil || !*configEntry.Absent) && configEntry.Value != nil { - present[common.GlobalConfigKey(configEntry.Key)] = common.GlobalConfigValue(*configEntry.Value) - } else { - absent[absentIndex] = common.GlobalConfigKey(configEntry.Key) - absentIndex++ + result[i] = domain.ConfigEntry{ + Key: libconfig.Key(v2Entry.Key), + Absent: absent, + Value: (*libconfig.Value)(v2Entry.Value), + Sensitive: sensitive, + SecretRef: secretRef, } } - return domain.GlobalConfig{ - Present: present, - Absent: absent, - } + return result } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go index f0bd7333..3de40994 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go @@ -5,7 +5,7 @@ import ( v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" ) @@ -16,27 +16,25 @@ var ( func Test_convertToDoguConfigDTO(t *testing.T) { tests := []struct { name string - config domain.DoguConfig + config domain.DoguConfigEntries want []v2.ConfigEntry }{ { name: "nil config", - config: domain.DoguConfig{}, + config: nil, want: nil, }, { - name: "empty config", - config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{}, - Absent: []common.DoguConfigKey{}, - }, - want: nil, + name: "empty config", + config: domain.DoguConfigEntries{}, + want: nil, }, { name: "convert present config", - config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - testDoguKey1: common.DoguConfigValue(val1), + config: domain.DoguConfigEntries{ + { + Key: testDoguKey1.Key, + Value: (*config.Value)(&val1), }, }, want: []v2.ConfigEntry{ @@ -45,9 +43,10 @@ func Test_convertToDoguConfigDTO(t *testing.T) { }, { name: "convert absent config", - config: domain.DoguConfig{ - Absent: []common.DoguConfigKey{ - testDoguKey1, + config: domain.DoguConfigEntries{ + { + Key: testDoguKey1.Key, + Absent: true, }, }, want: []v2.ConfigEntry{ @@ -62,69 +61,56 @@ func Test_convertToDoguConfigDTO(t *testing.T) { } } -func Test_convertToDoguConfigDomain(t *testing.T) { - type args struct { - doguName string - config []v2.ConfigEntry - } +func Test_convertToDoguConfigEntriesDomain(t *testing.T) { tests := []struct { - name string - args args - want domain.DoguConfig + name string + config []v2.ConfigEntry + want domain.DoguConfigEntries }{ { - name: "nil", - args: args{ - doguName: string(testDoguKey1.DoguName), - }, - want: domain.DoguConfig{}, + name: "nil", + config: nil, + want: nil, }, { - name: "empty config", - args: args{ - doguName: string(testDoguKey1.DoguName), - config: []v2.ConfigEntry{}, - }, - want: domain.DoguConfig{}, + name: "empty config", + config: []v2.ConfigEntry{}, + want: nil, }, { name: "convert present config", - args: args{ - doguName: string(testDoguKey1.DoguName), - config: []v2.ConfigEntry{ - { - Key: testDoguKey1.Key.String(), - Value: &val1, - }, + config: []v2.ConfigEntry{ + { + Key: testDoguKey1.Key.String(), + Value: &val1, }, }, - want: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - testDoguKey1: common.DoguConfigValue(val1), + want: domain.DoguConfigEntries{ + { + Key: testDoguKey1.Key, + Value: (*config.Value)(&val1), }, }, }, { name: "convert absent config", - args: args{ - doguName: string(testDoguKey1.DoguName), - config: []v2.ConfigEntry{ - { - Key: testDoguKey1.Key.String(), - Absent: &trueVar, - }, + config: []v2.ConfigEntry{ + { + Key: testDoguKey1.Key.String(), + Absent: &trueVar, }, }, - want: domain.DoguConfig{ - Absent: []common.DoguConfigKey{ - testDoguKey1, + want: domain.DoguConfigEntries{ + { + Key: testDoguKey1.Key, + Absent: true, }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertToDoguConfigDomain(tt.args.doguName, tt.args.config), "convertToDoguConfigDomain(%v, %v)", tt.args.doguName, tt.args.config) + assert.Equalf(t, tt.want, convertToDoguConfigEntriesDomain(tt.config), "convertToDoguConfigDomain%v)", tt.config) }) } } @@ -132,19 +118,25 @@ func Test_convertToDoguConfigDomain(t *testing.T) { func Test_convertToGlobalConfigDTO(t *testing.T) { tests := []struct { name string - config domain.GlobalConfig + config domain.GlobalConfigEntries want []v2.ConfigEntry }{ + { + name: "nil", + config: nil, + want: nil, + }, { name: "empty", - config: domain.GlobalConfig{}, + config: domain.GlobalConfigEntries{}, want: nil, }, { name: "convert present", - config: domain.GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "test": "val1", + config: domain.GlobalConfigEntries{ + { + Key: "test", + Value: (*config.Value)(&val1), }, }, want: []v2.ConfigEntry{ @@ -156,9 +148,10 @@ func Test_convertToGlobalConfigDTO(t *testing.T) { }, { name: "convert absent", - config: domain.GlobalConfig{ - Absent: []common.GlobalConfigKey{ - "test", + config: domain.GlobalConfigEntries{ + { + Key: "test", + Absent: true, }, }, want: []v2.ConfigEntry{ @@ -180,12 +173,12 @@ func Test_convertToGlobalConfigDomain(t *testing.T) { tests := []struct { name string config []v2.ConfigEntry - want domain.GlobalConfig + want domain.GlobalConfigEntries }{ { name: "nil", config: nil, - want: domain.GlobalConfig{}, + want: nil, }, { name: "convert present", @@ -195,9 +188,10 @@ func Test_convertToGlobalConfigDomain(t *testing.T) { Value: &val1, }, }, - want: domain.GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "test": "val1", + want: domain.GlobalConfigEntries{ + { + Key: "test", + Value: (*config.Value)(&val1), }, }, }, @@ -209,9 +203,10 @@ func Test_convertToGlobalConfigDomain(t *testing.T) { Absent: &trueVar, }, }, - want: domain.GlobalConfig{ - Absent: []common.GlobalConfigKey{ - "test", + want: domain.GlobalConfigEntries{ + { + Key: "test", + Absent: true, }, }, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go index 49061aa4..337a026a 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go @@ -34,7 +34,7 @@ func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.ConfigEntryDiff } } -func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) crd.ConfigDiff { +func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) crd.DoguConfigDiff { var dtoDiffs []crd.ConfigEntryDiff for _, domainDiff := range domainDiffs { dtoDiffs = append(dtoDiffs, convertToDoguConfigEntryDiffDTO(domainDiff, isSensitive)) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go index 802a9d1d..3752e68c 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go @@ -19,7 +19,7 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { tests := []struct { name string domainModel domain.DoguConfigDiffs - want crd.ConfigDiff + want crd.DoguConfigDiff isSensitive bool }{ { @@ -60,7 +60,7 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { NeededAction: domain.ConfigActionSet, }, }, - want: crd.ConfigDiff{ + want: crd.DoguConfigDiff{ { Key: "container_config/memory_limit", Actual: crd.ConfigValueState{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go index 9a89f3cd..44447d02 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go @@ -67,7 +67,7 @@ func TestConvertDogus(t *testing.T) { }, { name: "dogu with max proxy body size", - args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &volumeSizeString}}}}}, + args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString}}}}}, want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize}}}, wantErr: assert.NoError, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go index e58f6304..be4e6bba 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go @@ -6,7 +6,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) -func convertToGlobalConfigDiffDomain(dto crd.ConfigDiff) domain.GlobalConfigDiffs { +func convertToGlobalConfigDiffDomain(dto crd.GlobalConfigDiff) domain.GlobalConfigDiffs { if len(dto) == 0 { return nil } @@ -33,12 +33,12 @@ func convertToGlobalConfigEntryDiffDomain(dto crd.ConfigEntryDiff) domain.Global } } -func convertToGlobalConfigDiffDTO(domainModel domain.GlobalConfigDiffs) crd.ConfigDiff { +func convertToGlobalConfigDiffDTO(domainModel domain.GlobalConfigDiffs) crd.GlobalConfigDiff { if len(domainModel) == 0 { return nil } - globalConfigDiff := make(crd.ConfigDiff, len(domainModel)) + globalConfigDiff := make(crd.GlobalConfigDiff, len(domainModel)) for i, entryDiff := range domainModel { globalConfigDiff[i] = convertToGlobalConfigEntryDiffDTO(entryDiff) } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go deleted file mode 100644 index f505c287..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig.go +++ /dev/null @@ -1,80 +0,0 @@ -package serializer - -import ( - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "github.com/cloudogu/k8s-registry-lib/config" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" -) - -func convertToSensitiveDoguConfigDTO(config domain.SensitiveDoguConfig) []v2.ConfigEntry { - // empty struct -> nil - if len(config.Absent) == 0 && len(config.Present) == 0 { - return nil - } - - result := make([]v2.ConfigEntry, len(config.Present)+len(config.Absent)) - // we check for empty values to make good use of default values - // this makes testing easier - for key, value := range config.Present { - result = append(result, v2.ConfigEntry{ - Key: string(key.Key), - SecretRef: &v2.SecretReference{ - Name: value.SecretName, - Key: value.SecretKey, - }, - }) - } - - for _, key := range config.Absent { - truePtr := true - result = append(result, v2.ConfigEntry{ - Key: string(key.Key), - Absent: &truePtr, - }) - } - - return result -} - -func convertToSensitiveDoguConfigDomain(doguName string, doguConfig []v2.ConfigEntry) domain.SensitiveDoguConfig { - if doguConfig == nil { - return domain.SensitiveDoguConfig{} - } - - present := make(map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef, len(doguConfig)) - absent := make([]common.SensitiveDoguConfigKey, len(doguConfig)) - - absentIndex := 0 - for _, configEntry := range doguConfig { - if configEntry.Absent == nil || !*configEntry.Absent { - if configEntry.Sensitive != nil && !*configEntry.Sensitive || configEntry.SecretRef == nil { - continue - } - present[convertToSensitiveDoguConfigKeyDomain(doguName, configEntry.Key)] = domain.SensitiveValueRef{ - SecretName: configEntry.SecretRef.Name, - SecretKey: configEntry.SecretRef.Key, - } - } else { - absent[absentIndex] = common.SensitiveDoguConfigKey{ - DoguName: cescommons.SimpleName(doguName), - Key: config.Key(configEntry.Key), - } - absentIndex++ - } - } - - return domain.SensitiveDoguConfig{ - Present: present, - Absent: absent, - } -} - -func convertToSensitiveDoguConfigKeyDomain(doguName, key string) common.SensitiveDoguConfigKey { - return common.SensitiveDoguConfigKey{ - DoguName: cescommons.SimpleName(doguName), - Key: config.Key(key), - } -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go deleted file mode 100644 index 6e003211..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/sensitiveConfig_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package serializer - -import ( - "testing" - - v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/stretchr/testify/assert" -) - -func Test_convertToSensitiveDoguConfigDTO(t *testing.T) { - - tests := []struct { - name string - config domain.SensitiveDoguConfig - want []v2.ConfigEntry - }{ - { - name: "empty struct to nil", - config: domain.SensitiveDoguConfig{}, - want: nil, - }, - { - name: "empty config to nil", - config: domain.SensitiveDoguConfig{ - Present: nil, - Absent: nil, - }, - want: nil, - }, - { - name: "convert present config", - config: domain.SensitiveDoguConfig{ - Present: map[common.DoguConfigKey]domain.SensitiveValueRef{ - testDoguKey1: { - SecretName: "mySecret", - SecretKey: "myKey", - }, - }, - }, - want: []v2.ConfigEntry{ - { - Key: string(testDoguKey1.Key), - SecretRef: &v2.SecretReference{ - Name: "mySecret", - Key: "myKey", - }, - Sensitive: &trueVar, - }, - }, - }, - { - name: "convert absent config", - config: domain.SensitiveDoguConfig{ - Absent: []common.SensitiveDoguConfigKey{ - testDoguKey1, - }, - }, - want: []v2.ConfigEntry{ - { - Key: string(testDoguKey1.Key), - Absent: &trueVar, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertToSensitiveDoguConfigDTO(tt.config), "convertToSensitiveDoguConfigDTO(%v)", tt.config) - }) - } -} - -func Test_convertToSensitiveDoguConfigDomain(t *testing.T) { - type args struct { - doguName string - doguConfig []v2.ConfigEntry - } - tests := []struct { - name string - args args - want domain.SensitiveDoguConfig - }{ - { - name: "nil -> empty struct", - args: args{ - doguName: string(testDoguKey1.DoguName), - doguConfig: nil, - }, - want: domain.SensitiveDoguConfig{}, - }, - { - name: "empty", - args: args{ - doguName: string(testDoguKey1.DoguName), - doguConfig: []v2.ConfigEntry{}, - }, - want: domain.SensitiveDoguConfig{}, - }, - { - name: "convert present config", - args: args{ - doguName: string(testDoguKey1.DoguName), - doguConfig: []v2.ConfigEntry{ - { - Key: string(testDoguKey1.Key), - SecretRef: &v2.SecretReference{ - Name: "mySecret", - Key: "myKey", - }, - Sensitive: &trueVar, - }, - }, - }, - want: domain.SensitiveDoguConfig{ - Present: map[common.DoguConfigKey]domain.SensitiveValueRef{ - testDoguKey1: { - SecretName: "mySecret", - SecretKey: "myKey", - }, - }, - }, - }, - { - name: "convert absent config", - args: args{ - doguName: string(testDoguKey1.DoguName), - doguConfig: []v2.ConfigEntry{ - { - Key: string(testDoguKey1.Key), - Absent: &trueVar, - }, - }, - }, - want: domain.SensitiveDoguConfig{ - Absent: []common.SensitiveDoguConfigKey{ - testDoguKey1, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertToSensitiveDoguConfigDomain(tt.args.doguName, tt.args.doguConfig), "convertToSensitiveDoguConfigDomain(%v, %v)", tt.args.doguName, tt.args.doguConfig) - }) - } -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go index 9efc5ce0..5ab052a0 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go @@ -3,6 +3,7 @@ package serializer import ( "errors" "fmt" + "slices" cescommons "github.com/cloudogu/ces-commons-lib/dogu" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" @@ -20,22 +21,38 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) *crd.StateDiff { componentDiffs[string(componentDiff.Name)] = convertToComponentDiffDTO(componentDiff) } - var doguConfigDiffs map[string]crd.ConfigDiff + var dogus []cescommons.SimpleName + var combinedConfigDiffs map[string]crd.CombinedDoguConfigDiff + var doguConfigDiffByDogu map[cescommons.SimpleName]crd.DoguConfigDiff + var sensitiveDoguConfigDiff map[cescommons.SimpleName]crd.SensitiveDoguConfigDiff if len(domainModel.DoguConfigDiffs) != 0 || len(domainModel.SensitiveDoguConfigDiffs) != 0 { - doguConfigDiffs = make(map[string]crd.ConfigDiff) + combinedConfigDiffs = make(map[string]crd.CombinedDoguConfigDiff) + doguConfigDiffByDogu = make(map[cescommons.SimpleName]crd.DoguConfigDiff) for doguName, doguConfigDiff := range domainModel.DoguConfigDiffs { - doguConfigDiffs[doguName.String()] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, false) + doguConfigDiffByDogu[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, false) + dogus = append(dogus, doguName) } + sensitiveDoguConfigDiff = make(map[cescommons.SimpleName]crd.SensitiveDoguConfigDiff) for doguName, doguConfigDiff := range domainModel.SensitiveDoguConfigDiffs { - doguConfigDiffs[doguName.String()] = append(doguConfigDiffs[doguName.String()], convertToDoguConfigEntryDiffsDTO(doguConfigDiff, true)...) + sensitiveDoguConfigDiff[doguName] = convertToDoguConfigEntryDiffsDTO(doguConfigDiff, true) + dogus = append(dogus, doguName) + } + + // remove duplicates, so we have a complete list of all dogus with config + dogus = slices.Compact(dogus) + for _, doguName := range dogus { + combinedConfigDiffs[string(doguName)] = crd.CombinedDoguConfigDiff{ + DoguConfigDiff: doguConfigDiffByDogu[doguName], + SensitiveDoguConfigDiff: sensitiveDoguConfigDiff[doguName], + } } } return &crd.StateDiff{ DoguDiffs: doguDiffs, ComponentDiffs: componentDiffs, - DoguConfigDiffs: doguConfigDiffs, + DoguConfigDiffs: combinedConfigDiffs, GlobalConfigDiff: convertToGlobalConfigDiffDTO(domainModel.GlobalConfigDiffs), } } @@ -72,16 +89,16 @@ func ConvertToStateDiffDomain(dto *crd.StateDiff) (domain.StateDiff, error) { doguConfigDiffs = map[cescommons.SimpleName]domain.DoguConfigDiffs{} sensitiveDoguConfigDiffs = map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{} for doguName, doguConfigDiff := range dto.DoguConfigDiffs { - // TODO: remove sensitive config diff from domain - doguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, doguConfigDiff) - sensitiveDoguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, doguConfigDiff) + doguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, crd.ConfigDiff(doguConfigDiff.DoguConfigDiff)) + sensitiveDoguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, crd.ConfigDiff(doguConfigDiff.SensitiveDoguConfigDiff)) } } return domain.StateDiff{ - DoguDiffs: doguDiffs, - ComponentDiffs: componentDiffs, - DoguConfigDiffs: doguConfigDiffs, - GlobalConfigDiffs: convertToGlobalConfigDiffDomain(dto.GlobalConfigDiff), + DoguDiffs: doguDiffs, + ComponentDiffs: componentDiffs, + DoguConfigDiffs: doguConfigDiffs, + SensitiveDoguConfigDiffs: sensitiveDoguConfigDiffs, + GlobalConfigDiffs: convertToGlobalConfigDiffDomain(dto.GlobalConfigDiff), }, nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index 523e0d35..aca5a937 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -181,7 +181,7 @@ func TestConvertToDTO(t *testing.T) { want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.ConfigDiff{ + DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ "ldap": {}, "postfix": {}, }, @@ -528,14 +528,14 @@ func TestConvertToDomainModel(t *testing.T) { { name: "succeed for multiple dogu config diffs", dto: crd.StateDiff{ - DoguConfigDiffs: map[string]crd.ConfigDiff{ + DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ "ldap": { - crd.ConfigEntryDiff{}, - crd.ConfigEntryDiff{}, + crd.DoguConfigDiff{}, + crd.SensitiveDoguConfigDiff{}, }, "postfix": { - crd.ConfigEntryDiff{}, - crd.ConfigEntryDiff{}, + crd.DoguConfigDiff{}, + crd.SensitiveDoguConfigDiff{}, }, }, }, @@ -556,7 +556,7 @@ func TestConvertToDomainModel(t *testing.T) { { name: "succeed for global config diffs", dto: crd.StateDiff{ - GlobalConfigDiff: crd.ConfigDiff{{ + GlobalConfigDiff: crd.GlobalConfigDiff{{ Key: "fqdn", Actual: crd.ConfigValueState{ Value: &testFqdn1, @@ -785,33 +785,37 @@ func TestConvertToStateDiffDTO(t *testing.T) { want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.ConfigDiff{ + DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ testDogu.String(): { - crd.ConfigEntryDiff{ - Key: testDoguKey1.Key.String(), - Actual: crd.ConfigValueState{ - Value: &value1, - Exists: true, - }, - Expected: crd.ConfigValueState{ - Value: &value123, - Exists: true, + DoguConfigDiff: crd.DoguConfigDiff{ + { + Key: testDoguKey1.Key.String(), + Actual: crd.ConfigValueState{ + Value: &value1, + Exists: true, + }, + Expected: crd.ConfigValueState{ + Value: &value123, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), }, - NeededAction: crd.ConfigAction("set"), }, }, testDogu2.String(): { - crd.ConfigEntryDiff{ - Key: testDoguKey2.Key.String(), - Actual: crd.ConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: crd.ConfigValueState{ - Value: &value123, - Exists: true, + DoguConfigDiff: crd.DoguConfigDiff{ + { + Key: testDoguKey2.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: crd.ConfigValueState{ + Value: &value123, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), }, - NeededAction: crd.ConfigAction("set"), }, }, }, @@ -859,33 +863,37 @@ func TestConvertToStateDiffDTO(t *testing.T) { want: &crd.StateDiff{ DoguDiffs: map[string]crd.DoguDiff{}, ComponentDiffs: map[string]crd.ComponentDiff{}, - DoguConfigDiffs: map[string]crd.ConfigDiff{ + DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ testDogu.String(): { - crd.ConfigEntryDiff{ - Key: testDoguKey1.Key.String(), - Actual: crd.ConfigValueState{ - Value: nil, - Exists: true, - }, - Expected: crd.ConfigValueState{ - Value: nil, - Exists: true, + SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{ + { + Key: testDoguKey1.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + Expected: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), }, - NeededAction: crd.ConfigAction("set"), }, }, testDogu2.String(): { - crd.ConfigEntryDiff{ - Key: testDoguKey2.Key.String(), - Actual: crd.ConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: crd.ConfigValueState{ - Value: nil, - Exists: true, + SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{ + { + Key: testDoguKey2.Key.String(), + Actual: crd.ConfigValueState{ + Value: nil, + Exists: false, + }, + Expected: crd.ConfigValueState{ + Value: nil, + Exists: true, + }, + NeededAction: crd.ConfigAction("set"), }, - NeededAction: crd.ConfigAction("set"), }, }, }, diff --git a/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader.go b/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader.go index 7a76f560..b772310f 100644 --- a/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader.go +++ b/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" + "iter" + "maps" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "iter" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "maps" ) type SecretRefReader struct { @@ -23,7 +24,7 @@ func NewSecretRefReader(secretClient secretClient) *SecretRefReader { } } -func (reader *SecretRefReader) GetValues(ctx context.Context, refs map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef) (map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, error) { +func (reader *SecretRefReader) GetValues(ctx context.Context, refs map[common.DoguConfigKey]domain.SensitiveValueRef) (map[common.DoguConfigKey]common.SensitiveDoguConfigValue, error) { secretsByName, secretErrors := reader.loadNeededSecrets(ctx, maps.Values(refs)) sensitiveConfig, keyErrors := reader.loadKeysFromSecrets(refs, secretsByName) @@ -38,11 +39,11 @@ func (reader *SecretRefReader) GetValues(ctx context.Context, refs map[common.Se } func (reader *SecretRefReader) loadKeysFromSecrets( - refs map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef, + refs map[common.DoguConfigKey]domain.SensitiveValueRef, secretsByName map[string]*v1.Secret, -) (map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, error) { +) (map[common.DoguConfigKey]common.SensitiveDoguConfigValue, error) { var errs []error - loadedConfig := map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{} + loadedConfig := map[common.DoguConfigKey]common.SensitiveDoguConfigValue{} for configKey, ref := range refs { secret, found := secretsByName[ref.SecretName] diff --git a/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader_test.go b/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader_test.go index 7ad5f2dc..29f474ea 100644 --- a/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader_test.go +++ b/pkg/adapter/kubernetes/sensitiveconfigref/secretRefReader_test.go @@ -2,6 +2,8 @@ package sensitiveconfigref import ( "context" + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" @@ -10,7 +12,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "testing" ) var testCtx = context.TODO() @@ -21,11 +22,11 @@ var ( }, Data: map[string][]byte{"username": []byte("user1"), "password": []byte("123456")}, } - redmineCredentialsUsernameKey = common.SensitiveDoguConfigKey{ + redmineCredentialsUsernameKey = common.DoguConfigKey{ DoguName: "redmine", Key: "credentials/username", } - redmineCredentialsPasswordKey = common.SensitiveDoguConfigKey{ + redmineCredentialsPasswordKey = common.DoguConfigKey{ DoguName: "redmine", Key: "credentials/password", } @@ -36,11 +37,11 @@ var ( }, Data: map[string][]byte{"username": []byte("user2"), "password": []byte("789123")}, } - ldapCredentialsUsernameKey = common.SensitiveDoguConfigKey{ + ldapCredentialsUsernameKey = common.DoguConfigKey{ DoguName: "ldap", Key: "credentials/username", } - ldapCredentialsPasswordKey = common.SensitiveDoguConfigKey{ + ldapCredentialsPasswordKey = common.DoguConfigKey{ DoguName: "ldap", Key: "credentials/password", } @@ -51,9 +52,9 @@ func TestSecretRefReader_GetValues(t *testing.T) { secretMock := newMockSecretClient(t) refReader := NewSecretRefReader(secretMock) - result, err := refReader.GetValues(testCtx, map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{}) + result, err := refReader.GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}) require.NoError(t, err) - assert.Equal(t, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, result) + assert.Equal(t, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, result) }) t.Run("nothing to load with nil input", func(t *testing.T) { secretMock := newMockSecretClient(t) @@ -61,7 +62,7 @@ func TestSecretRefReader_GetValues(t *testing.T) { result, err := refReader.GetValues(testCtx, nil) require.NoError(t, err) - assert.Equal(t, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, result) + assert.Equal(t, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, result) }) t.Run("load secrets with keys", func(t *testing.T) { secretMock := newMockSecretClient(t) @@ -75,7 +76,7 @@ func TestSecretRefReader_GetValues(t *testing.T) { refReader := NewSecretRefReader(secretMock) result, err := refReader.GetValues(testCtx, - map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ + map[common.DoguConfigKey]domain.SensitiveValueRef{ redmineCredentialsUsernameKey: { SecretName: redmineSecret.Name, SecretKey: "username", @@ -95,7 +96,7 @@ func TestSecretRefReader_GetValues(t *testing.T) { }, ) require.NoError(t, err) - assert.Equal(t, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ + assert.Equal(t, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{ redmineCredentialsUsernameKey: "user1", redmineCredentialsPasswordKey: "123456", ldapCredentialsUsernameKey: "user2", @@ -119,7 +120,7 @@ func TestSecretRefReader_GetValues(t *testing.T) { refReader := NewSecretRefReader(secretMock) _, err := refReader.GetValues(testCtx, - map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ + map[common.DoguConfigKey]domain.SensitiveValueRef{ redmineCredentialsUsernameKey: { SecretName: redmineSecret.Name, SecretKey: "username", diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 24b09959..8f19a515 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -731,7 +731,7 @@ func getRemoveDoguConfigEntryDiff(key string, doguName cescommons.SimpleName) do func getSensitiveDoguConfigEntryDiffForAction(key, value string, doguName cescommons.SimpleName, action domain.ConfigAction) domain.SensitiveDoguConfigEntryDiff { return domain.SensitiveDoguConfigEntryDiff{ - Key: common.SensitiveDoguConfigKey{ + Key: common.DoguConfigKey{ Key: config.Key(key), DoguName: doguName, }, @@ -744,7 +744,7 @@ func getSensitiveDoguConfigEntryDiffForAction(key, value string, doguName cescom func getRemoveSensitiveDoguConfigEntryDiff(key string, doguName cescommons.SimpleName) domain.SensitiveDoguConfigEntryDiff { return domain.SensitiveDoguConfigEntryDiff{ - Key: common.SensitiveDoguConfigKey{ + Key: common.DoguConfigKey{ Key: config.Key(key), DoguName: doguName, }, diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index e4e8c2b9..a722aa7b 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -58,7 +58,7 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri logger.V(2).Info("load referenced sensitive config") // load referenced config before collecting ecosystem state // if an error happens here, we save a lot of heavy work - var referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue + var referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue var err error if blueprint.EffectiveBlueprint.Config != nil { referencedSensitiveConfig, err = useCase.sensitiveConfigRefReader.GetValues( diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index bd8a27b6..cb2d5b3c 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -362,12 +362,14 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: &domain.Config{ - Global: domain.GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "globalKey1": common.GlobalConfigValue(val1), + Global: domain.GlobalConfigEntries{ + { + Key: "globalKey1", + Value: (*config.Value)(&val1), }, - Absent: []common.GlobalConfigKey{ - "globalKey2", + { + Key: "globalKey2", + Absent: true, }, }, }, @@ -430,18 +432,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: &domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: common.DoguConfigValue(val3), - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, + { + Key: nginxStaticConfigKeyNginxKey1.Key, + Value: (*config.Value)(&val3), + }, + { + Key: nginxStaticConfigKeyNginxKey2.Key, + Absent: true, }, - SensitiveConfig: domain.SensitiveDoguConfig{}, }, }, }, @@ -518,19 +518,20 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Config: &domain.Config{ - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ nginxStatic: { - DoguName: nginxStatic, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ - nginxStaticSensitiveConfigKeyNginxKey1: { - SecretName: "nginx-conf", - SecretKey: "nginxKey1", - }, // val3 - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, - }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey1.Key, + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "nginx-conf", + SecretKey: "nginxKey1", + }, // val3 + }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey2.Key, + Sensitive: true, + Absent: true, }, }, }, @@ -574,7 +575,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { configRefReaderMock.EXPECT(). GetValues( testCtx, - blueprint.EffectiveBlueprint.Config.Dogus[nginxStatic].SensitiveConfig.Present, + blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), ). Return(map[common.DoguConfigKey]config.Value{ nginxStaticConfigKeyNginxKey1: config.Value(val3), @@ -624,36 +625,39 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ Config: &domain.Config{ - Global: domain.GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "globalKey1": "globalValue", + Global: domain.GlobalConfigEntries{ + { + Key: "globalKey1", + Value: (*config.Value)(&val1), }, - Absent: []common.GlobalConfigKey{ - "globalKey2", + { + Key: "globalKey2", + Absent: true, }, }, - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, + { + Key: nginxStaticConfigKeyNginxKey1.Key, + Value: (*config.Value)(&val1), }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ - nginxStaticSensitiveConfigKeyNginxKey1: { - SecretName: "nginx-conf", - SecretKey: "nginxKey1", - }, //"nginxVal1" - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, + { + Key: nginxStaticConfigKeyNginxKey2.Key, + Absent: true, + }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey1.Key, + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "nginx-conf", + SecretKey: "nginxKey1", }, }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey2.Key, + Sensitive: true, + Absent: true, + }, }, }, }, @@ -705,36 +709,39 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ Config: &domain.Config{ - Global: domain.GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "globalKey1": "globalValue", + Global: domain.GlobalConfigEntries{ + { + Key: "globalKey1", + Value: (*config.Value)(&val1), }, - Absent: []common.GlobalConfigKey{ - "globalKey2", + { + Key: "globalKey2", + Absent: true, }, }, - Dogus: map[cescommons.SimpleName]domain.CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ nginxStaticQualifiedDoguName.SimpleName: { - DoguName: nginxStaticQualifiedDoguName.SimpleName, - Config: domain.DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxStaticConfigKeyNginxKey1: "nginxVal1", - }, - Absent: []common.DoguConfigKey{ - nginxStaticConfigKeyNginxKey2, - }, + { + Key: nginxStaticConfigKeyNginxKey1.Key, + Value: (*config.Value)(&val1), }, - SensitiveConfig: domain.SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef{ - nginxStaticSensitiveConfigKeyNginxKey1: { - SecretName: "nginx-conf", - SecretKey: "nginxKey1", - }, //"nginxVal1" - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxStaticSensitiveConfigKeyNginxKey2, + { + Key: nginxStaticConfigKeyNginxKey2.Key, + Absent: true, + }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey1.Key, + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "nginx-conf", + SecretKey: "nginxKey1", }, }, + { + Key: nginxStaticSensitiveConfigKeyNginxKey2.Key, + Sensitive: true, + Absent: true, + }, }, }, }, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 99db5e67..271f6812 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -248,7 +248,7 @@ func (spec *BlueprintSpec) MissingConfigReferences(error error) { // changed an error if the BlueprintSpec is not in the necessary state to determine the stateDiff. func (spec *BlueprintSpec) DetermineStateDiff( ecosystemState ecosystem.EcosystemState, - referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, + referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) error { doguDiffs := determineDoguDiffs(spec.EffectiveBlueprint.Dogus, ecosystemState.InstalledDogus) compDiffs, err := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index cb2e1b61..4f198cf7 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -175,8 +175,8 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ - officialDogu1.SimpleName: {}, + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ + officialDogu1.SimpleName: {ConfigEntry{Key: "test", Value: &val1}}, }, } @@ -236,7 +236,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { t.Run("validate only config for dogus in blueprint", func(t *testing.T) { config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ "my-dogu": {}, }, } @@ -299,7 +299,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { } // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) // then stateDiff := StateDiff{ @@ -347,7 +347,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { } // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) // then require.NoError(t, err) @@ -383,7 +383,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { } // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) @@ -417,7 +417,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { } // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) @@ -451,7 +451,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { } // when - err := spec.DetermineStateDiff(clusterState, map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}) + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index fcf8f6ca..1ce6cc76 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -60,10 +60,10 @@ func Test_validate_multipleErrors(t *testing.T) { Dogus: dogus, Components: components, Config: &Config{ - Global: GlobalConfig{ - Present: nil, - Absent: []common.GlobalConfigKey{ - "", + Global: GlobalConfigEntries{ + { + Key: "", + Absent: true, }, }, }, @@ -77,7 +77,7 @@ func Test_validate_multipleErrors(t *testing.T) { assert.ErrorContains(t, err, "dogu version must not be empty") assert.ErrorContains(t, err, "component name must not be empty") assert.ErrorContains(t, err, `namespace of component "" must not be empty`) - assert.ErrorContains(t, err, `key for absent global config should not be empty`) + assert.ErrorContains(t, err, `key for global config should not be empty`) } func Test_validateDogus_ok(t *testing.T) { diff --git a/pkg/domain/common/configNames.go b/pkg/domain/common/configNames.go index 9c4d2bcf..8a4982fd 100644 --- a/pkg/domain/common/configNames.go +++ b/pkg/domain/common/configNames.go @@ -1,7 +1,6 @@ package common import ( - "errors" "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -15,24 +14,10 @@ type DoguConfigKey struct { Key config.Key } -func (k DoguConfigKey) Validate() error { - var errs []error - if k.DoguName == "" { - errs = append(errs, fmt.Errorf("dogu name for dogu config key %q should not be empty", k.Key)) - } - if string(k.Key) == "" { - errs = append(errs, fmt.Errorf("key for dogu config of dogu %q should not be empty", k.DoguName)) - } - - return errors.Join(errs...) -} - func (k DoguConfigKey) String() string { return fmt.Sprintf("key %q of dogu %q", k.Key, k.DoguName) } -type SensitiveDoguConfigKey = DoguConfigKey - // GlobalConfigValue is a single global config value type GlobalConfigValue = config.Value diff --git a/pkg/domain/config.go b/pkg/domain/config.go index 1ac6c110..73c23b5f 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -3,33 +3,36 @@ package domain import ( "errors" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" - "golang.org/x/exp/maps" - "slices" + libconfig "github.com/cloudogu/k8s-registry-lib/config" ) type Config struct { - Dogus map[cescommons.SimpleName]CombinedDoguConfig - Global GlobalConfig -} - -type CombinedDoguConfig struct { - DoguName cescommons.SimpleName - Config DoguConfig - SensitiveConfig SensitiveDoguConfig -} - -type DoguConfig struct { - Present map[common.DoguConfigKey]common.DoguConfigValue - Absent []common.DoguConfigKey -} - -type SensitiveDoguConfig struct { - Present map[common.DoguConfigKey]SensitiveValueRef - Absent []common.DoguConfigKey -} + Dogus DoguConfig + Global GlobalConfigEntries +} + +type ConfigEntry struct { + // Key is the configuration key name + Key libconfig.Key + // Absent indicates whether this key should be deleted (true) or set (false) + Absent bool + // Value is used for regular (non-sensitive) configuration entries + // Mutually exclusive with SecretRef + Value *libconfig.Value + // Sensitive indicates whether this config is sensitive and should be stored securely (true) or not (false) + Sensitive bool + // SecretRef is used for sensitive configuration entries + // Mutually exclusive with Value + SecretRef *SensitiveValueRef +} + +type DoguConfig map[cescommons.SimpleName]DoguConfigEntries +type DoguConfigEntries ConfigEntries +type GlobalConfigEntries ConfigEntries +type ConfigEntries []ConfigEntry type SensitiveValueRef struct { // SecretName is the name of the secret, from which the config key should be loaded. @@ -40,63 +43,71 @@ type SensitiveValueRef struct { SecretKey string `json:"secretKey"` } -type GlobalConfig struct { - Present map[common.GlobalConfigKey]common.GlobalConfigValue - Absent []common.GlobalConfigKey -} - -func (config GlobalConfig) GetGlobalConfigKeys() []common.GlobalConfigKey { +func (config GlobalConfigEntries) GetGlobalConfigKeys() []common.GlobalConfigKey { var keys []common.GlobalConfigKey - keys = append(keys, maps.Keys(config.Present)...) - keys = append(keys, config.Absent...) + for _, entry := range config { + keys = append(keys, entry.Key) + } return keys } func (config Config) GetDoguConfigKeys() []common.DoguConfigKey { - var keys []common.DoguConfigKey - for _, doguConfig := range config.Dogus { - keys = append(keys, maps.Keys(doguConfig.Config.Present)...) - keys = append(keys, doguConfig.Config.Absent...) - } - return keys + return config.getDoguKeysBySensitivity(false) } -func (config Config) GetSensitiveConfigReferences() map[common.SensitiveDoguConfigKey]SensitiveValueRef { - refs := map[common.SensitiveDoguConfigKey]SensitiveValueRef{} - for _, doguConfig := range config.Dogus { - for key, ref := range doguConfig.SensitiveConfig.Present { - refs[key] = ref +func (config Config) GetSensitiveConfigReferences() map[common.DoguConfigKey]SensitiveValueRef { + refs := map[common.DoguConfigKey]SensitiveValueRef{} + for doguName, doguConfig := range config.Dogus { + for _, entry := range doguConfig { + if entry.SecretRef != nil { + key := common.DoguConfigKey{ + DoguName: doguName, + Key: entry.Key, + } + refs[key] = *entry.SecretRef + } } } return refs } -func (config Config) GetSensitiveDoguConfigKeys() []common.SensitiveDoguConfigKey { - var keys []common.SensitiveDoguConfigKey - for _, doguConfig := range config.Dogus { - keys = append(keys, maps.Keys(doguConfig.SensitiveConfig.Present)...) - keys = append(keys, doguConfig.SensitiveConfig.Absent...) +func (config Config) GetSensitiveDoguConfigKeys() []common.DoguConfigKey { + return config.getDoguKeysBySensitivity(true) +} + +func (config Config) getDoguKeysBySensitivity(isSensitive bool) []common.DoguConfigKey { + var keys []common.DoguConfigKey + for doguName, doguConfig := range config.Dogus { + for _, entry := range doguConfig { + if entry.Sensitive == isSensitive { + keys = append(keys, common.DoguConfigKey{ + DoguName: doguName, + Key: entry.Key, + }) + } + } } return keys } // GetDogusWithChangedConfig returns a list of dogus for which possible config changes are needed. func (config Config) GetDogusWithChangedConfig() []cescommons.SimpleName { - var dogus []cescommons.SimpleName - for dogu, doguConfig := range config.Dogus { - if len(doguConfig.Config.Present) != 0 || len(doguConfig.Config.Absent) != 0 { - dogus = append(dogus, dogu) - } - } - return dogus + return config.getDogusWithChangedConfigBySenisitivity(false) } // GetDogusWithChangedSensitiveConfig returns a list of dogus for which possible sensitive config changes are needed. func (config Config) GetDogusWithChangedSensitiveConfig() []cescommons.SimpleName { + return config.getDogusWithChangedConfigBySenisitivity(true) +} + +func (config Config) getDogusWithChangedConfigBySenisitivity(isSensitive bool) []cescommons.SimpleName { var dogus []cescommons.SimpleName for dogu, doguConfig := range config.Dogus { - if len(doguConfig.SensitiveConfig.Present) != 0 || len(doguConfig.SensitiveConfig.Absent) != 0 { - dogus = append(dogus, dogu) + for _, entry := range doguConfig { + if entry.Sensitive == isSensitive { + dogus = append(dogus, dogu) + break // we only need to know if this dogu has at least one desired config value, so we can break to next outer loop + } } } return dogus @@ -105,156 +116,107 @@ func (config Config) GetDogusWithChangedSensitiveConfig() []cescommons.SimpleNam func (config Config) validate() error { var errs []error for doguName, doguConfig := range config.Dogus { - if doguName != doguConfig.DoguName { - errs = append(errs, fmt.Errorf("dogu name %q in map and dogu name %q in value are not equal", doguName, doguConfig.DoguName)) + if doguName == "" { + errs = append(errs, fmt.Errorf("dogu name for dogu config should not be empty")) + } - errs = append(errs, doguConfig.validate()) + errs = append(errs, doguConfig.validate(doguName)) } errs = append(errs, config.Global.validate()) return errors.Join(errs...) } -func (config CombinedDoguConfig) validate() error { - err := errors.Join( - config.Config.validate(config.DoguName), - config.SensitiveConfig.validate(config.DoguName), - config.validateConflictingConfigKeys(), - ) +func (config DoguConfigEntries) validate(doguName cescommons.SimpleName) error { + var allErrs error + for _, entry := range config { + allErrs = errors.Join(allErrs, entry.validate()) + } + allErrs = errors.Join(allErrs, ConfigEntries(config).validateConflictingConfigKeys()) - if err != nil { - return fmt.Errorf("config for dogu %q is invalid: %w", config.DoguName, err) + if allErrs != nil { + return fmt.Errorf("config for dogu %q is invalid: %w", doguName, allErrs) } return nil } // validateConflictingConfigKeys checks that there are no conflicting keys in normal config and sensitive config. // This is a problem as both config types are loaded via the same API in dogus at the moment. -func (config CombinedDoguConfig) validateConflictingConfigKeys() error { - var normalKeys []common.DoguConfigKey - normalKeys = append(normalKeys, maps.Keys(config.Config.Present)...) - normalKeys = append(normalKeys, config.Config.Absent...) - var sensitiveKeys []common.SensitiveDoguConfigKey - sensitiveKeys = append(sensitiveKeys, maps.Keys(config.SensitiveConfig.Present)...) - sensitiveKeys = append(sensitiveKeys, config.SensitiveConfig.Absent...) - +func (config ConfigEntries) validateConflictingConfigKeys() error { var errorList []error - - for _, sensitiveKey := range sensitiveKeys { - if slices.Contains(normalKeys, sensitiveKey) { - errorList = append(errorList, fmt.Errorf("dogu config key %q of dogu %q cannot be in normal and sensitive configuration at the same time", sensitiveKey.Key, sensitiveKey.DoguName)) + seen := make(map[libconfig.Key]struct{}) + for _, entry := range config { + if _, exists := seen[entry.Key]; exists { + errorList = append(errorList, fmt.Errorf("duplicate dogu config Key found: %s", entry.Key)) } + seen[entry.Key] = struct{}{} } return errors.Join(errorList...) } -func validateDoguConfigKeys(keys []common.DoguConfigKey, referencedDoguName cescommons.SimpleName) error { +// Validate ensures ConfigEntry has valid state +func (config ConfigEntry) validate() error { var errs []error - for _, configKey := range keys { - err := configKey.Validate() - if err != nil { - errs = append(errs, fmt.Errorf("dogu config key is invalid: %w", err)) - } - // validate that all keys are of the same dogu - if referencedDoguName != configKey.DoguName { - errs = append(errs, fmt.Errorf("key %q of dogu %q does not match superordinate dogu name %q", configKey.Key, configKey.DoguName, referencedDoguName)) - } + if config.Key == "" { + errs = append(errs, fmt.Errorf("key for config should not be empty")) } - return errors.Join(errs...) -} -func validateNoDuplicates(presentKeys []common.DoguConfigKey, absentKeys []common.DoguConfigKey) error { - var errs []error - // no present keys in absent - // a duplicate needs to be in the present and the absent list, therefore we only need to check one of the lists. - for _, presentKey := range presentKeys { - if slices.Contains(absentKeys, presentKey) { - errs = append(errs, fmt.Errorf("key %q of dogu %q cannot be present and absent at the same time", presentKey.Key, presentKey.DoguName)) + if config.Absent { + if config.Value != nil || config.SecretRef != nil { + errs = append(errs, fmt.Errorf("absent entries cannot have value or secretRef")) } + return errors.Join(errs...) } - // no absent duplicates - absentDuplicates := util.GetDuplicates(absentKeys) - if len(absentDuplicates) > 0 { - errs = append(errs, fmt.Errorf("absent dogu config should not contain duplicate keys: %v", absentDuplicates)) - } - - // present keys cannot have duplicates because of their data structure - - return errors.Join(errs...) -} - -func (config DoguConfig) validate(referencedDoguName cescommons.SimpleName) error { - var errs []error - - presentKeyErr := validateDoguConfigKeys(maps.Keys(config.Present), referencedDoguName) - if presentKeyErr != nil { - errs = append(errs, fmt.Errorf("present dogu config is invalid: %w", presentKeyErr)) - } + // For present entries, exactly one of Value or SecretRef must be set + hasValue := config.Value != nil + hasSecretRef := config.SecretRef != nil - absentKeyErr := validateDoguConfigKeys(config.Absent, referencedDoguName) - if absentKeyErr != nil { - errs = append(errs, fmt.Errorf("absent dogu config is invalid: %w", absentKeyErr)) - } - - duplicatesErr := validateNoDuplicates(maps.Keys(config.Present), config.Absent) - if duplicatesErr != nil { - errs = append(errs, fmt.Errorf("dogu config is invalid: %w", duplicatesErr)) + // TODO: See test, is both nil allowed? + if hasValue == hasSecretRef { + errs = append(errs, fmt.Errorf("config entries can have either a value or a secretRef")) } return errors.Join(errs...) } -func (config SensitiveDoguConfig) validate(referencedDoguName cescommons.SimpleName) error { - var errs []error - - presentKeyErr := validateDoguConfigKeys(maps.Keys(config.Present), referencedDoguName) - if presentKeyErr != nil { - errs = append(errs, fmt.Errorf("present sensitive dogu config is invalid: %w", presentKeyErr)) +func (config GlobalConfigEntries) validate() error { + var allErrs error + for _, entry := range config { + allErrs = errors.Join(allErrs, entry.validateGlobal()) } + allErrs = errors.Join(allErrs, ConfigEntries(config).validateConflictingConfigKeys()) - absentKeyErr := validateDoguConfigKeys(config.Absent, referencedDoguName) - if absentKeyErr != nil { - errs = append(errs, fmt.Errorf("absent sensitive dogu config is invalid: %w", absentKeyErr)) + if allErrs != nil { + return fmt.Errorf("global config is invalid: %w", allErrs) } - - duplicatesErr := validateNoDuplicates(maps.Keys(config.Present), config.Absent) - if duplicatesErr != nil { - errs = append(errs, fmt.Errorf("dogu config is invalid: %w", duplicatesErr)) - } - - return errors.Join(errs...) + return nil } -func (config GlobalConfig) validate() error { +func (config ConfigEntry) validateGlobal() error { var errs []error - for configKey := range config.Present { - // empty key is not allowed - if string(configKey) == "" { - errs = append(errs, fmt.Errorf("key for present global config should not be empty")) - } + if config.Key == "" { + errs = append(errs, fmt.Errorf("key for global config should not be empty")) } - for _, configKey := range config.Absent { - - // empty key is not allowed - if string(configKey) == "" { - errs = append(errs, fmt.Errorf("key for absent global config should not be empty")) - } + if config.Sensitive || config.SecretRef != nil { + errs = append(errs, fmt.Errorf("global entries cannot be sensitive")) + } - // absent keys cannot be present - _, isPresent := config.Present[configKey] - if isPresent { - errs = append(errs, fmt.Errorf("global config key %q cannot be present and absent at the same time", configKey)) + if config.Absent { + if config.Value != nil { + errs = append(errs, fmt.Errorf("absent entries cannot have value")) } + return errors.Join(errs...) } - absentDuplicates := util.GetDuplicates(config.Absent) - if len(absentDuplicates) > 0 { - errs = append(errs, fmt.Errorf("absent global config should not contain duplicate keys: %v", absentDuplicates)) - } + // For present entries, a Value must be set + // TODO: See test, is empty string allowed? + //if config.Value == nil { + // errs = append(errs, fmt.Errorf("config entries must have a value")) + //} return errors.Join(errs...) } diff --git a/pkg/domain/config_test.go b/pkg/domain/config_test.go index c110924a..9d2529c1 100644 --- a/pkg/domain/config_test.go +++ b/pkg/domain/config_test.go @@ -1,26 +1,35 @@ package domain import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + libconfig "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" - "testing" ) +var confgiVal1 = libconfig.Value("value1") + func TestGlobalConfig_validate(t *testing.T) { t.Run("empty config is ok", func(t *testing.T) { - config := GlobalConfig{} + config := GlobalConfigEntries{} err := config.validate() assert.NoError(t, err) }) t.Run("config is ok", func(t *testing.T) { - config := GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "my/key1": "", //empty values are ok - "my/key2": "test", + config := GlobalConfigEntries{ + { + Key: "my/key1", + Value: nil, //empty values are ok }, - Absent: []common.GlobalConfigKey{ - "key3", + { + Key: "my/key2", + Value: &confgiVal1, + }, + { + Key: "key3", + Absent: true, }, } @@ -29,204 +38,193 @@ func TestGlobalConfig_validate(t *testing.T) { assert.NoError(t, err) }) t.Run("no empty present keys", func(t *testing.T) { - config := GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "": "", + config := GlobalConfigEntries{ + { + Key: "", + Value: nil, }, } err := config.validate() - assert.ErrorContains(t, err, "key for present global config should not be empty") + assert.ErrorContains(t, err, "key for global config should not be empty") }) t.Run("no empty absent keys", func(t *testing.T) { - config := GlobalConfig{ - Absent: []common.GlobalConfigKey{""}, + config := GlobalConfigEntries{ + { + Key: "", + Absent: true, + }, } err := config.validate() - assert.ErrorContains(t, err, "key for absent global config should not be empty") + assert.ErrorContains(t, err, "key for global config should not be empty") }) t.Run("not present and absent at the same time", func(t *testing.T) { - config := GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "my/key1": "test", + config := GlobalConfigEntries{ + { + Key: "my/key1", + Value: &confgiVal1, }, - Absent: []common.GlobalConfigKey{ - "my/key1", + { + Key: "my/key1", + Absent: true, }, } err := config.validate() - assert.ErrorContains(t, err, "config key \"my/key1\" cannot be present and absent at the same time") + assert.ErrorContains(t, err, "duplicate dogu config Key found: my/key1") }) t.Run("combine errors", func(t *testing.T) { - config := GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "": "", - "my/key1": "test", + config := GlobalConfigEntries{ + { + Key: "", + Value: &confgiVal1, + }, + { + Key: "my/key1", + Value: &confgiVal1, }, - Absent: []common.GlobalConfigKey{ - "my/key1", + { + Key: "my/key1", + Absent: true, }, } err := config.validate() - assert.ErrorContains(t, err, "key for present global config should not be empty") - assert.ErrorContains(t, err, "config key \"my/key1\" cannot be present and absent at the same time") - }) - t.Run("not same key multiple times", func(t *testing.T) { - config := GlobalConfig{ - Absent: []common.GlobalConfigKey{"my/key", "my/key"}, - } - err := config.validate() - assert.ErrorContains(t, err, "absent global config should not contain duplicate keys") + assert.ErrorContains(t, err, "key for global config should not be empty") + assert.ErrorContains(t, err, "duplicate dogu config Key found: my/key1") }) } func TestDoguConfig_validate(t *testing.T) { t.Run("empty is ok", func(t *testing.T) { - config := DoguConfig{} + config := DoguConfigEntries{} err := config.validate("dogu1") assert.NoError(t, err) }) t.Run("config is ok", func(t *testing.T) { - config := DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - common.DoguConfigKey{DoguName: "dogu1", Key: "my/key1"}: "value1", + config := DoguConfigEntries{ + { + Key: "my/key1", + Value: &confgiVal1, }, - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: "my/key2"}, + { + Key: "my/key2", + Absent: true, }, } err := config.validate("dogu1") assert.NoError(t, err) }) t.Run("not absent and present at the same time", func(t *testing.T) { - config := DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - common.DoguConfigKey{DoguName: "dogu1", Key: "my/key"}: "value1", + config := DoguConfigEntries{ + { + Key: "my/key1", + Value: &confgiVal1, }, - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, + { + Key: "my/key1", + Absent: true, }, } err := config.validate("dogu1") - assert.ErrorContains(t, err, "key \"my/key\" of dogu \"dogu1\" cannot be present and absent at the same time") + assert.ErrorContains(t, err, "duplicate dogu config Key found: my/key1") }) t.Run("not same key multiple times", func(t *testing.T) { - config := DoguConfig{ - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, - {DoguName: "dogu1", Key: "my/key"}, + config := DoguConfigEntries{ + { + Key: "my/key1", + Value: &confgiVal1, }, - } - err := config.validate("dogu1") - assert.ErrorContains(t, err, "absent dogu config should not contain duplicate keys: [key \"my/key\" of dogu \"dogu1\"]") - }) - t.Run("only one referenced dogu name", func(t *testing.T) { - config := DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - common.DoguConfigKey{DoguName: "dogu1", Key: "test"}: "value1", - }, - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, + { + Key: "my/key1", + Value: &confgiVal1, }, } - err := config.validate("dogu2") - assert.ErrorContains(t, err, "dogu config is invalid: key \"test\" of dogu \"dogu1\" does not match superordinate dogu name \"dogu2\"") - assert.ErrorContains(t, err, "absent dogu config is invalid: key \"my/key\" of dogu \"dogu1\" does not match superordinate dogu name \"dogu2\"") + err := config.validate("dogu1") + assert.ErrorContains(t, err, "duplicate dogu config Key found: my/key1") }) - t.Run("combine errors", func(t *testing.T) { - config := DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - common.DoguConfigKey{DoguName: "dogu1", Key: ""}: "value1", - }, - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: ""}, + t.Run("no empty present keys", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "", + Value: nil, }, } + err := config.validate("dogu1") - assert.ErrorContains(t, err, "present dogu config is invalid") - assert.ErrorContains(t, err, "absent dogu config is invalid") - }) -} -func TestSensitiveDoguConfig_validate(t *testing.T) { - t.Run("empty is ok", func(t *testing.T) { - config := SensitiveDoguConfig{} - err := config.validate("") - assert.NoError(t, err) + assert.ErrorContains(t, err, "key for config should not be empty") }) - t.Run("config is ok", func(t *testing.T) { - config := SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - common.SensitiveDoguConfigKey{DoguName: "dogu1", Key: "my/key1"}: { - SecretName: "mySecret", - SecretKey: "secretKey", - }, - }, - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: "my/key2"}, + t.Run("no empty absent keys", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "", + Absent: true, }, } + err := config.validate("dogu1") - assert.NoError(t, err) + + assert.ErrorContains(t, err, "key for config should not be empty") }) - t.Run("not absent and present at the same time", func(t *testing.T) { - config := SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - common.SensitiveDoguConfigKey{DoguName: "dogu1", Key: "my/key"}: { - SecretName: "mySecret", - SecretKey: "secretKey", - }, + t.Run("combine errors", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "", + Value: &confgiVal1, }, - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, + { + Key: "my/key1", + Value: &confgiVal1, + }, + { + Key: "my/key1", + Absent: true, }, } err := config.validate("dogu1") - assert.ErrorContains(t, err, "key \"my/key\" of dogu \"dogu1\" cannot be present and absent at the same time") + assert.ErrorContains(t, err, "config for dogu \"dogu1\" is invalid") + assert.ErrorContains(t, err, "key for config should not be empty") + assert.ErrorContains(t, err, "duplicate dogu config Key found: my/key1") }) - t.Run("not same key multiple times", func(t *testing.T) { - config := SensitiveDoguConfig{ - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, - {DoguName: "dogu1", Key: "my/key"}, + t.Run("No absent with value", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + Value: &confgiVal1, + Absent: true, }, } err := config.validate("dogu1") - assert.ErrorContains(t, err, "absent dogu config should not contain duplicate keys: [key \"my/key\" of dogu \"dogu1\"]") + assert.ErrorContains(t, err, "absent entries cannot have value or secretRef") }) - t.Run("only one referenced dogu name", func(t *testing.T) { - config := SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - common.SensitiveDoguConfigKey{DoguName: "dogu1", Key: "test"}: {}, - }, - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: "my/key"}, + t.Run("No absent with secret", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + SecretRef: &SensitiveValueRef{}, + Absent: true, }, } - err := config.validate("dogu2") - assert.ErrorContains(t, err, "present sensitive dogu config is invalid: key \"test\" of dogu \"dogu1\" does not match superordinate dogu name \"dogu2\"") - assert.ErrorContains(t, err, "absent sensitive dogu config is invalid: key \"my/key\" of dogu \"dogu1\" does not match superordinate dogu name \"dogu2\"") + err := config.validate("dogu1") + assert.ErrorContains(t, err, "absent entries cannot have value or secretRef") }) - t.Run("combine errors", func(t *testing.T) { - config := SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - common.SensitiveDoguConfigKey{DoguName: "dogu1", Key: ""}: {}, - }, - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: ""}, + t.Run("No secret and value", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + SecretRef: &SensitiveValueRef{}, + Value: &confgiVal1, }, } err := config.validate("dogu1") - assert.ErrorContains(t, err, "present sensitive dogu config is invalid") - assert.ErrorContains(t, err, "absent sensitive dogu config is invalid") + assert.ErrorContains(t, err, "config entries can have either a value or a secretRef") }) } @@ -241,44 +239,46 @@ func TestConfig_validate(t *testing.T) { // then assert.NoError(t, err) }) - t.Run("fail if dogu name in dogu config does not match dogu key", func(t *testing.T) { - // given - sut := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ - "some-name": {DoguName: "another-name"}, - }, - } - - // when - err := sut.validate() - - // then - assert.ErrorContains(t, err, "dogu name \"some-name\" in map and dogu name \"another-name\" in value are not equal") - }) t.Run("fail with multiple errors", func(t *testing.T) { // given sut := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ - "some-name": { - DoguName: "another-name", - Config: DoguConfig{ - Absent: []common.DoguConfigKey{{DoguName: ""}}, + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ + "dogu1": { + ConfigEntry{ + Key: "", + Value: &confgiVal1, + }, + }, + "dogu2": { + ConfigEntry{ + Key: "myKey1", + Value: &confgiVal1, }, - SensitiveConfig: SensitiveDoguConfig{ - Absent: []common.SensitiveDoguConfigKey{{DoguName: ""}}, + ConfigEntry{ + Key: "myKey1", + Absent: true, }, }, }, - Global: GlobalConfig{Absent: []common.GlobalConfigKey{""}}, + Global: GlobalConfigEntries{ + ConfigEntry{ + Key: "myKey", + Value: &confgiVal1, + Absent: true, + }, + }, } // when err := sut.validate() // then - assert.ErrorContains(t, err, "dogu name \"some-name\" in map and dogu name \"another-name\" in value are not equal") - assert.ErrorContains(t, err, "config for dogu \"another-name\" is invalid") - assert.ErrorContains(t, err, "key for absent global config should not be empty") + assert.ErrorContains(t, err, "config for dogu \"dogu1\" is invalid") + assert.ErrorContains(t, err, "key for config should not be empty") + assert.ErrorContains(t, err, "config for dogu \"dogu2\" is invalid") + assert.ErrorContains(t, err, "duplicate dogu config Key found: myKey1") + assert.ErrorContains(t, err, "global config is invalid") + assert.ErrorContains(t, err, "absent entries cannot have value") }) } @@ -287,12 +287,14 @@ func TestGlobalConfig_GetGlobalConfigKeys(t *testing.T) { globalKey1 = common.GlobalConfigKey("key1") globalKey2 = common.GlobalConfigKey("key2") ) - config := GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - globalKey1: "value", + config := GlobalConfigEntries{ + { + Key: globalKey1, + Value: &confgiVal1, }, - Absent: []common.GlobalConfigKey{ - globalKey2, + { + Key: globalKey2, + Absent: true, }, } @@ -309,32 +311,34 @@ func TestConfig_GetDoguConfigKeys(t *testing.T) { nginxKey2 = common.DoguConfigKey{DoguName: nginx, Key: "key2"} postfixKey1 = common.DoguConfigKey{DoguName: postfix, Key: "key1"} postfixKey2 = common.DoguConfigKey{DoguName: postfix, Key: "key2"} + postfixKey3 = common.DoguConfigKey{DoguName: postfix, Key: "key3"} ) config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ nginx: { - DoguName: nginx, - Config: DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - nginxKey1: "value", - }, - Absent: []common.DoguConfigKey{ - nginxKey2, - }, + { + Key: nginxKey1.Key, + Value: &confgiVal1, + }, + { + Key: nginxKey2.Key, + Absent: true, }, - SensitiveConfig: SensitiveDoguConfig{}, }, postfix: { - DoguName: postfix, - Config: DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - postfixKey1: "value", - }, - Absent: []common.DoguConfigKey{ - postfixKey2, - }, + { + Key: postfixKey1.Key, + Value: &confgiVal1, + }, + { + Key: postfixKey2.Key, + Absent: true, + }, + { + Key: postfixKey3.Key, + Absent: true, + Sensitive: true, }, - SensitiveConfig: SensitiveDoguConfig{}, }, }, } @@ -348,33 +352,40 @@ func TestConfig_GetSensitiveDoguConfigKeys(t *testing.T) { var ( nginx = cescommons.SimpleName("nginx") postfix = cescommons.SimpleName("postfix") - nginxKey1 = common.SensitiveDoguConfigKey{DoguName: nginx, Key: "key1"} - nginxKey2 = common.SensitiveDoguConfigKey{DoguName: nginx, Key: "key2"} - postfixKey1 = common.SensitiveDoguConfigKey{DoguName: postfix, Key: "key1"} - postfixKey2 = common.SensitiveDoguConfigKey{DoguName: postfix, Key: "key2"} + nginxKey1 = common.DoguConfigKey{DoguName: nginx, Key: "key1"} + nginxKey2 = common.DoguConfigKey{DoguName: nginx, Key: "key2"} + postfixKey1 = common.DoguConfigKey{DoguName: postfix, Key: "key1"} + postfixKey2 = common.DoguConfigKey{DoguName: postfix, Key: "key2"} + postfixKey3 = common.DoguConfigKey{DoguName: postfix, Key: "key3"} ) config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ nginx: { - DoguName: nginx, - SensitiveConfig: SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - nginxKey1: {}, - }, - Absent: []common.SensitiveDoguConfigKey{ - nginxKey2, - }, + { + Key: nginxKey1.Key, + Value: &confgiVal1, + Sensitive: true, + }, + { + Key: nginxKey2.Key, + Absent: true, + Sensitive: true, }, }, postfix: { - DoguName: postfix, - SensitiveConfig: SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - postfixKey1: {}, - }, - Absent: []common.SensitiveDoguConfigKey{ - postfixKey2, - }, + { + Key: postfixKey1.Key, + Value: &confgiVal1, + Sensitive: true, + }, + { + Key: postfixKey2.Key, + Absent: true, + Sensitive: true, + }, + { + Key: postfixKey3.Key, + Absent: true, }, }, }, @@ -382,53 +393,31 @@ func TestConfig_GetSensitiveDoguConfigKeys(t *testing.T) { keys := config.GetSensitiveDoguConfigKeys() - assert.ElementsMatch(t, keys, []common.SensitiveDoguConfigKey{nginxKey1, nginxKey2, postfixKey1, postfixKey2}) + assert.ElementsMatch(t, keys, []common.DoguConfigKey{nginxKey1, nginxKey2, postfixKey1, postfixKey2}) } -func TestCombinedDoguConfig_validate(t *testing.T) { - normalConfig := DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - common.DoguConfigKey{DoguName: "dogu1", Key: "my/key1"}: "value1", - }, - Absent: []common.DoguConfigKey{ - {DoguName: "dogu1", Key: "my/key2"}, - }, - } - sensitiveConfig := SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - common.SensitiveDoguConfigKey{DoguName: "dogu1", Key: "my/key1"}: { - SecretName: "mySecret", - SecretKey: "myKey", - }, +func TestConfig_GetDogusWithChangedConfig(t *testing.T) { + doguConfig := DoguConfigEntries{ + { + Key: dogu1Key1.Key, + Value: &confgiVal1, }, - Absent: []common.SensitiveDoguConfigKey{ - {DoguName: "dogu1", Key: "my/key2"}, + { + Key: dogu1Key2.Key, + Absent: true, }, } - - config := CombinedDoguConfig{ - DoguName: "dogu1", - Config: normalConfig, - SensitiveConfig: sensitiveConfig, - } - - err := config.validate() - - assert.ErrorContains(t, err, "dogu config key \"my/key1\" of dogu \"dogu1\" cannot be in normal and sensitive configuration at the same time") -} - -func TestConfig_GetDogusWithChangedConfig(t *testing.T) { - presentConfig := map[common.DoguConfigKey]common.DoguConfigValue{ - dogu1Key1: "val", + configEntryPresent := ConfigEntry{ + Key: dogu1Key1.Key, + Value: &confgiVal1, } - AbsentConfig := []common.DoguConfigKey{ - dogu1Key1, + configEntryAbsent := ConfigEntry{ + Key: dogu1Key2.Key, + Absent: true, } - emptyPresentConfig := map[common.DoguConfigKey]common.DoguConfigValue{} - var emptyAbsentConfig []common.DoguConfigKey type args struct { - doguConfig DoguConfig + doguConfig DoguConfigEntries withDogu2Change bool } @@ -440,53 +429,46 @@ func TestConfig_GetDogusWithChangedConfig(t *testing.T) { }{ { name: "should get multiple Dogus", - args: args{doguConfig: DoguConfig{Present: presentConfig, Absent: AbsentConfig}, withDogu2Change: true}, + args: args{doguConfig: DoguConfigEntries{configEntryPresent, configEntryAbsent}, withDogu2Change: true}, want: []cescommons.SimpleName{dogu1, dogu2}, }, { name: "should get Dogus with changed present and absent config", - args: args{doguConfig: DoguConfig{Present: presentConfig, Absent: AbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{configEntryPresent, configEntryAbsent}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should get Dogus with changed present config", - args: args{doguConfig: DoguConfig{Present: presentConfig, Absent: emptyAbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{configEntryPresent}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should get Dogus with changed absent config", - args: args{doguConfig: DoguConfig{Present: emptyPresentConfig, Absent: AbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{configEntryAbsent}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should not get Dogus with no config changes", - args: args{doguConfig: DoguConfig{Present: emptyPresentConfig, Absent: emptyAbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{}}, + want: emptyResult, + }, + { + name: "should not get Dogus with sensitive config changes", + args: args{doguConfig: DoguConfigEntries{ConfigEntry{Key: "mykey", Value: &confgiVal1, Sensitive: true}}}, want: emptyResult, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - emptyDoguConfig := struct { - Present map[common.DoguConfigKey]SensitiveValueRef - Absent []common.DoguConfigKey - }{} config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ - dogu1: { - DoguName: dogu1, - Config: tt.args.doguConfig, - SensitiveConfig: emptyDoguConfig, - }, + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ + dogu1: tt.args.doguConfig, }, - Global: GlobalConfig{}, + Global: GlobalConfigEntries{}, } if tt.args.withDogu2Change { - config.Dogus[dogu2] = CombinedDoguConfig{ - DoguName: dogu2, - Config: tt.args.doguConfig, - SensitiveConfig: emptyDoguConfig, - } + config.Dogus[dogu2] = doguConfig } changedDogus := config.GetDogusWithChangedConfig() @@ -499,20 +481,22 @@ func TestConfig_GetDogusWithChangedConfig(t *testing.T) { } func TestConfig_GetDogusWithChangedSensitiveConfig(t *testing.T) { - presentConfig := map[common.DoguConfigKey]SensitiveValueRef{ - dogu1Key1: { + presentConfig := ConfigEntry{ + Key: dogu1Key1.Key, + Sensitive: true, + SecretRef: &SensitiveValueRef{ SecretName: "mySecret", SecretKey: "myKey", }, } - AbsentConfig := []common.DoguConfigKey{ - dogu1Key1, + absentConfig := ConfigEntry{ + Key: dogu1Key1.Key, + Sensitive: true, + Absent: true, } - emptyPresentConfig := map[common.DoguConfigKey]SensitiveValueRef{} - var emptyAbsentConfig []common.DoguConfigKey type args struct { - doguConfig SensitiveDoguConfig + doguConfig DoguConfigEntries withDogu2Change bool } @@ -524,53 +508,46 @@ func TestConfig_GetDogusWithChangedSensitiveConfig(t *testing.T) { }{ { name: "should get multiple Dogus", - args: args{doguConfig: SensitiveDoguConfig{Present: presentConfig, Absent: AbsentConfig}, withDogu2Change: true}, + args: args{doguConfig: DoguConfigEntries{presentConfig, absentConfig}, withDogu2Change: true}, want: []cescommons.SimpleName{dogu1, dogu2}, }, { name: "should get Dogus with changed present and absent config", - args: args{doguConfig: SensitiveDoguConfig{Present: presentConfig, Absent: AbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{presentConfig, absentConfig}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should get Dogus with changed present config", - args: args{doguConfig: SensitiveDoguConfig{Present: presentConfig, Absent: emptyAbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{presentConfig}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should get Dogus with changed absent config", - args: args{doguConfig: SensitiveDoguConfig{Present: emptyPresentConfig, Absent: AbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{absentConfig}}, want: []cescommons.SimpleName{dogu1}, }, { name: "should not get Dogus with no config changes", - args: args{doguConfig: SensitiveDoguConfig{Present: emptyPresentConfig, Absent: emptyAbsentConfig}}, + args: args{doguConfig: DoguConfigEntries{}}, + want: emptyResult, + }, + { + name: "should not get Dogus with no sensitive config changes", + args: args{doguConfig: DoguConfigEntries{ConfigEntry{Key: "mykey", Value: &confgiVal1}}}, want: emptyResult, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - emptyDoguConfig := struct { - Present map[common.DoguConfigKey]common.DoguConfigValue - Absent []common.DoguConfigKey - }{} config := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ - dogu1: { - DoguName: dogu1, - Config: emptyDoguConfig, - SensitiveConfig: tt.args.doguConfig, - }, + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ + dogu1: tt.args.doguConfig, }, - Global: GlobalConfig{}, + Global: GlobalConfigEntries{}, } if tt.args.withDogu2Change { - config.Dogus[dogu2] = CombinedDoguConfig{ - DoguName: dogu2, - Config: emptyDoguConfig, - SensitiveConfig: tt.args.doguConfig, - } + config.Dogus[dogu2] = tt.args.doguConfig } changedDogus := config.GetDogusWithChangedSensitiveConfig() diff --git a/pkg/domain/ecosystem/ecosystemConfig.go b/pkg/domain/ecosystem/ecosystemConfig.go index c0d33d11..50cf0d4d 100644 --- a/pkg/domain/ecosystem/ecosystemConfig.go +++ b/pkg/domain/ecosystem/ecosystemConfig.go @@ -25,7 +25,7 @@ type DoguConfigEntry struct { } type SensitiveDoguConfigEntry struct { - Key common.SensitiveDoguConfigKey + Key common.DoguConfigKey Value common.SensitiveDoguConfigValue // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. diff --git a/pkg/domain/ecosystem/ecosystemHealth_test.go b/pkg/domain/ecosystem/ecosystemHealth_test.go index a75f4044..3a908ca6 100644 --- a/pkg/domain/ecosystem/ecosystemHealth_test.go +++ b/pkg/domain/ecosystem/ecosystemHealth_test.go @@ -1,10 +1,11 @@ package ecosystem import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" - "testing" ) func TestHealthResult_String(t *testing.T) { diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index 735526fd..a0b5ff8b 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -32,7 +32,7 @@ func determineConfigDiffs( globalConfig config.GlobalConfig, configByDogu map[cescommons.SimpleName]config.DoguConfig, SensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig, - referencedSensitiveConfig map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, + referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) ( map[cescommons.SimpleName]DoguConfigDiffs, map[cescommons.SimpleName]SensitiveDoguConfigDiffs, @@ -48,44 +48,43 @@ func determineConfigDiffs( } func determineDogusConfigDiffs( - combinedDoguConfigs map[cescommons.SimpleName]CombinedDoguConfig, + blueprintDoguConfigs map[cescommons.SimpleName]DoguConfigEntries, configByDogu map[cescommons.SimpleName]config.DoguConfig, ) map[cescommons.SimpleName]DoguConfigDiffs { diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} - for doguName, combinedDoguConfig := range combinedDoguConfigs { - diffsPerDogu[doguName] = determineDoguConfigDiffs(combinedDoguConfig.Config, configByDogu) + for doguName, bluprintDoguConfig := range blueprintDoguConfigs { + diffsPerDogu[doguName] = determineDoguConfigDiffs(doguName, bluprintDoguConfig, configByDogu, false) } return diffsPerDogu } func determineSensitiveDogusConfigDiffs( - combinedDoguConfigs map[cescommons.SimpleName]CombinedDoguConfig, + blueprintDoguConfigs map[cescommons.SimpleName]DoguConfigEntries, configByDogu map[cescommons.SimpleName]config.DoguConfig, referencedValues map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) map[cescommons.SimpleName]DoguConfigDiffs { diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} - for doguName, combinedDoguConfig := range combinedDoguConfigs { - expectedSensitiveConfig := createDoguConfigFromReferencedValues(combinedDoguConfig.SensitiveConfig, referencedValues) - diffsPerDogu[doguName] = determineDoguConfigDiffs(expectedSensitiveConfig, configByDogu) + for doguName, blueprintDoguConfig := range blueprintDoguConfigs { + setSensitiveConfigValues(doguName, blueprintDoguConfig, referencedValues) + diffsPerDogu[doguName] = determineDoguConfigDiffs(doguName, blueprintDoguConfig, configByDogu, true) } return diffsPerDogu } -// createDoguConfigFromReferencedValues maps the referenced values to normal dogu config, +// setSensitiveConfigValues maps the referenced values to normal dogu config, // so that the stateDiff can handle sensitive dogu config the same way as normal dogu config -func createDoguConfigFromReferencedValues( - sensitiveConfig SensitiveDoguConfig, - referencedValues map[common.DoguConfigKey]common.SensitiveDoguConfigValue, -) DoguConfig { - configEntries := map[common.DoguConfigKey]common.DoguConfigValue{} - for key := range sensitiveConfig.Present { - // we checked previously that all referenced values exist. Therefore, we need no error here. - // in case of a bug, this will cause, that no expected value gets set while applying the blueprint. - configEntries[key] = referencedValues[key] - } - return DoguConfig{ - Present: configEntries, - Absent: sensitiveConfig.Absent, +func setSensitiveConfigValues(doguName cescommons.SimpleName, configEntries DoguConfigEntries, referencedValues map[common.DoguConfigKey]common.SensitiveDoguConfigValue) { + for i, entry := range configEntries { + if !entry.Absent && entry.Sensitive { + key := common.DoguConfigKey{ + DoguName: doguName, + Key: entry.Key, + } + // we checked previously that all referenced values exist. Therefore, we need no error here. + // in case of a bug, this will cause, that no expected value gets set while applying the blueprint. + sensitiveValue := referencedValues[key] + configEntries[i].Value = &sensitiveValue + } } } diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index 87284732..483c4018 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -12,18 +12,15 @@ import ( ) var ( - dogu1 = cescommons.SimpleName("dogu1") - dogu2 = cescommons.SimpleName("dogu2") - dogu1Key1 = common.DoguConfigKey{DoguName: dogu1, Key: "key1"} - dogu1Key2 = common.DoguConfigKey{DoguName: dogu1, Key: "key2"} - dogu1Key3 = common.DoguConfigKey{DoguName: dogu1, Key: "key3"} - dogu1Key4 = common.DoguConfigKey{DoguName: dogu1, Key: "key4"} - sensitiveDogu1Key1 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key1"} - sensitiveDogu1Key2 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key2"} - sensitiveDogu1Key3 = common.SensitiveDoguConfigKey{DoguName: dogu1, Key: "key3"} - val1 = "value1" - val2 = "value2" - val3 = "value3" + dogu1 = cescommons.SimpleName("dogu1") + dogu2 = cescommons.SimpleName("dogu2") + dogu1Key1 = common.DoguConfigKey{DoguName: dogu1, Key: "key1"} + dogu1Key2 = common.DoguConfigKey{DoguName: dogu1, Key: "key2"} + dogu1Key3 = common.DoguConfigKey{DoguName: dogu1, Key: "key3"} + dogu1Key4 = common.DoguConfigKey{DoguName: dogu1, Key: "key4"} + val1 = config.Value("value1") + val2 = config.Value("value2") + val3 = config.Value("value3") ) func Test_determineConfigDiff(t *testing.T) { @@ -33,7 +30,7 @@ func Test_determineConfigDiff(t *testing.T) { config.CreateGlobalConfig(map[config.Key]config.Value{}), map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, ) assert.Nil(t, dogusConfigDiffs) @@ -48,7 +45,7 @@ func Test_determineConfigDiff(t *testing.T) { config.CreateGlobalConfig(map[config.Key]config.Value{}), map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, ) assert.Equal(t, map[cescommons.SimpleName]DoguConfigDiffs{}, dogusConfigDiffs) @@ -59,21 +56,30 @@ func Test_determineConfigDiff(t *testing.T) { //given ecosystem config entries, _ := config.MapToEntries(map[string]any{ - "key1": "value1", // for action none - "key2": "value2", // for action set - "key3": "value3", // for action delete + "key1": val1.String(), // for action none + "key2": val2.String(), // for action set + "key3": val3.String(), // for action delete // key4 is absent -> action none }) globalConfig := config.CreateGlobalConfig(entries) //given blueprint config givenConfig := Config{ - Global: GlobalConfig{ - Present: map[common.GlobalConfigKey]common.GlobalConfigValue{ - "key1": "value1", - "key2": "value3", + Global: GlobalConfigEntries{ + { + Key: "key1", + Value: &val1, }, - Absent: []common.GlobalConfigKey{ - "key3", "key4", + { + Key: "key2", + Value: &val3, + }, + { + Key: "key3", + Absent: true, + }, + { + Key: "key4", + Absent: true, }, }, } @@ -84,7 +90,7 @@ func Test_determineConfigDiff(t *testing.T) { globalConfig, map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, ) //then @@ -97,11 +103,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ Key: "key1", Actual: GlobalConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: GlobalConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, NeededAction: ConfigActionNone, @@ -112,11 +118,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ Key: "key2", Actual: GlobalConfigValueState{ - Value: &val2, + Value: (*string)(&val2), Exists: true, }, Expected: GlobalConfigValueState{ - Value: &val3, + Value: (*string)(&val3), Exists: true, }, NeededAction: ConfigActionSet, @@ -127,7 +133,7 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ Key: "key3", Actual: GlobalConfigValueState{ - Value: &val3, + Value: (*string)(&val3), Exists: true, }, Expected: GlobalConfigValueState{ @@ -172,17 +178,23 @@ func Test_determineConfigDiff(t *testing.T) { //given blueprint config givenConfig := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ "dogu1": { - DoguName: "dogu1", - Config: DoguConfig{ - Present: map[common.DoguConfigKey]common.DoguConfigValue{ - dogu1Key1: "value1", - dogu1Key2: "value2", - }, - Absent: []common.DoguConfigKey{ - dogu1Key3, dogu1Key4, - }, + { + Key: dogu1Key1.Key, + Value: &val1, + }, + { + Key: dogu1Key2.Key, + Value: &val2, + }, + { + Key: dogu1Key3.Key, + Absent: true, + }, + { + Key: dogu1Key4.Key, + Absent: true, }, }, }, @@ -196,7 +208,7 @@ func Test_determineConfigDiff(t *testing.T) { dogu1: doguConfig, }, map[cescommons.SimpleName]config.DoguConfig{}, - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{}, + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, ) //then assert.Equal(t, GlobalConfigDiffs(nil), globalConfigDiff) @@ -209,11 +221,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ Key: dogu1Key1, Actual: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, NeededAction: ConfigActionNone, @@ -224,11 +236,11 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ Key: dogu1Key2, Actual: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: DoguConfigValueState{ - Value: &val2, + Value: (*string)(&val2), Exists: true, }, NeededAction: ConfigActionSet, @@ -239,7 +251,7 @@ func Test_determineConfigDiff(t *testing.T) { assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ Key: dogu1Key3, Actual: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: DoguConfigValueState{ @@ -286,24 +298,29 @@ func Test_determineConfigDiff(t *testing.T) { //given blueprint config givenConfig := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ "dogu1": { - DoguName: "dogu1", - SensitiveConfig: SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - sensitiveDogu1Key1: { - SecretName: "mySecret1", - SecretKey: "myKey1", - }, - sensitiveDogu1Key2: { - SecretName: "mySecret2", - SecretKey: "myKey2", - }, + { + Key: dogu1Key1.Key, + Sensitive: true, + SecretRef: &SensitiveValueRef{ + SecretName: "mySecret1", + SecretKey: "myKey1", }, - Absent: []common.SensitiveDoguConfigKey{ - sensitiveDogu1Key3, + }, + { + Key: dogu1Key2.Key, + Sensitive: true, + SecretRef: &SensitiveValueRef{ + SecretName: "mySecret2", + SecretKey: "myKey2", }, }, + { + Key: dogu1Key3.Key, + Sensitive: true, + Absent: true, + }, }, }, } @@ -317,9 +334,9 @@ func Test_determineConfigDiff(t *testing.T) { dogu1: sensitiveDoguConfig, }, //loaded referenced sensitive config - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - sensitiveDogu1Key1: "value1", - sensitiveDogu1Key2: "value2", + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{ + dogu1Key1: "value1", + dogu1Key2: "value2", }, ) //then @@ -330,31 +347,31 @@ func Test_determineConfigDiff(t *testing.T) { entriesDogu1 := SensitiveDoguConfigDiffs{ { - Key: sensitiveDogu1Key1, + Key: dogu1Key1, Actual: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, NeededAction: ConfigActionNone, }, { - Key: sensitiveDogu1Key2, + Key: dogu1Key2, Actual: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, Expected: DoguConfigValueState{ - Value: &val2, + Value: (*string)(&val2), Exists: true, }, NeededAction: ConfigActionSet, }, { - Key: sensitiveDogu1Key3, + Key: dogu1Key3, Actual: DoguConfigValueState{ Value: nil, Exists: false, @@ -381,17 +398,15 @@ func Test_determineConfigDiff(t *testing.T) { //given blueprint config givenConfig := Config{ - Dogus: map[cescommons.SimpleName]CombinedDoguConfig{ + Dogus: map[cescommons.SimpleName]DoguConfigEntries{ "dogu1": { - DoguName: "dogu1", - SensitiveConfig: SensitiveDoguConfig{ - Present: map[common.SensitiveDoguConfigKey]SensitiveValueRef{ - sensitiveDogu1Key1: { - SecretName: "secret1", - SecretKey: "key1", - }, + { + Key: dogu1Key1.Key, + Sensitive: true, + SecretRef: &SensitiveValueRef{ + SecretName: "mySecret1", + SecretKey: "myKey1", }, - Absent: []common.SensitiveDoguConfigKey{}, }, }, }, @@ -408,8 +423,8 @@ func Test_determineConfigDiff(t *testing.T) { dogu1: sensitiveDoguConfig, }, //loaded referenced sensitive config - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue{ - sensitiveDogu1Key1: "value1", + map[common.DoguConfigKey]common.SensitiveDoguConfigValue{ + dogu1Key1: "value1", }, ) //then @@ -418,13 +433,13 @@ func Test_determineConfigDiff(t *testing.T) { require.NotNil(t, sensitiveConfigDiffs["dogu1"]) require.Equal(t, 1, len(sensitiveConfigDiffs["dogu1"])) assert.Empty(t, cmp.Diff(sensitiveConfigDiffs["dogu1"][0], SensitiveDoguConfigEntryDiff{ - Key: sensitiveDogu1Key1, + Key: dogu1Key1, Actual: DoguConfigValueState{ Value: nil, Exists: false, }, Expected: DoguConfigValueState{ - Value: &val1, + Value: (*string)(&val1), Exists: true, }, NeededAction: ConfigActionSet, @@ -448,14 +463,14 @@ func Test_getNeededConfigAction(t *testing.T) { }, { name: "action none, for some reason the values are different", - expected: ConfigValueState{Value: &val1, Exists: false}, - actual: ConfigValueState{Value: &val2, Exists: false}, + expected: ConfigValueState{Value: (*string)(&val1), Exists: false}, + actual: ConfigValueState{Value: (*string)(&val2), Exists: false}, want: ConfigActionNone, }, { name: "action none, equal values", - expected: ConfigValueState{Value: &val1, Exists: true}, - actual: ConfigValueState{Value: &val1, Exists: true}, + expected: ConfigValueState{Value: (*string)(&val1), Exists: true}, + actual: ConfigValueState{Value: (*string)(&val1), Exists: true}, want: ConfigActionNone, }, { @@ -466,8 +481,8 @@ func Test_getNeededConfigAction(t *testing.T) { }, { name: "update value", - expected: ConfigValueState{Value: &val1, Exists: true}, - actual: ConfigValueState{Value: &val2, Exists: true}, + expected: ConfigValueState{Value: (*string)(&val1), Exists: true}, + actual: ConfigValueState{Value: (*string)(&val2), Exists: true}, want: ConfigActionSet, }, { @@ -479,7 +494,7 @@ func Test_getNeededConfigAction(t *testing.T) { { name: "remove value", expected: ConfigValueState{Value: nil, Exists: false}, - actual: ConfigValueState{Value: &val3, Exists: true}, + actual: ConfigValueState{Value: (*string)(&val3), Exists: true}, want: ConfigActionRemove, }, } diff --git a/pkg/domain/stateDiffDoguConfig.go b/pkg/domain/stateDiffDoguConfig.go index 7c7764d8..fc45665b 100644 --- a/pkg/domain/stateDiffDoguConfig.go +++ b/pkg/domain/stateDiffDoguConfig.go @@ -69,28 +69,23 @@ func newDoguConfigEntryDiff( } } -func determineDoguConfigDiffs( - wantedConfig DoguConfig, - actualConfig map[cescommons.SimpleName]config.DoguConfig, -) DoguConfigDiffs { +func determineDoguConfigDiffs(doguName cescommons.SimpleName, wantedConfig DoguConfigEntries, actualConfig map[cescommons.SimpleName]config.DoguConfig, isSensitive bool) DoguConfigDiffs { var doguConfigDiff []DoguConfigEntryDiff - // present entries - for key, expectedValue := range wantedConfig.Present { - var actualValue *config.Value - actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) - if exists { - actualValue = &actualEntry + for _, expectedConfig := range wantedConfig { + if expectedConfig.Sensitive != isSensitive { + continue // skip if not match sensitivity } - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualValue, exists, &expectedValue, true)) - } - // absent entries - for _, key := range wantedConfig.Absent { + var actualValue *config.Value - actualEntry, exists := actualConfig[key.DoguName].Get(key.Key) + actualEntry, exists := actualConfig[doguName].Get(expectedConfig.Key) if exists { actualValue = &actualEntry } - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(key, actualValue, exists, nil, false)) + configKey := common.DoguConfigKey{ + DoguName: doguName, + Key: expectedConfig.Key, + } + doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(configKey, actualValue, exists, expectedConfig.Value, !expectedConfig.Absent)) } return doguConfigDiff } diff --git a/pkg/domain/stateDiffGlobalConfig.go b/pkg/domain/stateDiffGlobalConfig.go index f98ab08f..476295b3 100644 --- a/pkg/domain/stateDiffGlobalConfig.go +++ b/pkg/domain/stateDiffGlobalConfig.go @@ -63,28 +63,18 @@ func newGlobalConfigEntryDiff( } func determineGlobalConfigDiffs( - config GlobalConfig, + config GlobalConfigEntries, actualConfig config.GlobalConfig, ) GlobalConfigDiffs { var configDiffs []GlobalConfigEntryDiff - // present entries - for key, expectedValue := range config.Present { + for _, expectedConfig := range config { var actualValue *common.GlobalConfigValue - actualEntry, actualExists := actualConfig.Get(key) + actualEntry, actualExists := actualConfig.Get(expectedConfig.Key) if actualExists { actualValue = &actualEntry } - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualValue, actualExists, &expectedValue, true)) - } - // absent entries - for _, key := range config.Absent { - var actualValue *common.GlobalConfigValue - actualEntry, actualExists := actualConfig.Get(key) - if actualExists { - actualValue = &actualEntry - } - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(key, actualValue, actualExists, nil, false)) + configDiffs = append(configDiffs, newGlobalConfigEntryDiff(expectedConfig.Key, actualValue, actualExists, expectedConfig.Value, !expectedConfig.Absent)) } return configDiffs } diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index 87833b2f..81ff81c3 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -161,9 +161,9 @@ type SensitiveConfigRefReader interface { // - InternalError if any other error happens. GetValues( ctx context.Context, - refs map[common.SensitiveDoguConfigKey]domain.SensitiveValueRef, + refs map[common.DoguConfigKey]domain.SensitiveValueRef, ) ( - map[common.SensitiveDoguConfigKey]common.SensitiveDoguConfigValue, + map[common.DoguConfigKey]common.SensitiveDoguConfigValue, error, ) } From b55850948e9eb695e8ae084f4b3adc1ccb4c78d1 Mon Sep 17 00:00:00 2001 From: Benjamin Ernst Date: Mon, 15 Sep 2025 09:11:35 +0200 Subject: [PATCH 058/119] Bump version --- Dockerfile | 2 +- Makefile | 2 +- k8s/helm/component-patch-tpl.yaml | 2 +- k8s/helm/values.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4304dedb..df4e6d43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,7 @@ RUN make compile-generic FROM gcr.io/distroless/static:nonroot LABEL maintainer="hello@cloudogu.com" \ NAME="k8s-blueprint-operator" \ - VERSION="2.7.0" + VERSION="2.8.0" WORKDIR / COPY --from=builder /workspace/target/k8s-blueprint-operator . diff --git a/Makefile b/Makefile index 02b5d871..01e6e3b0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Set these to the desired values ARTIFACT_ID=k8s-blueprint-operator -VERSION=2.7.0 +VERSION=2.8.0 IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} GOTAG=1.24.3 MAKEFILES_VERSION=10.2.0 diff --git a/k8s/helm/component-patch-tpl.yaml b/k8s/helm/component-patch-tpl.yaml index 396f53d0..7cd34e69 100644 --- a/k8s/helm/component-patch-tpl.yaml +++ b/k8s/helm/component-patch-tpl.yaml @@ -1,7 +1,7 @@ apiVersion: v1 values: images: - blueprintOperator: cloudogu/k8s-blueprint-operator:2.7.0 + blueprintOperator: cloudogu/k8s-blueprint-operator:2.8.0 patches: values.yaml: manager: diff --git a/k8s/helm/values.yaml b/k8s/helm/values.yaml index f1ef7866..60c9efac 100644 --- a/k8s/helm/values.yaml +++ b/k8s/helm/values.yaml @@ -6,7 +6,7 @@ manager: image: registry: docker.io repository: cloudogu/k8s-blueprint-operator - tag: 2.7.0 + tag: 2.8.0 imagePullPolicy: IfNotPresent env: logLevel: info From 8ff6e30df00c6ce79731efb525e3cc70c700ad45 Mon Sep 17 00:00:00 2001 From: Benjamin Ernst Date: Mon, 15 Sep 2025 09:13:47 +0200 Subject: [PATCH 059/119] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 324c70bb..847f4e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [v2.8.0] - 2025-09-15 ### Changed - [#125] ignore nginx dependencies +### Removed +- [#125] component-dependency for component-oprator-crd + - it is replaced by a helm capabilities-check for the component CRD + ## [v2.7.0] - 2025-07-17 ### Fixed - [#117] configuring operator log-level From 2fde50155d81e157bef27f5b757a8a73813ec654 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 15 Sep 2025 13:37:27 +0200 Subject: [PATCH 060/119] #121 add tests --- go.mod | 2 +- go.sum | 2 + .../doguregistry/doguDescriptorRepository.go | 2 +- .../v2/blueprintSpecCRRepository.go | 6 +- .../v2/blueprintSpecCRRepository_test.go | 39 +++ .../v2/serializer/blueprint_test.go | 265 +++++++++++------- .../blueprintcr/v2/serializer/doguDiff.go | 13 +- .../v2/serializer/doguDiff_test.go | 157 +++++++++++ pkg/application/doguInstallationUseCase.go | 4 +- pkg/domain/blueprintSpec_test.go | 46 ++- pkg/domain/effectiveBlueprint_test.go | 43 +++ 11 files changed, 459 insertions(+), 120 deletions(-) create mode 100644 pkg/domain/effectiveBlueprint_test.go diff --git a/go.mod b/go.mod index b9dfe596..b326d37a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1 github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index 1102f1fc..813e9eab 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323 h1:l github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 h1:8H1XAdX77EP1Q55qS+qsuuEY+H0aIG8dNxKvuWbYozE= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1 h1:CcZow0wfH5Jm6pOEPrd+e3MYgYPbjzi79GRySWUhl4g= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index eed50cc8..595e4165 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -38,7 +38,7 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer } // TODO: doesn't work with "old" dogu operator - //err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) + err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) if err != nil { // just log the error, no need to fail the reconcilation logger.Info("failed to add dogu to local repository", diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 85b89767..2d271e1c 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -69,9 +69,9 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) } } - conditions := blueprintCR.Status.Conditions - if conditions == nil { - conditions = []domain.Condition{} + var conditions []domain.Condition + if blueprintCR.Status != nil && blueprintCR.Status.Conditions != nil { + conditions = blueprintCR.Status.Conditions } blueprintSpec := &domain.BlueprintSpec{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index 0c5549cf..dc39aa86 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -77,6 +77,45 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { }, spec) }) + t.Run("all ok without status", func(t *testing.T) { + // given + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + + cr := &bpv2.Blueprint{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ResourceVersion: "abc"}, + Spec: &bpv2.BlueprintSpec{ + Blueprint: bpv2.BlueprintManifest{}, + BlueprintMask: &bpv2.BlueprintMask{}, + AllowDoguNamespaceSwitch: &trueVar, + IgnoreDoguHealth: &trueVar, + Stopped: &trueVar, + }, + } + restClientMock.EXPECT().Get(ctx, blueprintId, metav1.GetOptions{}).Return(cr, nil) + + // when + spec, err := repo.GetById(ctx, blueprintId) + + // then + require.NoError(t, err) + persistenceContext := make(map[string]interface{}) + persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} + assert.Equal(t, &domain.BlueprintSpec{ + Id: blueprintId, + Config: domain.BlueprintConfiguration{ + IgnoreDoguHealth: true, + AllowDoguNamespaceSwitch: true, + DryRun: true, + }, + StateDiff: domain.StateDiff{}, + PersistenceContext: persistenceContext, + Conditions: nil, + }, spec) + }) + t.Run("invalid blueprint and mask", func(t *testing.T) { // given restClientMock := newMockBlueprintInterface(t) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index 4d6f176b..1e28b1b0 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -151,128 +151,173 @@ func TestConvertToBlueprintDTO(t *testing.T) { } func TestConvertToEffectiveBlueprintDomain(t *testing.T) { - //given - subfolder := "subfolder" - convertedDogus := []crd.Dogu{ - {Name: "official/dogu1", Version: &version3211.Raw, Absent: &trueVar}, - {Name: "official/dogu2", Absent: &trueVar}, - {Name: "premium/dogu3", Version: &version3212.Raw, Absent: &falseVar}, - {Name: "premium/dogu4", Version: &version1233.Raw, Absent: &falseVar}, - { - Name: "premium/dogu5", - Version: &version1233.Raw, - Absent: &falseVar, - PlatformConfig: &crd.PlatformConfig{ - ResourceConfig: nil, - ReverseProxyConfig: nil, - AdditionalMountsConfig: []crd.AdditionalMount{ - { - SourceType: crd.DataSourceConfigMap, - Name: "config", - Volume: "volume", - Subfolder: &subfolder, + t.Run("all ok", func(t *testing.T) { + //given + subfolder := "subfolder" + convertedDogus := []crd.Dogu{ + {Name: "official/dogu1", Version: &version3211.Raw, Absent: &trueVar}, + {Name: "official/dogu2", Absent: &trueVar}, + {Name: "premium/dogu3", Version: &version3212.Raw, Absent: &falseVar}, + {Name: "premium/dogu4", Version: &version1233.Raw, Absent: &falseVar}, + { + Name: "premium/dogu5", + Version: &version1233.Raw, + Absent: &falseVar, + PlatformConfig: &crd.PlatformConfig{ + ResourceConfig: nil, + ReverseProxyConfig: nil, + AdditionalMountsConfig: []crd.AdditionalMount{ + { + SourceType: crd.DataSourceConfigMap, + Name: "config", + Volume: "volume", + Subfolder: &subfolder, + }, }, }, }, - }, - } + } - convertedComponents := []crd.Component{ - {Name: "k8s/component1", Version: &version3211.Raw, Absent: &trueVar}, - {Name: "k8s/component2", Absent: &trueVar}, - {Name: "k8s-testing/component3", Version: &version3212.Raw, Absent: &falseVar}, - {Name: "k8s-testing/component4", Version: &version1233.Raw, Absent: &falseVar}, - } + convertedComponents := []crd.Component{ + {Name: "k8s/component1", Version: &version3211.Raw, Absent: &trueVar}, + {Name: "k8s/component2", Absent: &trueVar}, + {Name: "k8s-testing/component3", Version: &version3212.Raw, Absent: &falseVar}, + {Name: "k8s-testing/component4", Version: &version1233.Raw, Absent: &falseVar}, + } - value42 := "42" - dto := crd.BlueprintManifest{ - Dogus: convertedDogus, - Components: convertedComponents, - Config: &crd.Config{ - Dogus: map[string][]crd.ConfigEntry{ - "my-dogu": { - crd.ConfigEntry{ - Key: "config", - Value: &value42, - }, - crd.ConfigEntry{ - Key: "sensitive-config", - Sensitive: &trueVar, - SecretRef: &crd.SecretReference{ - Name: "mySecret", - Key: "myKey", + value42 := "42" + dto := crd.BlueprintManifest{ + Dogus: convertedDogus, + Components: convertedComponents, + Config: &crd.Config{ + Dogus: map[string][]crd.ConfigEntry{ + "my-dogu": { + crd.ConfigEntry{ + Key: "config", + Value: &value42, + }, + crd.ConfigEntry{ + Key: "sensitive-config", + Sensitive: &trueVar, + SecretRef: &crd.SecretReference{ + Name: "mySecret", + Key: "myKey", + }, }, }, }, - }, - Global: []crd.ConfigEntry{ - { - Key: "test/key", - Absent: &trueVar, + Global: []crd.ConfigEntry{ + { + Key: "test/key", + Absent: &trueVar, + }, }, }, - }, - } - //when - blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) - - //then - dogus := []domain.Dogu{ - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: &version3211, Absent: true}, - {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu2"}, Absent: true}, - {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: &version3212, Absent: false}, - {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu4"}, Version: &version1233}, - { - Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu5"}, - Version: &version1233, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "config", - Volume: "volume", - Subfolder: &subfolder, + } + //when + blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) + + //then + dogus := []domain.Dogu{ + {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu1"}, Version: &version3211, Absent: true}, + {Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "dogu2"}, Absent: true}, + {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu3"}, Version: &version3212, Absent: false}, + {Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu4"}, Version: &version1233}, + { + Name: cescommons.QualifiedName{Namespace: "premium", SimpleName: "dogu5"}, + Version: &version1233, + AdditionalMounts: []ecosystem.AdditionalMount{ + { + SourceType: ecosystem.DataSourceConfigMap, + Name: "config", + Volume: "volume", + Subfolder: &subfolder, + }, }, }, - }, - } + } - components := []domain.Component{ - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component1"}, Version: compVersion3211, Absent: true}, - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component2"}, Absent: true}, - {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component3"}, Version: compVersion3212, Absent: false}, - {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component4"}, Version: compVersion1233}, - } - expected := domain.EffectiveBlueprint{ - Dogus: dogus, - Components: components, - Config: &domain.Config{ - Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ - "my-dogu": { - { - Key: "config", - Value: (*config.Value)(&value42), + components := []domain.Component{ + {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component1"}, Version: compVersion3211, Absent: true}, + {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component2"}, Absent: true}, + {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component3"}, Version: compVersion3212, Absent: false}, + {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component4"}, Version: compVersion1233}, + } + expected := domain.EffectiveBlueprint{ + Dogus: dogus, + Components: components, + Config: &domain.Config{ + Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ + "my-dogu": { + { + Key: "config", + Value: (*config.Value)(&value42), + }, + { + Key: "sensitive-config", + Sensitive: true, + SecretRef: &domain.SensitiveValueRef{ + SecretName: "mySecret", + SecretKey: "myKey", + }, + }, }, + }, + Global: domain.GlobalConfigEntries{ { - Key: "sensitive-config", - Sensitive: true, - SecretRef: &domain.SensitiveValueRef{ - SecretName: "mySecret", - SecretKey: "myKey", - }, + Key: "test/key", + Absent: true, }, }, }, - Global: domain.GlobalConfigEntries{ - { - Key: "test/key", - Absent: true, - }, + } + + require.NoError(t, err) + assert.Empty(t, cmp.Diff(expected, blueprint)) + }) + + t.Run("when manifest is nil return empty effective blueprint", func(t *testing.T) { + //when + blueprint, err := ConvertToEffectiveBlueprintDomain(nil) + + //then + require.NoError(t, err) + assert.Equal(t, domain.EffectiveBlueprint{}, blueprint) + }) + + t.Run("when convert dogu error return empty effective blueprint and error", func(t *testing.T) { + //given + dto := crd.BlueprintManifest{ + Dogus: []crd.Dogu{ + {Name: "dogu1", Version: &version3211.Raw}, // name contains no "/" }, - }, - } + } + + //when + blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) + + //then + require.Error(t, err) + assert.ErrorContains(t, err, "cannot deserialize effective blueprint") + assert.Equal(t, domain.EffectiveBlueprint{}, blueprint) + }) + + t.Run("when convert component error return empty effective blueprint and error", func(t *testing.T) { + //given + dto := crd.BlueprintManifest{ + Components: []crd.Component{ + {Name: "k8s/k8s-dogu-operator", Version: &wrongVersion}, // name contains no "/" + }, + } + + //when + blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) - require.NoError(t, err) - assert.Empty(t, cmp.Diff(expected, blueprint)) + //then + require.Error(t, err) + assert.ErrorContains(t, err, "cannot deserialize effective blueprint") + assert.Equal(t, domain.EffectiveBlueprint{}, blueprint) + }) } func TestConvertToBlueprintDomain(t *testing.T) { @@ -352,7 +397,7 @@ func TestConvertToBlueprintDomain(t *testing.T) { func TestConvertToBlueprintMaskDomain(t *testing.T) { type args struct { - mask crd.BlueprintMask + mask *crd.BlueprintMask } tests := []struct { name string @@ -360,15 +405,21 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { want domain.BlueprintMask wantErr assert.ErrorAssertionFunc }{ + { + name: "nil", + args: args{mask: nil}, + want: domain.BlueprintMask{}, + wantErr: assert.NoError, + }, { name: "empty", - args: args{mask: crd.BlueprintMask{}}, + args: args{mask: &crd.BlueprintMask{}}, want: domain.BlueprintMask{}, wantErr: assert.NoError, }, { name: "will convert a MaskDogu", - args: args{mask: crd.BlueprintMask{ + args: args{mask: &crd.BlueprintMask{ Dogus: []crd.MaskDogu{ { Name: "official/ldap", @@ -391,7 +442,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { }, { name: "error if invalid mask", - args: args{mask: crd.BlueprintMask{ + args: args{mask: &crd.BlueprintMask{ Dogus: []crd.MaskDogu{ { Name: "invalid name", @@ -407,7 +458,7 @@ func TestConvertToBlueprintMaskDomain(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ConvertToBlueprintMaskDomain(&tt.args.mask) + got, err := ConvertToBlueprintMaskDomain(tt.args.mask) if !tt.wantErr(t, err, fmt.Sprintf("ConvertToBlueprintMaskDomain(%v)", tt.args.mask)) { return } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index 36065240..2ff51822 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -91,8 +91,8 @@ func convertToDoguDiffDomain(doguName string, dto crd.DoguDiff) (domain.DoguDiff actualStateErr = fmt.Errorf("failed to convert actual dogu diff state: %w", actualStateErr) } expectedState, expectedStateErr := convertDoguDiffStateDomain(dto.Expected) - if actualStateErr != nil { - actualStateErr = fmt.Errorf("failed to convert expected dogu diff state: %w", actualStateErr) + if expectedStateErr != nil { + expectedStateErr = fmt.Errorf("failed to convert expected dogu diff state: %w", expectedStateErr) } err := errors.Join(actualStateErr, expectedStateErr) @@ -124,10 +124,9 @@ func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, er coreVersion, err = core.ParseVersion(*dto.Version) version = &coreVersion if err != nil { - err = fmt.Errorf("failed to parse version %q: %w", *dto.Version, err) + errorList = append(errorList, fmt.Errorf("failed to parse version %q: %w", *dto.Version, err)) } } - errorList = append(errorList, err) var minVolumeSize, maxBodySize *resource.Quantity if dto.ResourceConfig != nil && dto.ResourceConfig.MinVolumeSize != nil { @@ -154,6 +153,10 @@ func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, er } } + if len(errorList) != 0 { + return domain.DoguDiffState{}, errors.Join(errorList...) + } + return domain.DoguDiffState{ Namespace: cescommons.Namespace(dto.Namespace), Version: version, @@ -161,7 +164,7 @@ func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, er MinVolumeSize: minVolumeSize, ReverseProxyConfig: reverseProxyConfig, AdditionalMounts: convertAdditionalMountsToDoguDiffDomain(dto.AdditionalMounts), - }, errors.Join(errorList...) + }, nil } func convertAdditionalMountsToDoguDiffDomain(mounts []crd.AdditionalMount) []ecosystem.AdditionalMount { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index 43072c8d..d633c47e 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -3,8 +3,12 @@ package serializer import ( "testing" + crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" ) @@ -38,3 +42,156 @@ func Test_convertMinimumVolumeSizeToDTO(t *testing.T) { }) } } + +func Test_convertToDoguDiffStateDTO(t *testing.T) { + t.Run("should convert empty reverse proxy config", func(t *testing.T) { + // given + domainDiffState := domain.DoguDiffState{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{}, + } + // when + result := convertToDoguDiffStateDTO(domainDiffState) + // then + assert.NotNil(t, result.ReverseProxyConfig) + assert.Nil(t, result.ReverseProxyConfig.RewriteTarget) + assert.Nil(t, result.ReverseProxyConfig.AdditionalConfig) + assert.Nil(t, result.ReverseProxyConfig.MaxBodySize) + }) + + t.Run("should convert reverse proxy config", func(t *testing.T) { + // given + domainDiffState := domain.DoguDiffState{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + MaxBodySize: &proxyBodySize, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + } + // when + result := convertToDoguDiffStateDTO(domainDiffState) + // then + want := crd.DoguDiffState{ + ReverseProxyConfig: &crd.ReverseProxyConfig{ + MaxBodySize: &proxyBodySizeString, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + } + + assert.NotNil(t, result.ReverseProxyConfig) + assert.Empty(t, cmp.Diff(want, result)) + }) + + t.Run("should convert resource config", func(t *testing.T) { + // given + domainDiffState := domain.DoguDiffState{ + MinVolumeSize: &volumeSize, + } + // when + result := convertToDoguDiffStateDTO(domainDiffState) + // then + want := crd.DoguDiffState{ + ResourceConfig: &crd.ResourceConfig{ + MinVolumeSize: &volumeSizeString, + }, + } + + assert.NotNil(t, result.ResourceConfig) + assert.Empty(t, cmp.Diff(want, result)) + }) +} + +func Test_convertDoguDiffStateDomain(t *testing.T) { + t.Run("should convert empty reverse proxy config", func(t *testing.T) { + // given + crdDiffState := crd.DoguDiffState{ + ReverseProxyConfig: &crd.ReverseProxyConfig{}, + } + // when + result, err := convertDoguDiffStateDomain(crdDiffState) + // then + require.NoError(t, err) + assert.NotNil(t, result.ReverseProxyConfig) + assert.Nil(t, result.ReverseProxyConfig.RewriteTarget) + assert.Nil(t, result.ReverseProxyConfig.AdditionalConfig) + assert.Nil(t, result.ReverseProxyConfig.MaxBodySize) + }) + + t.Run("should convert reverse proxy config", func(t *testing.T) { + // given + crdDiffState := crd.DoguDiffState{ + ReverseProxyConfig: &crd.ReverseProxyConfig{ + MaxBodySize: &proxyBodySizeString, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + } + // when + result, err := convertDoguDiffStateDomain(crdDiffState) + // then + want := domain.DoguDiffState{ + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + MaxBodySize: &proxyBodySize, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + } + + require.NoError(t, err) + assert.NotNil(t, result.ReverseProxyConfig) + assert.Empty(t, cmp.Diff(want, result)) + }) + + t.Run("should throw error on reverse proxy config convert error", func(t *testing.T) { + // given + wrongBodySize := "1Z" + crdDiffState := crd.DoguDiffState{ + ReverseProxyConfig: &crd.ReverseProxyConfig{ + MaxBodySize: &wrongBodySize, + }, + } + // when + result, err := convertDoguDiffStateDomain(crdDiffState) + + // then + require.Error(t, err) + assert.Equal(t, domain.DoguDiffState{}, result) + assert.ErrorContains(t, err, "failed to parse maximum proxy body size") + }) + + t.Run("should convert resource config", func(t *testing.T) { + // given + crdDiffState := crd.DoguDiffState{ + ResourceConfig: &crd.ResourceConfig{ + MinVolumeSize: &volumeSizeString, + }, + } + // when + result, err := convertDoguDiffStateDomain(crdDiffState) + + // then + want := domain.DoguDiffState{ + MinVolumeSize: &volumeSize, + } + require.NoError(t, err) + assert.NotNil(t, result.MinVolumeSize) + assert.Empty(t, cmp.Diff(want, result)) + }) + + t.Run("should return error on resource config convert error", func(t *testing.T) { + // given + wrongVolumeSize := "1Gu" + crdDiffState := crd.DoguDiffState{ + ResourceConfig: &crd.ResourceConfig{ + MinVolumeSize: &wrongVolumeSize, + }, + } + // when + result, err := convertDoguDiffStateDomain(crdDiffState) + + // then + require.Error(t, err) + assert.Equal(t, domain.DoguDiffState{}, result) + assert.ErrorContains(t, err, "failed to parse minimum volume size") + }) +} diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index debc916d..db47ea8b 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -32,7 +32,7 @@ func NewDoguInstallationUseCase( func (useCase *DoguInstallationUseCase) CheckDoguHealth(ctx context.Context) (ecosystem.DoguHealthResult, error) { logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.CheckDoguHealth") - logger.Info("check dogu health...") + logger.V(2).Info("check dogu health...") installedDogus, err := useCase.doguRepo.GetAll(ctx) if err != nil { return ecosystem.DoguHealthResult{}, fmt.Errorf("cannot evaluate dogu health states: %w", err) @@ -45,7 +45,7 @@ func (useCase *DoguInstallationUseCase) CheckDoguHealth(ctx context.Context) (ec // Fail-fast here, so that the possible damage is as small as possible. func (useCase *DoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.ApplyDoguChanges") - logger.Info("apply dogu states") + logger.V(2).Info("apply dogu states") // DoguDiff contains all installed dogus anyway (but some with action none) so we can load them all at once dogus, err := useCase.doguRepo.GetAll(ctx) if err != nil { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 4f198cf7..b13f0fc2 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -514,7 +514,7 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { } func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { - t.Run("should be applied", func(t *testing.T) { + t.Run("should be applied on global config change", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ DryRun: false, @@ -530,6 +530,50 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { } assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) + t.Run("should be applied on dogu config change", func(t *testing.T) { + doguKey := common.DoguConfigKey{ + DoguName: "testDogu", + Key: "testKey", + } + spec := &BlueprintSpec{ + Config: BlueprintConfiguration{ + DryRun: false, + }, + StateDiff: StateDiff{ + DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{ + cescommons.SimpleName("testDogu"): { + { + Key: doguKey, + NeededAction: ConfigActionSet, + }, + }, + }, + }, + } + assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) + t.Run("should be applied on sensitive dogu config change", func(t *testing.T) { + doguKey := common.DoguConfigKey{ + DoguName: "testDogu", + Key: "testKey", + } + spec := &BlueprintSpec{ + Config: BlueprintConfiguration{ + DryRun: false, + }, + StateDiff: StateDiff{ + SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ + cescommons.SimpleName("testDogu"): { + { + Key: doguKey, + NeededAction: ConfigActionSet, + }, + }, + }, + }, + } + assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) t.Run("should not be applied without any changes", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ diff --git a/pkg/domain/effectiveBlueprint_test.go b/pkg/domain/effectiveBlueprint_test.go new file mode 100644 index 00000000..88448836 --- /dev/null +++ b/pkg/domain/effectiveBlueprint_test.go @@ -0,0 +1,43 @@ +package domain + +import ( + "testing" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/stretchr/testify/assert" +) + +func TestEffectiveBlueprint_GetWantedDogus(t *testing.T) { + t.Run("should get only present dogus", func(t *testing.T) { + ldapDogu := Dogu{ + Name: cescommons.QualifiedName{ + SimpleName: "ldap", + Namespace: "official", + }, + Version: &version3213, + } + absentMysqlDogu := Dogu{ + Name: cescommons.QualifiedName{ + SimpleName: "mysql", + Namespace: "official", + }, + Absent: true, + } + postgresqlDogu := Dogu{ + Name: cescommons.QualifiedName{ + SimpleName: "postgresql", + Namespace: "official", + }, + Version: &version3213, + } + effectiveBlueprint := &EffectiveBlueprint{ + Dogus: []Dogu{ldapDogu, absentMysqlDogu, postgresqlDogu}, + } + + result := effectiveBlueprint.GetWantedDogus() + + assert.Len(t, result, 2) + assert.Contains(t, result, ldapDogu) + assert.Contains(t, result, postgresqlDogu) + }) +} From 8deed0e95a92a4c19f0d6e4500a2e9b3e31169df Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 15 Sep 2025 13:59:02 +0200 Subject: [PATCH 061/119] #121 incorporate cesmarvin review --- .../doguregistry/doguDescriptorRepository.go | 4 +-- .../blueprintcr/v2/serializer/config.go | 2 +- .../blueprintcr/v2/serializer/doguDiff.go | 9 ------ .../v2/serializer/doguDiff_test.go | 32 ------------------- 4 files changed, 3 insertions(+), 44 deletions(-) diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index 595e4165..21e977bf 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -32,7 +32,7 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer return dogu, nil } - dogu, err := r.getRemoteDogu(ctx, qualifiedDoguVersion, dogu) + dogu, err := r.getRemoteDogu(ctx, qualifiedDoguVersion) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer return dogu, nil } -func (r *DoguDescriptorRepository) getRemoteDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion, dogu *core.Dogu) (*core.Dogu, error) { +func (r *DoguDescriptorRepository) getRemoteDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion) (*core.Dogu, error) { // do not retry here. If any error happens, just reconcile later. We only do retries in application level. // This makes the code way easier and non-blocking. dogu, err := r.remoteRepository.Get(ctx, qualifiedDoguVersion) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 015e98af..29f7fcc1 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -62,7 +62,7 @@ func convertToGlobalConfigDTO(config domain.GlobalConfigEntries) []v2.ConfigEntr } func convertToConfigEntriesDTO(config domain.ConfigEntries) []v2.ConfigEntry { - if config == nil || len(config) == 0 { + if len(config) == 0 { return nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index 2ff51822..ffe061b6 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -57,15 +57,6 @@ func convertToDoguDiffStateDTO(domainModel domain.DoguDiffState) crd.DoguDiffSta } } -func convertMinimumVolumeSizeToDTO(minVolSize *ecosystem.VolumeSize) *string { - if minVolSize == nil || minVolSize.IsZero() { - return nil - } else { - s := minVolSize.String() - return &s - } -} - func convertAdditionalMountsToDoguDiffDTO(mounts []ecosystem.AdditionalMount) []crd.AdditionalMount { if len(mounts) == 0 { // an empty slice and nil are serialized differently diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index d633c47e..77d76d29 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -9,40 +9,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/resource" ) -func Test_convertMinimumVolumeSizeToDTO(t *testing.T) { - volumeSize1g := resource.MustParse("1Gi") - val1Gi := "1Gi" - tests := []struct { - name string - minVolSize *ecosystem.VolumeSize - want *string - }{ - { - name: "nil", - minVolSize: nil, - want: nil, - }, - { - name: "empty", - minVolSize: &ecosystem.VolumeSize{}, - want: nil, - }, - { - name: "1Gi", - minVolSize: &volumeSize1g, - want: &val1Gi, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertMinimumVolumeSizeToDTO(tt.minVolSize), "convertMinimumVolumeSizeToDTO(%v)", tt.minVolSize) - }) - } -} - func Test_convertToDoguDiffStateDTO(t *testing.T) { t.Run("should convert empty reverse proxy config", func(t *testing.T) { // given From 0746b2c0f5b742eb4f37ea4d67d29a4f1b19c6e0 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 15 Sep 2025 16:01:11 +0200 Subject: [PATCH 062/119] #121 display only changes in stateDiff --- pkg/application/stateDiffUseCase_test.go | 21 +-- pkg/domain/stateDiffComponent.go | 20 ++- pkg/domain/stateDiffComponent_test.go | 52 ++---- pkg/domain/stateDiffConfig.go | 10 +- pkg/domain/stateDiffConfig_test.go | 98 +---------- pkg/domain/stateDiffDogu.go | 23 ++- pkg/domain/stateDiffDoguConfig.go | 6 +- pkg/domain/stateDiffDogu_test.go | 211 +++-------------------- pkg/domain/stateDiffGlobalConfig.go | 6 +- 9 files changed, 89 insertions(+), 358 deletions(-) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index cb2d5b3c..af8f3c45 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -325,20 +325,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, NeededActions: []domain.Action{domain.ActionUpgrade}, }, - { - DoguName: "nginx-ingress", - Actual: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersionToPtr(t, "1.8.5"), - Absent: false, - }, - Expected: domain.DoguDiffState{ - Namespace: "k8s", - Version: mustParseVersionToPtr(t, "1.8.5"), - Absent: false, - }, - NeededActions: nil, - }, { DoguName: "nginx-static", Actual: domain.DoguDiffState{ @@ -409,6 +395,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { // then require.NoError(t, err) + // only changes are expected expectedConfigDiff := []domain.GlobalConfigEntryDiff{ { Key: "globalKey1", @@ -416,12 +403,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Expected: domain.GlobalConfigValueState{Value: &val1, Exists: true}, NeededAction: domain.ConfigActionSet, }, - { - Key: "globalKey2", - Actual: domain.GlobalConfigValueState{Value: nil, Exists: false}, - Expected: domain.GlobalConfigValueState{Value: nil, Exists: false}, - NeededAction: domain.ConfigActionNone, - }, } assert.ElementsMatch(t, expectedConfigDiff, blueprint.StateDiff.GlobalConfigDiffs) }) diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go index 9e2235e3..57b37244 100644 --- a/pkg/domain/stateDiffComponent.go +++ b/pkg/domain/stateDiffComponent.go @@ -113,7 +113,10 @@ func determineComponentDiffs(blueprintComponents []Component, installedComponent if err != nil { return nil, err } - componentDiffs[blueprintComponent.Name.SimpleName] = compDiff + // only add changes to diff + if compDiff != nil { + componentDiffs[blueprintComponent.Name.SimpleName] = *compDiff + } } for _, installedComponent := range installedComponents { @@ -125,7 +128,10 @@ func determineComponentDiffs(blueprintComponents []Component, installedComponent if err != nil { return nil, err } - componentDiffs[installedComponent.Name.SimpleName] = compDiff + // only add changes to diff + if compDiff != nil { + componentDiffs[installedComponent.Name.SimpleName] = *compDiff + } } } return maps.Values(componentDiffs), nil @@ -134,7 +140,7 @@ func determineComponentDiffs(blueprintComponents []Component, installedComponent // determineComponentDiff creates a ComponentDiff out of a Component from the blueprint and the ecosystem.ComponentInstallation in the ecosystem. // If the Component is nil (was not in the blueprint), the actual state is also the expected state. // If the installedComponent is nil, it is considered to be not installed currently. -func determineComponentDiff(blueprintComponent *Component, installedComponent *ecosystem.ComponentInstallation) (ComponentDiff, error) { +func determineComponentDiff(blueprintComponent *Component, installedComponent *ecosystem.ComponentInstallation) (*ComponentDiff, error) { var expectedState, actualState ComponentDiffState componentName := common.SimpleComponentName("") // either blueprintComponent or installedComponent could be nil @@ -165,10 +171,14 @@ func determineComponentDiff(blueprintComponent *Component, installedComponent *e nextActions, err := getComponentActions(expectedState, actualState) if err != nil { - return ComponentDiff{}, fmt.Errorf("failed to determine diff for component %q : %w", componentName, err) + return nil, fmt.Errorf("failed to determine diff for component %q : %w", componentName, err) + } + + if len(nextActions) == 0 { + return nil, nil } - return ComponentDiff{ + return &ComponentDiff{ Name: componentName, Expected: expectedState, Actual: actualState, diff --git a/pkg/domain/stateDiffComponent_test.go b/pkg/domain/stateDiffComponent_test.go index 350ae55d..ede9af7a 100644 --- a/pkg/domain/stateDiffComponent_test.go +++ b/pkg/domain/stateDiffComponent_test.go @@ -34,7 +34,7 @@ func Test_determineComponentDiff(t *testing.T) { tests := []struct { name string args args - want ComponentDiff + want *ComponentDiff }{ { name: "equal, no action", @@ -42,12 +42,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - NeededActions: nil, - }, + want: nil, }, { name: "install", @@ -55,7 +50,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: nil, }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState("", nil, true, nil), Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), @@ -68,7 +63,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(nil, true, nil), installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), Expected: mockComponentDiffState(testDistributionNamespace, nil, true, nil), @@ -81,7 +76,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3212, false, nil), installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), @@ -94,7 +89,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), @@ -107,7 +102,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), @@ -120,7 +115,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: mockTargetComponent(compVersion3211, false, nil), installedComponent: mockComponentInstallation(compVersion3212), }, - want: ComponentDiff{ + want: &ComponentDiff{ Name: testComponentName.SimpleName, Actual: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), @@ -133,12 +128,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: nil, installedComponent: mockComponentInstallation(compVersion3211), }, - want: ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - NeededActions: nil, - }, + want: nil, }, { name: "should stay absent, no action", // this is empty set comparison is weird and should basically not occur @@ -146,12 +136,7 @@ func Test_determineComponentDiff(t *testing.T) { blueprintComponent: nil, installedComponent: nil, }, - want: ComponentDiff{ - Name: "", - Actual: ComponentDiffState{Absent: true}, - Expected: ComponentDiffState{Absent: true}, - NeededActions: nil, - }, + want: nil, }, } for _, tt := range tests { @@ -279,22 +264,7 @@ func Test_determineComponentDiffs(t *testing.T) { }, }, }, - want: []ComponentDiff{ - { - Name: testComponentName.SimpleName, - Actual: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - Absent: false, - }, - Expected: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - Absent: false, - }, - NeededActions: nil, - }, - }, + want: []ComponentDiff{}, }, { name: "determine distribution namespace switch", diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index a0b5ff8b..eca8e281 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -53,7 +53,10 @@ func determineDogusConfigDiffs( ) map[cescommons.SimpleName]DoguConfigDiffs { diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} for doguName, bluprintDoguConfig := range blueprintDoguConfigs { - diffsPerDogu[doguName] = determineDoguConfigDiffs(doguName, bluprintDoguConfig, configByDogu, false) + configDiffs := determineDoguConfigDiffs(doguName, bluprintDoguConfig, configByDogu, false) + if len(configDiffs) > 0 { + diffsPerDogu[doguName] = configDiffs + } } return diffsPerDogu } @@ -66,7 +69,10 @@ func determineSensitiveDogusConfigDiffs( diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} for doguName, blueprintDoguConfig := range blueprintDoguConfigs { setSensitiveConfigValues(doguName, blueprintDoguConfig, referencedValues) - diffsPerDogu[doguName] = determineDoguConfigDiffs(doguName, blueprintDoguConfig, configByDogu, true) + configDiffs := determineDoguConfigDiffs(doguName, blueprintDoguConfig, configByDogu, true) + if len(configDiffs) > 0 { + diffsPerDogu[doguName] = configDiffs + } } return diffsPerDogu } diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index 483c4018..9b9930ea 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -96,24 +96,9 @@ func Test_determineConfigDiff(t *testing.T) { //then assert.Equal(t, map[cescommons.SimpleName]DoguConfigDiffs{}, dogusConfigDiffs) assert.Equal(t, map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, sensitiveConfigDiffs) - assert.Equal(t, 4, len(globalConfigDiff)) + assert.Equal(t, 2, len(globalConfigDiff)) // only changes hitKeys := make(map[string]bool) for _, diff := range globalConfigDiff { - if diff.Key == "key1" { - assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ - Key: "key1", - Actual: GlobalConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - Expected: GlobalConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - NeededAction: ConfigActionNone, - })) - hitKeys["key1"] = true - } if diff.Key == "key2" { assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ Key: "key2", @@ -144,23 +129,8 @@ func Test_determineConfigDiff(t *testing.T) { })) hitKeys["key3"] = true } - if diff.Key == "key4" { - assert.Empty(t, cmp.Diff(diff, GlobalConfigEntryDiff{ - Key: "key4", - Actual: GlobalConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: GlobalConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionNone, - })) - hitKeys["key4"] = true - } } - assert.Equal(t, 4, len(hitKeys)) + assert.Equal(t, 2, len(hitKeys)) }) t.Run("all actions normal dogu config", func(t *testing.T) { @@ -214,24 +184,9 @@ func Test_determineConfigDiff(t *testing.T) { assert.Equal(t, GlobalConfigDiffs(nil), globalConfigDiff) require.NotNil(t, dogusConfigDiffs["dogu1"]) assert.Equal(t, SensitiveDoguConfigDiffs(nil), sensitiveConfigDiffs["dogu1"]) - assert.Equal(t, 4, len(dogusConfigDiffs["dogu1"])) + assert.Equal(t, 2, len(dogusConfigDiffs["dogu1"])) // only changes hitKeys := make(map[common.DoguConfigKey]bool) for _, diff := range dogusConfigDiffs["dogu1"] { - if diff.Key == dogu1Key1 { - assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ - Key: dogu1Key1, - Actual: DoguConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - NeededAction: ConfigActionNone, - })) - hitKeys[dogu1Key1] = true - } if diff.Key == dogu1Key2 { assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ Key: dogu1Key2, @@ -262,27 +217,8 @@ func Test_determineConfigDiff(t *testing.T) { })) hitKeys[dogu1Key3] = true } - //domain.DoguConfigEntryDiff{Key:common.DoguConfigKey{DoguName:"dogu1", Key:"key3"}, - //Actual:domain.DoguConfigValueState{Value:"value", Exists:true}, - //Expected:domain.DoguConfigValueState{Value:"", Exists:false}, - //NeededAction:"set"} - if diff.Key == dogu1Key4 { - assert.Empty(t, cmp.Diff(diff, DoguConfigEntryDiff{ - Key: dogu1Key4, - Actual: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionNone, - })) - hitKeys[dogu1Key4] = true - } } - assert.Equal(t, 4, len(hitKeys)) + assert.Equal(t, 2, len(hitKeys)) }) t.Run("all actions for sensitive dogu config for present dogu", func(t *testing.T) { //given ecosystem config @@ -343,21 +279,9 @@ func Test_determineConfigDiff(t *testing.T) { assert.Equal(t, GlobalConfigDiffs(nil), globalConfigDiff) assert.Equal(t, DoguConfigDiffs(nil), dogusConfigDiffs["dogu1"]) require.NotNil(t, sensitiveConfigDiffs["dogu1"]) - assert.Equal(t, 3, len(sensitiveConfigDiffs["dogu1"])) + assert.Equal(t, 1, len(sensitiveConfigDiffs["dogu1"])) // only changes entriesDogu1 := SensitiveDoguConfigDiffs{ - { - Key: dogu1Key1, - Actual: DoguConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - Expected: DoguConfigValueState{ - Value: (*string)(&val1), - Exists: true, - }, - NeededAction: ConfigActionNone, - }, { Key: dogu1Key2, Actual: DoguConfigValueState{ @@ -370,18 +294,6 @@ func Test_determineConfigDiff(t *testing.T) { }, NeededAction: ConfigActionSet, }, - { - Key: dogu1Key3, - Actual: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - Expected: DoguConfigValueState{ - Value: nil, - Exists: false, - }, - NeededAction: ConfigActionNone, - }, } assert.ElementsMatch(t, sensitiveConfigDiffs["dogu1"], entriesDogu1) }) diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index b9b288c9..6e219b91 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -79,14 +79,22 @@ func determineDoguDiffs(blueprintDogus []Dogu, installedDogus map[cescommons.Sim var doguDiffs = map[cescommons.SimpleName]DoguDiff{} for _, blueprintDogu := range blueprintDogus { installedDogu := installedDogus[blueprintDogu.Name.SimpleName] - doguDiffs[blueprintDogu.Name.SimpleName] = determineDoguDiff(&blueprintDogu, installedDogu) + determinedDoguDiff := determineDoguDiff(&blueprintDogu, installedDogu) + // only add changes to diff + if determinedDoguDiff != nil { + doguDiffs[blueprintDogu.Name.SimpleName] = *determinedDoguDiff + } } for _, installedDogu := range installedDogus { _, found := FindDoguByName(blueprintDogus, installedDogu.Name.SimpleName) // Only create DoguDiff if the installed dogu is not found in the blueprint. // If the installed dogu is in blueprint the DoguDiff was already determined above. if !found { - doguDiffs[installedDogu.Name.SimpleName] = determineDoguDiff(nil, installedDogu) + determinedDoguDiff := determineDoguDiff(nil, installedDogu) + // only add changes to diff + if determinedDoguDiff != nil { + doguDiffs[installedDogu.Name.SimpleName] = *determinedDoguDiff + } } } return maps.Values(doguDiffs) @@ -96,7 +104,7 @@ func determineDoguDiffs(blueprintDogus []Dogu, installedDogus map[cescommons.Sim // if the Dogu is nil (was not in the blueprint), the actual state is also the expected state. // if the installedDogu is nil, it is considered to be not installed currently. // returns a DoguDiff -func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstallation) DoguDiff { +func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstallation) *DoguDiff { var expectedState, actualState DoguDiffState var doguName cescommons.SimpleName = "" // either blueprintDogu or installedDogu could be nil @@ -129,11 +137,16 @@ func determineDoguDiff(blueprintDogu *Dogu, installedDogu *ecosystem.DoguInstall } } - return DoguDiff{ + actions := getNeededDoguActions(expectedState, actualState) + if len(actions) == 0 { + return nil + } + + return &DoguDiff{ DoguName: doguName, Expected: expectedState, Actual: actualState, - NeededActions: getNeededDoguActions(expectedState, actualState), + NeededActions: actions, } } diff --git a/pkg/domain/stateDiffDoguConfig.go b/pkg/domain/stateDiffDoguConfig.go index fc45665b..26d18ffb 100644 --- a/pkg/domain/stateDiffDoguConfig.go +++ b/pkg/domain/stateDiffDoguConfig.go @@ -85,7 +85,11 @@ func determineDoguConfigDiffs(doguName cescommons.SimpleName, wantedConfig DoguC DoguName: doguName, Key: expectedConfig.Key, } - doguConfigDiff = append(doguConfigDiff, newDoguConfigEntryDiff(configKey, actualValue, exists, expectedConfig.Value, !expectedConfig.Absent)) + diff := newDoguConfigEntryDiff(configKey, actualValue, exists, expectedConfig.Value, !expectedConfig.Absent) + // only add diff if there are changes + if diff.NeededAction != ConfigActionNone { + doguConfigDiff = append(doguConfigDiff, diff) + } } return doguConfigDiff } diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index db28df2d..a273a8d2 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -32,7 +32,7 @@ func Test_determineDoguDiff(t *testing.T) { tests := []struct { name string args args - want DoguDiff + want *DoguDiff }{ { name: "equal, no action", @@ -47,20 +47,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3211, }, }, - want: DoguDiff{ - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - Absent: false, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - Absent: false, - }, - NeededActions: nil, - }, + want: nil, }, { name: "install", @@ -72,7 +59,7 @@ func Test_determineDoguDiff(t *testing.T) { }, installedDogu: nil, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Absent: true, @@ -97,7 +84,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3211, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -124,7 +111,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3211, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -152,7 +139,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3211, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -181,7 +168,7 @@ func Test_determineDoguDiff(t *testing.T) { MinVolumeSize: &quantity10M, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Expected: DoguDiffState{ Namespace: officialNamespace, @@ -210,20 +197,7 @@ func Test_determineDoguDiff(t *testing.T) { MinVolumeSize: &quantity100M, }, }, - want: DoguDiff{ - DoguName: "nexus", - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - MinVolumeSize: &quantity100M, - }, - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - MinVolumeSize: &quantity100M, - }, - NeededActions: nil, - }, + want: nil, }, { name: "don't update minVolSize if actual > expected", @@ -239,20 +213,7 @@ func Test_determineDoguDiff(t *testing.T) { MinVolumeSize: &quantity100M, }, }, - want: DoguDiff{ - DoguName: "nexus", - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - MinVolumeSize: &quantity10M, - }, - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - MinVolumeSize: &quantity100M, - }, - NeededActions: nil, - }, + want: nil, }, { name: "multiple update actions", @@ -273,7 +234,7 @@ func Test_determineDoguDiff(t *testing.T) { MinVolumeSize: &volumeSize1, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -305,7 +266,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3212, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -327,18 +288,7 @@ func Test_determineDoguDiff(t *testing.T) { Version: version3211, }, }, - want: DoguDiff{ - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - }, - NeededActions: nil, - }, + want: nil, }, { name: "should stay absent, no action", @@ -346,16 +296,7 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: nil, installedDogu: nil, }, - want: DoguDiff{ - DoguName: "", - Actual: DoguDiffState{ - Absent: true, - }, - Expected: DoguDiffState{ - Absent: true, - }, - NeededActions: []Action{}, - }, + want: nil, }, { name: "update proxy body size", @@ -375,7 +316,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -412,7 +353,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -449,24 +390,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ - MaxBodySize: nil, - }, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ - MaxBodySize: nil, - }, - }, - NeededActions: nil, - }, + want: nil, }, { name: "no action if additional mounts are equal", @@ -508,46 +432,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "configmap", - Volume: "volume", - Subfolder: &subfolder, - }, - { - SourceType: ecosystem.DataSourceSecret, - Name: "secret", - Volume: "secvolume", - Subfolder: &subfolder2, - }, - }, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "configmap", - Volume: "volume", - Subfolder: &subfolder, - }, - { - SourceType: ecosystem.DataSourceSecret, - Name: "secret", - Volume: "secvolume", - Subfolder: &subfolder2, - }, - }, - }, - NeededActions: nil, - }, + want: nil, }, { name: "no action if additional mounts are equal but order is different", @@ -589,46 +474,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "configmap", - Volume: "volume", - Subfolder: &subfolder, - }, - { - SourceType: ecosystem.DataSourceSecret, - Name: "secret", - Volume: "secvolume", - Subfolder: &subfolder2, - }, - }, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3212, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceSecret, - Name: "secret", - Volume: "secvolume", - Subfolder: &subfolder2, - }, - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "configmap", - Volume: "volume", - Subfolder: &subfolder, - }, - }, - }, - NeededActions: nil, - }, + want: nil, }, { name: "needs update action for additional mounts if the size is different", @@ -664,7 +510,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -739,7 +585,7 @@ func Test_determineDoguDiff(t *testing.T) { }, }, }, - want: DoguDiff{ + want: &DoguDiff{ DoguName: "nexus", Actual: DoguDiffState{ Namespace: officialNamespace, @@ -844,22 +690,7 @@ func Test_determineDoguDiffs(t *testing.T) { }, }, }, - want: []DoguDiff{ - { - DoguName: "nexus", - Actual: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - Absent: false, - }, - Expected: DoguDiffState{ - Namespace: officialNamespace, - Version: &version3211, - Absent: false, - }, - NeededActions: nil, - }, - }, + want: []DoguDiff{}, }, { name: "an installed dogu which is also in the blueprint", diff --git a/pkg/domain/stateDiffGlobalConfig.go b/pkg/domain/stateDiffGlobalConfig.go index 476295b3..6455c2e4 100644 --- a/pkg/domain/stateDiffGlobalConfig.go +++ b/pkg/domain/stateDiffGlobalConfig.go @@ -74,7 +74,11 @@ func determineGlobalConfigDiffs( if actualExists { actualValue = &actualEntry } - configDiffs = append(configDiffs, newGlobalConfigEntryDiff(expectedConfig.Key, actualValue, actualExists, expectedConfig.Value, !expectedConfig.Absent)) + diff := newGlobalConfigEntryDiff(expectedConfig.Key, actualValue, actualExists, expectedConfig.Value, !expectedConfig.Absent) + // only add diff if there are changes + if diff.NeededAction != ConfigActionNone { + configDiffs = append(configDiffs, diff) + } } return configDiffs } From 83d63113662394736f98503e520437c2fc1e4a11 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 16 Sep 2025 15:55:03 +0200 Subject: [PATCH 063/119] #121 incorporate review comments --- .../doguregistry/doguDescriptorRepository.go | 2 +- .../componentInstallationRepo_test.go | 9 +++-- .../componentcr/componentSerializer.go | 4 +-- .../reconciler/blueprint_controller.go | 2 +- .../applyComponentsUseCase_test.go | 1 + pkg/application/applyDogusUseCase_test.go | 1 + pkg/application/ecosystemHealthUseCase.go | 4 +-- pkg/domain/blueprintSpec.go | 11 +++---- pkg/domain/blueprintSpec_test.go | 33 ++++++++++--------- 9 files changed, 34 insertions(+), 33 deletions(-) diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index 21e977bf..d7e5b8c2 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -38,7 +38,7 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer } // TODO: doesn't work with "old" dogu operator - err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) + //err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) if err != nil { // just log the error, no need to fail the reconcilation logger.Info("failed to add dogu to local repository", diff --git a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go index c5f5ef9d..a9316317 100644 --- a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go @@ -2,9 +2,10 @@ package componentcr import ( "context" + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "k8s.io/apimachinery/pkg/types" - "testing" "github.com/Masterminds/semver/v3" "github.com/stretchr/testify/assert" @@ -345,8 +346,7 @@ func Test_componentInstallationRepo_Create(t *testing.T) { ComponentVersionLabelKey: component.ExpectedVersion.String(), "app": "ces", "k8s.cloudogu.com/app": "ces", - "dogu.name": string(component.Name.SimpleName), - "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "component.name": string(component.Name.SimpleName), "app.kubernetes.io/name": string(component.Name.SimpleName), "app.kubernetes.io/version": component.ExpectedVersion.String(), "app.kubernetes.io/part-of": "ces", @@ -410,8 +410,7 @@ func Test_componentInstallationRepo_Create(t *testing.T) { ComponentVersionLabelKey: component.ExpectedVersion.String(), "app": "ces", "k8s.cloudogu.com/app": "ces", - "dogu.name": string(component.Name.SimpleName), - "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "component.name": string(component.Name.SimpleName), "app.kubernetes.io/name": string(component.Name.SimpleName), "app.kubernetes.io/version": component.ExpectedVersion.String(), "app.kubernetes.io/part-of": "ces", diff --git a/pkg/adapter/kubernetes/componentcr/componentSerializer.go b/pkg/adapter/kubernetes/componentcr/componentSerializer.go index 426352eb..36fdaba1 100644 --- a/pkg/adapter/kubernetes/componentcr/componentSerializer.go +++ b/pkg/adapter/kubernetes/componentcr/componentSerializer.go @@ -3,6 +3,7 @@ package componentcr import ( "encoding/json" "fmt" + "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -109,8 +110,7 @@ func toComponentCR(component *ecosystem.ComponentInstallation) (*compV1.Componen ComponentVersionLabelKey: component.ExpectedVersion.String(), "app": "ces", "k8s.cloudogu.com/app": "ces", - "dogu.name": string(component.Name.SimpleName), - "k8s.cloudogu.com/dogu.name": string(component.Name.SimpleName), + "component.name": string(component.Name.SimpleName), "app.kubernetes.io/name": string(component.Name.SimpleName), "app.kubernetes.io/version": component.ExpectedVersion.String(), "app.kubernetes.io/part-of": "ces", diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 2a7c796a..8025f69f 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -31,7 +31,7 @@ func NewBlueprintReconciler( return &BlueprintReconciler{blueprintChangeHandler: blueprintChangeHandler} } -// +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints,verbs=get;list;watch;update;patch +// +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints,verbs=get;watch;update;patch // +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints/status,verbs=get;update;patch // +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints/finalizers,verbs=update diff --git a/pkg/application/applyComponentsUseCase_test.go b/pkg/application/applyComponentsUseCase_test.go index 4aa80a79..eb6134fe 100644 --- a/pkg/application/applyComponentsUseCase_test.go +++ b/pkg/application/applyComponentsUseCase_test.go @@ -47,6 +47,7 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { } repoMock := newMockBlueprintSpecRepository(t) + // Here is the important part: we expect the update to be called only once repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil).Twice() diff --git a/pkg/application/applyDogusUseCase_test.go b/pkg/application/applyDogusUseCase_test.go index 4460629b..b9287a8e 100644 --- a/pkg/application/applyDogusUseCase_test.go +++ b/pkg/application/applyDogusUseCase_test.go @@ -46,6 +46,7 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { } repoMock := newMockBlueprintSpecRepository(t) + // Here is the important part: we expect the update to be called only once repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) diff --git a/pkg/application/ecosystemHealthUseCase.go b/pkg/application/ecosystemHealthUseCase.go index 9db804d8..9535646e 100644 --- a/pkg/application/ecosystemHealthUseCase.go +++ b/pkg/application/ecosystemHealthUseCase.go @@ -42,8 +42,8 @@ func (useCase *EcosystemHealthUseCase) CheckEcosystemHealth( blueprint.Config.IgnoreDoguHealth, blueprint.Config.IgnoreComponentHealth, ) - infoChanged := blueprint.HandleHealthResult(health, determineHealthError) - if infoChanged { + healthChanged := blueprint.HandleHealthResult(health, determineHealthError) + if healthChanged { updateErr := useCase.blueprintRepo.Update(ctx, blueprint) if updateErr != nil { return ecosystem.HealthResult{}, fmt.Errorf( diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 271f6812..43290dcb 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -64,7 +64,7 @@ type BlueprintConfiguration struct { // ValidateStatically checks the blueprintSpec for semantic errors and sets the status to the result. // Here will be only checked, what can be checked without any external information, e.g. without dogu specification. -// changed a domain.InvalidBlueprintError if blueprint is invalid +// returns a domain.InvalidBlueprintError if blueprint is invalid // or nil otherwise. func (spec *BlueprintSpec) ValidateStatically() error { var errorList []error @@ -245,7 +245,7 @@ func (spec *BlueprintSpec) MissingConfigReferences(error error) { // installedDogus are a map in the form of simpleDoguName->*DoguInstallation. There should be no nil values. // The StateDiff is an 'as is' representation, therefore no error is thrown, e.g. if dogu namespaces are different and namespace changes are not allowed. // If there are not allowed actions should be considered at the start of the execution of the blueprint. -// changed an error if the BlueprintSpec is not in the necessary state to determine the stateDiff. +// returns an error if the BlueprintSpec is not in the necessary state to determine the stateDiff. func (spec *BlueprintSpec) DetermineStateDiff( ecosystemState ecosystem.EcosystemState, referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue, @@ -331,7 +331,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( // HandleHealthResult sets the healthCondition accordingly to the healthResult and a possible error. // if an error is given, the condition will be set to unknown. -// The function changed true if the condition changed, otherwise false. +// The function returns true if the condition changed, otherwise false. func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResult, err error) bool { if err != nil { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ @@ -375,9 +375,8 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul return conditionChanged } -// ShouldBeApplied changed true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. +// ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. func (spec *BlueprintSpec) ShouldBeApplied() bool { - // wrote it in the long form to reduce complexity if spec.Config.DryRun { return false } @@ -635,7 +634,7 @@ func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { } // Complete is used to mark the blueprint as completed and to inform the user. -// Returns true if anything changed, false otherwise. +// Returns true if the condition changed, false otherwise. func (spec *BlueprintSpec) Complete() bool { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionCompleted, diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index b13f0fc2..66964f7e 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -471,7 +471,7 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { condition := meta.FindStatusCondition(blueprint.Conditions, ConditionCompleted) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Completed", condition.Reason) - assert.Equal(t, "", condition.Message) + assert.Empty(t, condition.Message) assert.Equal(t, []Event{CompletedEvent{}}, blueprint.Events) }) t.Run("no change if executed twice", func(t *testing.T) { @@ -487,7 +487,7 @@ func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { condition := meta.FindStatusCondition(blueprint.Conditions, ConditionCompleted) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Completed", condition.Reason) - assert.Equal(t, "", condition.Message) + assert.Empty(t, condition.Message) assert.Equal(t, 0, len(blueprint.Events)) }) } @@ -510,6 +510,7 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { blueprint.ValidateDynamically(givenErr) require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, "BlueprintSpecInvalid", blueprint.Events[0].Name()) }) } @@ -703,7 +704,7 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { }, } - t.Run("applied", func(t *testing.T) { + t.Run("set condition to applied when no change", func(t *testing.T) { blueprint := BlueprintSpec{ StateDiff: diff, } @@ -719,7 +720,7 @@ func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { assert.Equal(t, ComponentsAppliedEvent{Diffs: diff.ComponentDiffs}, blueprint.Events[0]) }) - t.Run("error", func(t *testing.T) { + t.Run("set condition to cannotApply on error", func(t *testing.T) { blueprint := BlueprintSpec{ StateDiff: diff, } @@ -900,7 +901,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { expected expected }{ { - name: "none, diff -> NeedToApply, event", + name: "no condition + diff -> NeedToApply + event", given: given{ hasDiff: true, }, @@ -916,7 +917,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "none, no diff -> Applied, event", + name: "no condition + no diff -> Applied + event", given: given{ hasDiff: false, }, @@ -932,7 +933,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "Unknown, diff -> NeedToApply, event", + name: "Condition Unknown + diff -> NeedToApply + event", given: given{ hasDiff: true, condition: Condition{ @@ -953,7 +954,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "Unknown, no diff -> Applied, event", + name: "Condition Unknown + no diff -> Applied + event", given: given{ hasDiff: false, }, @@ -969,7 +970,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "NeedToApply, diff -> NeedToApply, event", + name: "Condition NeedToApply + diff -> NeedToApply + event", given: given{ hasDiff: true, condition: Condition{ @@ -991,7 +992,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "NeedToApply, diff -> NeedToApply, no event", + name: "Condition NeedToApply + diff -> NeedToApply + no event", given: given{ hasDiff: true, condition: Condition{ @@ -1013,7 +1014,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "NeedToApply, no diff -> Applied, event", + name: "Condition NeedToApply + no diff -> Applied + event", given: given{ hasDiff: false, condition: Condition{ @@ -1035,7 +1036,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "Applied, diff -> NeedToApply, event", + name: "Condition Applied + diff -> NeedToApply + event", given: given{ hasDiff: true, condition: Condition{ @@ -1057,7 +1058,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "Applied, no diff -> no change", + name: "Condition Applied + no diff -> no change", given: given{ hasDiff: false, condition: Condition{ @@ -1073,7 +1074,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "CannotApply, no diff -> no change", + name: "Condition CannotApply + no diff -> no change", given: given{ hasDiff: false, condition: Condition{ @@ -1089,7 +1090,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "CannotApply, diff -> no change", + name: "Condition CannotApply + diff -> no change", given: given{ hasDiff: true, condition: Condition{ @@ -1105,7 +1106,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, { - name: "error given", + name: "on error -> Condition Unknown + error", given: given{ hasDiff: false, diffErr: assert.AnError, From b3db9e0bacafa3de5eb5c8499ecf636b16d08566 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 17 Sep 2025 10:29:53 +0200 Subject: [PATCH 064/119] #121 rename domain DryRun to Stopped --- pkg/adapter/doguregistry/doguDescriptorRepository.go | 6 ++---- .../blueprintcr/v2/blueprintSpecCRRepository.go | 2 +- .../blueprintcr/v2/blueprintSpecCRRepository_test.go | 4 ++-- pkg/application/blueprintSpecChangeUseCase.go | 1 + pkg/application/blueprintSpecChangeUseCase_test.go | 2 +- pkg/domain/blueprintSpec.go | 6 +++--- pkg/domain/blueprintSpec_test.go | 10 +++++----- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index d7e5b8c2..7f9204b8 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -74,12 +74,10 @@ func (r *DoguDescriptorRepository) getRemoteDogu(ctx context.Context, qualifiedD func (r *DoguDescriptorRepository) getLocalDogu(ctx context.Context, qualifiedDoguVersion cescommons.QualifiedVersion, logger logr.Logger) *core.Dogu { dogu, err := r.localRepository.Get(ctx, cescommons.NewSimpleNameVersion(qualifiedDoguVersion.Name.SimpleName, qualifiedDoguVersion.Version)) if err == nil { - // TODO: move to V(2) later - logger.Info("local dogu descriptor hit", "dogu", qualifiedDoguVersion.Name.SimpleName) + logger.V(2).Info("local dogu descriptor hit", "dogu", qualifiedDoguVersion.Name.SimpleName) return dogu } else { - // TODO: move to V(2) later - logger.Info("local dogu descriptor miss", "error", err) + logger.V(2).Info("local dogu descriptor miss", "error", err) return nil } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 2d271e1c..49fd1300 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -83,7 +83,7 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) IgnoreDoguHealth: boolPtrToValue(blueprintCR.Spec.IgnoreDoguHealth), IgnoreComponentHealth: boolPtrToValue(blueprintCR.Spec.IgnoreComponentHealth), AllowDoguNamespaceSwitch: boolPtrToValue(blueprintCR.Spec.AllowDoguNamespaceSwitch), - DryRun: boolPtrToValue(blueprintCR.Spec.Stopped), + Stopped: boolPtrToValue(blueprintCR.Spec.Stopped), }, } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index dc39aa86..eae3f006 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -69,7 +69,7 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: true, AllowDoguNamespaceSwitch: true, - DryRun: true, + Stopped: true, }, StateDiff: domain.StateDiff{}, PersistenceContext: persistenceContext, @@ -108,7 +108,7 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: true, AllowDoguNamespaceSwitch: true, - DryRun: true, + Stopped: true, }, StateDiff: domain.StateDiff{}, PersistenceContext: persistenceContext, diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index aa35d7d9..2c6e9d73 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -128,6 +128,7 @@ func (useCase *BlueprintSpecChangeUseCase) applyBlueprint(ctx context.Context, b return err } err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) + // TODO: prevent Dogu restarts here to avoid starting Dogus with config not fitting to the expected dogu version if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 7041bdbc..77d1c8c1 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -63,7 +63,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { } testDryRunBlueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, - Config: domain.BlueprintConfiguration{DryRun: true}, + Config: domain.BlueprintConfiguration{Stopped: true}, } type fields struct { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 43290dcb..054e5d17 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -58,8 +58,8 @@ type BlueprintConfiguration struct { IgnoreComponentHealth bool // AllowDoguNamespaceSwitch allows the blueprint upgrade to switch a dogus namespace AllowDoguNamespaceSwitch bool - // DryRun lets the user test a blueprint run to check if all attributes of the blueprint are correct and avoid a result with a failure state. - DryRun bool + // Stopped lets the user test a blueprint run to check if all attributes of the blueprint are correct and avoid a result with a failure state. + Stopped bool } // ValidateStatically checks the blueprintSpec for semantic errors and sets the status to the result. @@ -377,7 +377,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul // ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. func (spec *BlueprintSpec) ShouldBeApplied() bool { - if spec.Config.DryRun { + if spec.Config.Stopped { return false } return spec.StateDiff.HasChanges() diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 66964f7e..d221cbeb 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -518,7 +518,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { t.Run("should be applied on global config change", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ - DryRun: false, + Stopped: false, }, StateDiff: StateDiff{ GlobalConfigDiffs: []GlobalConfigEntryDiff{ @@ -538,7 +538,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { } spec := &BlueprintSpec{ Config: BlueprintConfiguration{ - DryRun: false, + Stopped: false, }, StateDiff: StateDiff{ DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{ @@ -560,7 +560,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { } spec := &BlueprintSpec{ Config: BlueprintConfiguration{ - DryRun: false, + Stopped: false, }, StateDiff: StateDiff{ SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ @@ -578,7 +578,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { t.Run("should not be applied without any changes", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ - DryRun: false, + Stopped: false, }, } assert.Falsef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") @@ -586,7 +586,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { t.Run("should not be applied due to dry run", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ - DryRun: true, + Stopped: true, }, } assert.Falsef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") From d0acd5b5ec0bbc8a4bde3a1df8fae10de712daa3 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 17 Sep 2025 10:35:07 +0200 Subject: [PATCH 065/119] #121 blueprint should be applied until completed --- pkg/application/blueprintSpecChangeUseCase.go | 1 + pkg/domain/blueprintSpec.go | 3 +- pkg/domain/blueprintSpec_test.go | 32 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 2c6e9d73..387bef42 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -86,6 +86,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C // === Apply from here on === err = useCase.applyBlueprint(ctx, blueprint) if err != nil { + logger.Error(err, "cannot apply blueprint") return err } diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 054e5d17..624d0854 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -380,7 +380,8 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { if spec.Config.Stopped { return false } - return spec.StateDiff.HasChanges() + // not true does not equal IsStatusConditionFalse here, because not true includes status "unknown" + return !meta.IsStatusConditionTrue(spec.Conditions, ConditionCompleted) || spec.StateDiff.HasChanges() } func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index d221cbeb..b9f9d936 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -515,6 +515,10 @@ func TestBlueprintSpec_ValidateDynamically(t *testing.T) { } func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { + conditionCompleted := Condition{ + Type: ConditionCompleted, + Status: metav1.ConditionTrue, + } t.Run("should be applied on global config change", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ @@ -528,6 +532,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { }, }, }, + Conditions: []Condition{conditionCompleted}, } assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) @@ -550,6 +555,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { }, }, }, + Conditions: []Condition{conditionCompleted}, } assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) @@ -572,6 +578,31 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { }, }, }, + Conditions: []Condition{conditionCompleted}, + } + assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) + t.Run("should be applied on condition completed false", func(t *testing.T) { + spec := &BlueprintSpec{ + Conditions: []Condition{{ + Type: ConditionCompleted, + Status: metav1.ConditionFalse, + }}, + } + assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) + t.Run("should be applied on condition completed unknown", func(t *testing.T) { + spec := &BlueprintSpec{ + Conditions: []Condition{{ + Type: ConditionCompleted, + Status: metav1.ConditionUnknown, + }}, + } + assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") + }) + t.Run("should be applied on condition completed not set", func(t *testing.T) { + spec := &BlueprintSpec{ + Conditions: []Condition{}, } assert.Truef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) @@ -580,6 +611,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { Config: BlueprintConfiguration{ Stopped: false, }, + Conditions: []Condition{conditionCompleted}, } assert.Falsef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) From 637c8d436e76214f853fc948e1c2bad5489ef48e Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 18 Sep 2025 12:28:12 +0200 Subject: [PATCH 066/119] #121 apply blueprint if not completed --- .../reconciler/blueprint_controller.go | 5 +++ .../reconciler/blueprint_controller_test.go | 23 ++++++++++- pkg/application/blueprintSpecChangeUseCase.go | 1 - pkg/application/completeBlueprintUseCase.go | 4 ++ .../completeBlueprintUseCase_test.go | 24 ++++++++++++ .../componentInstallationUseCase.go | 5 ++- .../componentInstallationUseCase_test.go | 25 ++++++++++++ pkg/application/doguInstallationUseCase.go | 3 ++ .../doguInstallationUseCase_test.go | 23 +++++++++++ pkg/domain/blueprintSpec.go | 35 +++++++++++++---- pkg/domain/errors.go | 9 +++++ pkg/domain/stateDiffComponent.go | 39 +++++++------------ pkg/domain/stateDiffComponent_test.go | 6 +-- 13 files changed, 163 insertions(+), 39 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 8025f69f..30acabd4 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -63,6 +63,7 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { var invalidBlueprintError *domain.InvalidBlueprintError var healthError *domain.UnhealthyEcosystemError var awaitSelfUpgradeError *domain.AwaitSelfUpgradeError + var stateDiffNotEmptyError *domain.StateDiffNotEmptyError switch { case errors.As(err, &internalError): errLogger.Error(err, "An internal error occurred and can maybe be fixed by retrying it later") @@ -89,6 +90,10 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { case errors.As(err, &awaitSelfUpgradeError): errLogger.Info("wait for self upgrade") return ctrl.Result{RequeueAfter: 5 * time.Second}, nil + case errors.As(err, &stateDiffNotEmptyError): + errLogger.Info("requeue until state diff is empty") + // fast requeue here since state diff has to be determined again + return ctrl.Result{RequeueAfter: 1 * time.Second}, nil default: errLogger.Error(err, "An unknown error type occurred. Retry with default backoff") return ctrl.Result{}, err // automatic requeue because of non-nil err diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index 228fc435..6cea4512 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -4,12 +4,13 @@ import ( "context" "errors" "fmt" + "testing" + "time" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/application" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/go-logr/logr" - "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -201,6 +202,24 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{}, actual) assert.Contains(t, logSinkMock.output, "0: Blueprint is invalid, therefore there will be no further evaluation.") }) + t.Run("should catch wrapped StateDiffNotEmptyError, issue a log line and requeue timely", func(t *testing.T) { + // given + logSinkMock := newTrivialTestLogSink() + testLogger := logr.New(logSinkMock) + + intermediateErr := &domain.StateDiffNotEmptyError{ + Message: "a generic oh-noez", + } + errorChain := fmt.Errorf("could not do the thing: %w", intermediateErr) + + // when + actual, err := decideRequeueForError(testLogger, errorChain) + + // then + require.NoError(t, err) + assert.Equal(t, ctrl.Result{RequeueAfter: 1 * time.Second}, actual) + assert.Contains(t, logSinkMock.output, "0: requeue until state diff is empty") + }) t.Run("should catch general errors, issue a log line and return requeue with error", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 387bef42..2c6e9d73 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -86,7 +86,6 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C // === Apply from here on === err = useCase.applyBlueprint(ctx, blueprint) if err != nil { - logger.Error(err, "cannot apply blueprint") return err } diff --git a/pkg/application/completeBlueprintUseCase.go b/pkg/application/completeBlueprintUseCase.go index c8a0c746..45844d61 100644 --- a/pkg/application/completeBlueprintUseCase.go +++ b/pkg/application/completeBlueprintUseCase.go @@ -24,6 +24,10 @@ func NewCompleteBlueprintUseCase( // CompleteBlueprint handles the completion of the blueprint after all other steps were successful. // returns a domainservice.InternalError on any error. func (useCase *CompleteBlueprintUseCase) CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + // Only complete if there are no changes left + if blueprint.StateDiff.HasChanges() { + return &domain.StateDiffNotEmptyError{Message: "cannot complete blueprint because the StateDiff has still changes"} + } changed := blueprint.Complete() if changed { err := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/completeBlueprintUseCase_test.go b/pkg/application/completeBlueprintUseCase_test.go index 162c57a2..8969248d 100644 --- a/pkg/application/completeBlueprintUseCase_test.go +++ b/pkg/application/completeBlueprintUseCase_test.go @@ -33,6 +33,30 @@ func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionCompleted)) }) + t.Run("stateDiffNotEnmptyError, if StateDiff not empty", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + StateDiff: domain.StateDiff{ + ComponentDiffs: []domain.ComponentDiff{ + { + Name: "k8s-dogu-operator", + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + }, + }, + } + + repoMock := newMockBlueprintSpecRepository(t) + useCase := NewCompleteBlueprintUseCase(repoMock) + + err := useCase.CompleteBlueprint(testCtx, blueprint) + + require.Error(t, err) + var targetErr *domain.StateDiffNotEmptyError + assert.ErrorAs(t, err, &targetErr) + assert.ErrorContains(t, err, "cannot complete blueprint because the StateDiff has still changes") + assert.Empty(t, blueprint.Conditions) + }) t.Run("no change if already completed", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, diff --git a/pkg/application/componentInstallationUseCase.go b/pkg/application/componentInstallationUseCase.go index d41633b3..6555c699 100644 --- a/pkg/application/componentInstallationUseCase.go +++ b/pkg/application/componentInstallationUseCase.go @@ -61,7 +61,7 @@ func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Co logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplyComponentStates") if len(blueprint.StateDiff.ComponentDiffs) == 0 { - logger.Info("apply no components because blueprint has no component state differences") + logger.V(2).Info("apply no components because blueprint has no component state differences") return nil } @@ -99,6 +99,9 @@ func (useCase *ComponentInstallationUseCase) applyComponentState( }, componentDiff.Expected.Version, componentDiff.Expected.DeployConfig) return useCase.componentRepo.Create(ctx, newComponent) case domain.ActionUninstall: + if componentInstallation == nil { + return &domainservice.NotFoundError{Message: fmt.Sprintf("component %q not found", componentDiff.Name)} + } logger.Info("uninstall component") return useCase.componentRepo.Delete(ctx, componentInstallation.Name.SimpleName) case domain.ActionUpgrade: diff --git a/pkg/application/componentInstallationUseCase_test.go b/pkg/application/componentInstallationUseCase_test.go index 462909e7..cf98ba69 100644 --- a/pkg/application/componentInstallationUseCase_test.go +++ b/pkg/application/componentInstallationUseCase_test.go @@ -8,6 +8,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -218,6 +219,30 @@ func TestComponentInstallationUseCase_applyComponentState(t *testing.T) { require.NoError(t, err) }) + t.Run("should throw NotFoundError on action uninstall when component not found", func(t *testing.T) { + // given + blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) + componentRepoMock := newMockComponentInstallationRepository(t) + + componentDiff := domain.ComponentDiff{ + Name: componentName1, + NeededActions: []domain.Action{domain.ActionUninstall}, + } + + sut := &ComponentInstallationUseCase{ + blueprintSpecRepo: blueprintSpecRepoMock, + componentRepo: componentRepoMock, + } + + // when + err := sut.applyComponentState(testCtx, componentDiff, nil) + + // then + require.Error(t, err) + var targetError *domainservice.NotFoundError + assert.ErrorAs(t, err, &targetError) + }) + t.Run("should update component on action upgrade", func(t *testing.T) { // given blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index db47ea8b..7c70bb94 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -83,6 +83,9 @@ func (useCase *DoguInstallationUseCase) applyDoguState( ) return useCase.doguRepo.Create(ctx, newDogu) case domain.ActionUninstall: + if doguInstallation == nil { + return &domainservice.NotFoundError{Message: fmt.Sprintf("dogu %q not found", doguDiff.DoguName)} + } logger.Info("uninstall dogu") return useCase.doguRepo.Delete(ctx, doguInstallation.Name.SimpleName) case domain.ActionUpgrade: diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 9cb710f8..6deb20d9 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -8,6 +8,7 @@ import ( "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" @@ -144,6 +145,28 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { require.NoError(t, err) }) + t.Run("action uninstall throws NotFoundError when dogu not found", func(t *testing.T) { + doguRepoMock := newMockDoguInstallationRepository(t) + + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + + // when + err := sut.applyDoguState( + testCtx, + domain.DoguDiff{ + DoguName: "postgresql", + NeededActions: []domain.Action{domain.ActionUninstall}, + }, + nil, + domain.BlueprintConfiguration{}, + ) + + // then + require.Error(t, err) + var targetError *domainservice.NotFoundError + assert.ErrorAs(t, err, &targetError) + }) + t.Run("action upgrade", func(t *testing.T) { dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 624d0854..73ec2e2a 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -13,6 +13,7 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/log" ) type BlueprintSpec struct { @@ -251,13 +252,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) error { doguDiffs := determineDoguDiffs(spec.EffectiveBlueprint.Dogus, ecosystemState.InstalledDogus) - compDiffs, err := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) - if err != nil { - // FIXME: a proper state and event should be set, so that this error doesn't lead to an endless retry. - // The error here occurs, if a targetState is not properly set in components. We can remove this case - // when we introduce the absent flag in the domain or we just ignore this error like for dogu targetState - return err - } + compDiffs := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) doguConfigDiffs, sensitiveDoguConfigDiffs, globalConfigDiffs := determineConfigDiffs( spec.EffectiveBlueprint.Config, ecosystemState.GlobalConfig, @@ -276,6 +271,7 @@ func (spec *BlueprintSpec) DetermineStateDiff( //TODO: we need the possible error from the use case to set the condition to Unknown spec.setDogusAppliedConditionAfterStateDiff(nil) + spec.setCompletedConditionAfterStateDiff(nil) spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) spec.Events = append(spec.Events, GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: spec.StateDiff.GlobalConfigDiffs}) spec.Events = append(spec.Events, NewDoguConfigDiffDeterminedEvent(spec.StateDiff.DoguConfigDiffs)) @@ -482,6 +478,28 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) return false } +func (spec *BlueprintSpec) setCompletedConditionAfterStateDiff(diffErr error) bool { + if diffErr != nil { + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ + Type: ConditionCompleted, + Status: metav1.ConditionUnknown, + Reason: "CannotDetermineStateDiff", + Message: diffErr.Error(), + }) + return conditionChanged + } else if spec.StateDiff.HasChanges() { + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ + Type: ConditionCompleted, + Status: metav1.ConditionFalse, + Reason: "StateDiffHasChanges", + Message: "Blueprint is not completed", + }) + return conditionChanged + } + + return false +} + func (spec *BlueprintSpec) setDogusNeedToApply() bool { event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ @@ -643,7 +661,10 @@ func (spec *BlueprintSpec) Complete() bool { Reason: "Completed", }) if conditionChanged { + log.Log.Info("########## Add Event") spec.Events = append(spec.Events, CompletedEvent{}) + } else { + log.Log.Info("########## Add No Event") } return conditionChanged } diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index 24a9a5cc..bafce784 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -2,6 +2,7 @@ package domain import ( "fmt" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" ) @@ -60,3 +61,11 @@ type AwaitSelfUpgradeError struct { func (e *AwaitSelfUpgradeError) Error() string { return e.Message } + +type StateDiffNotEmptyError struct { + Message string +} + +func (e *StateDiffNotEmptyError) Error() string { + return e.Message +} diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go index 57b37244..f07a7082 100644 --- a/pkg/domain/stateDiffComponent.go +++ b/pkg/domain/stateDiffComponent.go @@ -105,14 +105,11 @@ func (diff *ComponentDiffState) getSafeVersionString() string { } // determineComponentDiffs creates ComponentDiffs for all components in the blueprint and all installed components as well. -func determineComponentDiffs(blueprintComponents []Component, installedComponents map[common.SimpleComponentName]*ecosystem.ComponentInstallation) ([]ComponentDiff, error) { +func determineComponentDiffs(blueprintComponents []Component, installedComponents map[common.SimpleComponentName]*ecosystem.ComponentInstallation) []ComponentDiff { var componentDiffs = map[common.SimpleComponentName]ComponentDiff{} for _, blueprintComponent := range blueprintComponents { installedComponent := installedComponents[blueprintComponent.Name.SimpleName] - compDiff, err := determineComponentDiff(&blueprintComponent, installedComponent) - if err != nil { - return nil, err - } + compDiff := determineComponentDiff(&blueprintComponent, installedComponent) // only add changes to diff if compDiff != nil { componentDiffs[blueprintComponent.Name.SimpleName] = *compDiff @@ -124,23 +121,20 @@ func determineComponentDiffs(blueprintComponents []Component, installedComponent // Only create ComponentDiff if the installed component is not found in the blueprint. // If the installed component is in blueprint the ComponentDiff was already determined above. if !found { - compDiff, err := determineComponentDiff(nil, installedComponent) - if err != nil { - return nil, err - } + compDiff := determineComponentDiff(nil, installedComponent) // only add changes to diff if compDiff != nil { componentDiffs[installedComponent.Name.SimpleName] = *compDiff } } } - return maps.Values(componentDiffs), nil + return maps.Values(componentDiffs) } // determineComponentDiff creates a ComponentDiff out of a Component from the blueprint and the ecosystem.ComponentInstallation in the ecosystem. // If the Component is nil (was not in the blueprint), the actual state is also the expected state. // If the installedComponent is nil, it is considered to be not installed currently. -func determineComponentDiff(blueprintComponent *Component, installedComponent *ecosystem.ComponentInstallation) (*ComponentDiff, error) { +func determineComponentDiff(blueprintComponent *Component, installedComponent *ecosystem.ComponentInstallation) *ComponentDiff { var expectedState, actualState ComponentDiffState componentName := common.SimpleComponentName("") // either blueprintComponent or installedComponent could be nil @@ -169,13 +163,10 @@ func determineComponentDiff(blueprintComponent *Component, installedComponent *e } } - nextActions, err := getComponentActions(expectedState, actualState) - if err != nil { - return nil, fmt.Errorf("failed to determine diff for component %q : %w", componentName, err) - } + nextActions := getComponentActions(expectedState, actualState) if len(nextActions) == 0 { - return nil, nil + return nil } return &ComponentDiff{ @@ -183,7 +174,7 @@ func determineComponentDiff(blueprintComponent *Component, installedComponent *e Expected: expectedState, Actual: actualState, NeededActions: nextActions, - }, nil + } } func findComponentByName(components []Component, name common.SimpleComponentName) (Component, bool) { @@ -195,7 +186,7 @@ func findComponentByName(components []Component, name common.SimpleComponentName return Component{}, false } -func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) ([]Action, error) { +func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) []Action { if expected.Absent == actual.Absent { return decideOnEqualState(expected, actual) } @@ -203,13 +194,13 @@ func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) return decideOnDifferentState(expected) } -func decideOnEqualState(expected ComponentDiffState, actual ComponentDiffState) ([]Action, error) { +func decideOnEqualState(expected ComponentDiffState, actual ComponentDiffState) []Action { var neededActions []Action if expected.Absent { - return neededActions, nil + return neededActions } else { - return getActionsForEqualPresentState(expected, actual), nil + return getActionsForEqualPresentState(expected, actual) } } @@ -237,11 +228,11 @@ func getActionsForEqualPresentState(expected ComponentDiffState, actual Componen return neededActions } -func decideOnDifferentState(expected ComponentDiffState) ([]Action, error) { +func decideOnDifferentState(expected ComponentDiffState) []Action { // at this place, the actual state is always the opposite to the expected state so just follow the expected state. if expected.Absent { - return []Action{ActionUninstall}, nil + return []Action{ActionUninstall} } else { - return []Action{ActionInstall}, nil + return []Action{ActionInstall} } } diff --git a/pkg/domain/stateDiffComponent_test.go b/pkg/domain/stateDiffComponent_test.go index ede9af7a..c91721c6 100644 --- a/pkg/domain/stateDiffComponent_test.go +++ b/pkg/domain/stateDiffComponent_test.go @@ -141,8 +141,7 @@ func Test_determineComponentDiff(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compDiff, err := determineComponentDiff(tt.args.blueprintComponent, tt.args.installedComponent) - assert.NoError(t, err) + compDiff := determineComponentDiff(tt.args.blueprintComponent, tt.args.installedComponent) assert.Equalf(t, tt.want, compDiff, "determineComponentDiff(%v, %v, %v)", tt.args.logger, tt.args.blueprintComponent, tt.args.installedComponent) }) } @@ -337,8 +336,7 @@ func Test_determineComponentDiffs(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - compDiffs, err := determineComponentDiffs(tt.args.blueprintComponents, tt.args.installedComponents) - assert.NoError(t, err) + compDiffs := determineComponentDiffs(tt.args.blueprintComponents, tt.args.installedComponents) assert.Equalf(t, tt.want, compDiffs, "determineComponentDiffs(%v, %v, %v)", tt.args.logger, tt.args.blueprintComponents, tt.args.installedComponents) }) } From e518deaeda7d369c1cd4cb331ec1af71cb29bc03 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 18 Sep 2025 13:29:18 +0200 Subject: [PATCH 067/119] #121 improve tests --- pkg/application/stateDiffUseCase_test.go | 130 ++++++++++++++--------- 1 file changed, 79 insertions(+), 51 deletions(-) diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index 21b53b88..aa4d96fb 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -12,11 +12,11 @@ import ( "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/resource" ) var ( nsOfficial = cescommons.Namespace("official") - nsK8s = cescommons.Namespace("k8s") postfix = cescommons.SimpleName("postfix") ldap = cescommons.SimpleName("ldap") @@ -34,14 +34,14 @@ var ( ) var ( - internalTestError = domainservice.NewInternalError(assert.AnError, "internal error") + internalTestError = domainservice.NewInternalError(assert.AnError, "internal error") ldapConfigKeyNginxKey1 = common.DoguConfigKey{DoguName: "ldap", Key: "ldapKey1"} ldapConfigKeyNginxKey2 = common.DoguConfigKey{DoguName: "ldap", Key: "ldapKey2"} ldapSensitiveConfigKeyNginxKey1 = ldapConfigKeyNginxKey1 ldapSensitiveConfigKeyNginxKey2 = ldapConfigKeyNginxKey2 - val1 = "val1" - val2 = "val2" - val3 = "val3" + val1 = "val1" + val2 = "val2" + val3 = "val3" ) func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { @@ -165,45 +165,44 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { assert.ErrorContains(t, err, "could not determine state diff") assert.ErrorContains(t, err, "could not collect ecosystem state") }) - //t.Run("should fail to get sensitive dogu config", func(t *testing.T) { - // // given - // blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - // - // doguInstallRepoMock := newMockDoguInstallationRepository(t) - // doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - // componentInstallRepoMock := newMockComponentInstallationRepository(t) - // componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - // - // globalConfigRepoMock := newMockGlobalConfigRepository(t) - // entries, _ := config.MapToEntries(map[string]any{}) - // globalConfig := config.CreateGlobalConfig(entries) - // globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - // - // doguConfigRepoMock := newMockDoguConfigRepository(t) - // doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - // sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - // sensitiveDoguConfigRepoMock.EXPECT(). - // GetAllExisting(testCtx, nilDoguNameList). - // Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) - // configRefReaderMock := newMockSensitiveConfigRefReader(t) - // configRefReaderMock.EXPECT(). - // GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - // Return(map[common.DoguConfigKey]config.Value{}, nil) - // - // sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) - // - // // when - // err := sut.DetermineStateDiff(testCtx, blueprint) - // - // // then - // require.Error(t, err) - // assert.ErrorIs(t, err, assert.AnError) - // var internalError *domainservice.InternalError - // assert.ErrorAs(t, err, &internalError) - // assert.ErrorContains(t, err, "could not determine state diff") - // assert.ErrorContains(t, err, "could not collect ecosystem state") - //}) - // TODO: Instead we should have a test with a forbidden diff action + t.Run("should fail to get sensitive dogu config", func(t *testing.T) { + // given + blueprint := &domain.BlueprintSpec{Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{Config: &domain.Config{}}} + + doguInstallRepoMock := newMockDoguInstallationRepository(t) + doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) + componentInstallRepoMock := newMockComponentInstallationRepository(t) + componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + entries, _ := config.MapToEntries(map[string]any{}) + globalConfig := config.CreateGlobalConfig(entries) + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT(). + GetAllExisting(testCtx, nilDoguNameList). + Return(map[cescommons.SimpleName]config.DoguConfig{}, domainservice.NewInternalError(assert.AnError, "internal error")) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) + + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + + // when + err := sut.DetermineStateDiff(testCtx, blueprint) + + // then + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + var internalError *domainservice.InternalError + assert.ErrorAs(t, err, &internalError) + assert.ErrorContains(t, err, "could not determine state diff") + assert.ErrorContains(t, err, "could not collect ecosystem state") + }) t.Run("should fail to update blueprint", func(t *testing.T) { // given blueprint := &domain.BlueprintSpec{ @@ -236,15 +235,31 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should succeed for dogu diff", func(t *testing.T) { // given + volumeSize := resource.MustParse("2Gi") + bodySize := resource.MustParse("2G") blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ Dogus: []domain.Dogu{ { - Name: postfixQualifiedDoguName, - Version: mustParseVersionToPtr(t, "2.9.0"), - Absent: false, + Name: postfixQualifiedDoguName, + Version: mustParseVersionToPtr(t, "2.9.0"), + Absent: false, + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + MaxBodySize: &bodySize, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + AdditionalMounts: []ecosystem.AdditionalMount{ + { + SourceType: ecosystem.DataSourceConfigMap, + Name: "configmap", + Volume: "volume", + Subfolder: &subfolder, + }, + }, }, { Name: ldapQualifiedDoguName, @@ -253,7 +268,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }, }, }, - // TODO: add config to test } blueprintRepoMock := newMockBlueprintSpecRepository(t) @@ -284,9 +298,23 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { DoguName: "postfix", Actual: domain.DoguDiffState{Absent: true}, Expected: domain.DoguDiffState{ - Namespace: "official", - Version: mustParseVersionToPtr(t, "2.9.0"), - Absent: false, + Namespace: "official", + Version: mustParseVersionToPtr(t, "2.9.0"), + Absent: false, + MinVolumeSize: &volumeSize, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + MaxBodySize: &bodySize, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, + AdditionalMounts: []ecosystem.AdditionalMount{ + { + SourceType: ecosystem.DataSourceConfigMap, + Name: "configmap", + Volume: "volume", + Subfolder: &subfolder, + }, + }, }, NeededActions: []domain.Action{domain.ActionInstall}, }, From 04a12f9542db3acae68f1f78b45836aa7c901afe Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 18 Sep 2025 15:58:16 +0200 Subject: [PATCH 068/119] #121 reduce state diff events --- pkg/domain/blueprintSpec.go | 35 ++++------- pkg/domain/blueprintSpec_test.go | 100 +++++++++++++++++++++++-------- pkg/domain/events.go | 83 +++++++++---------------- pkg/domain/events_test.go | 46 ++++++-------- pkg/domain/stateDiffConfig.go | 12 ++-- 5 files changed, 137 insertions(+), 139 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 73ec2e2a..7adb9a53 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -271,11 +271,16 @@ func (spec *BlueprintSpec) DetermineStateDiff( //TODO: we need the possible error from the use case to set the condition to Unknown spec.setDogusAppliedConditionAfterStateDiff(nil) - spec.setCompletedConditionAfterStateDiff(nil) - spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) - spec.Events = append(spec.Events, GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: spec.StateDiff.GlobalConfigDiffs}) - spec.Events = append(spec.Events, NewDoguConfigDiffDeterminedEvent(spec.StateDiff.DoguConfigDiffs)) - spec.Events = append(spec.Events, NewSensitiveDoguConfigDiffDeterminedEvent(spec.StateDiff.SensitiveDoguConfigDiffs)) + spec.resetCompletedConditionAfterStateDiff() + if spec.StateDiff.DoguDiffs.HasChanges() { + spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) + } + if spec.StateDiff.ComponentDiffs.HasChanges() { + spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) + } + if spec.StateDiff.HasConfigChanges() { + spec.Events = append(spec.Events, NewConfigDiffDeterminedEvent(spec.StateDiff)) + } invalidBlueprintError := spec.validateStateDiff() if invalidBlueprintError != nil { @@ -451,9 +456,6 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) Reason: "Applied", Message: event.Message(), }) - if conditionChanged { - spec.Events = append(spec.Events, event) - } return conditionChanged } } @@ -478,21 +480,13 @@ func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) return false } -func (spec *BlueprintSpec) setCompletedConditionAfterStateDiff(diffErr error) bool { - if diffErr != nil { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionCompleted, - Status: metav1.ConditionUnknown, - Reason: "CannotDetermineStateDiff", - Message: diffErr.Error(), - }) - return conditionChanged - } else if spec.StateDiff.HasChanges() { +func (spec *BlueprintSpec) resetCompletedConditionAfterStateDiff() bool { + if spec.StateDiff.HasChanges() { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionCompleted, Status: metav1.ConditionFalse, Reason: "StateDiffHasChanges", - Message: "Blueprint is not completed", + Message: "Blueprint is being applied.", }) return conditionChanged } @@ -508,9 +502,6 @@ func (spec *BlueprintSpec) setDogusNeedToApply() bool { Reason: "NeedToApply", Message: event.Message(), }) - if conditionChanged { - spec.Events = append(spec.Events, event) - } return conditionChanged } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index b9f9d936..a5bccee6 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -5,6 +5,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -309,13 +310,78 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) require.NoError(t, err) - require.Equal(t, 5, len(spec.Events)) + require.Empty(t, spec.Events) + assert.Equal(t, stateDiff, spec.StateDiff) + }) + + t.Run("all ok with filled blueprint", func(t *testing.T) { + // given + spec := BlueprintSpec{ + EffectiveBlueprint: EffectiveBlueprint{ + Dogus: []Dogu{{Name: officialNexus, Version: &version3211}}, + Components: []Component{{Name: testComponentName, Version: compVersion3211}}, + Config: &Config{Global: GlobalConfigEntries{{Key: "test", Value: &val1}}}, + }, + } + + clusterState := ecosystem.EcosystemState{ + InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, + InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, + } + + // when + err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) + + // then + stateDiff := StateDiff{ + DoguDiffs: DoguDiffs{ + { + DoguName: "nexus", + Actual: DoguDiffState{ + Absent: true, + }, + Expected: DoguDiffState{ + Namespace: "official", + Version: &version3211, + }, + NeededActions: []Action{ActionInstall}, + }, + }, + ComponentDiffs: ComponentDiffs{ + { + Name: "my-component", + Actual: ComponentDiffState{ + Absent: true, + }, + Expected: ComponentDiffState{ + Namespace: "k8s", + Version: compVersion3211, + }, + NeededActions: []Action{ActionInstall}, + }, + }, + GlobalConfigDiffs: GlobalConfigDiffs{ + { + Key: "test", + Actual: GlobalConfigValueState{}, + Expected: GlobalConfigValueState{ + Value: (*string)(&val1), + Exists: true, + }, + NeededAction: ConfigActionSet, + }, + }, + DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{}, + SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, + } + + assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) + require.NoError(t, err) + require.Equal(t, 3, len(spec.Events)) assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) assert.Equal(t, newStateDiffComponentEvent(stateDiff.ComponentDiffs), spec.Events[1]) - assert.Equal(t, GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: GlobalConfigDiffs(nil)}, spec.Events[2]) - assert.Equal(t, DoguConfigDiffDeterminedEvent{}, spec.Events[3]) - assert.Equal(t, SensitiveDoguConfigDiffDeterminedEvent{}, spec.Events[4]) - assert.Equal(t, stateDiff, spec.StateDiff) + assert.Equal(t, NewConfigDiffDeterminedEvent(stateDiff), spec.Events[2]) + assert.Empty(t, cmp.Diff(stateDiff, spec.StateDiff)) }) t.Run("ok with allowed dogu namespace switch", func(t *testing.T) { @@ -925,7 +991,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { type expected struct { changed bool condition *Condition - hasEvent bool } tests := []struct { name string @@ -945,7 +1010,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "NeedToApply", Message: diffEventMsg, }, - hasEvent: true, }, }, { @@ -961,7 +1025,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "Applied", Message: noDiffEventMsg, }, - hasEvent: true, }, }, { @@ -982,7 +1045,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "NeedToApply", Message: diffEventMsg, }, - hasEvent: true, }, }, { @@ -998,7 +1060,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "Applied", Message: noDiffEventMsg, }, - hasEvent: true, }, }, { @@ -1020,7 +1081,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "NeedToApply", Message: diffEventMsg, }, - hasEvent: true, }, }, { @@ -1042,7 +1102,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "NeedToApply", Message: diffEventMsg, }, - hasEvent: false, }, }, { @@ -1064,7 +1123,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "Applied", Message: noDiffEventMsg, }, - hasEvent: true, }, }, { @@ -1086,7 +1144,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "NeedToApply", Message: diffEventMsg, }, - hasEvent: true, }, }, { @@ -1101,8 +1158,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, expected: expected{ - changed: false, - hasEvent: false, + changed: false, }, }, { @@ -1117,8 +1173,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, expected: expected{ - changed: false, - hasEvent: false, + changed: false, }, }, { @@ -1133,8 +1188,7 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { }, }, expected: expected{ - changed: false, - hasEvent: false, + changed: false, }, }, { @@ -1151,7 +1205,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { Reason: "CannotDetermineStateDiff", Message: assert.AnError.Error(), }, - hasEvent: false, }, }, } @@ -1189,11 +1242,6 @@ func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { condition := conditions[0] assert.Equal(t, tt.given.condition, condition) } - //events - if tt.expected.hasEvent { - require.Equal(t, 1, len(spec.Events), "should have an event") - assert.Equal(t, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs), spec.Events[0]) - } }) } } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 0455c0a2..80cabe16 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -28,46 +28,26 @@ func (b BlueprintSpecInvalidEvent) Message() string { return b.ValidationError.Error() } -type GlobalConfigDiffDeterminedEvent struct { - GlobalConfigDiffs GlobalConfigDiffs +type ConfigDiffDeterminedEvent struct { + GlobalConfigDiffs GlobalConfigDiffs + DoguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs + SensitiveConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs } -func (e GlobalConfigDiffDeterminedEvent) Name() string { - return "GlobalConfigDiffDetermined" -} - -func (e GlobalConfigDiffDeterminedEvent) Message() string { - var stringPerAction []string - var actionsCounter int - for action, amount := range e.GlobalConfigDiffs.countByAction() { - stringPerAction = append(stringPerAction, fmt.Sprintf("%q: %d", action, amount)) - if action != ConfigActionNone { - actionsCounter += amount - } +func NewConfigDiffDeterminedEvent(ConfigDiffs StateDiff) ConfigDiffDeterminedEvent { + return ConfigDiffDeterminedEvent{ + DoguConfigDiffs: ConfigDiffs.DoguConfigDiffs, + GlobalConfigDiffs: ConfigDiffs.GlobalConfigDiffs, + SensitiveConfigDiffs: ConfigDiffs.SensitiveDoguConfigDiffs, } - slices.Sort(stringPerAction) - return fmt.Sprintf("global config diff determined: %d changes (%s)", actionsCounter, strings.Join(stringPerAction, ", ")) -} - -type DoguConfigDiffDeterminedEvent struct { - DoguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs } -func NewDoguConfigDiffDeterminedEvent( - doguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs, -) DoguConfigDiffDeterminedEvent { - return DoguConfigDiffDeterminedEvent{DoguConfigDiffs: doguConfigDiffs} +func (e ConfigDiffDeterminedEvent) Name() string { + return "ConfigDiffDetermined" } -func (e DoguConfigDiffDeterminedEvent) Name() string { - return "DoguConfigDiffDetermined" -} - -func (e DoguConfigDiffDeterminedEvent) Message() string { - return fmt.Sprintf( - "dogu config diff determined: %s", - generateDoguConfigChangeCounter(e.DoguConfigDiffs), - ) +func (e ConfigDiffDeterminedEvent) Message() string { + return fmt.Sprintf("config diff determined: %s", e.generateConfigChangeCounter()) } type MissingConfigReferencesEvent struct { @@ -86,31 +66,24 @@ func (e MissingConfigReferencesEvent) Message() string { return e.err.Error() } -type SensitiveDoguConfigDiffDeterminedEvent struct { - SensitiveDoguConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs -} - -func NewSensitiveDoguConfigDiffDeterminedEvent( - sensitiveDoguConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs, -) SensitiveDoguConfigDiffDeterminedEvent { - return SensitiveDoguConfigDiffDeterminedEvent{SensitiveDoguConfigDiffs: sensitiveDoguConfigDiffs} -} - -func (e SensitiveDoguConfigDiffDeterminedEvent) Name() string { - return "SensitiveDoguConfigDiffDetermined" -} - -func (e SensitiveDoguConfigDiffDeterminedEvent) Message() string { - return fmt.Sprintf( - "sensitive dogu config diff determined: %s", - generateDoguConfigChangeCounter(e.SensitiveDoguConfigDiffs), - ) -} +func (e ConfigDiffDeterminedEvent) generateConfigChangeCounter() string { + configActions := util.Map(e.GlobalConfigDiffs, func(entryDiff GlobalConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + }) + for _, doguDiff := range e.DoguConfigDiffs { + configActions = append(configActions, util.Map(doguDiff, func(entryDiff DoguConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + })...) + } + for _, doguDiff := range e.SensitiveConfigDiffs { + configActions = append(configActions, util.Map(doguDiff, func(entryDiff SensitiveDoguConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + })...) + } -func generateDoguConfigChangeCounter(doguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs) string { var stringPerAction []string var actionsCounter int - for action, amount := range countByAction(doguConfigDiffs) { + for action, amount := range countByAction(configActions) { stringPerAction = append(stringPerAction, fmt.Sprintf("%q: %d", action, amount)) if action != ConfigActionNone { actionsCounter += amount diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 606862d0..c939fb9d 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -93,19 +93,14 @@ func TestEvents(t *testing.T) { expectedMessage: "component state diff determined: 9 actions (\"component namespace switch\": 1, \"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update component package config\": 1, \"upgrade\": 1)", }, { - name: "global config diff determined", - event: GlobalConfigDiffDeterminedEvent{GlobalConfigDiffs: GlobalConfigDiffs{ - {NeededAction: ConfigActionNone}, - {NeededAction: ConfigActionNone}, - {NeededAction: ConfigActionSet}, - {NeededAction: ConfigActionRemove}, - }}, - expectedName: "GlobalConfigDiffDetermined", - expectedMessage: "global config diff determined: 2 changes (\"none\": 2, \"remove\": 1, \"set\": 1)", - }, - { - name: "dogu config diff determined", - event: DoguConfigDiffDeterminedEvent{ + name: "config diff determined", + event: ConfigDiffDeterminedEvent{ + GlobalConfigDiffs: GlobalConfigDiffs{ + {NeededAction: ConfigActionNone}, + {NeededAction: ConfigActionNone}, + {NeededAction: ConfigActionSet}, + {NeededAction: ConfigActionRemove}, + }, DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{ "dogu1": []DoguConfigEntryDiff{ {NeededAction: ConfigActionNone}, @@ -113,9 +108,16 @@ func TestEvents(t *testing.T) { {NeededAction: ConfigActionRemove}, }, }, + SensitiveConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ + "dogu1": []SensitiveDoguConfigEntryDiff{ + {NeededAction: ConfigActionNone}, + {NeededAction: ConfigActionSet}, + {NeededAction: ConfigActionRemove}, + }, + }, }, - expectedName: "DoguConfigDiffDetermined", - expectedMessage: "dogu config diff determined: 2 changes (\"none\": 1, \"remove\": 1, \"set\": 1)", + expectedName: "ConfigDiffDetermined", + expectedMessage: "config diff determined: 6 changes (\"none\": 4, \"remove\": 3, \"set\": 3)", }, { name: "config references missing", @@ -125,20 +127,6 @@ func TestEvents(t *testing.T) { expectedName: "MissingConfigReferences", expectedMessage: assert.AnError.Error(), }, - { - name: "sensitive dogu config diff determined", - event: SensitiveDoguConfigDiffDeterminedEvent{ - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ - "dogu1": []SensitiveDoguConfigEntryDiff{ - {NeededAction: ConfigActionNone}, - {NeededAction: ConfigActionSet}, - {NeededAction: ConfigActionRemove}, - }, - }, - }, - expectedName: "SensitiveDoguConfigDiffDetermined", - expectedMessage: "sensitive dogu config diff determined: 2 changes (\"none\": 1, \"remove\": 1, \"set\": 1)", - }, { name: "components applied", event: ComponentsAppliedEvent{ diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index eca8e281..b13f6411 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -17,14 +17,12 @@ const ( ConfigActionRemove ConfigAction = "remove" ) -func countByAction(diffsByDogu map[cescommons.SimpleName]DoguConfigDiffs) map[ConfigAction]int { - countByAction := map[ConfigAction]int{} - for _, doguDiffs := range diffsByDogu { - for _, diff := range doguDiffs { - countByAction[diff.NeededAction]++ - } +func countByAction(configActions []ConfigAction) map[ConfigAction]int { + result := map[ConfigAction]int{} + for _, action := range configActions { + result[action]++ } - return countByAction + return result } func determineConfigDiffs( From fa4e54e0c2f354d55d2f88b4f2901f3ae8d1466c Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 18 Sep 2025 21:57:31 +0200 Subject: [PATCH 069/119] #121 remove applied conditions in favor of LastApplySucceeded The LastApplySucceeded contains any errors happening in the apply steps. It greatly reduces the complexity of setting conditions, because it will not reset at StateDiff in contrast to the Applied-Conditions before. --- pkg/application/applyComponentsUseCase.go | 12 +- .../applyComponentsUseCase_test.go | 41 +- pkg/application/applyDogusUseCase.go | 12 +- pkg/application/applyDogusUseCase_test.go | 40 +- pkg/application/ecosystemConfigUseCase.go | 55 +-- .../ecosystemConfigUseCase_test.go | 188 +------- pkg/domain/blueprintSpec.go | 215 ++------- pkg/domain/blueprintSpec_test.go | 456 ------------------ pkg/domain/events.go | 16 +- pkg/domain/events_test.go | 6 - 10 files changed, 146 insertions(+), 895 deletions(-) diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go index 19ffbb15..cdd1f346 100644 --- a/pkg/application/applyComponentsUseCase.go +++ b/pkg/application/applyComponentsUseCase.go @@ -30,13 +30,17 @@ func NewApplyComponentsUseCase( // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { err := useCase.componentUseCase.ApplyComponentStates(ctx, blueprint) - changed := blueprint.SetComponentsAppliedCondition(err) + isComponentsApplied := blueprint.StateDiff.ComponentDiffs.HasChanges() && err == nil + if isComponentsApplied { + blueprint.Events = append(blueprint.Events, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}) + } + conditionChanged := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtComponents, err) - if changed { + if isComponentsApplied || conditionChanged { updateErr := useCase.repo.Update(ctx, blueprint) if updateErr != nil { - return changed, fmt.Errorf("cannot update condition while applying components: %w", errors.Join(updateErr, err)) + return isComponentsApplied, fmt.Errorf("cannot update status while applying components: %w", errors.Join(updateErr, err)) } } - return changed, err + return isComponentsApplied, err } diff --git a/pkg/application/applyComponentsUseCase_test.go b/pkg/application/applyComponentsUseCase_test.go index eb6134fe..8fcb9005 100644 --- a/pkg/application/applyComponentsUseCase_test.go +++ b/pkg/application/applyComponentsUseCase_test.go @@ -34,10 +34,10 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { changed, err := useCase.ApplyComponents(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) assert.True(t, changed) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) + require.Empty(t, blueprint.Conditions) }) t.Run("no update without condition change", func(t *testing.T) { @@ -50,17 +50,21 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { // Here is the important part: we expect the update to be called only once repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil).Twice() + componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError).Twice() useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - changed, err := useCase.ApplyComponents(testCtx, blueprint) - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) - assert.True(t, changed) - changed, err = useCase.ApplyComponents(testCtx, blueprint) - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) - assert.False(t, changed) + componentsApplied, err := useCase.ApplyComponents(testCtx, blueprint) + require.Error(t, err) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, componentsApplied) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) + // second apply + componentsApplied, err = useCase.ApplyComponents(testCtx, blueprint) + require.Error(t, err) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, componentsApplied) + require.Equal(t, 1, len(blueprint.Events)) }) t.Run("fail to apply components", func(t *testing.T) { @@ -77,13 +81,22 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { changed, err := useCase.ApplyComponents(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionComponentsApplied)) - assert.True(t, changed) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, changed) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) }) t.Run("fail to update blueprint", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, + StateDiff: domain.StateDiff{ + ComponentDiffs: domain.ComponentDiffs{ + { + NeededActions: []domain.Action{domain.ActionInstall}, + }, + }, + }, } repoMock := newMockBlueprintSpecRepository(t) @@ -95,7 +108,9 @@ func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { changed, err := useCase.ApplyComponents(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionComponentsApplied)) + assert.Empty(t, blueprint.Conditions) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) assert.True(t, changed) }) } diff --git a/pkg/application/applyDogusUseCase.go b/pkg/application/applyDogusUseCase.go index 8634cee3..d6dc10d9 100644 --- a/pkg/application/applyDogusUseCase.go +++ b/pkg/application/applyDogusUseCase.go @@ -30,13 +30,17 @@ func NewApplyDogusUseCase( // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { err := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) - changed := blueprint.SetDogusAppliedCondition(err) + isDogusApplied := blueprint.StateDiff.DoguDiffs.HasChanges() && err == nil + if isDogusApplied { + blueprint.Events = append(blueprint.Events, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}) + } + conditionChanged := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtDogus, err) - if changed { + if isDogusApplied || conditionChanged { updateErr := useCase.repo.Update(ctx, blueprint) if updateErr != nil { - return changed, fmt.Errorf("cannot update condition while applying dogus: %w", errors.Join(updateErr, err)) + return isDogusApplied, fmt.Errorf("cannot update status while applying dogus: %w", errors.Join(updateErr, err)) } } - return changed, err + return isDogusApplied, err } diff --git a/pkg/application/applyDogusUseCase_test.go b/pkg/application/applyDogusUseCase_test.go index b9287a8e..a66addb8 100644 --- a/pkg/application/applyDogusUseCase_test.go +++ b/pkg/application/applyDogusUseCase_test.go @@ -34,10 +34,11 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { changed, err := useCase.ApplyDogus(testCtx, blueprint) require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) assert.True(t, changed) require.Equal(t, 1, len(blueprint.Events)) assert.Equal(t, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}, blueprint.Events[0]) + require.Empty(t, blueprint.Conditions) + }) t.Run("no update without condition change", func(t *testing.T) { @@ -49,17 +50,21 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { // Here is the important part: we expect the update to be called only once repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) - doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(nil) + doguInstallUseCaseMock.EXPECT().ApplyDoguStates(testCtx, blueprint).Return(assert.AnError) useCase := NewApplyDogusUseCase(repoMock, doguInstallUseCaseMock) - changed, err := useCase.ApplyDogus(testCtx, blueprint) - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) - assert.True(t, changed) - changed, err = useCase.ApplyDogus(testCtx, blueprint) - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) - assert.False(t, changed) + dogusApplied, err := useCase.ApplyDogus(testCtx, blueprint) + require.Error(t, err) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, dogusApplied) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) + // second apply + dogusApplied, err = useCase.ApplyDogus(testCtx, blueprint) + require.Error(t, err) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, dogusApplied) + require.Equal(t, 1, len(blueprint.Events)) }) t.Run("fail to apply dogus", func(t *testing.T) { @@ -76,13 +81,20 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { changed, err := useCase.ApplyDogus(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionDogusApplied)) - assert.True(t, changed) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.False(t, changed) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) }) t.Run("fail to update blueprint", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, + StateDiff: domain.StateDiff{ + DoguDiffs: domain.DoguDiffs{ + {NeededActions: []domain.Action{domain.ActionInstall}}, + }, + }, } repoMock := newMockBlueprintSpecRepository(t) @@ -94,7 +106,9 @@ func TestApplyDogusUseCase_ApplyDogus(t *testing.T) { changed, err := useCase.ApplyDogus(testCtx, blueprint) require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionDogusApplied)) + assert.Empty(t, blueprint.Conditions) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}, blueprint.Events[0]) assert.True(t, changed) }) } diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 61183b11..2a0bf17a 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -37,16 +37,9 @@ func NewEcosystemConfigUseCase( // ApplyConfig fetches the dogu and global config stateDiff of the blueprint and applies these keys to the repositories. func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { - if !blueprint.StateDiff.HasConfigChanges() { - return useCase.markConfigApplied(ctx, blueprint) - } - - err := useCase.markApplyConfigStart(ctx, blueprint) - if err != nil { - return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, err) - } + logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig") - err = applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) + err := applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply normal dogu config: %w", err)) } @@ -59,7 +52,15 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply global config: %w", err)) } - return useCase.markConfigApplied(ctx, blueprint) + blueprint.Events = append(blueprint.Events, domain.EcosystemConfigAppliedEvent{}) + repoErr := useCase.blueprintRepository.Update(ctx, blueprint) + + if repoErr != nil { + repoErr = errors.Join(repoErr, err) + logger.Error(repoErr, "cannot update blueprint events") + return fmt.Errorf("cannot update blueprint events: %w", repoErr) + } + return nil } func (useCase *EcosystemConfigUseCase) applyGlobalConfigDiffs(ctx context.Context, globalConfigDiffsByAction map[domain.ConfigAction][]domain.GlobalConfigEntryDiff) error { @@ -143,41 +144,25 @@ func saveDoguConfigs( return nil } -func (useCase *EcosystemConfigUseCase) markApplyConfigStart(ctx context.Context, blueprint *domain.BlueprintSpec) error { - blueprint.StartApplyEcosystemConfig() - err := useCase.blueprintRepository.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot mark blueprint as applying config: %w", err) - } - return nil -} - func (useCase *EcosystemConfigUseCase) handleFailedApplyEcosystemConfig(ctx context.Context, blueprint *domain.BlueprintSpec, err error) error { logger := log.FromContext(ctx). WithName("EcosystemConfigUseCase.handleFailedApplyEcosystemConfig"). WithValues("blueprintId", blueprint.Id) // sets condition - blueprint.MarkApplyEcosystemConfigFailed(err) - repoErr := useCase.blueprintRepository.Update(ctx, blueprint) - - if repoErr != nil { - repoErr = errors.Join(repoErr, err) - logger.Error(repoErr, "cannot mark blueprint config apply as failed") - return fmt.Errorf("cannot mark blueprint config apply as failed: %w", repoErr) + changed := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtConfig, err) + if changed { + repoErr := useCase.blueprintRepository.Update(ctx, blueprint) + + if repoErr != nil { + repoErr = errors.Join(repoErr, err) + logger.Error(repoErr, "cannot mark blueprint config apply as failed") + return fmt.Errorf("cannot mark blueprint config apply as failed: %w", repoErr) + } } return err } -func (useCase *EcosystemConfigUseCase) markConfigApplied(ctx context.Context, blueprintSpec *domain.BlueprintSpec) error { - blueprintSpec.MarkEcosystemConfigApplied() - err := useCase.blueprintRepository.Update(ctx, blueprintSpec) - if err != nil { - return fmt.Errorf("failed to mark ecosystem config applied: %w", err) - } - return nil -} - // applyDiff merges the given changes from the doguConfigDiff in the DoguConfig. // Works with normal dogu config and with sensitive config as well. func applyDiff(doguConfig config.DoguConfig, diffs []domain.DoguConfigEntryDiff) (config.Config, error) { diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 8f19a515..79fcb991 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -93,7 +93,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) globalConfigRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, nil) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) + blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigRepoMock) @@ -102,60 +102,8 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { // then require.NoError(t, err) - }) - - t.Run("mark applied if diffs are empty", func(t *testing.T) { - // given - blueprintRepoMock := newMockBlueprintSpecRepository(t) - - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - StateDiff: domain.StateDiff{ - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{}, - }, - } - - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(1) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.ApplyConfig(testCtx, blueprint) - - // then - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) - require.NoError(t, err) - }) - - t.Run("should return on mark apply config start error", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - StateDiff: domain.StateDiff{ - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{}, - GlobalConfigDiffs: domain.GlobalConfigDiffs{ - getSetGlobalConfigEntryDiff("key", "value"), - }, - }, - } - - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(assert.AnError).Times(1) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(1) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.ApplyConfig(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot mark blueprint as applying config") - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) }) t.Run("error applying dogu config", func(t *testing.T) { @@ -186,7 +134,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { cas: config.CreateDoguConfig(cas, map[config.Key]config.Value{}), }, nil) doguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) + blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) @@ -197,11 +145,10 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { require.Error(t, err) assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply normal dogu config") - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - require.Len(t, blueprint.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) - assert.Contains(t, blueprint.Events[1].Message(), err.Error()) + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) }) t.Run("error applying sensitive config", func(t *testing.T) { // given @@ -235,7 +182,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { cas: config.CreateDoguConfig(cas, map[config.Key]config.Value{}), }, nil) sensitiveDoguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) - blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil).Times(2) + blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) @@ -247,14 +194,14 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply sensitive dogu config") - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - require.Len(t, blueprint.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) - assert.Contains(t, blueprint.Events[1].Message(), "could not apply sensitive dogu config") + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) + assert.Contains(t, blueprint.Events[0].Message(), "could not apply sensitive dogu config") // cannot check for dogu name here as the order of the events is not fixed. It could be either redmine or cas - assert.Contains(t, blueprint.Events[1].Message(), "could not persist config for dogu") - assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") + assert.Contains(t, blueprint.Events[0].Message(), "could not persist config for dogu") + assert.Contains(t, blueprint.Events[0].Message(), "assert.AnError general error for testing") }) t.Run("error applying global config", func(t *testing.T) { // given @@ -288,7 +235,7 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock.EXPECT().Get(testCtx).Return(globalConfig, nil) globalConfigMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, assert.AnError) - blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Times(2) + blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) @@ -300,12 +247,12 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { assert.ErrorIs(t, err, assert.AnError) assert.ErrorContains(t, err, "could not apply global config") - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - require.Len(t, blueprint.Events, 2) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) - assert.Contains(t, blueprint.Events[1].Message(), "could not apply global config") - assert.Contains(t, blueprint.Events[1].Message(), "assert.AnError general error for testing") + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) + assert.Contains(t, blueprint.Events[0].Message(), "could not apply global config") + assert.Contains(t, blueprint.Events[0].Message(), "assert.AnError general error for testing") }) } @@ -552,95 +499,6 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { }) } -func TestEcosystemConfigUseCase_markConfigApplied(t *testing.T) { - t.Run("should set applied condition and event", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - } - blueprintRepoMock := newMockBlueprintSpecRepository(t) - - blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.markConfigApplied(testCtx, blueprint) - - // then - require.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) - }) - - t.Run("should return an error on update error", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - } - - blueprintRepoMock := newMockBlueprintSpecRepository(t) - blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.markConfigApplied(testCtx, blueprint) - - // then - assert.ErrorContains(t, err, "failed to mark ecosystem config applied") - assert.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionConfigApplied)) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) - }) -} - -func TestEcosystemConfigUseCase_markApplyConfigStart(t *testing.T) { - t.Run("should set condition and event apply config", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - } - blueprintRepoMock := newMockBlueprintSpecRepository(t) - - blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.markApplyConfigStart(testCtx, blueprint) - - // then - require.NoError(t, err) - - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) - require.Len(t, blueprint.Events, 1) - assert.Equal(t, domain.ApplyEcosystemConfigEvent{}, blueprint.Events[0]) - }) - - t.Run("should return an error on update error", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - } - blueprintRepoMock := newMockBlueprintSpecRepository(t) - - blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - sut := EcosystemConfigUseCase{blueprintRepository: blueprintRepoMock} - - // when - err := sut.markApplyConfigStart(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "cannot mark blueprint as applying config") - assert.ErrorIs(t, err, assert.AnError) - }) -} - func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { t.Run("should set applied condition and event", func(t *testing.T) { // given @@ -658,11 +516,11 @@ func TestEcosystemConfigUseCase_handleFailedApplyEcosystemConfig(t *testing.T) { // then require.Error(t, err) - condition := meta.FindStatusCondition(blueprint.Conditions, domain.ConditionConfigApplied) + condition := meta.FindStatusCondition(blueprint.Conditions, domain.ConditionLastApplySucceeded) require.NotNil(t, condition) assert.Equal(t, err.Error(), condition.Message) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionConfigApplied)) - assert.IsType(t, domain.ApplyEcosystemConfigFailedEvent{}, blueprint.Events[0]) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) + assert.IsType(t, domain.ExecutionFailedEvent{}, blueprint.Events[0]) }) t.Run("should return error on update error", func(t *testing.T) { diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 7adb9a53..62e10e16 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -13,7 +13,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/log" ) type BlueprintSpec struct { @@ -38,12 +37,12 @@ const ( ConditionExecutable = "Executable" ConditionEcosystemHealthy = "EcosystemHealthy" ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" - ConditionConfigApplied = "ConfigApplied" - ConditionComponentsApplied = "ComponentsApplied" - ConditionDogusApplied = "DogusApplied" ConditionCompleted = "Completed" + ConditionLastApplySucceeded = "LastApplySuccessful" - ReasonCannotApply = "CannotApply" + ReasonLastApplyErrorAtComponents = "ComponentApplyFailure" + ReasonLastApplyErrorAtDogus = "DoguApplyFailure" + ReasonLastApplyErrorAtConfig = "ConfigApplyFailure" ) var ( @@ -270,7 +269,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( } //TODO: we need the possible error from the use case to set the condition to Unknown - spec.setDogusAppliedConditionAfterStateDiff(nil) spec.resetCompletedConditionAfterStateDiff() if spec.StateDiff.DoguDiffs.HasChanges() { spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) @@ -407,79 +405,6 @@ func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { } } -// setDogusAppliedConditionAfterStateDiff sets the ConditionDogusApplied based on the diff, and it's current state. -// This function gets called after creating the stateDiff. The ConditionDogusApplied could be applied in a previous run -// either by the state diff or later while applying the dogus. -// If there is a condition from the DoguApply-step, then do not override it, unless it is obviously outdated. -// -// decision table: -// current condition withDiff withoutDiff DiffError -// ======================================================= -// none NeedToApply Applied Unknown -// Unknown NeedToApply Applied Unknown -// NeedToApply NeedToApply Applied Unknown -// Applied NeedToApply no change Unknown -// CannotApply no change no change Unknown -func (spec *BlueprintSpec) setDogusAppliedConditionAfterStateDiff(diffErr error) bool { - condition := meta.FindStatusCondition(spec.Conditions, ConditionDogusApplied) - - // current condition DiffError - // =========================== - // none Unknown - // Unknown Unknown - // NeedToApply Unknown - // Applied Unknown - // CannotApply Unknown - if diffErr != nil { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionUnknown, - Reason: "CannotDetermineStateDiff", - Message: diffErr.Error(), - }) - return conditionChanged - } - - // current condition withDiff withoutDiff - // ============================================ - // none NeedToApply Applied - // Unknown NeedToApply Applied - // NeedToApply NeedToApply Applied - if condition == nil || condition.Status == metav1.ConditionUnknown || condition.Reason == "NeedToApply" { - if spec.StateDiff.DoguDiffs.HasChanges() { - return spec.setDogusNeedToApply() - } else { - event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "Applied", - Message: event.Message(), - }) - return conditionChanged - } - } - // current condition withDiff withoutDiff - // ============================================ - // CannotApply no change no change - if condition.Reason == ReasonCannotApply { - return false - } - - // current condition withDiff withoutDiff - // ============================================ - // Applied NeedToApply no change - if condition.Reason == "Applied" { - if spec.StateDiff.DoguDiffs.HasChanges() { - return spec.setDogusNeedToApply() - } else { - return false - } - } - // should never happen - return false -} - func (spec *BlueprintSpec) resetCompletedConditionAfterStateDiff() bool { if spec.StateDiff.HasChanges() { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ @@ -494,75 +419,6 @@ func (spec *BlueprintSpec) resetCompletedConditionAfterStateDiff() bool { return false } -func (spec *BlueprintSpec) setDogusNeedToApply() bool { - event := newStateDiffDoguEvent(spec.StateDiff.DoguDiffs) - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: event.Message(), - }) - return conditionChanged -} - -// SetComponentsAppliedCondition informs the user about the state of the component apply. -// If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. -// Returns true if the condition changed, otherwise false. -func (spec *BlueprintSpec) SetComponentsAppliedCondition(err error) bool { - if err != nil { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionComponentsApplied, - Status: metav1.ConditionFalse, - Reason: ReasonCannotApply, - Message: err.Error(), - }) - if conditionChanged { - spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) - } - return conditionChanged - } - event := ComponentsAppliedEvent{Diffs: spec.StateDiff.ComponentDiffs} - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionComponentsApplied, - Status: metav1.ConditionTrue, - Reason: "Applied", - Message: event.Message(), - }) - if conditionChanged && spec.StateDiff.ComponentDiffs.HasChanges() { - spec.Events = append(spec.Events, event) - } - return conditionChanged -} - -// SetDogusAppliedCondition informs the user about the state of the dogu apply. -// If an error is given, it will set the condition to failed accordingly, otherwise it marks it as a success. -// Returns true if the condition changed, otherwise false. -func (spec *BlueprintSpec) SetDogusAppliedCondition(err error) bool { - if err != nil { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: ReasonCannotApply, - Message: err.Error(), - }) - if conditionChanged { - spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) - } - return conditionChanged - } - event := DogusAppliedEvent{Diffs: spec.StateDiff.DoguDiffs} - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionTrue, - Reason: "Applied", - Message: event.Message(), - }) - if conditionChanged && spec.StateDiff.DoguDiffs.HasChanges() { - spec.Events = append(spec.Events, event) - } - return conditionChanged -} - func (spec *BlueprintSpec) validateStateDiff() error { var invalidBlueprintErrors []error @@ -607,42 +463,6 @@ func getActionNotAllowedError(action Action) *InvalidBlueprintError { } } -func (spec *BlueprintSpec) StartApplyEcosystemConfig() { - event := ApplyEcosystemConfigEvent{} - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionConfigApplied, - Status: metav1.ConditionFalse, - Reason: "Applying", - Message: event.Message(), - }) - if conditionChanged { - spec.Events = append(spec.Events, ApplyEcosystemConfigEvent{}) - } -} - -func (spec *BlueprintSpec) MarkApplyEcosystemConfigFailed(err error) { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionConfigApplied, - Status: metav1.ConditionFalse, - Reason: "ApplyingFailed", - Message: err.Error(), - }) - if conditionChanged { - spec.Events = append(spec.Events, ApplyEcosystemConfigFailedEvent{err: err}) - } -} - -func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionConfigApplied, - Status: metav1.ConditionTrue, - Reason: "Applied", - }) - if conditionChanged { - spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) - } -} - // Complete is used to mark the blueprint as completed and to inform the user. // Returns true if the condition changed, false otherwise. func (spec *BlueprintSpec) Complete() bool { @@ -651,11 +471,32 @@ func (spec *BlueprintSpec) Complete() bool { Status: metav1.ConditionTrue, Reason: "Completed", }) + meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ + Type: ConditionLastApplySucceeded, + Status: metav1.ConditionTrue, + Reason: "ApplySucceeded", + }) + if conditionChanged { - log.Log.Info("########## Add Event") spec.Events = append(spec.Events, CompletedEvent{}) - } else { - log.Log.Info("########## Add No Event") } return conditionChanged } + +func (spec *BlueprintSpec) SetLastApplySucceededCondition(reason string, err error) bool { + if err != nil { + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ + Type: ConditionLastApplySucceeded, + Status: metav1.ConditionFalse, + Reason: reason, + Message: err.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, ExecutionFailedEvent{err: err}) + } + + return conditionChanged + } + + return false +} diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index a5bccee6..e7d546fd 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -789,459 +789,3 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { assert.False(t, changed, "condition should not change here") }) } - -func TestBlueprintSpec_SetComponentAppliedCondition(t *testing.T) { - diff := StateDiff{ - ComponentDiffs: ComponentDiffs{ - ComponentDiff{ - Name: "k8s-dogu-operator", - NeededActions: []Action{ - ActionUpgrade, ActionSwitchComponentNamespace, - }, - }, - }, - } - - t.Run("set condition to applied when no change", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetComponentsAppliedCondition(nil) - - assert.True(t, changed) - condition := meta.FindStatusCondition(blueprint.Conditions, ConditionComponentsApplied) - assert.Equal(t, metav1.ConditionTrue, condition.Status) - assert.Equal(t, "Applied", condition.Reason) - assert.Equal(t, "components applied: \"k8s-dogu-operator\": [upgrade, component namespace switch]", condition.Message) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, ComponentsAppliedEvent{Diffs: diff.ComponentDiffs}, blueprint.Events[0]) - }) - - t.Run("set condition to cannotApply on error", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetComponentsAppliedCondition(assert.AnError) - - assert.True(t, changed) - condition := meta.FindStatusCondition(blueprint.Conditions, ConditionComponentsApplied) - assert.Equal(t, metav1.ConditionFalse, condition.Status) - assert.Equal(t, "CannotApply", condition.Reason) - assert.Equal(t, assert.AnError.Error(), condition.Message) - }) - - t.Run("no condition change", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetComponentsAppliedCondition(assert.AnError) - assert.True(t, changed) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) - blueprint.Events = nil - - changed = blueprint.SetComponentsAppliedCondition(assert.AnError) - assert.False(t, changed) - require.Equal(t, 0, len(blueprint.Events)) - }) -} - -func TestBlueprintSpec_SetDogusAppliedCondition(t *testing.T) { - diff := StateDiff{ - DoguDiffs: DoguDiffs{ - DoguDiff{ - DoguName: "cas", - NeededActions: []Action{ - ActionUpgrade, ActionSwitchDoguNamespace, - }, - }, - }, - } - - t.Run("applied", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetDogusAppliedCondition(nil) - - assert.True(t, changed) - condition := meta.FindStatusCondition(blueprint.Conditions, ConditionDogusApplied) - assert.Equal(t, metav1.ConditionTrue, condition.Status) - assert.Equal(t, "Applied", condition.Reason) - assert.Equal(t, "dogus applied: \"cas\": [upgrade, dogu namespace switch]", condition.Message) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, DogusAppliedEvent{Diffs: diff.DoguDiffs}, blueprint.Events[0]) - }) - - t.Run("error", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetDogusAppliedCondition(assert.AnError) - - assert.True(t, changed) - condition := meta.FindStatusCondition(blueprint.Conditions, ConditionDogusApplied) - assert.Equal(t, metav1.ConditionFalse, condition.Status) - assert.Equal(t, "CannotApply", condition.Reason) - assert.Equal(t, assert.AnError.Error(), condition.Message) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) - }) - - t.Run("no condition change", func(t *testing.T) { - blueprint := BlueprintSpec{ - StateDiff: diff, - } - - changed := blueprint.SetDogusAppliedCondition(assert.AnError) - assert.True(t, changed) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, blueprint.Events[0]) - blueprint.Events = nil - - changed = blueprint.SetDogusAppliedCondition(assert.AnError) - assert.False(t, changed) - require.Equal(t, 0, len(blueprint.Events)) - }) -} - -// TODO: write tests -//func TestBlueprintSpec_setComponentsAppliedConditionAfterStateDiff(t *testing.T) { -// // decision table: -// // current condition withDiff withoutDiff DiffError -// // ======================================================= -// // none NeedToApply Applied Unknown -// // Unknown NeedToApply Applied Unknown -// // NeedToApply NeedToApply Applied Unknown -// // Applied NeedToApply no change Unknown -// // CannotApply no change no change Unknown -// t.Run("no condition before but changes", func(t *testing.T) { -// diff := StateDiff{ -// DoguDiffs: DoguDiffs{ -// DoguDiff{ -// DoguName: "cas", -// NeededActions: []Action{ -// ActionUpgrade, ActionSwitchDoguNamespace, -// }, -// }, -// }, -// } -// blueprint := BlueprintSpec{ -// Conditions: &[]Condition{}, -// StateDiff: diff, -// } -// -// blueprint.setDogusAppliedConditionAfterStateDiff() -// -// event := newStateDiffDoguEvent(diff.DoguDiffs) -// require.Equal(t, 1, len(blueprint.Events)) -// assert.Equal(t, event, blueprint.Events[0]) -// condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) -// assert.Equal(t, metav1.ConditionFalse, condition.Status) -// assert.Equal(t, "NeedToApply", condition.Reason) -// assert.Equal(t, event.Message(), condition.Message) -// }) -// t.Run("no condition before and no changes", func(t *testing.T) { -// diff := StateDiff{ -// DoguDiffs: DoguDiffs{ -// DoguDiff{ -// DoguName: "cas", -// NeededActions: nil, -// }, -// }, -// } -// blueprint := BlueprintSpec{ -// Conditions: &[]Condition{}, -// StateDiff: diff, -// } -// -// blueprint.setDogusAppliedConditionAfterStateDiff() -// -// event := newStateDiffDoguEvent(diff.DoguDiffs) -// require.Equal(t, 1, len(blueprint.Events)) -// assert.Equal(t, event, blueprint.Events[0]) -// condition := meta.FindStatusCondition(*blueprint.Conditions, ConditionDogusApplied) -// assert.Equal(t, metav1.ConditionFalse, condition.Status) -// assert.Equal(t, "Applied", condition.Reason) -// assert.Equal(t, event.Message(), condition.Message) -// }) -//} - -func TestBlueprintSpec_setDogusAppliedConditionAfterStateDiff(t *testing.T) { - // current condition withDiff withoutDiff DiffError - // ======================================================= - // none NeedToApply Applied Unknown - // Unknown NeedToApply Applied Unknown - // NeedToApply NeedToApply Applied Unknown - // Applied NeedToApply no change Unknown - // CannotApply no change no change Unknown - - diffEventMsg := "dogu state diff determined: 2 actions (\"dogu namespace switch\": 1, \"upgrade\": 1)" - noDiffEventMsg := "dogu state diff determined: 0 actions ()" - type given struct { - hasDiff bool - condition Condition - diffErr error - } - type expected struct { - changed bool - condition *Condition - } - tests := []struct { - name string - given given - expected expected - }{ - { - name: "no condition + diff -> NeedToApply + event", - given: given{ - hasDiff: true, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - }, - { - name: "no condition + no diff -> Applied + event", - given: given{ - hasDiff: false, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "Applied", - Message: noDiffEventMsg, - }, - }, - }, - { - name: "Condition Unknown + diff -> NeedToApply + event", - given: given{ - hasDiff: true, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionUnknown, - Reason: "CannotDetermineStateDiff", - }, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - }, - { - name: "Condition Unknown + no diff -> Applied + event", - given: given{ - hasDiff: false, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "Applied", - Message: noDiffEventMsg, - }, - }, - }, - { - name: "Condition NeedToApply + diff -> NeedToApply + event", - given: given{ - hasDiff: true, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionTrue, - Reason: "NeedToApply", - Message: "other msg", - }, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - }, - { - name: "Condition NeedToApply + diff -> NeedToApply + no event", - given: given{ - hasDiff: true, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - expected: expected{ - changed: false, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - }, - { - name: "Condition NeedToApply + no diff -> Applied + event", - given: given{ - hasDiff: false, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: "error msg", - }, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "Applied", - Message: noDiffEventMsg, - }, - }, - }, - { - name: "Condition Applied + diff -> NeedToApply + event", - given: given{ - hasDiff: true, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionTrue, - Reason: "Applied", - Message: "other msg", - }, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "NeedToApply", - Message: diffEventMsg, - }, - }, - }, - { - name: "Condition Applied + no diff -> no change", - given: given{ - hasDiff: false, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: "Applied", - Message: "any msg", - }, - }, - expected: expected{ - changed: false, - }, - }, - { - name: "Condition CannotApply + no diff -> no change", - given: given{ - hasDiff: false, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: ReasonCannotApply, - Message: "error msg", - }, - }, - expected: expected{ - changed: false, - }, - }, - { - name: "Condition CannotApply + diff -> no change", - given: given{ - hasDiff: true, - condition: Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionFalse, - Reason: ReasonCannotApply, - Message: "error msg", - }, - }, - expected: expected{ - changed: false, - }, - }, - { - name: "on error -> Condition Unknown + error", - given: given{ - hasDiff: false, - diffErr: assert.AnError, - }, - expected: expected{ - changed: true, - condition: &Condition{ - Type: ConditionDogusApplied, - Status: metav1.ConditionUnknown, - Reason: "CannotDetermineStateDiff", - Message: assert.AnError.Error(), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - //given - spec := &BlueprintSpec{ - Conditions: []Condition{tt.given.condition}, - } - if tt.given.hasDiff { - spec.StateDiff = StateDiff{ - DoguDiffs: DoguDiffs{ - DoguDiff{ - DoguName: "redmine", - NeededActions: []Action{ - ActionUpgrade, ActionSwitchDoguNamespace, - }, - }, - }, - } - } - //when - hasChanged := spec.setDogusAppliedConditionAfterStateDiff(tt.given.diffErr) - //then - assert.Equal(t, tt.expected.changed, hasChanged, "function should return %v", hasChanged) - //condition - condition := meta.FindStatusCondition(spec.Conditions, ConditionDogusApplied) - if tt.expected.changed && tt.expected.condition != nil { - require.NotNil(t, condition) - assert.Equal(t, tt.expected.condition.Status, condition.Status) - assert.Equal(t, tt.expected.condition.Message, condition.Message) - assert.Equal(t, tt.expected.condition.Reason, condition.Reason) - } else { - conditions := spec.Conditions - condition := conditions[0] - assert.Equal(t, tt.given.condition, condition) - } - }) - } -} diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 80cabe16..c5c9f825 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -265,6 +265,10 @@ type ExecutionFailedEvent struct { err error } +func NewExecutionFailedEvent(err error) ExecutionFailedEvent { + return ExecutionFailedEvent{err: err} +} + func (e ExecutionFailedEvent) Name() string { return "ExecutionFailed" } @@ -293,18 +297,6 @@ func (e ApplyEcosystemConfigEvent) Message() string { return "apply ecosystem config" } -type ApplyEcosystemConfigFailedEvent struct { - err error -} - -func (e ApplyEcosystemConfigFailedEvent) Name() string { - return "ApplyEcosystemConfigFailed" -} - -func (e ApplyEcosystemConfigFailedEvent) Message() string { - return e.err.Error() -} - type EcosystemConfigAppliedEvent struct{} func (e EcosystemConfigAppliedEvent) Name() string { diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index c939fb9d..ba6a9ddd 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -187,12 +187,6 @@ func TestEvents(t *testing.T) { expectedName: "EcosystemConfigApplied", expectedMessage: "ecosystem config applied", }, - { - name: "applying ecosystem config failed", - event: ApplyEcosystemConfigFailedEvent{fmt.Errorf("test-error")}, - expectedName: "ApplyEcosystemConfigFailed", - expectedMessage: "test-error", - }, { name: "await self upgrade", event: AwaitSelfUpgradeEvent{}, From 0e61e6a12c9a6df59babf8e518598d1bb4f8c665 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 19 Sep 2025 11:28:54 +0200 Subject: [PATCH 070/119] #121 set conditions to unknown on first reconcile --- pkg/application/blueprintSpecChangeUseCase.go | 9 +- .../blueprintSpecChangeUseCase_test.go | 116 ++++++++++++- .../initiaiteBlueprintStatusUseCase.go | 49 ++++++ .../initiaiteBlueprintStatusUseCase_test.go | 163 ++++++++++++++++++ pkg/application/interfaces.go | 4 + ...mock_initialBlueprintStatusUseCase_test.go | 84 +++++++++ pkg/bootstrap.go | 3 +- pkg/domain/blueprintSpec.go | 5 +- 8 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 pkg/application/initiaiteBlueprintStatusUseCase.go create mode 100644 pkg/application/initiaiteBlueprintStatusUseCase_test.go create mode 100644 pkg/application/mock_initialBlueprintStatusUseCase_test.go diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 2c6e9d73..20e39e4c 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -10,6 +10,7 @@ import ( type BlueprintSpecChangeUseCase struct { repo blueprintSpecRepository + initialStatus initialBlueprintStatusUseCase validation blueprintSpecValidationUseCase effectiveBlueprint effectiveBlueprintUseCase stateDiff stateDiffUseCase @@ -23,6 +24,7 @@ type BlueprintSpecChangeUseCase struct { func NewBlueprintSpecChangeUseCase( repo blueprintSpecRepository, + initialStatus initialBlueprintStatusUseCase, validation blueprintSpecValidationUseCase, effectiveBlueprint effectiveBlueprintUseCase, stateDiff stateDiffUseCase, @@ -35,6 +37,7 @@ func NewBlueprintSpecChangeUseCase( ) *BlueprintSpecChangeUseCase { return &BlueprintSpecChangeUseCase{ repo: repo, + initialStatus: initialStatus, validation: validation, effectiveBlueprint: effectiveBlueprint, stateDiff: stateDiff, @@ -94,7 +97,11 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } func (useCase *BlueprintSpecChangeUseCase) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - err := useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) + err := useCase.initialStatus.InitateConditions(ctx, blueprint) + if err != nil { + return err + } + err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 77d1c8c1..34a03704 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -3,13 +3,14 @@ package application import ( "context" "fmt" + "testing" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "testing" ) var testCtx = context.Background() @@ -18,6 +19,7 @@ var testBlueprintId = "testBlueprint1" func TestNewBlueprintSpecChangeUseCase(t *testing.T) { // given blueprintSpecRepositoryMock := newMockBlueprintSpecRepository(t) + initialStatusUseCaseMock := newMockInitialBlueprintStatusUseCase(t) validationUseCaseMock := newMockBlueprintSpecValidationUseCase(t) effectiveUseCaseMock := newMockEffectiveBlueprintUseCase(t) stateDiffUseCaseMock := newMockStateDiffUseCase(t) @@ -31,6 +33,7 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { // when result := NewBlueprintSpecChangeUseCase( blueprintSpecRepositoryMock, + initialStatusUseCaseMock, validationUseCaseMock, effectiveUseCaseMock, stateDiffUseCaseMock, @@ -68,6 +71,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { type fields struct { repo func(t *testing.T) blueprintSpecRepository + initialStatus func(t *testing.T) initialBlueprintStatusUseCase validation func(t *testing.T) blueprintSpecValidationUseCase effectiveBlueprint func(t *testing.T) effectiveBlueprintUseCase stateDiff func(t *testing.T) stateDiffUseCase @@ -112,6 +116,26 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { return assert.ErrorContains(t, err, "cannot load blueprint spec") }, }, + { + name: "should return error on error initially setting the blueprint status", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(assert.AnError) + + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, { name: "should return error on error validating blueprint statically", fields: fields{ @@ -120,6 +144,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(assert.AnError) @@ -140,6 +170,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -165,6 +201,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -190,6 +232,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -220,6 +268,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -255,6 +309,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testDryRunBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testDryRunBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testDryRunBlueprintSpec).Return(nil) @@ -288,6 +348,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -328,6 +394,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -373,6 +445,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -423,6 +501,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -474,6 +558,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -529,6 +619,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -585,6 +681,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -645,6 +747,12 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) return m }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) @@ -703,6 +811,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { repo = tt.fields.repo(t) } + var initialStatus initialBlueprintStatusUseCase + if tt.fields.initialStatus != nil { + initialStatus = tt.fields.initialStatus(t) + } + var validation blueprintSpecValidationUseCase if tt.fields.validation != nil { validation = tt.fields.validation(t) @@ -750,6 +863,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { useCase := &BlueprintSpecChangeUseCase{ repo: repo, + initialStatus: initialStatus, validation: validation, effectiveBlueprint: effectiveBlueprint, stateDiff: stateDiff, diff --git a/pkg/application/initiaiteBlueprintStatusUseCase.go b/pkg/application/initiaiteBlueprintStatusUseCase.go new file mode 100644 index 00000000..b1989824 --- /dev/null +++ b/pkg/application/initiaiteBlueprintStatusUseCase.go @@ -0,0 +1,49 @@ +package application + +import ( + "context" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// InitiateBlueprintStatusUseCase contains all use cases which are needed to initiate the +// blueprint status after the determining the state diff. +// +// Use cases: +// - InitateConditions +type InitiateBlueprintStatusUseCase struct { + repo blueprintSpecRepository +} + +func NewInitiateBlueprintStatusUseCase( + repo blueprintSpecRepository, +) *InitiateBlueprintStatusUseCase { + return &InitiateBlueprintStatusUseCase{ + repo: repo, + } +} + +// InitateConditions handles the initial setting of the conditions to unknown if they are not set yet. +// returns a domainservice.InternalError on any error. +func (useCase *InitiateBlueprintStatusUseCase) InitateConditions(ctx context.Context, blueprint *domain.BlueprintSpec) error { + if len(blueprint.Conditions) != len(domain.BlueprintConditions) { + for _, condition := range domain.BlueprintConditions { + if meta.FindStatusCondition(blueprint.Conditions, condition) == nil { + meta.SetStatusCondition(&blueprint.Conditions, metav1.Condition{ + Type: condition, + Status: metav1.ConditionUnknown, + Reason: "InitialSyncPending", + Message: "controller has not determined this condition yet", + }) + } + } + err := useCase.repo.Update(ctx, blueprint) + if err != nil { + return fmt.Errorf("cannot save blueprint spec %q after initially setting the conditions to unknown: %w", blueprint.Id, err) + } + } + return nil +} diff --git a/pkg/application/initiaiteBlueprintStatusUseCase_test.go b/pkg/application/initiaiteBlueprintStatusUseCase_test.go new file mode 100644 index 00000000..0d1b443b --- /dev/null +++ b/pkg/application/initiaiteBlueprintStatusUseCase_test.go @@ -0,0 +1,163 @@ +package application + +import ( + "context" + "testing" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestInitiateBlueprintStatusUseCase_InitateConditions(t *testing.T) { + //type expectFn func(m *mockBlueprintSpecRepository) *mock.Call + type args struct { + blueprint *domain.BlueprintSpec + } + tests := []struct { + name string + args args + wantUnknownConditions []string + wantErr error + }{ + { + name: "nil conditions", + args: args{ + blueprint: &domain.BlueprintSpec{ + Conditions: nil, + }, + }, + wantUnknownConditions: domain.BlueprintConditions, + wantErr: nil, + }, + { + name: "empty conditions", + args: args{ + blueprint: &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + }, + }, + wantUnknownConditions: domain.BlueprintConditions, + wantErr: nil, + }, + { + name: "some conditions", + args: args{ + blueprint: &domain.BlueprintSpec{ + Conditions: []domain.Condition{ + { + Type: domain.ConditionValid, + }, + { + Type: domain.ConditionLastApplySucceeded, + }, + }, + }, + }, + wantUnknownConditions: []string{domain.ConditionExecutable, domain.ConditionEcosystemHealthy, domain.ConditionCompleted, domain.ConditionSelfUpgradeCompleted}, + wantErr: nil, + }, + { + name: "all conditions", + args: args{ + blueprint: &domain.BlueprintSpec{ + Conditions: []domain.Condition{ + { + Type: domain.ConditionValid, + }, + { + Type: domain.ConditionExecutable, + }, + { + Type: domain.ConditionEcosystemHealthy, + }, + { + Type: domain.ConditionSelfUpgradeCompleted, + }, + { + Type: domain.ConditionCompleted, + }, + { + Type: domain.ConditionLastApplySucceeded, + }, + }, + }, + }, + wantUnknownConditions: nil, + wantErr: nil, + }, + { + name: "update error", + args: args{ + blueprint: &domain.BlueprintSpec{ + Conditions: nil, + }, + }, + wantUnknownConditions: domain.BlueprintConditions, + wantErr: assert.AnError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + repoMock := newMockBlueprintSpecRepository(t) + if len(tt.wantUnknownConditions) > 0 { + repoMock.EXPECT().Update(ctx, mock.AnythingOfType("*domain.BlueprintSpec")).RunAndReturn(func(ctx context.Context, bp *domain.BlueprintSpec) error { + assert.Len(t, bp.Conditions, len(domain.BlueprintConditions)) + for _, condition := range tt.wantUnknownConditions { + assert.True(t, meta.IsStatusConditionPresentAndEqual(bp.Conditions, condition, metav1.ConditionUnknown)) + bpCondition := meta.FindStatusCondition(bp.Conditions, condition) + assert.Equal(t, "InitialSyncPending", bpCondition.Reason) + assert.Equal(t, "controller has not determined this condition yet", bpCondition.Message) + } + return tt.wantErr + }) + } + + useCase := &InitiateBlueprintStatusUseCase{ + repo: repoMock, + } + err := useCase.InitateConditions(ctx, tt.args.blueprint) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestNewInitiateBlueprintStatusUseCase(t *testing.T) { + type args struct { + repo blueprintSpecRepository + } + tests := []struct { + name string + args args + want *InitiateBlueprintStatusUseCase + }{ + { + name: "nil repo", + args: args{ + repo: nil, + }, + want: &InitiateBlueprintStatusUseCase{}, + }, + { + name: "repo", + args: args{ + repo: &mockBlueprintSpecRepository{}, + }, + want: &InitiateBlueprintStatusUseCase{ + repo: &mockBlueprintSpecRepository{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, NewInitiateBlueprintStatusUseCase(tt.args.repo), "NewInitiateBlueprintStatusUseCase(%v)", tt.args.repo) + }) + } +} diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index b2b0f9dc..3354a262 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -8,6 +8,10 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" ) +type initialBlueprintStatusUseCase interface { + InitateConditions(ctx context.Context, blueprint *domain.BlueprintSpec) error +} + type blueprintSpecValidationUseCase interface { ValidateBlueprintSpecStatically(ctx context.Context, blueprint *domain.BlueprintSpec) error ValidateBlueprintSpecDynamically(ctx context.Context, blueprint *domain.BlueprintSpec) error diff --git a/pkg/application/mock_initialBlueprintStatusUseCase_test.go b/pkg/application/mock_initialBlueprintStatusUseCase_test.go new file mode 100644 index 00000000..34b060c6 --- /dev/null +++ b/pkg/application/mock_initialBlueprintStatusUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockInitialBlueprintStatusUseCase is an autogenerated mock type for the initialBlueprintStatusUseCase type +type mockInitialBlueprintStatusUseCase struct { + mock.Mock +} + +type mockInitialBlueprintStatusUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockInitialBlueprintStatusUseCase) EXPECT() *mockInitialBlueprintStatusUseCase_Expecter { + return &mockInitialBlueprintStatusUseCase_Expecter{mock: &_m.Mock} +} + +// InitateConditions provides a mock function with given fields: ctx, blueprint +func (_m *mockInitialBlueprintStatusUseCase) InitateConditions(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for InitateConditions") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockInitialBlueprintStatusUseCase_InitateConditions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InitateConditions' +type mockInitialBlueprintStatusUseCase_InitateConditions_Call struct { + *mock.Call +} + +// InitateConditions is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockInitialBlueprintStatusUseCase_Expecter) InitateConditions(ctx interface{}, blueprint interface{}) *mockInitialBlueprintStatusUseCase_InitateConditions_Call { + return &mockInitialBlueprintStatusUseCase_InitateConditions_Call{Call: _e.mock.On("InitateConditions", ctx, blueprint)} +} + +func (_c *mockInitialBlueprintStatusUseCase_InitateConditions_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockInitialBlueprintStatusUseCase_InitateConditions_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockInitialBlueprintStatusUseCase_InitateConditions_Call) Return(_a0 error) *mockInitialBlueprintStatusUseCase_InitateConditions_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockInitialBlueprintStatusUseCase_InitateConditions_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockInitialBlueprintStatusUseCase_InitateConditions_Call { + _c.Call.Return(run) + return _c +} + +// newMockInitialBlueprintStatusUseCase creates a new instance of mockInitialBlueprintStatusUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockInitialBlueprintStatusUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockInitialBlueprintStatusUseCase { + mock := &mockInitialBlueprintStatusUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index 7c77ff76..e675f1e2 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -76,6 +76,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name componentRepo := componentcr.NewComponentInstallationRepo(componentsInterface.Components(namespace)) healthConfigRepo := adapterhealthconfig.NewHealthConfigProvider(ecosystemClientSet.CoreV1().ConfigMaps(namespace)) + initialBlueprintStateUseCase := application.NewInitiateBlueprintStatusUseCase(blueprintRepo) validateDependenciesUseCase := domainservice.NewValidateDependenciesDomainUseCase(remoteDoguRegistry) validateMountsUseCase := domainservice.NewValidateAdditionalMountsDomainUseCase(remoteDoguRegistry) blueprintValidationUseCase := application.NewBlueprintSpecValidationUseCase(blueprintRepo, validateDependenciesUseCase, validateMountsUseCase) @@ -91,7 +92,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( - blueprintRepo, blueprintValidationUseCase, + blueprintRepo, initialBlueprintStateUseCase, blueprintValidationUseCase, effectiveBlueprintUseCase, stateDiffUseCase, applyBlueprintSpecUseCase, ConfigUseCase, selfUpgradeUseCase, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 62e10e16..64f4faf5 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -38,7 +38,7 @@ const ( ConditionEcosystemHealthy = "EcosystemHealthy" ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" ConditionCompleted = "Completed" - ConditionLastApplySucceeded = "LastApplySuccessful" + ConditionLastApplySucceeded = "LastApplySucceeded" ReasonLastApplyErrorAtComponents = "ComponentApplyFailure" ReasonLastApplyErrorAtDogus = "DoguApplyFailure" @@ -46,6 +46,8 @@ const ( ) var ( + BlueprintConditions = []string{ConditionValid, ConditionExecutable, ConditionEcosystemHealthy, ConditionSelfUpgradeCompleted, ConditionCompleted, ConditionLastApplySucceeded} + notAllowedComponentActions = []Action{ActionDowngrade, ActionSwitchComponentNamespace} // ActionSwitchDoguNamespace is an exception and should be handled with the blueprint config. notAllowedDoguActions = []Action{ActionDowngrade, ActionSwitchDoguNamespace} @@ -268,7 +270,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( GlobalConfigDiffs: globalConfigDiffs, } - //TODO: we need the possible error from the use case to set the condition to Unknown spec.resetCompletedConditionAfterStateDiff() if spec.StateDiff.DoguDiffs.HasChanges() { spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) From e5911f0f139fb29ee6c4ac87d9cf731df74599b6 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 19 Sep 2025 13:51:03 +0200 Subject: [PATCH 071/119] #121 fix sonar issues --- .../doguregistry/doguDescriptorRepository.go | 2 +- .../v2/blueprintSpecCRRepository.go | 48 +++++++--- .../blueprintcr/v2/serializer/config.go | 2 +- .../blueprintcr/v2/serializer/dogu.go | 95 ++++++++++--------- pkg/application/blueprintSpecChangeUseCase.go | 85 +++++++++++------ .../blueprintSpecChangeUseCase_test.go | 63 +++++++----- .../initiaiteBlueprintStatusUseCase_test.go | 3 +- pkg/bootstrap.go | 17 +++- 8 files changed, 192 insertions(+), 123 deletions(-) diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index 7f9204b8..b97bcfa5 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -38,7 +38,7 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer } // TODO: doesn't work with "old" dogu operator - //err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) + err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) if err != nil { // just log the error, no need to fail the reconcilation logger.Info("failed to add dogu to local repository", diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 49fd1300..a4d936a9 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -55,18 +55,9 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) } } - var effectiveBlueprint domain.EffectiveBlueprint - var stateDiff domain.StateDiff - if blueprintCR.Status != nil { - effectiveBlueprint, err = serializerv2.ConvertToEffectiveBlueprintDomain(blueprintCR.Status.EffectiveBlueprint) - if err != nil { - return nil, err - } - - stateDiff, err = serializerv2.ConvertToStateDiffDomain(blueprintCR.Status.StateDiff) - if err != nil { - return nil, err - } + effectiveBlueprint, stateDiff, err := convertBlueprintStatus(blueprintCR) + if err != nil { + return nil, err } var conditions []domain.Condition @@ -87,6 +78,16 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) }, } + err = repo.serializeBlueprintAndMask(blueprintSpec, blueprintCR, blueprintId) + if err != nil { + return nil, err + } + + setPersistenceContext(blueprintCR, blueprintSpec) + return blueprintSpec, nil +} + +func (repo *blueprintSpecRepo) serializeBlueprintAndMask(blueprintSpec *domain.BlueprintSpec, blueprintCR *v2.Blueprint, blueprintId string) error { blueprint, blueprintErr := serializerv2.ConvertToBlueprintDomain(blueprintCR.Spec.Blueprint) if blueprintErr != nil { blueprintErrorEvent := domain.BlueprintSpecInvalidEvent{ValidationError: blueprintErr} @@ -101,13 +102,30 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) serializationErr := errors.Join(blueprintErr, maskErr) if serializationErr != nil { - return nil, fmt.Errorf("could not deserialize blueprint CR %q: %w", blueprintId, serializationErr) + return fmt.Errorf("could not deserialize blueprint CR %q: %w", blueprintId, serializationErr) } - setPersistenceContext(blueprintCR, blueprintSpec) blueprintSpec.Blueprint = blueprint blueprintSpec.BlueprintMask = blueprintMask - return blueprintSpec, nil + return nil +} + +func convertBlueprintStatus(blueprintCR *v2.Blueprint) (domain.EffectiveBlueprint, domain.StateDiff, error) { + var effectiveBlueprint domain.EffectiveBlueprint + var stateDiff domain.StateDiff + var err error + if blueprintCR.Status != nil { + effectiveBlueprint, err = serializerv2.ConvertToEffectiveBlueprintDomain(blueprintCR.Status.EffectiveBlueprint) + if err != nil { + return domain.EffectiveBlueprint{}, domain.StateDiff{}, err + } + + stateDiff, err = serializerv2.ConvertToStateDiffDomain(blueprintCR.Status.StateDiff) + if err != nil { + return domain.EffectiveBlueprint{}, domain.StateDiff{}, err + } + } + return effectiveBlueprint, stateDiff, nil } func boolPtrToValue(boolPtr *bool) bool { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 29f7fcc1..3accf6bb 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -104,7 +104,7 @@ func convertToGlobalConfigDomain(config []v2.ConfigEntry) domain.GlobalConfigEnt } func convertToConfigEntriesDomain(config []v2.ConfigEntry) domain.ConfigEntries { - if config == nil || len(config) == 0 { + if len(config) == 0 { return nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index 72ad27c9..0350d48a 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -19,11 +19,13 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { var errorList []error for _, dogu := range dogus { + result := domain.Dogu{} name, err := cescommons.QualifiedNameFromString(dogu.Name) if err != nil { errorList = append(errorList, err) continue } + result.Name = name var version *core.Version if dogu.Version != nil && *dogu.Version != "" { @@ -34,58 +36,21 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { continue } } + result.Version = version absent := false if dogu.Absent != nil { absent = *dogu.Absent } + result.Absent = absent - var minVolumeSize, maxBodySize *resource.Quantity - var reverseProxyConfig *ecosystem.ReverseProxyConfig - var rewriteTarget, additionalConfig *string - var additionalMounts []ecosystem.AdditionalMount - if dogu.PlatformConfig != nil { - if dogu.PlatformConfig.ResourceConfig != nil && dogu.PlatformConfig.ResourceConfig.MinVolumeSize != nil { - minVolumeSizeStr := dogu.PlatformConfig.ResourceConfig.MinVolumeSize - minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) - if err != nil { - errorList = append(errorList, fmt.Errorf("could not parse minimum volume size %q for dogu %q", *minVolumeSizeStr, dogu.Name)) - continue - } - } - - if dogu.PlatformConfig.ReverseProxyConfig != nil { - if dogu.PlatformConfig.ReverseProxyConfig.MaxBodySize != nil { - maxBodySizeStr := dogu.PlatformConfig.ReverseProxyConfig.MaxBodySize - maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) - if err != nil { - errorList = append(errorList, fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", *maxBodySizeStr, dogu.Name)) - continue - } - } - - rewriteTarget = dogu.PlatformConfig.ReverseProxyConfig.RewriteTarget - additionalConfig = dogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig - reverseProxyConfig = &ecosystem.ReverseProxyConfig{ - MaxBodySize: maxBodySize, - RewriteTarget: rewriteTarget, - AdditionalConfig: additionalConfig, - } - } - - if dogu.PlatformConfig.AdditionalMountsConfig != nil { - additionalMounts = convertAdditionalMountsFromDTOToDomain(dogu.PlatformConfig.AdditionalMountsConfig) - } + err = convertPlatformConfigFromDTOToDomain(&dogu, &result) + if err != nil { + errorList = append(errorList, err) + continue } - convertedDogus = append(convertedDogus, domain.Dogu{ - Name: name, - Version: version, - Absent: absent, - MinVolumeSize: minVolumeSize, - ReverseProxyConfig: reverseProxyConfig, - AdditionalMounts: additionalMounts, - }) + convertedDogus = append(convertedDogus, result) } err := errors.Join(errorList...) @@ -96,6 +61,44 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { return convertedDogus, err } +func convertPlatformConfigFromDTOToDomain(dtoDogu *bpv2.Dogu, domainDogu *domain.Dogu) error { + var minVolumeSize, maxBodySize *resource.Quantity + var additionalMounts []ecosystem.AdditionalMount + var err error + if dtoDogu.PlatformConfig != nil { + if dtoDogu.PlatformConfig.ResourceConfig != nil && dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize != nil { + minVolumeSizeStr := dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize + minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) + if err != nil { + return fmt.Errorf("could not parse minimum volume size %q for dogu %q", *minVolumeSizeStr, dtoDogu.Name) + } + domainDogu.MinVolumeSize = minVolumeSize + } + + if dtoDogu.PlatformConfig.ReverseProxyConfig != nil { + if dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize != nil { + maxBodySizeStr := dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize + maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) + if err != nil { + return fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", *maxBodySizeStr, dtoDogu.Name) + } + } + + domainDogu.ReverseProxyConfig = &ecosystem.ReverseProxyConfig{ + MaxBodySize: maxBodySize, + RewriteTarget: dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, + } + } + + if dtoDogu.PlatformConfig.AdditionalMountsConfig != nil { + additionalMounts = convertAdditionalMountsFromDTOToDomain(dtoDogu.PlatformConfig.AdditionalMountsConfig) + domainDogu.AdditionalMounts = additionalMounts + } + } + return nil +} + func ConvertMaskDogus(dogus []bpv2.MaskDogu) ([]domain.MaskDogu, error) { var convertedDogus []domain.MaskDogu var errorList []error @@ -174,7 +177,7 @@ func convertPlatformConfigDTO(dogu domain.Dogu) *bpv2.PlatformConfig { config := bpv2.PlatformConfig{} config.ResourceConfig = convertResourceConfigDTO(dogu) config.ReverseProxyConfig = convertReverseProxyConfigDTO(dogu) - config.AdditionalMountsConfig = convertAdditionalMountsConfig(dogu) + config.AdditionalMountsConfig = convertAdditionalMountsConfigDTO(dogu) return &config } @@ -197,7 +200,7 @@ func convertResourceConfigDTO(dogu domain.Dogu) *bpv2.ResourceConfig { return &config } -func convertAdditionalMountsConfig(dogu domain.Dogu) []bpv2.AdditionalMount { +func convertAdditionalMountsConfigDTO(dogu domain.Dogu) []bpv2.AdditionalMount { var config []bpv2.AdditionalMount for _, m := range dogu.AdditionalMounts { config = append(config, bpv2.AdditionalMount{ diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 20e39e4c..b1382ef5 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -8,13 +8,32 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -type BlueprintSpecChangeUseCase struct { - repo blueprintSpecRepository - initialStatus initialBlueprintStatusUseCase - validation blueprintSpecValidationUseCase - effectiveBlueprint effectiveBlueprintUseCase - stateDiff stateDiffUseCase - applyUseCase completeBlueprintUseCase +type BlueprintPreparationUseCases struct { + initialStatus initialBlueprintStatusUseCase + validation blueprintSpecValidationUseCase + effectiveBlueprint effectiveBlueprintUseCase + stateDiff stateDiffUseCase + healthUseCase ecosystemHealthUseCase +} + +func NewBlueprintPreparationUseCases( + initialStatus initialBlueprintStatusUseCase, + validation blueprintSpecValidationUseCase, + effectiveBlueprint effectiveBlueprintUseCase, + stateDiff stateDiffUseCase, + ecosystemHealthUseCase ecosystemHealthUseCase, +) BlueprintPreparationUseCases { + return BlueprintPreparationUseCases{ + initialStatus: initialStatus, + validation: validation, + effectiveBlueprint: effectiveBlueprint, + stateDiff: stateDiff, + healthUseCase: ecosystemHealthUseCase, + } +} + +type BlueprintApplyUseCases struct { + completeUseCase completeBlueprintUseCase ecosystemConfigUseCase ecosystemConfigUseCase selfUpgradeUseCase selfUpgradeUseCase applyComponentUseCase applyComponentsUseCase @@ -22,31 +41,39 @@ type BlueprintSpecChangeUseCase struct { healthUseCase ecosystemHealthUseCase } -func NewBlueprintSpecChangeUseCase( - repo blueprintSpecRepository, - initialStatus initialBlueprintStatusUseCase, - validation blueprintSpecValidationUseCase, - effectiveBlueprint effectiveBlueprintUseCase, - stateDiff stateDiffUseCase, - applyUseCase completeBlueprintUseCase, +func NewBlueprintApplyUseCases( + completeUseCase completeBlueprintUseCase, ecosystemConfigUseCase ecosystemConfigUseCase, selfUpgradeUseCase selfUpgradeUseCase, applyComponentUseCase applyComponentsUseCase, applyDogusUseCase applyDogusUseCase, - ecosystemHealthUseCase ecosystemHealthUseCase, -) *BlueprintSpecChangeUseCase { - return &BlueprintSpecChangeUseCase{ - repo: repo, - initialStatus: initialStatus, - validation: validation, - effectiveBlueprint: effectiveBlueprint, - stateDiff: stateDiff, - applyUseCase: applyUseCase, + healthUseCase ecosystemHealthUseCase, +) BlueprintApplyUseCases { + return BlueprintApplyUseCases{ + completeUseCase: completeUseCase, ecosystemConfigUseCase: ecosystemConfigUseCase, selfUpgradeUseCase: selfUpgradeUseCase, applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDogusUseCase, - healthUseCase: ecosystemHealthUseCase, + healthUseCase: healthUseCase, + } +} + +type BlueprintSpecChangeUseCase struct { + repo blueprintSpecRepository + preparationUseCases BlueprintPreparationUseCases + applyUseCases BlueprintApplyUseCases +} + +func NewBlueprintSpecChangeUseCase( + repo blueprintSpecRepository, + preparationUseCases BlueprintPreparationUseCases, + applyUseCases BlueprintApplyUseCases, +) *BlueprintSpecChangeUseCase { + return &BlueprintSpecChangeUseCase{ + repo: repo, + preparationUseCases: preparationUseCases, + applyUseCases: applyUseCases, } } @@ -76,7 +103,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C logger.V(1).Info("handle blueprint") - err = useCase.prepareBlueprint(ctx, blueprint) + err = useCase.preparationUseCases.prepareBlueprint(ctx, blueprint) if err != nil { return err } @@ -87,7 +114,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } // === Apply from here on === - err = useCase.applyBlueprint(ctx, blueprint) + err = useCase.applyUseCases.applyBlueprint(ctx, blueprint) if err != nil { return err } @@ -96,7 +123,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return nil } -func (useCase *BlueprintSpecChangeUseCase) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (useCase *BlueprintPreparationUseCases) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { err := useCase.initialStatus.InitateConditions(ctx, blueprint) if err != nil { return err @@ -128,7 +155,7 @@ func (useCase *BlueprintSpecChangeUseCase) prepareBlueprint(ctx context.Context, return nil } -func (useCase *BlueprintSpecChangeUseCase) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { +func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) if err != nil { // could be a domain.AwaitSelfUpgradeError to trigger another reconcile @@ -162,7 +189,7 @@ func (useCase *BlueprintSpecChangeUseCase) applyBlueprint(ctx context.Context, b } } - err = useCase.applyUseCase.CompleteBlueprint(ctx, blueprint) + err = useCase.completeUseCase.CompleteBlueprint(ctx, blueprint) if err != nil { return err } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 34a03704..e641eea3 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -30,13 +30,15 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { applyDoguUseCaseMock := newMockApplyDogusUseCase(t) ecosystemHealthUseCaseMock := newMockEcosystemHealthUseCase(t) - // when - result := NewBlueprintSpecChangeUseCase( - blueprintSpecRepositoryMock, + preparationUseCases := NewBlueprintPreparationUseCases( initialStatusUseCaseMock, validationUseCaseMock, effectiveUseCaseMock, stateDiffUseCaseMock, + ecosystemHealthUseCaseMock, + ) + + applyUseCases := NewBlueprintApplyUseCases( completeUseCaseMock, ecosystemConfigUseCaseMock, selfUpgradeUseCaseMock, @@ -45,18 +47,24 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { ecosystemHealthUseCaseMock, ) + // when + result := NewBlueprintSpecChangeUseCase(blueprintSpecRepositoryMock, preparationUseCases, applyUseCases) + // then require.NotNil(t, result) assert.Equal(t, blueprintSpecRepositoryMock, result.repo) - assert.Equal(t, validationUseCaseMock, result.validation) - assert.Equal(t, effectiveUseCaseMock, result.effectiveBlueprint) - assert.Equal(t, stateDiffUseCaseMock, result.stateDiff) - assert.Equal(t, ecosystemConfigUseCaseMock, result.ecosystemConfigUseCase) - assert.Equal(t, selfUpgradeUseCaseMock, result.selfUpgradeUseCase) - assert.Equal(t, applyComponentUseCaseMock, result.applyComponentUseCase) - assert.Equal(t, applyDoguUseCaseMock, result.applyDogusUseCase) - assert.Equal(t, ecosystemHealthUseCaseMock, result.healthUseCase) - assert.Equal(t, completeUseCaseMock, result.applyUseCase) + // preparation use cases + assert.Equal(t, validationUseCaseMock, result.preparationUseCases.validation) + assert.Equal(t, effectiveUseCaseMock, result.preparationUseCases.effectiveBlueprint) + assert.Equal(t, stateDiffUseCaseMock, result.preparationUseCases.stateDiff) + assert.Equal(t, ecosystemHealthUseCaseMock, result.preparationUseCases.healthUseCase) + assert.Equal(t, ecosystemConfigUseCaseMock, result.applyUseCases.ecosystemConfigUseCase) + // apply use cases + assert.Equal(t, selfUpgradeUseCaseMock, result.applyUseCases.selfUpgradeUseCase) + assert.Equal(t, applyComponentUseCaseMock, result.applyUseCases.applyComponentUseCase) + assert.Equal(t, applyDoguUseCaseMock, result.applyUseCases.applyDogusUseCase) + assert.Equal(t, completeUseCaseMock, result.applyUseCases.completeUseCase) + assert.Equal(t, ecosystemHealthUseCaseMock, result.applyUseCases.healthUseCase) } func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { @@ -831,9 +839,9 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { stateDiff = tt.fields.stateDiff(t) } - var applyUseCase completeBlueprintUseCase + var completeUseCase completeBlueprintUseCase if tt.fields.applyUseCase != nil { - applyUseCase = tt.fields.applyUseCase(t) + completeUseCase = tt.fields.applyUseCase(t) } var ecoConfigUseCase ecosystemConfigUseCase @@ -862,17 +870,22 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { } useCase := &BlueprintSpecChangeUseCase{ - repo: repo, - initialStatus: initialStatus, - validation: validation, - effectiveBlueprint: effectiveBlueprint, - stateDiff: stateDiff, - applyUseCase: applyUseCase, - ecosystemConfigUseCase: ecoConfigUseCase, - selfUpgradeUseCase: selfUpgrade, - applyComponentUseCase: applyComponentUseCase, - applyDogusUseCase: applyDoguUseCase, - healthUseCase: ecoHealthUseCase, + repo: repo, + preparationUseCases: BlueprintPreparationUseCases{ + initialStatus: initialStatus, + validation: validation, + effectiveBlueprint: effectiveBlueprint, + stateDiff: stateDiff, + healthUseCase: ecoHealthUseCase, + }, + applyUseCases: BlueprintApplyUseCases{ + completeUseCase: completeUseCase, + ecosystemConfigUseCase: ecoConfigUseCase, + selfUpgradeUseCase: selfUpgrade, + applyComponentUseCase: applyComponentUseCase, + applyDogusUseCase: applyDoguUseCase, + healthUseCase: ecoHealthUseCase, + }, } tt.wantErr(t, useCase.HandleUntilApplied(tt.args.givenCtx, tt.args.blueprintId), fmt.Sprintf("HandleUntilApplied(%v, %v)", tt.args.givenCtx, tt.args.blueprintId)) }) diff --git a/pkg/application/initiaiteBlueprintStatusUseCase_test.go b/pkg/application/initiaiteBlueprintStatusUseCase_test.go index 0d1b443b..3b2cb27c 100644 --- a/pkg/application/initiaiteBlueprintStatusUseCase_test.go +++ b/pkg/application/initiaiteBlueprintStatusUseCase_test.go @@ -121,7 +121,8 @@ func TestInitiateBlueprintStatusUseCase_InitateConditions(t *testing.T) { } err := useCase.InitateConditions(ctx, tt.args.blueprint) if tt.wantErr != nil { - assert.EqualError(t, err, tt.wantErr.Error()) + assert.ErrorIs(t, err, tt.wantErr) + assert.ErrorContains(t, err, "cannot save blueprint spec \"\" after initially setting the conditions to unknown") } else { assert.NoError(t, err) } diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index e675f1e2..c58f645f 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -85,21 +85,28 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, healthConfigRepo) componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintRepo, componentRepo, healthConfigRepo) ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintRepo) - applyBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) + completeBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintRepo, componentInstallationUseCase) applyDogusUseCase := application.NewApplyDogusUseCase(blueprintRepo, doguInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) - blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase( - blueprintRepo, initialBlueprintStateUseCase, blueprintValidationUseCase, - effectiveBlueprintUseCase, stateDiffUseCase, - applyBlueprintSpecUseCase, ConfigUseCase, + preparationUseCases := application.NewBlueprintPreparationUseCases( + initialBlueprintStateUseCase, + blueprintValidationUseCase, + effectiveBlueprintUseCase, + stateDiffUseCase, + ecosystemHealthUseCase, + ) + applyUseCases := application.NewBlueprintApplyUseCases( + completeBlueprintSpecUseCase, + ConfigUseCase, selfUpgradeUseCase, applyComponentUseCase, applyDogusUseCase, ecosystemHealthUseCase, ) + blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase(blueprintRepo, preparationUseCases, applyUseCases) blueprintReconciler := reconciler.NewBlueprintReconciler(blueprintChangeUseCase) return &ApplicationContext{ From 109bfd0e65522a59ed08e301b033729442aef538 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 19 Sep 2025 14:03:29 +0200 Subject: [PATCH 072/119] #121 reduce complexity further --- .../blueprintcr/v2/serializer/dogu.go | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index 0350d48a..cd45942e 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -62,40 +62,43 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { } func convertPlatformConfigFromDTOToDomain(dtoDogu *bpv2.Dogu, domainDogu *domain.Dogu) error { + if dtoDogu.PlatformConfig == nil { + return nil + } + var minVolumeSize, maxBodySize *resource.Quantity var additionalMounts []ecosystem.AdditionalMount var err error - if dtoDogu.PlatformConfig != nil { - if dtoDogu.PlatformConfig.ResourceConfig != nil && dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize != nil { - minVolumeSizeStr := dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize - minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) - if err != nil { - return fmt.Errorf("could not parse minimum volume size %q for dogu %q", *minVolumeSizeStr, dtoDogu.Name) - } - domainDogu.MinVolumeSize = minVolumeSize + if dtoDogu.PlatformConfig.ResourceConfig != nil && dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize != nil { + minVolumeSizeStr := dtoDogu.PlatformConfig.ResourceConfig.MinVolumeSize + minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) + if err != nil { + return fmt.Errorf("could not parse minimum volume size %q for dogu %q", *minVolumeSizeStr, dtoDogu.Name) } + domainDogu.MinVolumeSize = minVolumeSize + } - if dtoDogu.PlatformConfig.ReverseProxyConfig != nil { - if dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize != nil { - maxBodySizeStr := dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize - maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) - if err != nil { - return fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", *maxBodySizeStr, dtoDogu.Name) - } - } - - domainDogu.ReverseProxyConfig = &ecosystem.ReverseProxyConfig{ - MaxBodySize: maxBodySize, - RewriteTarget: dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, + if dtoDogu.PlatformConfig.ReverseProxyConfig != nil { + if dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize != nil { + maxBodySizeStr := dtoDogu.PlatformConfig.ReverseProxyConfig.MaxBodySize + maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) + if err != nil { + return fmt.Errorf("could not parse maximum proxy body size %q for dogu %q", *maxBodySizeStr, dtoDogu.Name) } } - if dtoDogu.PlatformConfig.AdditionalMountsConfig != nil { - additionalMounts = convertAdditionalMountsFromDTOToDomain(dtoDogu.PlatformConfig.AdditionalMountsConfig) - domainDogu.AdditionalMounts = additionalMounts + domainDogu.ReverseProxyConfig = &ecosystem.ReverseProxyConfig{ + MaxBodySize: maxBodySize, + RewriteTarget: dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, } } + + if dtoDogu.PlatformConfig.AdditionalMountsConfig != nil { + additionalMounts = convertAdditionalMountsFromDTOToDomain(dtoDogu.PlatformConfig.AdditionalMountsConfig) + domainDogu.AdditionalMounts = additionalMounts + } + return nil } From ed11c5d406e16fb732334bd05857f41b1c2f32cf Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 22 Sep 2025 09:24:06 +0200 Subject: [PATCH 073/119] #121 remove serialization of statediff to domain This is unnecessary, because it will be recalculated on every reconciliation --- .../v2/blueprintSpecCRRepository.go | 5 - .../blueprintcr/v2/serializer/stateDiff.go | 48 -- .../v2/serializer/stateDiff_test.go | 432 ------------------ pkg/application/blueprintSpecChangeUseCase.go | 1 + 4 files changed, 1 insertion(+), 485 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index a4d936a9..7bd17947 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -119,11 +119,6 @@ func convertBlueprintStatus(blueprintCR *v2.Blueprint) (domain.EffectiveBlueprin if err != nil { return domain.EffectiveBlueprint{}, domain.StateDiff{}, err } - - stateDiff, err = serializerv2.ConvertToStateDiffDomain(blueprintCR.Status.StateDiff) - if err != nil { - return domain.EffectiveBlueprint{}, domain.StateDiff{}, err - } } return effectiveBlueprint, stateDiff, nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go index 5ab052a0..d0c4f89f 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go @@ -1,8 +1,6 @@ package serializer import ( - "errors" - "fmt" "slices" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -56,49 +54,3 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) *crd.StateDiff { GlobalConfigDiff: convertToGlobalConfigDiffDTO(domainModel.GlobalConfigDiffs), } } - -func ConvertToStateDiffDomain(dto *crd.StateDiff) (domain.StateDiff, error) { - if dto == nil { - return domain.StateDiff{}, nil - } - - var errs []error - - var doguDiffs []domain.DoguDiff - for doguName, doguDiff := range dto.DoguDiffs { - doguDiffDomainModel, err := convertToDoguDiffDomain(doguName, doguDiff) - errs = append(errs, err) - doguDiffs = append(doguDiffs, doguDiffDomainModel) - } - - var componentDiffs []domain.ComponentDiff - for componentName, componentDiff := range dto.ComponentDiffs { - componentDiffDomainModel, err := convertToComponentDiffDomain(componentName, componentDiff) - errs = append(errs, err) - componentDiffs = append(componentDiffs, componentDiffDomainModel) - } - - err := errors.Join(errs...) - if err != nil { - return domain.StateDiff{}, fmt.Errorf("failed to convert state diff DTO to domain model: %w", err) - } - - var doguConfigDiffs map[cescommons.SimpleName]domain.DoguConfigDiffs - var sensitiveDoguConfigDiffs map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs - if len(dto.DoguConfigDiffs) != 0 { - doguConfigDiffs = map[cescommons.SimpleName]domain.DoguConfigDiffs{} - sensitiveDoguConfigDiffs = map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{} - for doguName, doguConfigDiff := range dto.DoguConfigDiffs { - doguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, crd.ConfigDiff(doguConfigDiff.DoguConfigDiff)) - sensitiveDoguConfigDiffs[cescommons.SimpleName(doguName)] = convertToDoguConfigDiffsDomain(doguName, crd.ConfigDiff(doguConfigDiff.SensitiveDoguConfigDiff)) - } - } - - return domain.StateDiff{ - DoguDiffs: doguDiffs, - ComponentDiffs: componentDiffs, - DoguConfigDiffs: doguConfigDiffs, - SensitiveDoguConfigDiffs: sensitiveDoguConfigDiffs, - GlobalConfigDiffs: convertToGlobalConfigDiffDomain(dto.GlobalConfigDiff), - }, nil -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index aca5a937..ede5e0f8 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -1,9 +1,7 @@ package serializer import ( - "cmp" "reflect" - "slices" "testing" "github.com/Masterminds/semver/v3" @@ -307,437 +305,7 @@ func mustParseVersion(raw string) core.Version { return version } -func TestConvertToDomainModel(t *testing.T) { - wrongVersionABCD := "a.b.c-d" - tests := []struct { - name string - dto crd.StateDiff - want domain.StateDiff - wantErr assert.ErrorAssertionFunc - }{ - { - name: "fail to parse actual version of single dogu diff", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHighStr, - Absent: false, - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, - { - name: "fail to parse expected version of single dogu diff", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHighStr, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - Absent: false, - }, - NeededActions: []crd.DoguAction{"downgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, - { - name: "fail with multiple errors in single dogu diff", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - }, - NeededActions: []crd.DoguAction{"none"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, - { - name: "fail for one of multiple dogu diffs", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "postfix": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLowStr, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHighStr, - Absent: false, - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLowStr, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - Absent: false, - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") - }, - }, - { - name: "fail for multiple dogu diffs", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "postfix": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHighStr, - }, - NeededActions: []crd.DoguAction{"none"}, - }, - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLowStr, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &wrongVersionABCD, - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to parse version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"ldap\" to domain model") && - assert.ErrorContains(t, err, "failed to convert dogu diff dto \"postfix\" to domain model") - }, - }, - { - name: "succeed for multiple dogu diffs", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "postfix": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLowStr, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHighStr, - Absent: false, - }, - NeededActions: []crd.DoguAction{"upgrade"}, - }, - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLowStr, - Absent: false, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Absent: true, - }, - NeededActions: []crd.DoguAction{"uninstall"}, - }, - }, - }, - want: domain.StateDiff{DoguDiffs: []domain.DoguDiff{ - { - DoguName: "ldap", - Actual: domain.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLow, - Absent: false, - }, - Expected: domain.DoguDiffState{ - Namespace: "official", - Absent: true, - }, NeededActions: []domain.Action{domain.ActionUninstall}, - }, - { - DoguName: "postfix", - Actual: domain.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionLow, - Absent: false, - }, - Expected: domain.DoguDiffState{ - Namespace: "official", - Version: &testCoreVersionHigh, - Absent: false, - }, - NeededActions: []domain.Action{domain.ActionUpgrade}, - }, - }}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.NoError(t, err) - }, - }, - { - name: "succeed for multiple dogu config diffs", - dto: crd.StateDiff{ - DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ - "ldap": { - crd.DoguConfigDiff{}, - crd.SensitiveDoguConfigDiff{}, - }, - "postfix": { - crd.DoguConfigDiff{}, - crd.SensitiveDoguConfigDiff{}, - }, - }, - }, - want: domain.StateDiff{ - DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ - "ldap": nil, - "postfix": nil, - }, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{ - "ldap": nil, - "postfix": nil, - }, - }, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.NoError(t, err) - }, - }, - { - name: "succeed for global config diffs", - dto: crd.StateDiff{ - GlobalConfigDiff: crd.GlobalConfigDiff{{ - Key: "fqdn", - Actual: crd.ConfigValueState{ - Value: &testFqdn1, - Exists: true, - }, - Expected: crd.ConfigValueState{ - Value: &testFqdn2, - Exists: true, - }, - NeededAction: "set", - }}, - }, - want: domain.StateDiff{ - GlobalConfigDiffs: []domain.GlobalConfigEntryDiff{{ - Key: "fqdn", - Actual: domain.GlobalConfigValueState{ - Value: &testFqdn1, - Exists: true, - }, - Expected: domain.GlobalConfigValueState{ - Value: &testFqdn2, - Exists: true, - }, - NeededAction: domain.ConfigActionSet, - }}, - }, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.NoError(t, err) - }, - }, - { - name: "succeed for multiple component diffs", - dto: crd.StateDiff{ - ComponentDiffs: map[string]crd.ComponentDiff{ - testComponentName: { - Actual: crd.ComponentDiffState{Version: &testSemverVersionLowRaw, Absent: false}, - Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, - NeededActions: []crd.ComponentAction{"upgrade", "component namespace switch"}, - }, - "my-component-2": { - Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, - Expected: crd.ComponentDiffState{Absent: true}, - NeededActions: []crd.ComponentAction{"uninstall"}, - }, - }, - }, - want: domain.StateDiff{ComponentDiffs: []domain.ComponentDiff{ - { - Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testSemverVersionLow, Absent: false}, - Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, - NeededActions: []domain.Action{domain.ActionUpgrade, domain.ActionSwitchComponentNamespace}, - }, - { - Name: "my-component-2", - Actual: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, - Expected: domain.ComponentDiffState{Absent: true}, - NeededActions: []domain.Action{domain.ActionUninstall}, - }, - }}, - wantErr: assert.NoError, - }, - { - name: "fail for multiple component diffs", - dto: crd.StateDiff{ - ComponentDiffs: map[string]crd.ComponentDiff{ - testComponentName: { - Actual: crd.ComponentDiffState{ - Version: &wrongVersionABCD, - }, - Expected: crd.ComponentDiffState{ - Version: &testCoreVersionHighStr, - }, - NeededActions: []crd.ComponentAction{"none"}, - }, - "my-component-2": { - Actual: crd.ComponentDiffState{ - Version: &testCoreVersionLowStr, - }, - Expected: crd.ComponentDiffState{ - Version: &wrongVersionABCD, - }, - NeededActions: []crd.ComponentAction{"upgrade"}, - }, - }, - }, - want: domain.StateDiff{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse actual version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to parse actual version \"a.b.c-d\"") && - assert.ErrorContains(t, err, "failed to convert component diff dto \"my-component\" to domain model") && - assert.ErrorContains(t, err, "failed to convert component diff dto \"my-component-2\" to domain model") - }, - }, - - { - name: "should convert additional mounts", - dto: crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{ - "ldap": { - Actual: crd.DoguDiffState{ - Namespace: "official", - Absent: false, - AdditionalMounts: []crd.AdditionalMount{ - { - SourceType: crd.DataSourceConfigMap, - Name: "config", - Volume: "volume", - Subfolder: &testSubfolderStr, - }, - }, - }, - Expected: crd.DoguDiffState{ - Namespace: "official", - Absent: false, - AdditionalMounts: []crd.AdditionalMount{ - { - SourceType: crd.DataSourceConfigMap, - Name: "config-different", - Volume: "volume", - Subfolder: &testSubfolderStr, - }, - }, - }, - NeededActions: []crd.DoguAction{"update additional mounts"}, - }, - }, - }, - want: domain.StateDiff{DoguDiffs: []domain.DoguDiff{ - { - DoguName: "ldap", - Actual: domain.DoguDiffState{ - Namespace: "official", - Absent: false, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "config", - Volume: "volume", - Subfolder: &testSubfolderStr, - }, - }, - }, - Expected: domain.DoguDiffState{ - Namespace: "official", - Absent: false, - AdditionalMounts: []ecosystem.AdditionalMount{ - { - SourceType: ecosystem.DataSourceConfigMap, - Name: "config-different", - Volume: "volume", - Subfolder: &testSubfolderStr, - }, - }, - }, - NeededActions: []domain.Action{domain.ActionUpdateAdditionalMounts}, - }, - }}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.NoError(t, err) - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ConvertToStateDiffDomain(&tt.dto) - tt.wantErr(t, err) - // sort to avoid flaky tests - slices.SortFunc(got.DoguDiffs, func(a, b domain.DoguDiff) int { - return cmp.Compare(a.DoguName, b.DoguName) - }) - slices.SortFunc(got.ComponentDiffs, func(a, b domain.ComponentDiff) int { - return cmp.Compare(a.Name, b.Name) - }) - assert.Equalf(t, tt.want, got, "ConvertToStateDiffDomain(%v)", tt.dto) - }) - } -} - func TestConvertToStateDiffDTO(t *testing.T) { - value1 := "1" value123 := "123" tests := []struct { diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index b1382ef5..b82504e6 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -188,6 +188,7 @@ func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, bluep return err } } + // TODO: check if config in dogus is already up to date and if installed Version is up to date err = useCase.completeUseCase.CompleteBlueprint(ctx, blueprint) if err != nil { From 5395651cfbd1d246ad8cf37fbee57a2eba434bbc Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 22 Sep 2025 10:00:27 +0200 Subject: [PATCH 074/119] #121 remove serialization of statediff to domain This is unnecessary, because it will be recalculated on every reconciliation --- .../v2/blueprintSpecCRRepository.go | 10 +-- .../v2/serializer/doguConfigDiff.go | 29 ------- .../v2/serializer/doguConfigDiff_test.go | 78 ------------------- .../v2/serializer/globalConfigDiff.go | 28 ------- 4 files changed, 4 insertions(+), 141 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 7bd17947..89649cf8 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -55,7 +55,7 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) } } - effectiveBlueprint, stateDiff, err := convertBlueprintStatus(blueprintCR) + effectiveBlueprint, err := convertBlueprintStatus(blueprintCR) if err != nil { return nil, err } @@ -68,7 +68,6 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) blueprintSpec := &domain.BlueprintSpec{ Id: blueprintId, EffectiveBlueprint: effectiveBlueprint, - StateDiff: stateDiff, Conditions: conditions, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: boolPtrToValue(blueprintCR.Spec.IgnoreDoguHealth), @@ -110,17 +109,16 @@ func (repo *blueprintSpecRepo) serializeBlueprintAndMask(blueprintSpec *domain.B return nil } -func convertBlueprintStatus(blueprintCR *v2.Blueprint) (domain.EffectiveBlueprint, domain.StateDiff, error) { +func convertBlueprintStatus(blueprintCR *v2.Blueprint) (domain.EffectiveBlueprint, error) { var effectiveBlueprint domain.EffectiveBlueprint - var stateDiff domain.StateDiff var err error if blueprintCR.Status != nil { effectiveBlueprint, err = serializerv2.ConvertToEffectiveBlueprintDomain(blueprintCR.Status.EffectiveBlueprint) if err != nil { - return domain.EffectiveBlueprint{}, domain.StateDiff{}, err + return domain.EffectiveBlueprint{}, err } } - return effectiveBlueprint, stateDiff, nil + return effectiveBlueprint, nil } func boolPtrToValue(boolPtr *bool) bool { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go index 337a026a..de8d65c4 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff.go @@ -1,39 +1,10 @@ package serializer import ( - cescommons "github.com/cloudogu/ces-commons-lib/dogu" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-registry-lib/config" ) -func convertToDoguConfigDiffsDomain(doguName string, dtoDiffs crd.ConfigDiff) domain.DoguConfigDiffs { - var doguConfigDiff domain.DoguConfigDiffs - for _, entryDiff := range dtoDiffs { - doguConfigDiff = append(doguConfigDiff, convertToDoguConfigEntryDiffDomain(doguName, entryDiff)) - } - return doguConfigDiff -} - -func convertToDoguConfigEntryDiffDomain(doguName string, dto crd.ConfigEntryDiff) domain.DoguConfigEntryDiff { - return domain.DoguConfigEntryDiff{ - Key: common.DoguConfigKey{ - DoguName: cescommons.SimpleName(doguName), - Key: config.Key(dto.Key), - }, - Actual: domain.DoguConfigValueState{ - Value: dto.Actual.Value, - Exists: dto.Actual.Exists, - }, - Expected: domain.DoguConfigValueState{ - Value: dto.Expected.Value, - Exists: dto.Expected.Exists, - }, - NeededAction: domain.ConfigAction(dto.NeededAction), - } -} - func convertToDoguConfigEntryDiffsDTO(domainDiffs domain.DoguConfigDiffs, isSensitive bool) crd.DoguConfigDiff { var dtoDiffs []crd.ConfigEntryDiff for _, domainDiff := range domainDiffs { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go index 3752e68c..20c4aee4 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguConfigDiff_test.go @@ -93,81 +93,3 @@ func Test_convertToDoguConfigEntryDiffsDTO(t *testing.T) { }) } } - -func Test_convertToDoguConfigDiffsDomain(t *testing.T) { - tests := []struct { - name string - dto crd.ConfigDiff - want domain.DoguConfigDiffs - }{ - { - name: "should exit early if slices are empty", - dto: crd.ConfigDiff{}, - want: nil, - }, - { - name: "should convert multiple dogu config diffs", - dto: crd.ConfigDiff{ - { - Key: "container_config/memory_limit", - Actual: crd.ConfigValueState{ - Value: &limit512, - Exists: true, - }, - Expected: crd.ConfigValueState{ - Value: &limit1024, - Exists: true, - }, - NeededAction: "set", - }, - { - Key: "container_config/swap_limit", - Actual: crd.ConfigValueState{ - Exists: false, - }, - Expected: crd.ConfigValueState{ - Value: &limit512, - Exists: true, - }, - NeededAction: "set", - }, - }, - want: domain.DoguConfigDiffs{ - { - Key: common.DoguConfigKey{ - DoguName: "ldap", - Key: "container_config/memory_limit", - }, - Actual: domain.DoguConfigValueState{ - Value: &limit512, - Exists: true, - }, - Expected: domain.DoguConfigValueState{ - Value: &limit1024, - Exists: true, - }, - NeededAction: domain.ConfigActionSet, - }, - { - Key: common.DoguConfigKey{ - DoguName: "ldap", - Key: "container_config/swap_limit", - }, - Actual: domain.DoguConfigValueState{ - Exists: false, - }, - Expected: domain.DoguConfigValueState{ - Value: &limit512, - Exists: true, - }, - NeededAction: domain.ConfigActionSet, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, convertToDoguConfigDiffsDomain("ldap", tt.dto), "convertToDoguConfigDiffsDomain(%v, %v)", "ldap", tt.dto) - }) - } -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go index be4e6bba..74391050 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/globalConfigDiff.go @@ -3,36 +3,8 @@ package serializer import ( crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) -func convertToGlobalConfigDiffDomain(dto crd.GlobalConfigDiff) domain.GlobalConfigDiffs { - if len(dto) == 0 { - return nil - } - - globalConfigDiff := make(domain.GlobalConfigDiffs, len(dto)) - for i, entryDiff := range dto { - globalConfigDiff[i] = convertToGlobalConfigEntryDiffDomain(entryDiff) - } - return globalConfigDiff -} - -func convertToGlobalConfigEntryDiffDomain(dto crd.ConfigEntryDiff) domain.GlobalConfigEntryDiff { - return domain.GlobalConfigEntryDiff{ - Key: common.GlobalConfigKey(dto.Key), - Actual: domain.GlobalConfigValueState{ - Value: dto.Actual.Value, - Exists: dto.Actual.Exists, - }, - Expected: domain.GlobalConfigValueState{ - Value: dto.Expected.Value, - Exists: dto.Expected.Exists, - }, - NeededAction: domain.ConfigAction(dto.NeededAction), - } -} - func convertToGlobalConfigDiffDTO(domainModel domain.GlobalConfigDiffs) crd.GlobalConfigDiff { if len(domainModel) == 0 { return nil From d682af5bf1d1fad7603bd614d804a0421c154546 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 22 Sep 2025 21:59:56 +0200 Subject: [PATCH 075/119] #121 enforce singlton cr --- go.mod | 2 +- go.sum | 2 + .../v2/blueprintSpecCRRepository.go | 24 +++++ .../v2/blueprintSpecCRRepository_test.go | 98 +++++++++++++++++++ .../v2/mock_blueprintInterface_test.go | 59 +++++++++++ .../reconciler/blueprint_controller.go | 13 ++- .../reconciler/blueprint_controller_test.go | 40 +++++++- pkg/adapter/reconciler/interfaces.go | 2 + .../mock_BlueprintChangeHandler_test.go | 46 +++++++++ pkg/application/blueprintSpecChangeUseCase.go | 14 +++ .../blueprintSpecChangeUseCase_test.go | 34 +++++++ .../mock_blueprintSpecRepository_test.go | 46 +++++++++ pkg/domain/errors.go | 10 ++ pkg/domainservice/adapterInterfaces.go | 6 ++ .../mock_BlueprintSpecRepository_test.go | 46 +++++++++ 15 files changed, 438 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index b326d37a..c3354b42 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1 + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 github.com/cloudogu/k8s-component-operator v1.9.0 github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 github.com/cloudogu/k8s-registry-lib v0.5.1 diff --git a/go.sum b/go.sum index 813e9eab..ef225eac 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 h1:8 github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1 h1:CcZow0wfH5Jm6pOEPrd+e3MYgYPbjzi79GRySWUhl4g= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 h1:SjKSO5/5fNKQ6Z9v7t9laD3u62rl6Tr9o/0h8Ik6LrE= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 89649cf8..87fc2e4f 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -86,6 +86,30 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) return blueprintSpec, nil } +func (repo *blueprintSpecRepo) CheckSingleton(ctx context.Context) error { + // Ask for just 2 items: enough to detect "more than one" + limit := int64(2) + + list, err := repo.blueprintClient.List(ctx, metav1.ListOptions{Limit: limit}) + if err != nil { + return &domainservice.InternalError{ + WrappedError: err, + Message: fmt.Sprintf("error while listing blueprint resources"), + } + } + + if list == nil { + return nil + } + + switch len(list.Items) { + case 0, 1: + return nil + default: + return &domain.MultipleBlueprintsError{Message: "more than one blueprint CR found"} + } +} + func (repo *blueprintSpecRepo) serializeBlueprintAndMask(blueprintSpec *domain.BlueprintSpec, blueprintCR *v2.Blueprint, blueprintId string) error { blueprint, blueprintErr := serializerv2.ConvertToBlueprintDomain(blueprintCR.Spec.Blueprint) if blueprintErr != nil { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index eae3f006..fb3c986d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -396,3 +396,101 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { assert.Empty(t, spec.Events, "events in aggregate should be deleted after publishing them") }) } + +func Test_blueprintSpecRepo_CheckSingleton(t *testing.T) { + t.Run("No error when no blueprints found", func(t *testing.T) { + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + + restClientMock.EXPECT().List(ctx, metav1.ListOptions{Limit: 2}).Return(nil, nil) + + // when + err := repo.CheckSingleton(ctx) + + // then + require.NoError(t, err) + }) + + t.Run("No error on empty blueprintList", func(t *testing.T) { + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + restClientMock.EXPECT().List(ctx, metav1.ListOptions{Limit: 2}).Return(&bpv2.BlueprintList{}, nil) + + // when + err := repo.CheckSingleton(ctx) + + // then + require.NoError(t, err) + }) + + t.Run("No error on single blueprint resource", func(t *testing.T) { + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + list := &bpv2.BlueprintList{ + Items: []bpv2.Blueprint{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "blueprint-1", + }, + }, + }, + } + restClientMock.EXPECT().List(ctx, metav1.ListOptions{Limit: 2}).Return(list, nil) + + // when + err := repo.CheckSingleton(ctx) + + // then + require.NoError(t, err) + }) + + t.Run("MultipleBlueprintsError on two blueprint resources", func(t *testing.T) { + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + list := &bpv2.BlueprintList{ + Items: []bpv2.Blueprint{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "blueprint-1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "blueprint-2", + }, + }, + }, + } + restClientMock.EXPECT().List(ctx, metav1.ListOptions{Limit: 2}).Return(list, nil) + + // when + err := repo.CheckSingleton(ctx) + + // then + require.Error(t, err) + var targetErr *domain.MultipleBlueprintsError + assert.ErrorAs(t, err, &targetErr) + assert.ErrorContains(t, err, "more than one blueprint CR found") + }) + + t.Run("InternalError on List error", func(t *testing.T) { + restClientMock := newMockBlueprintInterface(t) + eventRecorderMock := newMockEventRecorder(t) + repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) + + restClientMock.EXPECT().List(ctx, metav1.ListOptions{Limit: 2}).Return(nil, assert.AnError) + + // when + err := repo.CheckSingleton(ctx) + + // then + require.Error(t, err) + var targetErr *domainservice.InternalError + assert.ErrorAs(t, err, &targetErr) + assert.ErrorContains(t, err, "error while listing blueprint resources") + }) +} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go index 47f0df5a..f03bbac1 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/mock_blueprintInterface_test.go @@ -245,6 +245,65 @@ func (_c *mockBlueprintInterface_Get_Call) RunAndReturn(run func(context.Context return _c } +// List provides a mock function with given fields: ctx, opts +func (_m *mockBlueprintInterface) List(ctx context.Context, opts v1.ListOptions) (*apiv2.BlueprintList, error) { + ret := _m.Called(ctx, opts) + + if len(ret) == 0 { + panic("no return value specified for List") + } + + var r0 *apiv2.BlueprintList + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) (*apiv2.BlueprintList, error)); ok { + return rf(ctx, opts) + } + if rf, ok := ret.Get(0).(func(context.Context, v1.ListOptions) *apiv2.BlueprintList); ok { + r0 = rf(ctx, opts) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*apiv2.BlueprintList) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, v1.ListOptions) error); ok { + r1 = rf(ctx, opts) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockBlueprintInterface_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' +type mockBlueprintInterface_List_Call struct { + *mock.Call +} + +// List is a helper method to define mock.On call +// - ctx context.Context +// - opts v1.ListOptions +func (_e *mockBlueprintInterface_Expecter) List(ctx interface{}, opts interface{}) *mockBlueprintInterface_List_Call { + return &mockBlueprintInterface_List_Call{Call: _e.mock.On("List", ctx, opts)} +} + +func (_c *mockBlueprintInterface_List_Call) Run(run func(ctx context.Context, opts v1.ListOptions)) *mockBlueprintInterface_List_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(v1.ListOptions)) + }) + return _c +} + +func (_c *mockBlueprintInterface_List_Call) Return(_a0 *apiv2.BlueprintList, _a1 error) *mockBlueprintInterface_List_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockBlueprintInterface_List_Call) RunAndReturn(run func(context.Context, v1.ListOptions) (*apiv2.BlueprintList, error)) *mockBlueprintInterface_List_Call { + _c.Call.Return(run) + return _c +} + // Patch provides a mock function with given fields: ctx, name, pt, data, opts, subresources func (_m *mockBlueprintInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (*apiv2.Blueprint, error) { _va := make([]interface{}, len(subresources)) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 30acabd4..8652e80c 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -45,7 +45,13 @@ func (r *BlueprintReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( WithName("BlueprintReconciler.Reconcile"). WithValues("resourceName", req.Name) - err := r.blueprintChangeHandler.HandleUntilApplied(ctx, req.Name) + err := r.blueprintChangeHandler.CheckForMultipleBlueprintResources(ctx) + + if err != nil { + return decideRequeueForError(logger, err) + } + + err = r.blueprintChangeHandler.HandleUntilApplied(ctx, req.Name) if err != nil { return decideRequeueForError(logger, err) @@ -64,6 +70,7 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { var healthError *domain.UnhealthyEcosystemError var awaitSelfUpgradeError *domain.AwaitSelfUpgradeError var stateDiffNotEmptyError *domain.StateDiffNotEmptyError + var multipleBlueprintsError *domain.MultipleBlueprintsError switch { case errors.As(err, &internalError): errLogger.Error(err, "An internal error occurred and can maybe be fixed by retrying it later") @@ -94,6 +101,10 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { errLogger.Info("requeue until state diff is empty") // fast requeue here since state diff has to be determined again return ctrl.Result{RequeueAfter: 1 * time.Second}, nil + case errors.As(err, &multipleBlueprintsError): + errLogger.Error(err, "Ecosystem contains multiple blueprints - delete all but one. Retry later") + // fast requeue here since state diff has to be determined again + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil default: errLogger.Error(err, "An unknown error type occurred. Retry with default backoff") return ctrl.Result{}, err // automatic requeue because of non-nil err diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index 6cea4512..0be1dfd5 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -85,6 +85,7 @@ func TestBlueprintReconciler_Reconcile(t *testing.T) { changeHandlerMock := NewMockBlueprintChangeHandler(t) sut := &BlueprintReconciler{blueprintChangeHandler: changeHandlerMock} + changeHandlerMock.EXPECT().CheckForMultipleBlueprintResources(testCtx).Return(nil) changeHandlerMock.EXPECT().HandleUntilApplied(testCtx, testBlueprint).Return(nil) // when actual, err := sut.Reconcile(testCtx, request) @@ -93,12 +94,29 @@ func TestBlueprintReconciler_Reconcile(t *testing.T) { require.NoError(t, err) assert.Equal(t, ctrl.Result{}, actual) }) - t.Run("should fail", func(t *testing.T) { + + t.Run("should succeed", func(t *testing.T) { + // given + request := ctrl.Request{NamespacedName: types.NamespacedName{Name: testBlueprint}} + changeHandlerMock := NewMockBlueprintChangeHandler(t) + sut := &BlueprintReconciler{blueprintChangeHandler: changeHandlerMock} + + changeHandlerMock.EXPECT().CheckForMultipleBlueprintResources(testCtx).Return(assert.AnError) + // when + _, err := sut.Reconcile(testCtx, request) + + // then + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + }) + + t.Run("should fail on HandleUntilApplied error", func(t *testing.T) { // given request := ctrl.Request{NamespacedName: types.NamespacedName{Name: testBlueprint}} changeHandlerMock := NewMockBlueprintChangeHandler(t) sut := &BlueprintReconciler{blueprintChangeHandler: changeHandlerMock} + changeHandlerMock.EXPECT().CheckForMultipleBlueprintResources(testCtx).Return(nil) changeHandlerMock.EXPECT().HandleUntilApplied(testCtx, testBlueprint).Return(errors.New("test")) // when _, err := sut.Reconcile(testCtx, request) @@ -145,7 +163,7 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{RequeueAfter: 1 * time.Second}, actual) assert.Contains(t, logSinkMock.output, "0: A concurrent update happened in conflict to the processing of the blueprint spec. A retry could fix this issue") }) - t.Run("should catch wrapped NotFoundError, issue a log line and do not requeue", func(t *testing.T) { + t.Run("should catch wrapped NotFoundError, issue a log line and do not requeue timely", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() testLogger := logr.New(logSinkMock) @@ -164,6 +182,24 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{}, actual) assert.Contains(t, logSinkMock.output, "0: Blueprint was not found, so maybe it was deleted in the meantime. No further evaluation will happen") }) + t.Run("should catch wrapped MultipleBlueprintsError, issue a error log line and requeue", func(t *testing.T) { + // given + logSinkMock := newTrivialTestLogSink() + testLogger := logr.New(logSinkMock) + + intermediateErr := &domain.MultipleBlueprintsError{ + Message: "multiple blueprints found", + } + errorChain := fmt.Errorf("could not do the thing: %w", intermediateErr) + + // when + actual, err := decideRequeueForError(testLogger, errorChain) + + // then + require.NoError(t, err) + assert.Equal(t, ctrl.Result{RequeueAfter: 10 * time.Second}, actual) + assert.Contains(t, logSinkMock.output, "0: Ecosystem contains multiple blueprints - delete all but one. Retry later") + }) t.Run("NotFoundError, should retry if referenced config is missing", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() diff --git a/pkg/adapter/reconciler/interfaces.go b/pkg/adapter/reconciler/interfaces.go index 3092005d..aacc651c 100644 --- a/pkg/adapter/reconciler/interfaces.go +++ b/pkg/adapter/reconciler/interfaces.go @@ -2,6 +2,7 @@ package reconciler import ( "context" + "sigs.k8s.io/controller-runtime/pkg/manager" ) @@ -15,4 +16,5 @@ type controllerManager interface { type BlueprintChangeHandler interface { HandleUntilApplied(ctx context.Context, blueprintId string) error + CheckForMultipleBlueprintResources(ctx context.Context) error } diff --git a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go index 9005793d..6a7b007f 100644 --- a/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go +++ b/pkg/adapter/reconciler/mock_BlueprintChangeHandler_test.go @@ -21,6 +21,52 @@ func (_m *MockBlueprintChangeHandler) EXPECT() *MockBlueprintChangeHandler_Expec return &MockBlueprintChangeHandler_Expecter{mock: &_m.Mock} } +// CheckForMultipleBlueprintResources provides a mock function with given fields: ctx +func (_m *MockBlueprintChangeHandler) CheckForMultipleBlueprintResources(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CheckForMultipleBlueprintResources") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckForMultipleBlueprintResources' +type MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call struct { + *mock.Call +} + +// CheckForMultipleBlueprintResources is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockBlueprintChangeHandler_Expecter) CheckForMultipleBlueprintResources(ctx interface{}) *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call { + return &MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call{Call: _e.mock.On("CheckForMultipleBlueprintResources", ctx)} +} + +func (_c *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call) Run(run func(ctx context.Context)) *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call) Return(_a0 error) *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call) RunAndReturn(run func(context.Context) error) *MockBlueprintChangeHandler_CheckForMultipleBlueprintResources_Call { + _c.Call.Return(run) + return _c +} + // HandleUntilApplied provides a mock function with given fields: ctx, blueprintId func (_m *MockBlueprintChangeHandler) HandleUntilApplied(ctx context.Context, blueprintId string) error { ret := _m.Called(ctx, blueprintId) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index b82504e6..a8dca7a2 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -196,3 +196,17 @@ func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, bluep } return nil } + +func (useCase *BlueprintSpecChangeUseCase) CheckForMultipleBlueprintResources(ctx context.Context) error { + logger := log.FromContext(ctx).WithName("BlueprintSpecChangeUseCase.CheckForMultipleBlueprintResources") + + logger.V(2).Info("check for multiple blueprints") + err := useCase.repo.CheckSingleton(ctx) + if err != nil { + errMsg := "check for multiple blueprints not successful" + logger.Error(err, errMsg) + return fmt.Errorf("%s: %w", errMsg, err) + } + + return nil +} diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index e641eea3..81948522 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -891,3 +891,37 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { }) } } + +func TestBlueprintSpecChangeUseCase_CheckForMultipleBlueprintResources(t *testing.T) { + t.Run("should succeed without error", func(t *testing.T) { + // given + mockRepo := newMockBlueprintSpecRepository(t) + mockRepo.EXPECT().CheckSingleton(t.Context()).Return(nil) + useCase := &BlueprintSpecChangeUseCase{ + repo: mockRepo, + } + + //when + err := useCase.CheckForMultipleBlueprintResources(t.Context()) + + // then + require.NoError(t, err) + }) + + t.Run("should return error on check error", func(t *testing.T) { + // given + mockRepo := newMockBlueprintSpecRepository(t) + mockRepo.EXPECT().CheckSingleton(t.Context()).Return(assert.AnError) + useCase := &BlueprintSpecChangeUseCase{ + repo: mockRepo, + } + + //when + err := useCase.CheckForMultipleBlueprintResources(t.Context()) + + // then + require.Error(t, err) + assert.ErrorIs(t, err, assert.AnError) + assert.ErrorContains(t, err, "check for multiple blueprints not successful") + }) +} diff --git a/pkg/application/mock_blueprintSpecRepository_test.go b/pkg/application/mock_blueprintSpecRepository_test.go index b6788b02..a92af831 100644 --- a/pkg/application/mock_blueprintSpecRepository_test.go +++ b/pkg/application/mock_blueprintSpecRepository_test.go @@ -22,6 +22,52 @@ func (_m *mockBlueprintSpecRepository) EXPECT() *mockBlueprintSpecRepository_Exp return &mockBlueprintSpecRepository_Expecter{mock: &_m.Mock} } +// CheckSingleton provides a mock function with given fields: ctx +func (_m *mockBlueprintSpecRepository) CheckSingleton(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CheckSingleton") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockBlueprintSpecRepository_CheckSingleton_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckSingleton' +type mockBlueprintSpecRepository_CheckSingleton_Call struct { + *mock.Call +} + +// CheckSingleton is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockBlueprintSpecRepository_Expecter) CheckSingleton(ctx interface{}) *mockBlueprintSpecRepository_CheckSingleton_Call { + return &mockBlueprintSpecRepository_CheckSingleton_Call{Call: _e.mock.On("CheckSingleton", ctx)} +} + +func (_c *mockBlueprintSpecRepository_CheckSingleton_Call) Run(run func(ctx context.Context)) *mockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockBlueprintSpecRepository_CheckSingleton_Call) Return(_a0 error) *mockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockBlueprintSpecRepository_CheckSingleton_Call) RunAndReturn(run func(context.Context) error) *mockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Return(run) + return _c +} + // GetById provides a mock function with given fields: ctx, blueprintId func (_m *mockBlueprintSpecRepository) GetById(ctx context.Context, blueprintId string) (*domain.BlueprintSpec, error) { ret := _m.Called(ctx, blueprintId) diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index bafce784..05166bd5 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -54,6 +54,16 @@ func NewUnhealthyEcosystemError( return &UnhealthyEcosystemError{WrappedError: wrappedError, Message: message, healthResult: healthResult} } +// MultipleBlueprintsError indicates that there are multiple blueprint-resources in this namespace, which the controller cannot handle. +type MultipleBlueprintsError struct { + Message string +} + +// Error marks the struct as an error. +func (e *MultipleBlueprintsError) Error() string { + return e.Message +} + type AwaitSelfUpgradeError struct { Message string } diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index 81ff81c3..d45f1062 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -73,6 +73,12 @@ type BlueprintSpecRepository interface { // a domain.InvalidBlueprintError together with a BlueprintSpec without blueprint and mask if the BlueprintSpec could not be parsed or // an InternalError if there is any other error. GetById(ctx context.Context, blueprintId string) (*domain.BlueprintSpec, error) + + // CheckSingleton checks if there is indeed only a single Blueprint-resource in the namespace of the repository or + // a domain.MultipleBlueprintsError if there are at least two Blueprint-resources or + // an InternalError if there is any other error. + CheckSingleton(ctx context.Context) error + // Update updates a given BlueprintSpec. // returns a ConflictError if there were changes on the BlueprintSpec in the meantime or // returns an InternalError if there is any other error diff --git a/pkg/domainservice/mock_BlueprintSpecRepository_test.go b/pkg/domainservice/mock_BlueprintSpecRepository_test.go index 70d6e932..dc8384d6 100644 --- a/pkg/domainservice/mock_BlueprintSpecRepository_test.go +++ b/pkg/domainservice/mock_BlueprintSpecRepository_test.go @@ -22,6 +22,52 @@ func (_m *MockBlueprintSpecRepository) EXPECT() *MockBlueprintSpecRepository_Exp return &MockBlueprintSpecRepository_Expecter{mock: &_m.Mock} } +// CheckSingleton provides a mock function with given fields: ctx +func (_m *MockBlueprintSpecRepository) CheckSingleton(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CheckSingleton") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockBlueprintSpecRepository_CheckSingleton_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckSingleton' +type MockBlueprintSpecRepository_CheckSingleton_Call struct { + *mock.Call +} + +// CheckSingleton is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockBlueprintSpecRepository_Expecter) CheckSingleton(ctx interface{}) *MockBlueprintSpecRepository_CheckSingleton_Call { + return &MockBlueprintSpecRepository_CheckSingleton_Call{Call: _e.mock.On("CheckSingleton", ctx)} +} + +func (_c *MockBlueprintSpecRepository_CheckSingleton_Call) Run(run func(ctx context.Context)) *MockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockBlueprintSpecRepository_CheckSingleton_Call) Return(_a0 error) *MockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockBlueprintSpecRepository_CheckSingleton_Call) RunAndReturn(run func(context.Context) error) *MockBlueprintSpecRepository_CheckSingleton_Call { + _c.Call.Return(run) + return _c +} + // GetById provides a mock function with given fields: ctx, blueprintId func (_m *MockBlueprintSpecRepository) GetById(ctx context.Context, blueprintId string) (*domain.BlueprintSpec, error) { ret := _m.Called(ctx, blueprintId) From 08f542e50f75d026153f1c688a14a1e275fe9f87 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 23 Sep 2025 14:46:31 +0200 Subject: [PATCH 076/119] #121 fix logging for multiple blueprint errors --- .../kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go | 2 +- pkg/application/blueprintSpecChangeUseCase.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 87fc2e4f..6bb41412 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -94,7 +94,7 @@ func (repo *blueprintSpecRepo) CheckSingleton(ctx context.Context) error { if err != nil { return &domainservice.InternalError{ WrappedError: err, - Message: fmt.Sprintf("error while listing blueprint resources"), + Message: "error while listing blueprint resources", } } diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index a8dca7a2..c8777afb 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -203,9 +203,7 @@ func (useCase *BlueprintSpecChangeUseCase) CheckForMultipleBlueprintResources(ct logger.V(2).Info("check for multiple blueprints") err := useCase.repo.CheckSingleton(ctx) if err != nil { - errMsg := "check for multiple blueprints not successful" - logger.Error(err, errMsg) - return fmt.Errorf("%s: %w", errMsg, err) + return fmt.Errorf("%s: %w", "check for multiple blueprints not successful", err) } return nil From 858795309009f12321ee06f8052dd80c4e78915c Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 24 Sep 2025 13:13:29 +0200 Subject: [PATCH 077/119] #121 Upgrade to Golang v1.25.1 and Makefiles to v10.3.0 --- Dockerfile | 2 +- Jenkinsfile | 2 +- Makefile | 4 +- build/make/bats.mk | 10 +- build/make/bats/Dockerfile | 2 +- build/make/bats/customBatsEntrypoint.sh | 9 +- build/make/build.mk | 2 +- build/make/k8s.mk | 2 +- build/make/static-analysis.mk | 2 +- go.mod | 176 ++++---- go.sum | 512 +++++++++++------------- 11 files changed, 345 insertions(+), 378 deletions(-) diff --git a/Dockerfile b/Dockerfile index df4e6d43..912006b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.24.3 AS builder +FROM golang:1.25.1 AS builder WORKDIR /workspace diff --git a/Jenkinsfile b/Jenkinsfile index ba8bfb2b..289a80f1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,7 +12,7 @@ github = new GitHub(this, git) changelog = new Changelog(this) Docker docker = new Docker(this) gpg = new Gpg(this, docker) -goVersion = "1.24.3" +goVersion = "1.25.1" Makefile makefile = new Makefile(this) componentOperatorVersion="1.10.0" diff --git a/Makefile b/Makefile index 01e6e3b0..2d16030c 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ ARTIFACT_ID=k8s-blueprint-operator VERSION=2.8.0 IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} -GOTAG=1.24.3 -MAKEFILES_VERSION=10.2.0 +GOTAG=1.25.1 +MAKEFILES_VERSION=10.3.0 STAGE?=production diff --git a/build/make/bats.mk b/build/make/bats.mk index 7e73553d..23680444 100644 --- a/build/make/bats.mk +++ b/build/make/bats.mk @@ -9,7 +9,7 @@ BATS_SUPPORT=$(BATS_LIBRARY_DIR)/bats-support BATS_FILE=$(BATS_LIBRARY_DIR)/bats-file BATS_BASE_IMAGE?=bats/bats BATS_CUSTOM_IMAGE?=cloudogu/bats -BATS_TAG?=1.11.0 +BATS_TAG?=1.12.0 BATS_DIR=build/make/bats BATS_WORKDIR="${WORKDIR}"/"${BATS_DIR}" @@ -18,15 +18,19 @@ unit-test-shell: unit-test-shell-$(ENVIRONMENT) $(BATS_ASSERT): @git clone --depth 1 https://github.com/bats-core/bats-assert $@ + @rm -rf $@/.git $(BATS_MOCK): @git clone --depth 1 https://github.com/grayhemp/bats-mock $@ + @rm -rf $@/.git $(BATS_SUPPORT): @git clone --depth 1 https://github.com/bats-core/bats-support $@ + @rm -rf $@/.git $(BATS_FILE): @git clone --depth 1 https://github.com/bats-core/bats-file $@ + @rm -rf $@/.git $(BASH_SRC): BASH_SRC:=$(shell find "${WORKDIR}" -type f -name "*.sh") @@ -49,10 +53,10 @@ unit-test-shell-local: $(BASH_SRC) $(PASSWD) $(ETCGROUP) $(HOME_DIR) buildTestIm "${BATS_DIR}"/customBatsEntrypoint.sh make unit-test-shell-generic-no-junit unit-test-shell-generic: - @bats --formatter junit --output ${BASH_TEST_REPORT_DIR} ${TESTS_DIR} + @bats --report-formatter junit --formatter junit --output ${BASH_TEST_REPORT_DIR} ${TESTS_DIR} unit-test-shell-generic-no-junit: - @bats ${TESTS_DIR} + @bats --report-formatter junit --output ${BASH_TEST_REPORT_DIR} ${TESTS_DIR} .PHONY buildTestImage: buildTestImage: diff --git a/build/make/bats/Dockerfile b/build/make/bats/Dockerfile index 7167a941..95a27875 100644 --- a/build/make/bats/Dockerfile +++ b/build/make/bats/Dockerfile @@ -1,7 +1,7 @@ ARG BATS_BASE_IMAGE ARG BATS_TAG -FROM ${BATS_BASE_IMAGE:-bats/bats}:${BATS_TAG:-1.11.0} +FROM ${BATS_BASE_IMAGE:-bats/bats}:${BATS_TAG:-1.12.0} # Make bash more findable by scripts and tests RUN apk add make git bash diff --git a/build/make/bats/customBatsEntrypoint.sh b/build/make/bats/customBatsEntrypoint.sh index 58856fe3..bfc192ba 100755 --- a/build/make/bats/customBatsEntrypoint.sh +++ b/build/make/bats/customBatsEntrypoint.sh @@ -3,4 +3,11 @@ set -o errexit set -o nounset set -o pipefail -"$@" \ No newline at end of file +targetReportDir="${PWD}"/target/shell_test_reports +uidgid=1000:1000 +exitcode=0 +"$@" || exitcode=$? +echo "Resetting file ownership to ${uidgid} in ${targetReportDir}/" +chown -R ${uidgid} "${targetReportDir}"/* +echo "exiting with code ${exitcode}" +exit ${exitcode} \ No newline at end of file diff --git a/build/make/build.mk b/build/make/build.mk index a29f2c5b..c2f512ce 100644 --- a/build/make/build.mk +++ b/build/make/build.mk @@ -3,7 +3,7 @@ ADDITIONAL_LDFLAGS?=-extldflags -static LDFLAGS?=-ldflags "$(ADDITIONAL_LDFLAGS) -X main.Version=$(VERSION) -X main.CommitID=$(COMMIT_ID)" GOIMAGE?=golang -GOTAG?=1.24 +GOTAG?=1.25 GOOS?=linux GOARCH?=amd64 PRE_COMPILE?= diff --git a/build/make/k8s.mk b/build/make/k8s.mk index a46ff79c..162abb69 100644 --- a/build/make/k8s.mk +++ b/build/make/k8s.mk @@ -11,7 +11,7 @@ BINARY_YQ_4_VERSION?=v4.40.3 BINARY_HELM = $(UTILITY_BIN_PATH)/helm BINARY_HELM_VERSION?=v3.13.0 CONTROLLER_GEN = $(UTILITY_BIN_PATH)/controller-gen -CONTROLLER_GEN_VERSION?=v0.14.0 +CONTROLLER_GEN_VERSION?=v0.19.0 # Setting SHELL to bash allows bash commands to be executed by recipes. # Options are set to exit when a recipe line exits non-zero or a piped command fails. diff --git a/build/make/static-analysis.mk b/build/make/static-analysis.mk index 0989b74d..a01371af 100644 --- a/build/make/static-analysis.mk +++ b/build/make/static-analysis.mk @@ -2,7 +2,7 @@ STATIC_ANALYSIS_DIR=$(TARGET_DIR)/static-analysis GOIMAGE?=golang -GOTAG?=1.24 +GOTAG?=1.25 CUSTOM_GO_MOUNT?=-v /tmp:/tmp REVIEW_DOG=$(TMP_DIR)/bin/reviewdog diff --git a/go.mod b/go.mod index c3354b42..f744f070 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,34 @@ module github.com/cloudogu/k8s-blueprint-operator/v2 -go 1.24.3 +go 1.25.1 require ( - github.com/Masterminds/semver/v3 v3.3.1 + github.com/Masterminds/semver/v3 v3.4.0 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 - github.com/cloudogu/k8s-component-operator v1.9.0 - github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 - github.com/cloudogu/k8s-registry-lib v0.5.1 + github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c + github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 + github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 - github.com/go-logr/logr v1.4.2 - github.com/stretchr/testify v1.10.0 + github.com/go-logr/logr v1.4.3 + github.com/google/go-cmp v0.7.0 + github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 - golang.org/x/net v0.40.0 + golang.org/x/net v0.44.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.33.3 - k8s.io/apimachinery v0.33.3 - k8s.io/client-go v0.33.3 - sigs.k8s.io/controller-runtime v0.21.0 + k8s.io/api v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + sigs.k8s.io/controller-runtime v0.22.1 ) require ( - dario.cat/mergo v1.0.1 // indirect - github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.5.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect @@ -39,47 +39,45 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.3 // indirect github.com/cloudogu/retry-lib v0.1.0 // indirect - github.com/containerd/cgroups/v3 v3.0.5 // indirect - github.com/containerd/containerd v1.7.27 // indirect + github.com/containerd/containerd v1.7.28 // indirect github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect - github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/cli v27.5.0+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v27.4.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.2 // indirect - github.com/docker/go-connections v0.5.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect github.com/eapache/go-resiliency v1.7.0 // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect - github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gammazero/toposort v0.1.1 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.22.0 // indirect + github.com/go-openapi/jsonreference v0.21.1 // indirect + github.com/go-openapi/swag v0.24.1 // indirect + github.com/go-openapi/swag/cmdutils v0.24.0 // indirect + github.com/go-openapi/swag/conv v0.24.0 // indirect + github.com/go-openapi/swag/fileutils v0.24.0 // indirect + github.com/go-openapi/swag/jsonname v0.24.0 // indirect + github.com/go-openapi/swag/jsonutils v0.24.0 // indirect + github.com/go-openapi/swag/loading v0.24.0 // indirect + github.com/go-openapi/swag/mangling v0.24.0 // indirect + github.com/go-openapi/swag/netutils v0.24.0 // indirect + github.com/go-openapi/swag/stringutils v0.24.0 // indirect + github.com/go-openapi/swag/typeutils v0.24.0 // indirect + github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/gobuffalo/flect v1.0.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect @@ -95,78 +93,78 @@ require ( github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.5.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/onsi/gomega v1.38.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rubenv/sql-migrate v1.7.1 // indirect + github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.33.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect - go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.9.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect - google.golang.org/grpc v1.69.2 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.13.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gotest.tools/v3 v3.5.1 // indirect - helm.sh/helm/v3 v3.17.3 // indirect - k8s.io/apiextensions-apiserver v0.33.0 // indirect - k8s.io/apiserver v0.33.0 // indirect - k8s.io/cli-runtime v0.32.2 // indirect - k8s.io/component-base v0.33.0 // indirect + helm.sh/helm/v3 v3.19.0 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/cli-runtime v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/kubectl v0.32.2 // indirect - k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect - oras.land/oras-go v1.2.6 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/kustomize/api v0.18.0 // indirect - sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/kubectl v0.34.0 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + oras.land/oras-go/v2 v2.6.0 // indirect + sigs.k8s.io/cluster-api v1.11.1 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index ef225eac..1dc17173 100644 --- a/go.sum +++ b/go.sum @@ -1,37 +1,31 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= -github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -48,74 +42,54 @@ github.com/cloudogu/ces-commons-lib v0.2.0 h1:yOEZWFl4W9N3J/6fok4svE3UufK5GQQtyx github.com/cloudogu/ces-commons-lib v0.2.0/go.mod h1:4rvR2RTDDaz5a6OZ1fW27G0MOnl5I3ackeiHxt4gn3o= github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSAaYi7U= github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250905050828-1e362fb757b4 h1:0SBU7WSD/NWJAsVCV2+R0z/xXdM3E3tMjSVvgbY0Wsk= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250905050828-1e362fb757b4/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017 h1:TNqkUkQNCBhb7V1KkQvIFS05LLELkFPQBhsjyB1dBAs= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250907065250-a18c341f0017/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f h1:IUUD93cn1uF2T5LyE5+jLy2S7xvM98JCbr5ubpTE05g= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250911050358-091a67262e5f/go.mod h1:Qyi8M+HJMHJfhXN6Zotey/tXjFuJDM9RIXn+FjQaJAU= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323 h1:l16+dYDhYKFA7KoouycRrfgJqTfFCcqEqWeC/L2+sKs= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915050301-327dae037323/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87 h1:8H1XAdX77EP1Q55qS+qsuuEY+H0aIG8dNxKvuWbYozE= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915051531-65b0cacebe87/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1 h1:CcZow0wfH5Jm6pOEPrd+e3MYgYPbjzi79GRySWUhl4g= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250915075423-0ee5535d49a1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 h1:SjKSO5/5fNKQ6Z9v7t9laD3u62rl6Tr9o/0h8Ik6LrE= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= -github.com/cloudogu/k8s-component-operator v1.9.0 h1:b1/gMcAPQBP93EIVTE1ctmAKFdVOqnTST+x6LOqu08g= -github.com/cloudogu/k8s-component-operator v1.9.0/go.mod h1:BdWqwpZWHoSFOduQ3FzcVV4uGJNb+t0DUYlLBHQ9wwQ= -github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0 h1:ae+LtiY+J2/qIX1AUJLcVxx/FQvlstq9ViVMwlJD26Y= -github.com/cloudogu/k8s-dogu-lib/v2 v2.8.0/go.mod h1:XvnrQKqCh+ksGc+tFkv+EXfKeZC7P1jPInidSElh5CM= +github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c h1:zgDSZ1LqdqwRabR7KEaWZlwZkAFG/qsM0uIJTuzoodc= +github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c/go.mod h1:1NmERGUjXJdMHGMOb2CP6U9Y8NRt7I8O7juUNTIDxKM= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 h1:3BzzLzUliyWFwqgQn4Mp3/rW3cYeQqv0en6HFn6SVsk= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= +github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 h1:OYMO6DYX6rtzo2a88hXXTzwf8+aXt7uV+BTaEmvlls4= +github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6/go.mod h1:NytbB2XBjc+gr5gOJFoPjCzyxs/TuhWpKSnDJzCLQ14= github.com/cloudogu/k8s-registry-lib v0.5.1 h1:gbdrhETUm53GP65LoljrS1kekDDl/onBPfrOQTQpt1s= github.com/cloudogu/k8s-registry-lib v0.5.1/go.mod h1:mdMOgknEOrGQH1zc/3K859iPhwpqwtzigK9QrjM3Vk0= github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 h1:NWoBvBwiE8yO8HqSId0+nOTBMGSjzX0yM8m6fC7Ll6E= github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1/go.mod h1:AGZ3bihl/sUUJ6iEtcxtALIt/UlkfUdK7HXJ6DtBivE= github.com/cloudogu/retry-lib v0.1.0 h1:gaAmtyjUqgHbxfCWMeUn0qnGbDH4TtZVSQkbZ1Nq6eI= github.com/cloudogu/retry-lib v0.1.0/go.mod h1:iG9y6zx8oJZT5ULtl9koZkYJLRsqam/2mTU+rgjxQ0g= -github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= -github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= -github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII= -github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0= -github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= -github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= +github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= -github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso= -github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= -github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0-beta.1 h1:X+ELTxPuZ1Xe5MsD3kp2wfGUhc8I+MPfRis8dZ818Ic= -github.com/distribution/distribution/v3 v3.0.0-beta.1/go.mod h1:O9O8uamhHzWWQVTjuQpyYUVm/ShPHPUDgvQMpHGVBDs= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM= -github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= @@ -124,14 +98,12 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= -github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= @@ -144,66 +116,75 @@ github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7Dlme github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg= github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= +github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= +github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= +github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= +github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= +github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= +github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= +github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= +github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= +github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= +github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= +github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= +github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= +github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= +github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= +github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= +github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= +github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= +github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= +github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= +github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= +github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= +github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= +github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= +github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= +github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= +github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= +github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -216,15 +197,13 @@ github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= @@ -237,17 +216,12 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -266,18 +240,16 @@ github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -288,52 +260,45 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= -github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= +github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -343,39 +308,31 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= -github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= +github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= +github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -384,25 +341,29 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw= github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= github.com/testcontainers/testcontainers-go/modules/k3s v0.33.0 h1:vKz46Z+vClMc6MNnwpcw8tNjOOprJxWzsn4J+1noTM4= @@ -413,185 +374,182 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= -github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= -go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0/go.mod h1:qcTO4xHAxZLaLxPd60TdE88rxtItPHgHWqOhOGRr0as= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0 h1:08qeJgaPC0YEBu2PQMbqU3rogTlyzpjhCI2b58Yn00w= -go.opentelemetry.io/otel/exporters/prometheus v0.44.0/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgYCza3PXRUGEyCB++y1sAqm6guWFesk= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= -go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= -go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= -helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= -k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= -k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= -k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= -k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= -k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= -k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= -k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= -k8s.io/cli-runtime v0.32.2 h1:aKQR4foh9qeyckKRkNXUccP9moxzffyndZAvr+IXMks= -k8s.io/cli-runtime v0.32.2/go.mod h1:a/JpeMztz3xDa7GCyyShcwe55p8pbcCVQxvqZnIwXN8= -k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= -k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= -k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= -k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= +helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= +helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us= -k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.6 h1:z8cmxQXBU8yZ4mkytWqXfo6tZcamPwjsuxYU81xJ8Lk= -oras.land/oras-go v1.2.6/go.mod h1:OVPc1PegSEe/K8YiLfosrlqlqTN9PUyFvOw5Y9gwrT8= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= -sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= -sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= -sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= +k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= +sigs.k8s.io/cluster-api v1.11.1 h1:7CyGCTxv1p3Y2kRe1ljTj/w4TcdIdWNj0CTBc4i1aBo= +sigs.k8s.io/cluster-api v1.11.1/go.mod h1:zyrjgJ5RbXhwKcAdUlGPNK5YOHpcmxXvur+5I8lkMUQ= +sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= +sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From d7976211f128f562c2c0ec7a63dfd9de35525a48 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 24 Sep 2025 13:15:57 +0200 Subject: [PATCH 078/119] #121 add DogusUpToDate Use Case --- CHANGELOG.md | 5 + .../config/kubernetes/doguConfigRepository.go | 1 + .../kubernetes/dogucr/doguSerializer.go | 9 +- .../kubernetes/dogucr/doguSerializer_test.go | 79 +++- .../reconciler/blueprint_controller.go | 6 + pkg/application/applyComponentsUseCase.go | 2 +- pkg/application/applyDogusUseCase.go | 2 +- pkg/application/blueprintSpecChangeUseCase.go | 10 +- .../blueprintSpecChangeUseCase_test.go | 86 +++++ pkg/application/doguInstallationUseCase.go | 44 +++ .../doguInstallationUseCase_test.go | 362 ++++++++++++++++-- pkg/application/dogusUpToDateUseCase.go | 50 +++ pkg/application/dogusUpToDateUseCase_test.go | 109 ++++++ pkg/application/ecosystemConfigUseCase.go | 2 +- pkg/application/interfaces.go | 6 + .../mock_doguInstallationUseCase_test.go | 60 +++ .../mock_dogusUpToDateUseCase_test.go | 84 ++++ pkg/bootstrap.go | 4 +- pkg/domain/blueprintSpec.go | 2 +- pkg/domain/ecosystem/doguInstallation.go | 15 +- pkg/domain/ecosystem/doguInstallation_test.go | 163 ++++++++ pkg/domain/errors.go | 9 + pkg/domain/events.go | 12 + 23 files changed, 1088 insertions(+), 34 deletions(-) create mode 100644 pkg/application/dogusUpToDateUseCase.go create mode 100644 pkg/application/dogusUpToDateUseCase_test.go create mode 100644 pkg/application/mock_dogusUpToDateUseCase_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c391a9b6..4a3ed2e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 *Breaking Change ahead!* +### Added +- [#121] Added use case to check if dogus actually use the desired version and config before completing the blueprint + ### Changed - [#119] *breaking* sensitive dogu config can now only be referenced with secrets - it was not safe to have these values in clear text in the blueprint @@ -19,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#121] *breaking* the current state will now be reflected via conditions instead of the `statusPhase` field - [#121] *breaking* events were reworked, some events are now more general, some events got removed completely - Note, that events are for humans. You should not compute them for automation as they have no consistency guarantees. +- [#121] Upgrade to Golang v1.25.1 +- [#121] Upgrade Makefiles to v10.3.0 ### Removed - [#119] *breaking* no support for v1 blueprint CRs anymore diff --git a/pkg/adapter/config/kubernetes/doguConfigRepository.go b/pkg/adapter/config/kubernetes/doguConfigRepository.go index dbdad768..2bbeb6f9 100644 --- a/pkg/adapter/config/kubernetes/doguConfigRepository.go +++ b/pkg/adapter/config/kubernetes/doguConfigRepository.go @@ -3,6 +3,7 @@ package kubernetes import ( "context" "fmt" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" liberrors "github.com/cloudogu/ces-commons-lib/errors" "github.com/cloudogu/k8s-registry-lib/config" diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index 114a157b..b618d9e1 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -23,6 +23,11 @@ func parseDoguCR(cr *v2.Dogu) (*ecosystem.DoguInstallation, error) { } // parse dogu fields version, versionErr := core.ParseVersion(cr.Spec.Version) + var installedVersion core.Version + var installedVersionErr error + if cr.Status.InstalledVersion != "" { + installedVersion, installedVersionErr = core.ParseVersion(cr.Status.InstalledVersion) + } doguName, nameErr := cescommons.QualifiedNameFromString(cr.Spec.Name) // the dogu-operator has a default of 2Gi if this field is 0 or not set @@ -33,7 +38,7 @@ func parseDoguCR(cr *v2.Dogu) (*ecosystem.DoguInstallation, error) { reverseProxyConfigEntries, proxyErr := parseDoguAdditionalIngressAnnotationsCR(cr.Spec.AdditionalIngressAnnotations) - err := errors.Join(versionErr, nameErr, volumeSizeErr, proxyErr) + err := errors.Join(versionErr, nameErr, volumeSizeErr, proxyErr, installedVersionErr) if err != nil { return nil, &domainservice.InternalError{ WrappedError: err, @@ -52,6 +57,8 @@ func parseDoguCR(cr *v2.Dogu) (*ecosystem.DoguInstallation, error) { Version: version, Status: cr.Status.Status, Health: ecosystem.HealthStatus(cr.Status.Health), + InstalledVersion: installedVersion, + StartedAt: cr.Status.StartedAt, UpgradeConfig: ecosystem.UpgradeConfig{AllowNamespaceSwitch: cr.Spec.UpgradeConfig.AllowNamespaceSwitch}, MinVolumeSize: &minVolumeSize, ReverseProxyConfig: reverseProxyConfigEntries, diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 29a96da4..918fbbcf 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "testing" + "time" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -28,12 +29,14 @@ var ( subfolder2 = "secsubfolder" rewriteTarget = "/" additionalConfig = "additional" + proxyBodySize = resource.MustParse("1G") ) func Test_parseDoguCR(t *testing.T) { type args struct { cr *v2.Dogu } + pointInTime := metav1.NewTime(time.Date(2024, 9, 23, 10, 0, 0, 0, time.UTC)) tests := []struct { name string args args @@ -63,8 +66,10 @@ func Test_parseDoguCR(t *testing.T) { }, }, Status: v2.DoguStatus{ - Status: v2.DoguStatusInstalled, - Health: v2.AvailableHealthStatus, + Status: v2.DoguStatusInstalled, + Health: v2.AvailableHealthStatus, + InstalledVersion: version3213.Raw, + StartedAt: pointInTime, }, }}, want: &ecosystem.DoguInstallation{ @@ -77,6 +82,8 @@ func Test_parseDoguCR(t *testing.T) { }, MinVolumeSize: &defaultVolSize, PersistenceContext: persistenceContext, + InstalledVersion: version3213, + StartedAt: pointInTime, }, wantErr: false, }, @@ -104,6 +111,66 @@ func Test_parseDoguCR(t *testing.T) { want: nil, wantErr: true, }, + { + name: "cannot parse installed version", + args: args{cr: &v2.Dogu{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "postgresql", + ResourceVersion: "abc", + }, + Spec: v2.DoguSpec{ + Name: "official/postgresql", + Version: version3214.Raw, + Resources: v2.DoguResources{}, + UpgradeConfig: v2.UpgradeConfig{ + AllowNamespaceSwitch: false, + }, + }, + Status: v2.DoguStatus{ + Status: v2.DoguStatusInstalled, + Health: v2.AvailableHealthStatus, + InstalledVersion: "xyvz", + }, + }}, + want: nil, + wantErr: true, + }, + { + name: "accepts empty installed version", + args: args{cr: &v2.Dogu{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "postgresql", + ResourceVersion: "abc", + }, + Spec: v2.DoguSpec{ + Name: "official/postgresql", + Version: version3214.Raw, + Resources: v2.DoguResources{}, + UpgradeConfig: v2.UpgradeConfig{ + AllowNamespaceSwitch: false, + }, + }, + Status: v2.DoguStatus{ + Status: v2.DoguStatusInstalled, + Health: v2.AvailableHealthStatus, + InstalledVersion: "", + }, + }}, + want: &ecosystem.DoguInstallation{ + Name: postgresDoguName, + Version: version3214, + Status: ecosystem.DoguStatusInstalled, + Health: ecosystem.AvailableHealthStatus, + UpgradeConfig: ecosystem.UpgradeConfig{ + AllowNamespaceSwitch: false, + }, + MinVolumeSize: &defaultVolSize, + PersistenceContext: persistenceContext, + }, + wantErr: false, + }, { name: "parse additional mounts", args: args{cr: &v2.Dogu{ @@ -443,7 +510,6 @@ func Test_toDoguCRPatchBytes(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ { - // TODO check ReverseProxy name: "ok", dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, @@ -454,11 +520,16 @@ func Test_toDoguCRPatchBytes(t *testing.T) { AllowNamespaceSwitch: true, }, MinVolumeSize: &quantity2, + ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + MaxBodySize: &proxyBodySize, + RewriteTarget: &rewriteTarget, + AdditionalConfig: &additionalConfig, + }, AdditionalMounts: []ecosystem.AdditionalMount{ {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, }, - want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", + want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":{\"nginx.ingress.kubernetes.io/configuration-snippet\":\"additional\",\"nginx.ingress.kubernetes.io/proxy-body-size\":\"1G\",\"nginx.ingress.kubernetes.io/rewrite-target\":\"/\"},\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", wantErr: assert.NoError, }, { diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 8652e80c..ac0ecf54 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -3,6 +3,7 @@ package reconciler import ( "context" "errors" + "fmt" "strings" "time" @@ -71,6 +72,7 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { var awaitSelfUpgradeError *domain.AwaitSelfUpgradeError var stateDiffNotEmptyError *domain.StateDiffNotEmptyError var multipleBlueprintsError *domain.MultipleBlueprintsError + var dogusNotUpToDateError *domain.DogusNotUpToDateError switch { case errors.As(err, &internalError): errLogger.Error(err, "An internal error occurred and can maybe be fixed by retrying it later") @@ -105,6 +107,10 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { errLogger.Error(err, "Ecosystem contains multiple blueprints - delete all but one. Retry later") // fast requeue here since state diff has to be determined again return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + case errors.As(err, &dogusNotUpToDateError): + // really normal case + errLogger.Info(fmt.Sprintf("Dogus are not up to date yet. Retry later: %s", err.Error())) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil default: errLogger.Error(err, "An unknown error type occurred. Retry with default backoff") return ctrl.Result{}, err // automatic requeue because of non-nil err diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go index cdd1f346..e9099a24 100644 --- a/pkg/application/applyComponentsUseCase.go +++ b/pkg/application/applyComponentsUseCase.go @@ -34,7 +34,7 @@ func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blue if isComponentsApplied { blueprint.Events = append(blueprint.Events, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}) } - conditionChanged := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtComponents, err) + conditionChanged := blueprint.SetLastApplySucceededConditionOnError(domain.ReasonLastApplyErrorAtComponents, err) if isComponentsApplied || conditionChanged { updateErr := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/applyDogusUseCase.go b/pkg/application/applyDogusUseCase.go index d6dc10d9..c9004b62 100644 --- a/pkg/application/applyDogusUseCase.go +++ b/pkg/application/applyDogusUseCase.go @@ -34,7 +34,7 @@ func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *dom if isDogusApplied { blueprint.Events = append(blueprint.Events, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}) } - conditionChanged := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtDogus, err) + conditionChanged := blueprint.SetLastApplySucceededConditionOnError(domain.ReasonLastApplyErrorAtDogus, err) if isDogusApplied || conditionChanged { updateErr := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index c8777afb..3a6ca0b7 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -39,6 +39,7 @@ type BlueprintApplyUseCases struct { applyComponentUseCase applyComponentsUseCase applyDogusUseCase applyDogusUseCase healthUseCase ecosystemHealthUseCase + dogusUpToDateUseCase dogusUpToDateUseCase } func NewBlueprintApplyUseCases( @@ -48,6 +49,7 @@ func NewBlueprintApplyUseCases( applyComponentUseCase applyComponentsUseCase, applyDogusUseCase applyDogusUseCase, healthUseCase ecosystemHealthUseCase, + dogusUpToDateUseCase dogusUpToDateUseCase, ) BlueprintApplyUseCases { return BlueprintApplyUseCases{ completeUseCase: completeUseCase, @@ -56,6 +58,7 @@ func NewBlueprintApplyUseCases( applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDogusUseCase, healthUseCase: healthUseCase, + dogusUpToDateUseCase: dogusUpToDateUseCase, } } @@ -188,7 +191,12 @@ func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, bluep return err } } - // TODO: check if config in dogus is already up to date and if installed Version is up to date + + err = useCase.dogusUpToDateUseCase.CheckDogus(ctx, blueprint) + if err != nil { + // could be a domain.AwaitSelfUpgradeError to trigger another reconcile + return err + } err = useCase.completeUseCase.CompleteBlueprint(ctx, blueprint) if err != nil { diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 81948522..70caa11b 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -29,6 +29,7 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { applyComponentUseCaseMock := newMockApplyComponentsUseCase(t) applyDoguUseCaseMock := newMockApplyDogusUseCase(t) ecosystemHealthUseCaseMock := newMockEcosystemHealthUseCase(t) + dogusUpToDateUseCaseMock := newMockDogusUpToDateUseCase(t) preparationUseCases := NewBlueprintPreparationUseCases( initialStatusUseCaseMock, @@ -45,6 +46,7 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { applyComponentUseCaseMock, applyDoguUseCaseMock, ecosystemHealthUseCaseMock, + dogusUpToDateUseCaseMock, ) // when @@ -65,6 +67,7 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { assert.Equal(t, applyDoguUseCaseMock, result.applyUseCases.applyDogusUseCase) assert.Equal(t, completeUseCaseMock, result.applyUseCases.completeUseCase) assert.Equal(t, ecosystemHealthUseCaseMock, result.applyUseCases.healthUseCase) + assert.Equal(t, dogusUpToDateUseCaseMock, result.applyUseCases.dogusUpToDateUseCase) } func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { @@ -89,6 +92,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { applyComponentUseCase func(t *testing.T) applyComponentsUseCase applyDogusUseCase func(t *testing.T) applyDogusUseCase healthUseCase func(t *testing.T) ecosystemHealthUseCase + upToDateUseCase func(t *testing.T) dogusUpToDateUseCase } type args struct { givenCtx context.Context @@ -681,6 +685,72 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { return assert.Error(t, err) }, }, + { + name: "should return error on error checking if dogus are up to date", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + return m + }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { + m := newMockSelfUpgradeUseCase(t) + m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { + m := newMockEcosystemConfigUseCase(t) + m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, + applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { + m := newMockApplyComponentsUseCase(t) + m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + applyDogusUseCase: func(t *testing.T) applyDogusUseCase { + m := newMockApplyDogusUseCase(t) + m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) + return m + }, + upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { + m := newMockDogusUpToDateUseCase(t) + m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(assert.AnError) + return m + }, + }, + args: testArgs, + wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { + return assert.Error(t, err) + }, + }, { name: "should return error on error complete blueprint", fields: fields{ @@ -736,6 +806,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) return m }, + upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { + m := newMockDogusUpToDateUseCase(t) + m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, applyUseCase: func(t *testing.T) completeBlueprintUseCase { m := newMockCompleteBlueprintUseCase(t) m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) @@ -802,6 +877,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) return m }, + upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { + m := newMockDogusUpToDateUseCase(t) + m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, applyUseCase: func(t *testing.T) completeBlueprintUseCase { m := newMockCompleteBlueprintUseCase(t) m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(nil) @@ -869,6 +949,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { ecoHealthUseCase = tt.fields.healthUseCase(t) } + var upToDateUseCase dogusUpToDateUseCase + if tt.fields.upToDateUseCase != nil { + upToDateUseCase = tt.fields.upToDateUseCase(t) + } + useCase := &BlueprintSpecChangeUseCase{ repo: repo, preparationUseCases: BlueprintPreparationUseCases{ @@ -885,6 +970,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDoguUseCase, healthUseCase: ecoHealthUseCase, + dogusUpToDateUseCase: upToDateUseCase, }, } tt.wantErr(t, useCase.HandleUntilApplied(tt.args.givenCtx, tt.args.blueprintId), fmt.Sprintf("HandleUntilApplied(%v, %v)", tt.args.givenCtx, tt.args.blueprintId)) diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index 7c70bb94..1b5e70e4 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -16,17 +16,23 @@ type DoguInstallationUseCase struct { blueprintSpecRepo blueprintSpecRepository doguRepo doguInstallationRepository waitConfigProvider healthWaitConfigProvider + doguConfigRepo doguConfigRepository + globalConfigRepo globalConfigRepository } func NewDoguInstallationUseCase( blueprintSpecRepo domainservice.BlueprintSpecRepository, doguRepo domainservice.DoguInstallationRepository, waitConfigProvider domainservice.HealthWaitConfigProvider, + doguConfigRepo doguConfigRepository, + globalConfigRepo globalConfigRepository, ) *DoguInstallationUseCase { return &DoguInstallationUseCase{ blueprintSpecRepo: blueprintSpecRepo, doguRepo: doguRepo, waitConfigProvider: waitConfigProvider, + doguConfigRepo: doguConfigRepo, + globalConfigRepo: globalConfigRepo, } } @@ -41,6 +47,44 @@ func (useCase *DoguInstallationUseCase) CheckDoguHealth(ctx context.Context) (ec return ecosystem.CalculateDoguHealthResult(maps.Values(installedDogus)), nil } +func (useCase *DoguInstallationUseCase) CheckDogusUpToDate(ctx context.Context) ([]cescommons.SimpleName, error) { + logger := log.FromContext(ctx).WithName("DoguInstallationUseCase.CheckDoguHealth") + logger.V(2).Info("check if dogus are up to date...") + installedDogus, err := useCase.doguRepo.GetAll(ctx) + if err != nil { + return nil, err + } + + globalConfig, err := useCase.globalConfigRepo.Get(ctx) + if err != nil { + return nil, err + } + globalConfigUpdateTime := globalConfig.LastUpdated + + var dogusNotUpToDate []cescommons.SimpleName + + for doguName, dogu := range installedDogus { + versionUpToDate := dogu.IsVersionUpToDate() + if !versionUpToDate { + dogusNotUpToDate = append(dogusNotUpToDate, doguName) + continue + } + + doguConfig, err := useCase.doguConfigRepo.Get(ctx, doguName) + if err != nil { + return nil, err + } + doguConfigUpdateTime := doguConfig.LastUpdated + configUpToDate := dogu.IsConfigUpToDate(globalConfigUpdateTime, doguConfigUpdateTime) + if !configUpToDate { + dogusNotUpToDate = append(dogusNotUpToDate, doguName) + continue + } + } + + return dogusNotUpToDate, nil +} + // ApplyDoguStates applies the expected dogu state from the Blueprint to the ecosystem. // Fail-fast here, so that the possible damage is as small as possible. func (useCase *DoguInstallationUseCase) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 6deb20d9..71e14ff6 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -3,15 +3,18 @@ package application import ( "fmt" "testing" + "time" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" + "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const blueprintId = "blueprint1" @@ -23,6 +26,14 @@ var postgresqlQualifiedName = cescommons.QualifiedName{ Namespace: "official", SimpleName: "postgresql", } +var ldapQualifiedName = cescommons.QualifiedName{ + Namespace: "official", + SimpleName: "ldap", +} +var casQualifiedName = cescommons.QualifiedName{ + Namespace: "official", + SimpleName: "cas", +} var ( rewriteTarget = "/" @@ -34,7 +45,7 @@ var ( func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action none", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) // when err := sut.applyDoguState(testCtx, domain.DoguDiff{ @@ -62,7 +73,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action install", func(t *testing.T) { volumeSize := resource.MustParse("2Gi") bodySize := resource.MustParse("2G") - config := ecosystem.ReverseProxyConfig{ + proxyConfig := ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -79,10 +90,10 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT(). Create(testCtx, - ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, &config, additionalMounts)). + ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, &proxyConfig, additionalMounts)). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -99,7 +110,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &config, + ReverseProxyConfig: &proxyConfig, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -125,7 +136,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Delete(testCtx, cescommons.SimpleName("postgresql")). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -148,7 +159,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action uninstall throws NotFoundError when dogu not found", func(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -177,7 +188,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Update(testCtx, dogu). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -205,7 +216,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: version3212, } - sut := NewDoguInstallationUseCase(nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -242,7 +253,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -282,7 +293,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -323,7 +334,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -364,7 +375,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -447,7 +458,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState(testCtx, diff, dogu, domain.BlueprintConfiguration{}) @@ -488,7 +499,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -526,7 +537,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: version3212, } - sut := NewDoguInstallationUseCase(nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -556,7 +567,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, dogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.applyDoguState( @@ -581,7 +592,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("unknown action", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -603,7 +614,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("should no fail with no actions", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -630,7 +641,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.ApplyDoguStates(testCtx, &domain.BlueprintSpec{}) @@ -658,7 +669,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil, nil, nil) // when err := sut.ApplyDoguStates(testCtx, blueprint) @@ -690,7 +701,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { }, }, nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) // when err := sut.ApplyDoguStates(testCtx, blueprint) @@ -700,3 +711,310 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { require.ErrorContains(t, err, "an error occurred while applying dogu state to the ecosystem") }) } + +func TestDoguInstallationUseCase_CheckDogusUpToDate(t *testing.T) { + timeMay := v1.NewTime(time.Date(2024, time.May, 23, 10, 0, 0, 0, time.UTC)) + timeJune := v1.NewTime(time.Date(2024, time.June, 23, 10, 0, 0, 0, time.UTC)) + timeJuly := v1.NewTime(time.Date(2024, time.July, 23, 10, 0, 0, 0, time.UTC)) + t.Run("is up to date", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJuly, + }, + "ldap": { + Name: ldapQualifiedName, + Version: version3212, + InstalledVersion: version3212, + StartedAt: timeJune, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConf := config.GlobalConfig{ + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConf, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + postgresDoguConf := config.DoguConfig{ + DoguName: postgresqlQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeJune, + }, + } + ldapDoguConf := config.DoguConfig{ + DoguName: ldapQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + doguConfigRepoMock.EXPECT().Get(testCtx, postgresqlQualifiedName.SimpleName).Return(postgresDoguConf, nil) + doguConfigRepoMock.EXPECT().Get(testCtx, ldapQualifiedName.SimpleName).Return(ldapDoguConf, nil) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.NoError(t, err) + require.Empty(t, dogusNotUpToDate) + }) + t.Run("version is not up to date", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3212, + StartedAt: timeJuly, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConf := config.GlobalConfig{ + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConf, nil) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: newMockDoguConfigRepository(t), + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.NoError(t, err) + assert.Len(t, dogusNotUpToDate, 1) + assert.Equal(t, dogusNotUpToDate[0], postgresqlQualifiedName.SimpleName) + }) + t.Run("global config is not up to date", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJune, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConf := config.GlobalConfig{ + Config: config.Config{ + LastUpdated: &timeJuly, + }, + } + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConf, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConf := config.DoguConfig{ + DoguName: postgresqlQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + doguConfigRepoMock.EXPECT().Get(testCtx, postgresqlQualifiedName.SimpleName).Return(doguConf, nil) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.NoError(t, err) + assert.Len(t, dogusNotUpToDate, 1) + assert.Equal(t, dogusNotUpToDate[0], postgresqlQualifiedName.SimpleName) + }) + t.Run("dogu config is not up to date", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJune, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConf := config.GlobalConfig{ + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConf, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConf := config.DoguConfig{ + DoguName: postgresqlQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeJuly, + }, + } + doguConfigRepoMock.EXPECT().Get(testCtx, postgresqlQualifiedName.SimpleName).Return(doguConf, nil) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.NoError(t, err) + assert.Len(t, dogusNotUpToDate, 1) + assert.Equal(t, dogusNotUpToDate[0], postgresqlQualifiedName.SimpleName) + }) + t.Run("multiple dogus are not up to date", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { // version is not up to date + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3212, + StartedAt: timeJuly, + }, + "ldap": { // dogu config is not up to date + Name: ldapQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJune, + }, + "cas": { // global config is not up to date + Name: casQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeMay, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConf := config.GlobalConfig{ + Config: config.Config{ + LastUpdated: &timeJune, + }, + } + globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConf, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + ldapDoguConf := config.DoguConfig{ + DoguName: postgresqlQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeJuly, + }, + } + casDoguConf := config.DoguConfig{ + DoguName: postgresqlQualifiedName.SimpleName, + Config: config.Config{ + LastUpdated: &timeMay, + }, + } + doguConfigRepoMock.EXPECT().Get(testCtx, ldapQualifiedName.SimpleName).Return(ldapDoguConf, nil) + doguConfigRepoMock.EXPECT().Get(testCtx, casQualifiedName.SimpleName).Return(casDoguConf, nil) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.NoError(t, err) + assert.Len(t, dogusNotUpToDate, 3) + assert.Contains(t, dogusNotUpToDate, postgresqlQualifiedName.SimpleName) + assert.Contains(t, dogusNotUpToDate, ldapQualifiedName.SimpleName) + assert.Contains(t, dogusNotUpToDate, casQualifiedName.SimpleName) + }) + + t.Run("error on dogu GetAll error", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + doguConfigRepoMock := newMockDoguConfigRepository(t) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.Error(t, err) + require.Nil(t, dogusNotUpToDate) + }) + t.Run("error on global config Get error", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConfigRepoMock.EXPECT().Get(testCtx).Return(config.GlobalConfig{}, assert.AnError) + doguConfigRepoMock := newMockDoguConfigRepository(t) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.Error(t, err) + require.Nil(t, dogusNotUpToDate) + }) + t.Run("error on dogu config Get error", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJuly, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConfigRepoMock.EXPECT().Get(testCtx).Return(config.GlobalConfig{}, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().Get(testCtx, postgresqlQualifiedName.SimpleName).Return(config.DoguConfig{}, assert.AnError) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.Error(t, err) + require.Nil(t, dogusNotUpToDate) + }) +} diff --git a/pkg/application/dogusUpToDateUseCase.go b/pkg/application/dogusUpToDateUseCase.go new file mode 100644 index 00000000..0132b8e1 --- /dev/null +++ b/pkg/application/dogusUpToDateUseCase.go @@ -0,0 +1,50 @@ +package application + +import ( + "context" + "errors" + "fmt" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// DogusUpToDateUseCase checks if all dogus are up to date, meaning they are on the desired version and configuration. +type DogusUpToDateUseCase struct { + repo blueprintSpecRepository + doguInstallUseCase doguInstallationUseCase +} + +func NewDogusUpToDateUseCase( + repo blueprintSpecRepository, + doguInstallUseCase doguInstallationUseCase, +) *DogusUpToDateUseCase { + return &DogusUpToDateUseCase{ + repo: repo, + doguInstallUseCase: doguInstallUseCase, + } +} + +// CheckDogus checks that all dogs are up to date. +// returns domainservice.ConflictError if there was a concurrent update to the blueprint or +// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. +func (useCase *DogusUpToDateUseCase) CheckDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { + logger := log.FromContext(ctx).WithName("DogusUpToDateUseCase.CheckDogus") + + dogusNotUpToDate, err := useCase.doguInstallUseCase.CheckDogusUpToDate(ctx) + if err != nil { + return err + } + if len(dogusNotUpToDate) > 0 { + // event and error + blueprint.Events = append(blueprint.Events, domain.DogusNotUpToDateEvent{DogusNotUpToDate: dogusNotUpToDate}) + updateErr := useCase.repo.Update(ctx, blueprint) + if updateErr != nil { + return fmt.Errorf("cannot update status while checking dogus: %w", errors.Join(updateErr, err)) + } + return &domain.DogusNotUpToDateError{Message: fmt.Sprintf("following dogus are not up to date yet: %v", dogusNotUpToDate)} + } + + logger.V(2).Info("all dogus are up to date") + return nil +} diff --git a/pkg/application/dogusUpToDateUseCase_test.go b/pkg/application/dogusUpToDateUseCase_test.go new file mode 100644 index 00000000..8769772d --- /dev/null +++ b/pkg/application/dogusUpToDateUseCase_test.go @@ -0,0 +1,109 @@ +package application + +import ( + "testing" + + cescommons "github.com/cloudogu/ces-commons-lib/dogu" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDogusUpToDateUseCase_CheckDogus(t *testing.T) { + t.Run("all dogus up to date", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + dogusNotUpToDate := []cescommons.SimpleName{} + doguInstallUseCaseMock.EXPECT().CheckDogusUpToDate(testCtx).Return(dogusNotUpToDate, nil) + useCase := NewDogusUpToDateUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.CheckDogus(testCtx, blueprint) + + require.NoError(t, err) + assert.Empty(t, blueprint.Events) + }) + t.Run("multiple dogus not up to date", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + dogusNotUpToDate := []cescommons.SimpleName{ldap, postfix} + doguInstallUseCaseMock.EXPECT().CheckDogusUpToDate(testCtx).Return(dogusNotUpToDate, nil) + useCase := NewDogusUpToDateUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.CheckDogus(testCtx, blueprint) + + require.Error(t, err) + var expectedErrorType *domain.DogusNotUpToDateError + assert.ErrorAs(t, err, &expectedErrorType) + assert.ErrorContains(t, err, "following dogus are not up to date yet:") + assert.ErrorContains(t, err, ldap.String()) + assert.ErrorContains(t, err, postfix.String()) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.DogusNotUpToDateEvent{DogusNotUpToDate: dogusNotUpToDate}, blueprint.Events[0]) + require.Empty(t, blueprint.Conditions) + }) + + t.Run("no update without not up to date dogus", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().CheckDogusUpToDate(testCtx).Return([]cescommons.SimpleName{}, nil) + useCase := NewDogusUpToDateUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.CheckDogus(testCtx, blueprint) + require.NoError(t, err) + require.Empty(t, blueprint.Events) + require.Empty(t, blueprint.Conditions) + }) + + t.Run("fail to check dogus", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + } + + repoMock := newMockBlueprintSpecRepository(t) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + doguInstallUseCaseMock.EXPECT().CheckDogusUpToDate(testCtx).Return(nil, assert.AnError) + useCase := NewDogusUpToDateUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.CheckDogus(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + require.Equal(t, 0, len(blueprint.Events)) + }) + + t.Run("fail to update blueprint", func(t *testing.T) { + blueprint := &domain.BlueprintSpec{ + Conditions: []domain.Condition{}, + StateDiff: domain.StateDiff{ + DoguDiffs: domain.DoguDiffs{ + {NeededActions: []domain.Action{domain.ActionInstall}}, + }, + }, + } + + repoMock := newMockBlueprintSpecRepository(t) + repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) + doguInstallUseCaseMock := newMockDoguInstallationUseCase(t) + dogusNotUpToDate := []cescommons.SimpleName{"ldap"} + doguInstallUseCaseMock.EXPECT().CheckDogusUpToDate(testCtx).Return(dogusNotUpToDate, nil) + useCase := NewDogusUpToDateUseCase(repoMock, doguInstallUseCaseMock) + + err := useCase.CheckDogus(testCtx, blueprint) + + require.ErrorIs(t, err, assert.AnError) + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, domain.DogusNotUpToDateEvent{DogusNotUpToDate: dogusNotUpToDate}, blueprint.Events[0]) + }) +} diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 2a0bf17a..42738f3d 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -150,7 +150,7 @@ func (useCase *EcosystemConfigUseCase) handleFailedApplyEcosystemConfig(ctx cont WithValues("blueprintId", blueprint.Id) // sets condition - changed := blueprint.SetLastApplySucceededCondition(domain.ReasonLastApplyErrorAtConfig, err) + changed := blueprint.SetLastApplySucceededConditionOnError(domain.ReasonLastApplyErrorAtConfig, err) if changed { repoErr := useCase.blueprintRepository.Update(ctx, blueprint) diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index 3354a262..d4cec8da 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -3,6 +3,7 @@ package application import ( "context" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" @@ -27,6 +28,7 @@ type stateDiffUseCase interface { type doguInstallationUseCase interface { CheckDoguHealth(ctx context.Context) (ecosystem.DoguHealthResult, error) + CheckDogusUpToDate(ctx context.Context) ([]cescommons.SimpleName, error) ApplyDoguStates(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -52,6 +54,10 @@ type ecosystemHealthUseCase interface { CheckEcosystemHealth(context.Context, *domain.BlueprintSpec) (ecosystem.HealthResult, error) } +type dogusUpToDateUseCase interface { + CheckDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error +} + type selfUpgradeUseCase interface { HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error } diff --git a/pkg/application/mock_doguInstallationUseCase_test.go b/pkg/application/mock_doguInstallationUseCase_test.go index 94600fc9..b7bf07c0 100644 --- a/pkg/application/mock_doguInstallationUseCase_test.go +++ b/pkg/application/mock_doguInstallationUseCase_test.go @@ -5,7 +5,9 @@ package application import ( context "context" + dogu "github.com/cloudogu/ces-commons-lib/dogu" domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" mock "github.com/stretchr/testify/mock" @@ -127,6 +129,64 @@ func (_c *mockDoguInstallationUseCase_CheckDoguHealth_Call) RunAndReturn(run fun return _c } +// CheckDogusUpToDate provides a mock function with given fields: ctx +func (_m *mockDoguInstallationUseCase) CheckDogusUpToDate(ctx context.Context) ([]dogu.SimpleName, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for CheckDogusUpToDate") + } + + var r0 []dogu.SimpleName + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]dogu.SimpleName, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []dogu.SimpleName); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]dogu.SimpleName) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockDoguInstallationUseCase_CheckDogusUpToDate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckDogusUpToDate' +type mockDoguInstallationUseCase_CheckDogusUpToDate_Call struct { + *mock.Call +} + +// CheckDogusUpToDate is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockDoguInstallationUseCase_Expecter) CheckDogusUpToDate(ctx interface{}) *mockDoguInstallationUseCase_CheckDogusUpToDate_Call { + return &mockDoguInstallationUseCase_CheckDogusUpToDate_Call{Call: _e.mock.On("CheckDogusUpToDate", ctx)} +} + +func (_c *mockDoguInstallationUseCase_CheckDogusUpToDate_Call) Run(run func(ctx context.Context)) *mockDoguInstallationUseCase_CheckDogusUpToDate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockDoguInstallationUseCase_CheckDogusUpToDate_Call) Return(_a0 []dogu.SimpleName, _a1 error) *mockDoguInstallationUseCase_CheckDogusUpToDate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockDoguInstallationUseCase_CheckDogusUpToDate_Call) RunAndReturn(run func(context.Context) ([]dogu.SimpleName, error)) *mockDoguInstallationUseCase_CheckDogusUpToDate_Call { + _c.Call.Return(run) + return _c +} + // newMockDoguInstallationUseCase creates a new instance of mockDoguInstallationUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func newMockDoguInstallationUseCase(t interface { diff --git a/pkg/application/mock_dogusUpToDateUseCase_test.go b/pkg/application/mock_dogusUpToDateUseCase_test.go new file mode 100644 index 00000000..4990227f --- /dev/null +++ b/pkg/application/mock_dogusUpToDateUseCase_test.go @@ -0,0 +1,84 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package application + +import ( + context "context" + + domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + mock "github.com/stretchr/testify/mock" +) + +// mockDogusUpToDateUseCase is an autogenerated mock type for the dogusUpToDateUseCase type +type mockDogusUpToDateUseCase struct { + mock.Mock +} + +type mockDogusUpToDateUseCase_Expecter struct { + mock *mock.Mock +} + +func (_m *mockDogusUpToDateUseCase) EXPECT() *mockDogusUpToDateUseCase_Expecter { + return &mockDogusUpToDateUseCase_Expecter{mock: &_m.Mock} +} + +// CheckDogus provides a mock function with given fields: ctx, blueprint +func (_m *mockDogusUpToDateUseCase) CheckDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { + ret := _m.Called(ctx, blueprint) + + if len(ret) == 0 { + panic("no return value specified for CheckDogus") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { + r0 = rf(ctx, blueprint) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockDogusUpToDateUseCase_CheckDogus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckDogus' +type mockDogusUpToDateUseCase_CheckDogus_Call struct { + *mock.Call +} + +// CheckDogus is a helper method to define mock.On call +// - ctx context.Context +// - blueprint *domain.BlueprintSpec +func (_e *mockDogusUpToDateUseCase_Expecter) CheckDogus(ctx interface{}, blueprint interface{}) *mockDogusUpToDateUseCase_CheckDogus_Call { + return &mockDogusUpToDateUseCase_CheckDogus_Call{Call: _e.mock.On("CheckDogus", ctx, blueprint)} +} + +func (_c *mockDogusUpToDateUseCase_CheckDogus_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockDogusUpToDateUseCase_CheckDogus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) + }) + return _c +} + +func (_c *mockDogusUpToDateUseCase_CheckDogus_Call) Return(_a0 error) *mockDogusUpToDateUseCase_CheckDogus_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockDogusUpToDateUseCase_CheckDogus_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockDogusUpToDateUseCase_CheckDogus_Call { + _c.Call.Return(run) + return _c +} + +// newMockDogusUpToDateUseCase creates a new instance of mockDogusUpToDateUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockDogusUpToDateUseCase(t interface { + mock.TestingT + Cleanup(func()) +}) *mockDogusUpToDateUseCase { + mock := &mockDogusUpToDateUseCase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index c58f645f..b1e0afe5 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -82,7 +82,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name blueprintValidationUseCase := application.NewBlueprintSpecValidationUseCase(blueprintRepo, validateDependenciesUseCase, validateMountsUseCase) effectiveBlueprintUseCase := application.NewEffectiveBlueprintUseCase(blueprintRepo) stateDiffUseCase := application.NewStateDiffUseCase(blueprintRepo, doguRepo, componentRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) - doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, healthConfigRepo) + doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, healthConfigRepo, doguConfigRepo, globalConfigRepoAdapter) componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintRepo, componentRepo, healthConfigRepo) ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintRepo) completeBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) @@ -90,6 +90,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name applyDogusUseCase := application.NewApplyDogusUseCase(blueprintRepo, doguInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) + dogusUpToDateUseCase := application.NewDogusUpToDateUseCase(blueprintRepo, doguInstallationUseCase) preparationUseCases := application.NewBlueprintPreparationUseCases( initialBlueprintStateUseCase, @@ -105,6 +106,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name applyComponentUseCase, applyDogusUseCase, ecosystemHealthUseCase, + dogusUpToDateUseCase, ) blueprintChangeUseCase := application.NewBlueprintSpecChangeUseCase(blueprintRepo, preparationUseCases, applyUseCases) blueprintReconciler := reconciler.NewBlueprintReconciler(blueprintChangeUseCase) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 64f4faf5..87d683e5 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -484,7 +484,7 @@ func (spec *BlueprintSpec) Complete() bool { return conditionChanged } -func (spec *BlueprintSpec) SetLastApplySucceededCondition(reason string, err error) bool { +func (spec *BlueprintSpec) SetLastApplySucceededConditionOnError(reason string, err error) bool { if err != nil { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionLastApplySucceeded, diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 7803679e..e64992cc 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -5,18 +5,23 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DoguInstallation represents an installed or to be installed dogu in the ecosystem. type DoguInstallation struct { // Name identifies the dogu by simple dogu name and namespace. Name cescommons.QualifiedName - // Version is the version of the dogu + // Version is the desired version of the dogu Version core.Version // Status is the installation status of the dogu in the ecosystem Status string // Health is the current health status of the dogu in the ecosystem Health HealthStatus + // InstalledVersion is the current version of the dogu + InstalledVersion core.Version + // StartedAt contains the time of the last restart of the dogu. + StartedAt metav1.Time // UpgradeConfig contains configuration for dogu upgrades UpgradeConfig UpgradeConfig // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. @@ -120,6 +125,14 @@ func (dogu *DoguInstallation) IsHealthy() bool { return dogu.Health == AvailableHealthStatus } +func (dogu *DoguInstallation) IsVersionUpToDate() bool { + return dogu.Version.IsEqualTo(dogu.InstalledVersion) +} + +func (dogu *DoguInstallation) IsConfigUpToDate(globalConfigUpdateTime *metav1.Time, doguConfigUpdateTime *metav1.Time) bool { + return !dogu.StartedAt.Before(globalConfigUpdateTime) && !dogu.StartedAt.Before(doguConfigUpdateTime) +} + func (dogu *DoguInstallation) Upgrade(newVersion *core.Version) { dogu.Version = core.Version{} if newVersion != nil { diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index ab8e0fda..432c8472 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -2,12 +2,14 @@ package ecosystem import ( "testing" + "time" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( @@ -178,3 +180,164 @@ func TestDoguInstallation_UpdateMinVolumeSize(t *testing.T) { assert.Equal(t, &volumeSize, dogu.MinVolumeSize) }) } + +func TestDoguInstallation_IsVersionUpToDate(t *testing.T) { + type fields struct { + Version core.Version + InstalledVersion core.Version + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "equal Versions is up to date", + fields: fields{ + Version: version1231, + InstalledVersion: version1231, + }, + want: true, + }, + { + name: "Version newer than installed version is not up to date", + fields: fields{ + Version: version1232, + InstalledVersion: version1231, + }, + want: false, + }, + { + name: "Installed version empty is not up to date", + fields: fields{ + Version: version1232, + InstalledVersion: core.Version{}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dogu := &DoguInstallation{ + Version: tt.fields.Version, + InstalledVersion: tt.fields.InstalledVersion, + } + assert.Equalf(t, tt.want, dogu.IsVersionUpToDate(), "IsVersionUpToDate()") + }) + } +} + +func TestDoguInstallation_IsConfigUpToDate(t *testing.T) { + timeMay := v1.NewTime(time.Date(2024, time.May, 23, 10, 0, 0, 0, time.UTC)) + timeJune := v1.NewTime(time.Date(2024, time.June, 23, 10, 0, 0, 0, time.UTC)) + timeJuly := v1.NewTime(time.Date(2024, time.July, 23, 10, 0, 0, 0, time.UTC)) + type args struct { + globalConfigUpdateTime *v1.Time + doguConfigUpdateTime *v1.Time + } + tests := []struct { + name string + StartedAt v1.Time + args args + want bool + }{ + { + name: "all equal is up to date", + StartedAt: timeMay, + args: args{ + globalConfigUpdateTime: &timeMay, + doguConfigUpdateTime: &timeMay, + }, + want: true, + }, + { + name: "StartedAt newest date is up to date", + StartedAt: timeJuly, + args: args{ + globalConfigUpdateTime: &timeMay, + doguConfigUpdateTime: &timeJune, + }, + want: true, + }, + { + name: "globalConfigUpdateTime newest date is not up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: &timeJuly, + doguConfigUpdateTime: &timeMay, + }, + want: false, + }, + { + name: "doguConfigUpdateTime newest date is not up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: &timeMay, + doguConfigUpdateTime: &timeJuly, + }, + want: false, + }, + { + name: "doguConfigUpdateTime nil and StartedAt newest is up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: &timeMay, + doguConfigUpdateTime: nil, + }, + want: true, + }, + { + name: "doguConfigUpdateTime nil and StartedAt not newest is not up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: &timeJuly, + doguConfigUpdateTime: nil, + }, + want: false, + }, + { + name: "globalConfigUpdateTime nil and StartedAt newest is up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: nil, + doguConfigUpdateTime: &timeMay, + }, + want: true, + }, + { + name: "globalConfigUpdateTime nil and StartedAt not newest is not up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: nil, + doguConfigUpdateTime: &timeJuly, + }, + want: false, + }, + { + name: "globalConfigUpdateTime nil and doguConfigUpdateTime nil is up to date", + StartedAt: timeJune, + args: args{ + globalConfigUpdateTime: nil, + doguConfigUpdateTime: nil, + }, + want: true, + }, + { + name: "StartedAt empty is not up to date", + StartedAt: v1.Time{}, + args: args{ + globalConfigUpdateTime: &timeMay, + doguConfigUpdateTime: nil, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dogu := &DoguInstallation{ + StartedAt: tt.StartedAt, + } + assert.Equalf(t, tt.want, dogu.IsConfigUpToDate(tt.args.globalConfigUpdateTime, tt.args.doguConfigUpdateTime), "IsConfigUpToDate(%v, %v)", tt.args.globalConfigUpdateTime, tt.args.doguConfigUpdateTime) + }) + } +} diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index 05166bd5..d85816f5 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -54,6 +54,15 @@ func NewUnhealthyEcosystemError( return &UnhealthyEcosystemError{WrappedError: wrappedError, Message: message, healthResult: healthResult} } +// DogusNotUpToDateError indicates that there are dogus that are not yet up to date. +type DogusNotUpToDateError struct { + Message string +} + +func (e *DogusNotUpToDateError) Error() string { + return e.Message +} + // MultipleBlueprintsError indicates that there are multiple blueprint-resources in this namespace, which the controller cannot handle. type MultipleBlueprintsError struct { Message string diff --git a/pkg/domain/events.go b/pkg/domain/events.go index c5c9f825..a9e49551 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -251,6 +251,18 @@ func (e DogusAppliedEvent) Message() string { return buffer.String() } +type DogusNotUpToDateEvent struct { + DogusNotUpToDate []cescommons.SimpleName +} + +func (e DogusNotUpToDateEvent) Name() string { + return "DogusNotUpToDate" +} + +func (e DogusNotUpToDateEvent) Message() string { + return fmt.Sprintf("dogus not up to date yet: %v", e.DogusNotUpToDate) +} + type BlueprintAppliedEvent struct{} func (e BlueprintAppliedEvent) Name() string { From c41e8f8b2e73f1b06c25b5fcec4eed8efe7e2a17 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 25 Sep 2025 10:14:05 +0200 Subject: [PATCH 079/119] #121 pause reconciliation on config and version change This is necessary to avoid potential restarts of dogus with unfitting config (for another dogu version) --- go.mod | 2 +- go.sum | 6 +- .../dogucr/doguInstallationRepo_test.go | 1 + .../kubernetes/dogucr/doguSerializer.go | 7 +- .../kubernetes/dogucr/doguSerializer_test.go | 8 +- pkg/application/blueprintSpecChangeUseCase.go | 1 - pkg/application/doguInstallationUseCase.go | 2 + .../doguInstallationUseCase_test.go | 2 + pkg/application/ecosystemConfigUseCase.go | 42 ++- .../ecosystemConfigUseCase_test.go | 315 +++++++++++++++++- pkg/bootstrap.go | 2 +- pkg/domain/ecosystem/doguInstallation.go | 2 + pkg/domain/events.go | 4 +- 13 files changed, 366 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index f744f070..ffa7a1aa 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cloudogu/cesapp-lib v0.18.1 github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c - github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 + github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0 github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 github.com/go-logr/logr v1.4.3 diff --git a/go.sum b/go.sum index 1dc17173..e459e0b5 100644 --- a/go.sum +++ b/go.sum @@ -48,10 +48,12 @@ github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c h1 github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c/go.mod h1:1NmERGUjXJdMHGMOb2CP6U9Y8NRt7I8O7juUNTIDxKM= github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 h1:3BzzLzUliyWFwqgQn4Mp3/rW3cYeQqv0en6HFn6SVsk= github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250923124722-b9569070ec13 h1:8/rEusXXKvbi+dLfUENsAvN84ItU0q6rFJDHKwh4BCQ= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250923124722-b9569070ec13/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0 h1:ohitXnpL655pmZOO0LVjW/weVi1ftFKSXG7jllbRQag= +github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 h1:OYMO6DYX6rtzo2a88hXXTzwf8+aXt7uV+BTaEmvlls4= github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6/go.mod h1:NytbB2XBjc+gr5gOJFoPjCzyxs/TuhWpKSnDJzCLQ14= -github.com/cloudogu/k8s-registry-lib v0.5.1 h1:gbdrhETUm53GP65LoljrS1kekDDl/onBPfrOQTQpt1s= -github.com/cloudogu/k8s-registry-lib v0.5.1/go.mod h1:mdMOgknEOrGQH1zc/3K859iPhwpqwtzigK9QrjM3Vk0= github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 h1:NWoBvBwiE8yO8HqSId0+nOTBMGSjzX0yM8m6fC7Ll6E= github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1/go.mod h1:AGZ3bihl/sUUJ6iEtcxtALIt/UlkfUdK7HXJ6DtBivE= github.com/cloudogu/retry-lib v0.1.0 h1:gaAmtyjUqgHbxfCWMeUn0qnGbDH4TtZVSQkbZ1Nq6eI= diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index 9d3f0670..1c3ecff8 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -341,6 +341,7 @@ func Test_doguInstallationRepo_Update(t *testing.T) { "\"dataVolumeSize\":\"\"," + "\"minDataVolumeSize\":\"0\"}," + "\"supportMode\":false," + + "\"pauseReconciliation\":false," + "\"upgradeConfig\":{\"allowNamespaceSwitch\":false,\"forceUpgrade\":false}," + "\"additionalIngressAnnotations\":null," + "\"additionalMounts\":null}" + diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index b618d9e1..227e25b4 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -146,7 +146,8 @@ func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { // we just always set this value, if a new dogu CR is created via blueprint MinDataVolumeSize: minVolumeSize, }, - SupportMode: false, + SupportMode: false, + PauseReconciliation: false, // should be always false on installation UpgradeConfig: v2.UpgradeConfig{ AllowNamespaceSwitch: dogu.UpgradeConfig.AllowNamespaceSwitch, ForceUpgrade: false, @@ -215,6 +216,7 @@ type doguSpecPatch struct { Version string `json:"version"` Resources doguResourcesPatch `json:"resources"` SupportMode bool `json:"supportMode"` + PauseReconciliation bool `json:"pauseReconciliation"` UpgradeConfig upgradeConfigPatch `json:"upgradeConfig"` AdditionalIngressAnnotations map[string]string `json:"additionalIngressAnnotations"` AdditionalMounts []v2.DataMount `json:"additionalMounts"` @@ -252,7 +254,8 @@ func toDoguCRPatch(dogu *ecosystem.DoguInstallation) *doguCRPatch { }, AdditionalIngressAnnotations: getNginxIngressAnnotations(dogu.ReverseProxyConfig), // always set this to false as a dogu cannot start in support mode - SupportMode: false, + SupportMode: false, + PauseReconciliation: dogu.PauseReconciliation, UpgradeConfig: upgradeConfigPatch{ AllowNamespaceSwitch: dogu.UpgradeConfig.AllowNamespaceSwitch, // this is a useful default as long as blueprints itself have no forceUpgrade flag implemented diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 918fbbcf..900e7635 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -325,6 +325,7 @@ func Test_toDoguCR(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, }, + PauseReconciliation: true, }, want: &v2.Dogu{ TypeMeta: metav1.TypeMeta{}, @@ -352,6 +353,7 @@ func Test_toDoguCR(t *testing.T) { ForceUpgrade: false, }, AdditionalIngressAnnotations: nil, + PauseReconciliation: false, // should be always false on installation }, Status: v2.DoguStatus{}, }, @@ -480,6 +482,7 @@ func Test_toDoguCRPatch(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, }, + PauseReconciliation: true, }, want: &doguCRPatch{ Spec: doguSpecPatch{ @@ -488,6 +491,7 @@ func Test_toDoguCRPatch(t *testing.T) { UpgradeConfig: upgradeConfigPatch{ AllowNamespaceSwitch: true, }, + PauseReconciliation: true, }, }, }, @@ -529,7 +533,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, }, - want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":{\"nginx.ingress.kubernetes.io/configuration-snippet\":\"additional\",\"nginx.ingress.kubernetes.io/proxy-body-size\":\"1G\",\"nginx.ingress.kubernetes.io/rewrite-target\":\"/\"},\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", + want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"pauseReconciliation\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":{\"nginx.ingress.kubernetes.io/configuration-snippet\":\"additional\",\"nginx.ingress.kubernetes.io/proxy-body-size\":\"1G\",\"nginx.ingress.kubernetes.io/rewrite-target\":\"/\"},\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", wantErr: assert.NoError, }, { @@ -546,7 +550,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, }, }, - want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"0\"},\"supportMode\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", + want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"0\"},\"supportMode\":false,\"pauseReconciliation\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", wantErr: assert.NoError, }, } diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 3a6ca0b7..7fc276fe 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -165,7 +165,6 @@ func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, bluep return err } err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) - // TODO: prevent Dogu restarts here to avoid starting Dogus with config not fitting to the expected dogu version if err != nil { return err } diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index 1b5e70e4..f06bdea1 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -176,6 +176,8 @@ func (useCase *DoguInstallationUseCase) applyDoguState( // If this routine did not terminate until this point, it is always an update. if len(doguDiff.NeededActions) > 0 { logger.Info("upgrade dogu") + // remove potential pause reconciliation flags here so that the dogu gets updates again + doguInstallation.PauseReconciliation = false return useCase.doguRepo.Update(ctx, doguInstallation) } diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 71e14ff6..2bc13887 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -190,6 +190,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + dogu.PauseReconciliation = true // test if it gets reset on update (the dogu in the EXPECT Update call has this to false) + // when err := sut.applyDoguState( testCtx, diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 42738f3d..4753304c 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -10,6 +10,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/cloudogu/k8s-registry-lib/config" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -19,19 +20,16 @@ type EcosystemConfigUseCase struct { doguConfigRepository doguConfigRepository sensitiveDoguConfigRepository sensitiveDoguConfigRepository globalConfigRepository globalConfigRepository + doguInstallationRepository doguInstallationRepository } -func NewEcosystemConfigUseCase( - blueprintRepository blueprintSpecRepository, - doguConfigRepository doguConfigRepository, - sensitiveDoguConfigRepository sensitiveDoguConfigRepository, - globalConfigRepository globalConfigRepository, -) *EcosystemConfigUseCase { +func NewEcosystemConfigUseCase(blueprintRepository blueprintSpecRepository, doguConfigRepository doguConfigRepository, sensitiveDoguConfigRepository sensitiveDoguConfigRepository, globalConfigRepository globalConfigRepository, doguInstallationRepository domainservice.DoguInstallationRepository) *EcosystemConfigUseCase { return &EcosystemConfigUseCase{ blueprintRepository: blueprintRepository, doguConfigRepository: doguConfigRepository, sensitiveDoguConfigRepository: sensitiveDoguConfigRepository, globalConfigRepository: globalConfigRepository, + doguInstallationRepository: doguInstallationRepository, } } @@ -39,7 +37,11 @@ func NewEcosystemConfigUseCase( func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig") - err := applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) + err := pauseReconciliationForDogus(ctx, useCase.doguInstallationRepository, blueprint.StateDiff) + if err != nil { + return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not pause reconciliation for some dogus: %w", err)) + } + err = applyDoguConfigDiffs(ctx, useCase.doguConfigRepository, blueprint.StateDiff.DoguConfigDiffs) if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply normal dogu config: %w", err)) } @@ -63,6 +65,32 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin return nil } +func pauseReconciliationForDogus(ctx context.Context, repository doguInstallationRepository, diff domain.StateDiff) error { + allDogus, err := repository.GetAll(ctx) + if err != nil { + return fmt.Errorf("error while attempting to load dogus: %w", err) + } + globalConfigChanges := diff.GlobalConfigDiffs.HasChanges() + for _, dogu := range allDogus { + for _, doguDiff := range diff.DoguDiffs { + if doguDiff.DoguName != dogu.Name.SimpleName { + continue + } + if slices.Contains(doguDiff.NeededActions, domain.ActionUpgrade) && + (globalConfigChanges || + diff.DoguConfigDiffs[dogu.Name.SimpleName].HasChanges() || + diff.SensitiveDoguConfigDiffs[dogu.Name.SimpleName].HasChanges()) { + dogu.PauseReconciliation = true + err = repository.Update(ctx, dogu) + if err != nil { + return fmt.Errorf("could not pause reconciliation for dogu: %w", err) + } + } + } + } + return nil +} + func (useCase *EcosystemConfigUseCase) applyGlobalConfigDiffs(ctx context.Context, globalConfigDiffsByAction map[domain.ConfigAction][]domain.GlobalConfigEntryDiff) error { var errs []error diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index 79fcb991..f11ff0d1 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -1,6 +1,7 @@ package application import ( + "context" "maps" "testing" @@ -8,6 +9,7 @@ import ( liberrors "github.com/cloudogu/ces-commons-lib/errors" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-registry-lib/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -94,8 +96,81 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, nil) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigRepoMock) + sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigRepoMock, doguInstallaltionRepoMock) + + // when + err := sut.ApplyConfig(testCtx, blueprint) + + // then + require.NoError(t, err) + require.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.EcosystemConfigAppliedEvent{}, blueprint.Events[0]) + }) + + t.Run("pause reconciliation for dogus with config and version changes", func(t *testing.T) { + // given + blueprintRepoMock := newMockBlueprintSpecRepository(t) + doguConfigMock := newMockDoguConfigRepository(t) + sensitiveDoguConfigMock := newMockSensitiveDoguConfigRepository(t) + globalConfigRepoMock := newMockGlobalConfigRepository(t) + + sensitiveCasDiff := getSensitiveDoguConfigEntryDiffForAction("key", "value", cas, domain.ConfigActionSet) + blueprint := &domain.BlueprintSpec{ + StateDiff: domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: cas, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + }, + DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ + redmine: { + getSetDoguConfigEntryDiff("key", "value", redmine), + }, + }, + SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{ + cas: { + sensitiveCasDiff, + }, + }, + }, + } + + // Just check if the routine hits the repos. Check values in concrete test of methods. + doguConfigMock.EXPECT(). + GetAllExisting(testCtx, []cescommons.SimpleName{redmine}). + Return(map[cescommons.SimpleName]config.DoguConfig{ + redmine: config.CreateDoguConfig(redmine, map[config.Key]config.Value{}), + }, nil) + doguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, nil) + + sensitiveDoguConfigMock.EXPECT(). + GetAllExisting(testCtx, []cescommons.SimpleName{cas}). + Return(map[cescommons.SimpleName]config.DoguConfig{ + cas: config.CreateDoguConfig(cas, map[config.Key]config.Value{}), + }, nil) + sensitiveDoguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, nil) + + globalConfigRepoMock.EXPECT().Get(testCtx).Return(config.GlobalConfig{}, nil) + + blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + cas: {Name: cescommons.QualifiedName{SimpleName: cas, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Run(func(ctx context.Context, dogu *ecosystem.DoguInstallation) { + assert.True(t, dogu.PauseReconciliation) + }).Return(nil).Times(2) + sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigRepoMock, doguInstallaltionRepoMock) // when err := sut.ApplyConfig(testCtx, blueprint) @@ -135,8 +210,10 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, nil) doguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) + sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock, doguInstallaltionRepoMock) // when err := sut.ApplyConfig(testCtx, blueprint) @@ -183,8 +260,9 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }, nil) sensitiveDoguConfigMock.EXPECT().UpdateOrCreate(testCtx, mock.Anything).Return(config.DoguConfig{}, assert.AnError).Times(1) blueprintRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(nil) - - sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) + sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock, doguInstallaltionRepoMock) // when err := sut.ApplyConfig(testCtx, blueprint) @@ -236,8 +314,9 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { globalConfigMock.EXPECT().Update(testCtx, mock.Anything).Return(globalConfig, assert.AnError) blueprintRepoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - - sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) + sut := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock, doguInstallaltionRepoMock) // when err := sut.ApplyConfig(testCtx, blueprint) @@ -256,6 +335,220 @@ func TestEcosystemConfigUseCase_ApplyConfig(t *testing.T) { }) } +func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { + t.Run("pause reconciliation for multiple dogus when dogu config changes", func(t *testing.T) { + // given + stateDiff := domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: cas, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: postfix, + NeededActions: []domain.Action{}, // no action, so no pause required + }, + }, + DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ + redmine: { + getSetDoguConfigEntryDiff("key", "value", redmine), + }, + cas: { + getSetDoguConfigEntryDiff("key", "value", cas), + }, + postfix: { + getSetDoguConfigEntryDiff("key", "value", postfix), + }, + }, + } + + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + cas: {Name: cescommons.QualifiedName{SimpleName: cas, Namespace: "namespace"}}, + postfix: {Name: cescommons.QualifiedName{SimpleName: postfix, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Run(func(ctx context.Context, dogu *ecosystem.DoguInstallation) { + assert.True(t, dogu.PauseReconciliation) + }).Return(nil).Times(2) + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + + // then + require.NoError(t, err) + }) + + t.Run("pause reconciliation for multiple dogus when sensitive config changes", func(t *testing.T) { + // given + stateDiff := domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: cas, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: postfix, + NeededActions: []domain.Action{}, // no action, so no pause required + }, + }, + SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{ + redmine: { + getSetDoguConfigEntryDiff("key", "value", redmine), + }, + cas: { + getSetDoguConfigEntryDiff("key", "value", cas), + }, + postfix: { + getSetDoguConfigEntryDiff("key", "value", postfix), + }, + }, + } + + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + cas: {Name: cescommons.QualifiedName{SimpleName: cas, Namespace: "namespace"}}, + postfix: {Name: cescommons.QualifiedName{SimpleName: postfix, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Run(func(ctx context.Context, dogu *ecosystem.DoguInstallation) { + assert.True(t, dogu.PauseReconciliation) + }).Return(nil).Times(2) + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + + // then + require.NoError(t, err) + }) + + t.Run("pause reconciliation for multiple dogus when global config changes", func(t *testing.T) { + // given + stateDiff := domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: cas, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: postfix, + NeededActions: []domain.Action{}, // no action, so no pause required + }, + }, + GlobalConfigDiffs: domain.GlobalConfigDiffs{ + getSetGlobalConfigEntryDiff("key", "value"), + }, + } + + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + cas: {Name: cescommons.QualifiedName{SimpleName: cas, Namespace: "namespace"}}, + postfix: {Name: cescommons.QualifiedName{SimpleName: postfix, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Run(func(ctx context.Context, dogu *ecosystem.DoguInstallation) { + assert.True(t, dogu.PauseReconciliation) + }).Return(nil).Times(2) + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + + // then + require.NoError(t, err) + }) + + t.Run("do not pause reconciliation for dogus without config changes", func(t *testing.T) { + // given + stateDiff := domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: cas, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + { + DoguName: postfix, + NeededActions: []domain.Action{}, // no action, so no pause required + }, + }, + } + + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + cas: {Name: cescommons.QualifiedName{SimpleName: cas, Namespace: "namespace"}}, + postfix: {Name: cescommons.QualifiedName{SimpleName: postfix, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + // No Update calls + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + + // then + require.NoError(t, err) + }) + + t.Run("error on get all dogus error", func(t *testing.T) { + // given + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, domain.StateDiff{}) + + // then + require.Error(t, err) + assert.ErrorContains(t, err, "error while attempting to load dogus") + }) + + t.Run("error on update error", func(t *testing.T) { + // given + stateDiff := domain.StateDiff{ + DoguDiffs: []domain.DoguDiff{ + { + DoguName: redmine, + NeededActions: []domain.Action{domain.ActionUpgrade}, + }, + }, + GlobalConfigDiffs: domain.GlobalConfigDiffs{ + getSetGlobalConfigEntryDiff("key", "value"), + }, + } + dogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + redmine: {Name: cescommons.QualifiedName{SimpleName: redmine, Namespace: "namespace"}}, + } + doguInstallaltionRepoMock := newMockDoguInstallationRepository(t) + doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(dogus, nil) + doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(assert.AnError) + + // when + err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + + // then + require.Error(t, err) + assert.ErrorContains(t, err, "could not pause reconciliation for dogu") + }) +} + func TestEcosystemConfigUseCase_applyDoguConfigDiffs(t *testing.T) { t.Run("should save diffs with action set", func(t *testing.T) { // given @@ -407,7 +700,7 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { t.Run("should save diffs with action set", func(t *testing.T) { // given globalConfigMock := newMockGlobalConfigRepository(t) - sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock) + sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock, nil) diff1 := getSetGlobalConfigEntryDiff("key1", "value1") diff2 := getSetGlobalConfigEntryDiff("key2", "value2") byAction := map[domain.ConfigAction][]domain.GlobalConfigEntryDiff{domain.ConfigActionSet: {diff1, diff2}} @@ -433,7 +726,7 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { t.Run("should delete diffs with action remove", func(t *testing.T) { // given globalConfigMock := newMockGlobalConfigRepository(t) - sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock) + sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock, nil) diff1 := getRemoveGlobalConfigEntryDiff("key") diff2 := getRemoveGlobalConfigEntryDiff("key1") byAction := map[domain.ConfigAction][]domain.GlobalConfigEntryDiff{domain.ConfigActionRemove: {diff1, diff2}} @@ -457,7 +750,7 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { t.Run("should return nil on action none", func(t *testing.T) { // given globalConfigMock := newMockGlobalConfigRepository(t) - sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock) + sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock, nil) diff1 := domain.GlobalConfigEntryDiff{ NeededAction: domain.ConfigActionNone, } @@ -478,7 +771,7 @@ func TestEcosystemConfigUseCase_applyGlobalConfigDiffs(t *testing.T) { t.Run("err when get fails", func(t *testing.T) { // given globalConfigMock := newMockGlobalConfigRepository(t) - sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock) + sut := NewEcosystemConfigUseCase(nil, nil, nil, globalConfigMock, nil) diff1 := domain.GlobalConfigEntryDiff{ NeededAction: domain.ConfigActionSet, } @@ -554,7 +847,7 @@ func TestNewEcosystemConfigUseCase(t *testing.T) { globalConfigMock := newMockGlobalConfigRepository(t) // when - useCase := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock) + useCase := NewEcosystemConfigUseCase(blueprintRepoMock, doguConfigMock, sensitiveDoguConfigMock, globalConfigMock, nil) // then assert.Equal(t, blueprintRepoMock, useCase.blueprintRepository) diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index b1e0afe5..a3effe87 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -88,7 +88,7 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name completeBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintRepo, componentInstallationUseCase) applyDogusUseCase := application.NewApplyDogusUseCase(blueprintRepo, doguInstallationUseCase) - ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter) + ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter, doguRepo) selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) dogusUpToDateUseCase := application.NewDogusUpToDateUseCase(blueprintRepo, doguInstallationUseCase) diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index e64992cc..53fd1e82 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -24,6 +24,8 @@ type DoguInstallation struct { StartedAt metav1.Time // UpgradeConfig contains configuration for dogu upgrades UpgradeConfig UpgradeConfig + // PauseReconciliation indicates whether the reconciliation loop should be running (pauseReconciliation=false) or not (pauseReconciliation=true). + PauseReconciliation bool // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. // This field has a generic map type as the values within it highly depend on the used type of repository. // This field should be ignored in the whole domain. diff --git a/pkg/domain/events.go b/pkg/domain/events.go index a9e49551..e5c7cf9f 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -260,7 +260,9 @@ func (e DogusNotUpToDateEvent) Name() string { } func (e DogusNotUpToDateEvent) Message() string { - return fmt.Sprintf("dogus not up to date yet: %v", e.DogusNotUpToDate) + dogusNotUpToDate := util.Map(e.DogusNotUpToDate, func(dogu cescommons.SimpleName) string { return string(dogu) }) + slices.Sort(dogusNotUpToDate) + return fmt.Sprintf("%d dogu(s) not up to date yet: %s", len(dogusNotUpToDate), strings.Join(dogusNotUpToDate, ", ")) } type BlueprintAppliedEvent struct{} From c31d1a1f6acd231e8ea63ec358f9d4d6e8b5498c Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 25 Sep 2025 10:57:13 +0200 Subject: [PATCH 080/119] #121 censor sensitive config values in effective Blueprint --- .../kubernetes/blueprintcr/v2/serializer/config.go | 6 +++++- .../blueprintcr/v2/serializer/config_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 3accf6bb..78fe478b 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -75,8 +75,12 @@ func convertToConfigEntriesDTO(config domain.ConfigEntries) []v2.ConfigEntry { } var sensitive *bool + var value *string if domainEntry.Sensitive { sensitive = &domainEntry.Sensitive + } else { + // only set value if not sensitive + value = (*string)(domainEntry.Value) } var secretRef *v2.SecretReference @@ -90,7 +94,7 @@ func convertToConfigEntriesDTO(config domain.ConfigEntries) []v2.ConfigEntry { result[i] = v2.ConfigEntry{ Key: domainEntry.Key.String(), Absent: absent, - Value: (*string)(domainEntry.Value), + Value: value, Sensitive: sensitive, SecretRef: secretRef, } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go index 3de40994..e0a576fb 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config_test.go @@ -53,6 +53,19 @@ func Test_convertToDoguConfigDTO(t *testing.T) { {Key: testDoguKey1.Key.String(), Absent: &trueVar}, }, }, + { + name: "censor sensitive config values", + config: domain.DoguConfigEntries{ + { + Key: testDoguKey1.Key, + Sensitive: true, + Value: (*config.Value)(&val1), + }, + }, + want: []v2.ConfigEntry{ + {Key: testDoguKey1.Key.String(), Sensitive: &trueVar}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From ba2159f8ec71510291ddbe17836240c3bd86fbaf Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 25 Sep 2025 11:31:10 +0200 Subject: [PATCH 081/119] #121 add display name to blueprint --- go.mod | 2 +- go.sum | 2 ++ .../kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go | 1 + .../blueprintcr/v2/blueprintSpecCRRepository_test.go | 4 +++- pkg/domain/blueprintSpec.go | 1 + 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index ffa7a1aa..9301109c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 - github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 + github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0 github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 diff --git a/go.sum b/go.sum index e459e0b5..a1c6f3ae 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSA github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 h1:SjKSO5/5fNKQ6Z9v7t9laD3u62rl6Tr9o/0h8Ik6LrE= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b h1:1VRisF6h7o/B8fOtPIRuMApMe4PQKiCgiYDLD2tyScw= +github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c h1:zgDSZ1LqdqwRabR7KEaWZlwZkAFG/qsM0uIJTuzoodc= github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c/go.mod h1:1NmERGUjXJdMHGMOb2CP6U9Y8NRt7I8O7juUNTIDxKM= github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 h1:3BzzLzUliyWFwqgQn4Mp3/rW3cYeQqv0en6HFn6SVsk= diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 6bb41412..b8c1dbaf 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -67,6 +67,7 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) blueprintSpec := &domain.BlueprintSpec{ Id: blueprintId, + DisplayName: blueprintCR.Spec.DisplayName, EffectiveBlueprint: effectiveBlueprint, Conditions: conditions, Config: domain.BlueprintConfiguration{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index fb3c986d..b54a8c58 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -45,6 +45,7 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ResourceVersion: "abc"}, Spec: &bpv2.BlueprintSpec{ + DisplayName: "MyBlueprint", Blueprint: bpv2.BlueprintManifest{}, BlueprintMask: &bpv2.BlueprintMask{}, AllowDoguNamespaceSwitch: &trueVar, @@ -65,7 +66,8 @@ func Test_blueprintSpecRepo_GetById(t *testing.T) { persistenceContext := make(map[string]interface{}) persistenceContext[blueprintSpecRepoContextKey] = blueprintSpecRepoContext{"abc"} assert.Equal(t, &domain.BlueprintSpec{ - Id: blueprintId, + Id: blueprintId, + DisplayName: "MyBlueprint", Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: true, AllowDoguNamespaceSwitch: true, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 87d683e5..7842c5f4 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -17,6 +17,7 @@ import ( type BlueprintSpec struct { Id string + DisplayName string Blueprint Blueprint BlueprintMask BlueprintMask EffectiveBlueprint EffectiveBlueprint From 8d6559e24c289ff1cf4fa8037ecef8387de142be Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 25 Sep 2025 16:53:27 +0200 Subject: [PATCH 082/119] #121 clean up roles --- k8s/helm/templates/blueprint-editor-role.yaml | 20 +++++++++++-- k8s/helm/templates/dogu-restart-role.yaml | 19 ------------- .../templates/dogu-restart-rolebinding.yaml | 13 --------- k8s/helm/templates/manager-role.yaml | 28 ------------------- 4 files changed, 17 insertions(+), 63 deletions(-) delete mode 100644 k8s/helm/templates/dogu-restart-role.yaml delete mode 100644 k8s/helm/templates/dogu-restart-rolebinding.yaml diff --git a/k8s/helm/templates/blueprint-editor-role.yaml b/k8s/helm/templates/blueprint-editor-role.yaml index 0f3b7fb2..f17b4b3c 100644 --- a/k8s/helm/templates/blueprint-editor-role.yaml +++ b/k8s/helm/templates/blueprint-editor-role.yaml @@ -1,25 +1,39 @@ +# Issue RBAC permissions to the operator to fulfill CR handling which includes reading and updating customer-created +# Blueprint CRs. The operator does not create or delete Blueprints by itself, though. + apiVersion: rbac.authorization.k8s.io/v1 +# the blueprint operator should only handle Blueprint CRs within its own namespace, not Blueprints in other namespaces kind: Role metadata: labels: {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} name: {{ include "k8s-blueprint-operator.name" . }}-blueprint-editor-role rules: + + # issue permissions to read/update fields beyond the status or finalizer fields - apiGroups: - k8s.cloudogu.com resources: - blueprints verbs: - - create - - delete - get - list - patch - update - watch + # issue permissions to update the finalizer field that may control CR deletion + - apiGroups: + - k8s.cloudogu.com + resources: + - blueprints/finalizers + verbs: + - update + # issue permissions to update the status which contains blueprint processing data - apiGroups: - k8s.cloudogu.com resources: - blueprints/status verbs: - - get \ No newline at end of file + - get + - patch + - update \ No newline at end of file diff --git a/k8s/helm/templates/dogu-restart-role.yaml b/k8s/helm/templates/dogu-restart-role.yaml deleted file mode 100644 index 61b2d92e..00000000 --- a/k8s/helm/templates/dogu-restart-role.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} - name: {{ include "k8s-blueprint-operator.name" . }}-dogu-restart-role -rules: - - apiGroups: - - k8s.cloudogu.com - resources: - - dogurestarts - verbs: - - create - - apiGroups: - - k8s.cloudogu.com - resources: - - dogurestarts/status - verbs: - - get \ No newline at end of file diff --git a/k8s/helm/templates/dogu-restart-rolebinding.yaml b/k8s/helm/templates/dogu-restart-rolebinding.yaml deleted file mode 100644 index f01e3992..00000000 --- a/k8s/helm/templates/dogu-restart-rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} - name: {{ include "k8s-blueprint-operator.name" . }}-dogu-restart-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "k8s-blueprint-operator.name" . }}-dogu-restart-role -subjects: - - kind: ServiceAccount - name: {{ include "k8s-blueprint-operator.name" . }}-controller-manager \ No newline at end of file diff --git a/k8s/helm/templates/manager-role.yaml b/k8s/helm/templates/manager-role.yaml index 95316428..8967a42d 100644 --- a/k8s/helm/templates/manager-role.yaml +++ b/k8s/helm/templates/manager-role.yaml @@ -9,34 +9,6 @@ metadata: {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} name: {{ include "k8s-blueprint-operator.name" . }}-manager-role rules: - -# issue permissions to read/update fields beyond the status or finalizer fields - - apiGroups: - - k8s.cloudogu.com - resources: - - blueprints - verbs: - - get - - list - - patch - - update - - watch -# issue permissions to update the finalizer field that may control CR deletion - - apiGroups: - - k8s.cloudogu.com - resources: - - blueprints/finalizers - verbs: - - update -# issue permissions to update the status which contains blueprint processing data - - apiGroups: - - k8s.cloudogu.com - resources: - - blueprints/status - verbs: - - get - - patch - - update # issue secret handling for Dogus - apiGroups: - "" From 7e92c855ad8bdc7a4ce3a6f8eac0ba44a2d8db78 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 07:01:10 +0200 Subject: [PATCH 083/119] #121 clean up dependencies --- CHANGELOG.md | 1 + k8s/helm/Chart.yaml | 3 +-- k8s/helm/values.yaml | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3ed2e1..5ce0145f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - make sure to persist your blueprints before upgrading - you need to transform your blueprints to the new v2 format yourself - [#121] remove maintenance mode + - remove dependency to k8s-service-discovery (maintenance-mode was the reason for this dependency) - [#121] *breaking* dogus will not be restarted by the blueprint operator anymore - this is now the responsibility of the dogu operator diff --git a/k8s/helm/Chart.yaml b/k8s/helm/Chart.yaml index d162a010..9cb8402a 100644 --- a/k8s/helm/Chart.yaml +++ b/k8s/helm/Chart.yaml @@ -26,5 +26,4 @@ appVersion: "0.0.0-replaceme" annotations: # TODO: need to update Dogu-CRD and blueprint-CRD dependencies "k8s.cloudogu.com/ces-dependency/k8s-blueprint-operator-crd": ">=1.3.0-0" - "k8s.cloudogu.com/ces-dependency/k8s-dogu-operator-crd": ">=2.8.0-0, <3.0.0-0" - "k8s.cloudogu.com/ces-dependency/k8s-service-discovery": ">=0.15.0-0" + "k8s.cloudogu.com/ces-dependency/k8s-dogu-operator-crd": ">=2.8.0-0, <3.0.0-0" \ No newline at end of file diff --git a/k8s/helm/values.yaml b/k8s/helm/values.yaml index 60c9efac..d6983251 100644 --- a/k8s/helm/values.yaml +++ b/k8s/helm/values.yaml @@ -22,7 +22,6 @@ healthConfig: components: required: - name: k8s-dogu-operator - - name: k8s-service-discovery - name: k8s-component-operator wait: timeout: 10m From 1aa023754514ba3a37faca9a6ec1465dc1433033 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 09:39:28 +0200 Subject: [PATCH 084/119] #121 fix todos --- .../doguregistry/doguDescriptorRepository.go | 1 - .../kubernetes/dogucr/doguInstallationRepo_test.go | 2 +- .../kubernetes/dogucr/doguSerializer_test.go | 14 +++++++------- pkg/domain/ecosystem/doguInstallation.go | 10 ---------- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/pkg/adapter/doguregistry/doguDescriptorRepository.go b/pkg/adapter/doguregistry/doguDescriptorRepository.go index b97bcfa5..617fa2c6 100644 --- a/pkg/adapter/doguregistry/doguDescriptorRepository.go +++ b/pkg/adapter/doguregistry/doguDescriptorRepository.go @@ -37,7 +37,6 @@ func (r *DoguDescriptorRepository) GetDogu(ctx context.Context, qualifiedDoguVer return nil, err } - // TODO: doesn't work with "old" dogu operator err = r.localRepository.Add(ctx, qualifiedDoguVersion.Name.SimpleName, dogu) if err != nil { // just log the error, no need to fail the reconcilation diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index 1c3ecff8..1a09f6c3 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -74,7 +74,7 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { assert.Equal(t, &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{}, PersistenceContext: persistenceContext, diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 900e7635..7a0ffd90 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -75,7 +75,7 @@ func Test_parseDoguCR(t *testing.T) { want: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, @@ -161,7 +161,7 @@ func Test_parseDoguCR(t *testing.T) { want: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: false, @@ -320,7 +320,7 @@ func Test_toDoguCR(t *testing.T) { dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, @@ -419,7 +419,7 @@ func Test_toDoguCR(t *testing.T) { dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, @@ -477,7 +477,7 @@ func Test_toDoguCRPatch(t *testing.T) { dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, @@ -518,7 +518,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, @@ -541,7 +541,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { dogu: &ecosystem.DoguInstallation{ Name: postgresDoguName, Version: version3214, - Status: ecosystem.DoguStatusInstalled, + Status: "installed", Health: ecosystem.AvailableHealthStatus, UpgradeConfig: ecosystem.UpgradeConfig{ AllowNamespaceSwitch: true, diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 53fd1e82..d6c91fc5 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -39,16 +39,6 @@ type DoguInstallation struct { AdditionalMounts []AdditionalMount } -// TODO: Unused constants needed? -const ( - DoguStatusNotInstalled = "" - DoguStatusInstalling = "installing" - DoguStatusUpgrading = "upgrading" - DoguStatusDeleting = "deleting" - DoguStatusInstalled = "installed" - DoguStatusPVCResizing = "resizing PVC" -) - // Specific Nginx annotations. In future those annotations will be replaced be generalized fields in the dogu cr. // The dogu-operator or service-discovery will interpret them. const ( From 6542ba85da303e1521ea3b724523cc6c090e2f69 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 09:39:55 +0200 Subject: [PATCH 085/119] #121 use k8s.ui/utils/ptr package instead of own helper --- .../v2/blueprintSpecCRRepository.go | 16 +-- .../blueprintcr/v2/serializer/component.go | 11 +- .../v2/serializer/componentDiff.go | 61 +--------- .../v2/serializer/componentDiff_test.go | 87 -------------- .../blueprintcr/v2/serializer/config.go | 15 +-- .../blueprintcr/v2/serializer/dogu.go | 15 +-- .../blueprintcr/v2/serializer/doguDiff.go | 109 ------------------ .../v2/serializer/doguDiff_test.go | 96 --------------- pkg/domain/config.go | 4 + pkg/domain/config_test.go | 21 ++++ 10 files changed, 42 insertions(+), 393 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index b8c1dbaf..41ba1233 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -10,6 +10,7 @@ import ( corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/log" bpv2client "github.com/cloudogu/k8s-blueprint-lib/v2/client" @@ -71,10 +72,10 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) EffectiveBlueprint: effectiveBlueprint, Conditions: conditions, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: boolPtrToValue(blueprintCR.Spec.IgnoreDoguHealth), - IgnoreComponentHealth: boolPtrToValue(blueprintCR.Spec.IgnoreComponentHealth), - AllowDoguNamespaceSwitch: boolPtrToValue(blueprintCR.Spec.AllowDoguNamespaceSwitch), - Stopped: boolPtrToValue(blueprintCR.Spec.Stopped), + IgnoreDoguHealth: ptr.Deref(blueprintCR.Spec.IgnoreDoguHealth, false), + IgnoreComponentHealth: ptr.Deref(blueprintCR.Spec.IgnoreComponentHealth, false), + AllowDoguNamespaceSwitch: ptr.Deref(blueprintCR.Spec.AllowDoguNamespaceSwitch, false), + Stopped: ptr.Deref(blueprintCR.Spec.Stopped, false), }, } @@ -146,13 +147,6 @@ func convertBlueprintStatus(blueprintCR *v2.Blueprint) (domain.EffectiveBlueprin return effectiveBlueprint, nil } -func boolPtrToValue(boolPtr *bool) bool { - if boolPtr != nil { - return *boolPtr - } - return false -} - // Update persists changes in the blueprint to the corresponding blueprint CR. func (repo *blueprintSpecRepo) Update(ctx context.Context, spec *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("blueprintSpecRepo.Update") diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go index dd5774f1..08b496ff 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go @@ -5,6 +5,7 @@ import ( "fmt" v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" + "k8s.io/utils/ptr" "github.com/Masterminds/semver/v3" @@ -30,11 +31,6 @@ func ConvertComponents(components []v2.Component) ([]domain.Component, error) { } } - absent := false - if component.Absent != nil { - absent = *component.Absent - } - name, err := common.QualifiedComponentNameFromString(component.Name) if err != nil { errorList = append(errorList, err) @@ -44,7 +40,7 @@ func ConvertComponents(components []v2.Component) ([]domain.Component, error) { convertedComponents = append(convertedComponents, domain.Component{ Name: name, Version: version, - Absent: absent, + Absent: ptr.Deref(component.Absent, false), DeployConfig: ecosystem.DeployConfig(component.DeployConfig), }) } @@ -65,8 +61,7 @@ func ConvertToComponentDTOs(components []domain.Component) []v2.Component { joinedComponentName := component.Name.String() var version *string if !component.Absent && component.Version != nil { - versionString := component.Version.String() - version = &versionString + version = ptr.To(component.Version.String()) } return v2.Component{ diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go index 65dc435d..d61eb989 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go @@ -1,26 +1,19 @@ package serializer import ( - "errors" - "fmt" - - "github.com/Masterminds/semver/v3" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" + "k8s.io/utils/ptr" ) func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDiff { var actualVersion, expectedVersion *string if domainModel.Actual.Version != nil { - actualVersionString := domainModel.Actual.Version.String() - actualVersion = &actualVersionString + actualVersion = ptr.To(domainModel.Actual.Version.String()) } if domainModel.Expected.Version != nil { - expectedVersionString := domainModel.Expected.Version.String() - expectedVersion = &expectedVersionString + expectedVersion = ptr.To(domainModel.Expected.Version.String()) } neededActions := domainModel.NeededActions @@ -45,51 +38,3 @@ func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDi NeededActions: componentActions, } } - -func convertToComponentDiffDomain(componentName string, dto crd.ComponentDiff) (domain.ComponentDiff, error) { - var actualVersion *semver.Version - var actualVersionErr error - if dto.Actual.Version != nil && *dto.Actual.Version != "" { - actualVersion, actualVersionErr = semver.NewVersion(*dto.Actual.Version) - if actualVersionErr != nil { - actualVersionErr = fmt.Errorf("failed to parse actual version %q: %w", *dto.Actual.Version, actualVersionErr) - } - } - - var expectedVersion *semver.Version - var expectedVersionErr error - if dto.Expected.Version != nil && *dto.Expected.Version != "" { - expectedVersion, expectedVersionErr = semver.NewVersion(*dto.Expected.Version) - if expectedVersionErr != nil { - expectedVersionErr = fmt.Errorf("failed to parse expected version %q: %w", *dto.Expected.Version, expectedVersionErr) - } - } - - neededActions := dto.NeededActions - componentActions := make([]domain.Action, 0, len(neededActions)) - for _, action := range neededActions { - componentActions = append(componentActions, domain.Action(action)) - } - - err := errors.Join(actualVersionErr, expectedVersionErr) - if err != nil { - return domain.ComponentDiff{}, fmt.Errorf("failed to convert component diff dto %q to domain model: %w", componentName, err) - } - - return domain.ComponentDiff{ - Name: common.SimpleComponentName(componentName), - Actual: domain.ComponentDiffState{ - Namespace: common.ComponentNamespace(dto.Actual.Namespace), - Version: actualVersion, - Absent: dto.Actual.Absent, - DeployConfig: ecosystem.DeployConfig(dto.Actual.DeployConfig), - }, - Expected: domain.ComponentDiffState{ - Namespace: common.ComponentNamespace(dto.Expected.Namespace), - Version: expectedVersion, - Absent: dto.Expected.Absent, - DeployConfig: ecosystem.DeployConfig(dto.Expected.DeployConfig), - }, - NeededActions: componentActions, - }, nil -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go index 3baef6f8..b11da904 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go @@ -3,12 +3,10 @@ package serializer import ( "testing" - "github.com/Masterminds/semver/v3" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var testNamespace = common.ComponentNamespace("k8s") @@ -56,88 +54,3 @@ func Test_convertToComponentDiffDTO(t *testing.T) { assert.Equal(t, expected, actual) }) } - -func Test_convertToComponentDiffDomain(t *testing.T) { - t.Run("should copy model diff to DTO diff - absent", func(t *testing.T) { - // given - diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - Expected: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &testSemverVersionHighRaw, DeployConfig: testDeployConfig}, - NeededActions: []crd.ComponentAction{domain.ActionInstall}, - } - - // when - actual, err := convertToComponentDiffDomain(testComponentName, diff) - - // then - require.NoError(t, err) - expected := domain.ComponentDiff{ - Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - Expected: domain.ComponentDiffState{Namespace: testNamespace, Version: testSemverVersionHigh, DeployConfig: testDeployConfig}, - NeededActions: []domain.Action{domain.ActionInstall}, - } - assert.Equal(t, expected, actual) - }) - t.Run("should copy model diff to DTO diff - present", func(t *testing.T) { - // given - diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &testSemverVersionHighRaw}, - Expected: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - NeededActions: []crd.ComponentAction{domain.ActionUninstall}, - } - - // when - actual, err := convertToComponentDiffDomain(testComponentName, diff) - - // then - require.NoError(t, err) - expected := domain.ComponentDiff{ - Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: testNamespace, Version: testSemverVersionHigh}, - Expected: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - NeededActions: []domain.Action{domain.ActionUninstall}, - } - assert.Equal(t, expected, actual) - }) - t.Run("should fail to parse version", func(t *testing.T) { - // given - versionABC := "a-b-c" - diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: "", Version: &versionABC}, - Expected: crd.ComponentDiffState{Namespace: "", Version: &versionABC}, - NeededActions: []crd.ComponentAction{domain.ActionUninstall}, - } - - // when - _, err := convertToComponentDiffDomain(testComponentName, diff) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to parse actual version") - assert.ErrorContains(t, err, "failed to parse expected version") - }) - t.Run("should accept dev version", func(t *testing.T) { - compVersion080dev := semver.MustParse("0.8.0-dev") - // given - Version080String := compVersion080dev.String() - diff := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Namespace: testNamespaceString, Version: &Version080String}, - Expected: crd.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - NeededActions: []crd.ComponentAction{domain.ActionUninstall}, - } - - // when - actual, err := convertToComponentDiffDomain(testComponentName, diff) - - // then - require.NoError(t, err) - expected := domain.ComponentDiff{ - Name: testComponentName, - Actual: domain.ComponentDiffState{Namespace: testNamespace, Version: compVersion080dev}, - Expected: domain.ComponentDiffState{Namespace: "", Version: nil, Absent: true}, - NeededActions: []domain.Action{domain.ActionUninstall}, - } - assert.Equal(t, expected, actual) - }) -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 78fe478b..64b3c1c1 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -4,6 +4,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" libconfig "github.com/cloudogu/k8s-registry-lib/config" + "k8s.io/utils/ptr" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" ) @@ -115,16 +116,6 @@ func convertToConfigEntriesDomain(config []v2.ConfigEntry) domain.ConfigEntries result := make([]domain.ConfigEntry, len(config)) for i, v2Entry := range config { - absent := false - if v2Entry.Absent != nil { - absent = *v2Entry.Absent - } - - sensitive := false - if v2Entry.Sensitive != nil { - sensitive = *v2Entry.Sensitive - } - var secretRef *domain.SensitiveValueRef if v2Entry.SecretRef != nil { secretRef = &domain.SensitiveValueRef{ @@ -135,9 +126,9 @@ func convertToConfigEntriesDomain(config []v2.ConfigEntry) domain.ConfigEntries result[i] = domain.ConfigEntry{ Key: libconfig.Key(v2Entry.Key), - Absent: absent, + Absent: ptr.Deref(v2Entry.Absent, false), Value: (*libconfig.Value)(v2Entry.Value), - Sensitive: sensitive, + Sensitive: ptr.Deref(v2Entry.Sensitive, false), SecretRef: secretRef, } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index cd45942e..dd565791 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -8,6 +8,7 @@ import ( "github.com/cloudogu/cesapp-lib/core" bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/utils/ptr" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" @@ -37,12 +38,7 @@ func ConvertDogus(dogus []bpv2.Dogu) ([]domain.Dogu, error) { } } result.Version = version - - absent := false - if dogu.Absent != nil { - absent = *dogu.Absent - } - result.Absent = absent + result.Absent = ptr.Deref(dogu.Absent, false) err = convertPlatformConfigFromDTOToDomain(&dogu, &result) if err != nil { @@ -122,15 +118,10 @@ func ConvertMaskDogus(dogus []bpv2.MaskDogu) ([]domain.MaskDogu, error) { } } - absent := false - if dogu.Absent != nil { - absent = *dogu.Absent - } - convertedDogus = append(convertedDogus, domain.MaskDogu{ Name: name, Version: version, - Absent: absent, + Absent: ptr.Deref(dogu.Absent, false), }) } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index ffe061b6..2be21aac 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -1,15 +1,9 @@ package serializer import ( - "errors" - "fmt" - - cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/cesapp-lib/core" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "k8s.io/apimachinery/pkg/api/resource" ) func convertToDoguDiffDTO(domainModel domain.DoguDiff) crd.DoguDiff { @@ -74,106 +68,3 @@ func convertAdditionalMountsToDoguDiffDTO(mounts []ecosystem.AdditionalMount) [] } return result } - -func convertToDoguDiffDomain(doguName string, dto crd.DoguDiff) (domain.DoguDiff, error) { - - actualState, actualStateErr := convertDoguDiffStateDomain(dto.Actual) - if actualStateErr != nil { - actualStateErr = fmt.Errorf("failed to convert actual dogu diff state: %w", actualStateErr) - } - expectedState, expectedStateErr := convertDoguDiffStateDomain(dto.Expected) - if expectedStateErr != nil { - expectedStateErr = fmt.Errorf("failed to convert expected dogu diff state: %w", expectedStateErr) - } - - err := errors.Join(actualStateErr, expectedStateErr) - if err != nil { - return domain.DoguDiff{}, fmt.Errorf("failed to convert dogu diff dto %q to domain model: %w", doguName, err) - } - - neededActions := dto.NeededActions - doguActions := make([]domain.Action, 0, len(neededActions)) - for _, action := range neededActions { - doguActions = append(doguActions, domain.Action(action)) - } - - return domain.DoguDiff{ - DoguName: cescommons.SimpleName(doguName), - Expected: expectedState, - Actual: actualState, - NeededActions: doguActions, - }, nil -} - -func convertDoguDiffStateDomain(dto crd.DoguDiffState) (domain.DoguDiffState, error) { - var errorList []error - - var version *core.Version - var err error - if dto.Version != nil && *dto.Version != "" { - var coreVersion core.Version - coreVersion, err = core.ParseVersion(*dto.Version) - version = &coreVersion - if err != nil { - errorList = append(errorList, fmt.Errorf("failed to parse version %q: %w", *dto.Version, err)) - } - } - - var minVolumeSize, maxBodySize *resource.Quantity - if dto.ResourceConfig != nil && dto.ResourceConfig.MinVolumeSize != nil { - minVolumeSizeStr := dto.ResourceConfig.MinVolumeSize - minVolumeSize, err = ecosystem.GetNonNilQuantityRef(*minVolumeSizeStr) - if err != nil { - errorList = append(errorList, fmt.Errorf("failed to parse minimum volume size %q: %w", *minVolumeSizeStr, err)) - } - } - - var reverseProxyConfig *ecosystem.ReverseProxyConfig - if dto.ReverseProxyConfig != nil { - if dto.ReverseProxyConfig.MaxBodySize != nil { - maxBodySizeStr := dto.ReverseProxyConfig.MaxBodySize - maxBodySize, err = ecosystem.GetQuantityReference(*maxBodySizeStr) - if err != nil { - errorList = append(errorList, fmt.Errorf("failed to parse maximum proxy body size %q: %w", *maxBodySizeStr, err)) - } - } - reverseProxyConfig = &ecosystem.ReverseProxyConfig{ - MaxBodySize: maxBodySize, - RewriteTarget: dto.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: dto.ReverseProxyConfig.AdditionalConfig, - } - } - - if len(errorList) != 0 { - return domain.DoguDiffState{}, errors.Join(errorList...) - } - - return domain.DoguDiffState{ - Namespace: cescommons.Namespace(dto.Namespace), - Version: version, - Absent: dto.Absent, - MinVolumeSize: minVolumeSize, - ReverseProxyConfig: reverseProxyConfig, - AdditionalMounts: convertAdditionalMountsToDoguDiffDomain(dto.AdditionalMounts), - }, nil -} - -func convertAdditionalMountsToDoguDiffDomain(mounts []crd.AdditionalMount) []ecosystem.AdditionalMount { - if len(mounts) == 0 { - // an empty slice and nil are serialized differently - // we want no entry instead of an empty json list if there are no mounts given - return nil - } - result := make([]ecosystem.AdditionalMount, len(mounts)) - - for index, mount := range mounts { - result[index] = ecosystem.AdditionalMount{ - SourceType: ecosystem.DataSourceType(mount.SourceType), - Name: mount.Name, - Volume: mount.Volume, - Subfolder: mount.Subfolder, - } - } - - return result -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index 77d76d29..5793ad6d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -8,7 +8,6 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_convertToDoguDiffStateDTO(t *testing.T) { @@ -68,98 +67,3 @@ func Test_convertToDoguDiffStateDTO(t *testing.T) { assert.Empty(t, cmp.Diff(want, result)) }) } - -func Test_convertDoguDiffStateDomain(t *testing.T) { - t.Run("should convert empty reverse proxy config", func(t *testing.T) { - // given - crdDiffState := crd.DoguDiffState{ - ReverseProxyConfig: &crd.ReverseProxyConfig{}, - } - // when - result, err := convertDoguDiffStateDomain(crdDiffState) - // then - require.NoError(t, err) - assert.NotNil(t, result.ReverseProxyConfig) - assert.Nil(t, result.ReverseProxyConfig.RewriteTarget) - assert.Nil(t, result.ReverseProxyConfig.AdditionalConfig) - assert.Nil(t, result.ReverseProxyConfig.MaxBodySize) - }) - - t.Run("should convert reverse proxy config", func(t *testing.T) { - // given - crdDiffState := crd.DoguDiffState{ - ReverseProxyConfig: &crd.ReverseProxyConfig{ - MaxBodySize: &proxyBodySizeString, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, - }, - } - // when - result, err := convertDoguDiffStateDomain(crdDiffState) - // then - want := domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ - MaxBodySize: &proxyBodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, - }, - } - - require.NoError(t, err) - assert.NotNil(t, result.ReverseProxyConfig) - assert.Empty(t, cmp.Diff(want, result)) - }) - - t.Run("should throw error on reverse proxy config convert error", func(t *testing.T) { - // given - wrongBodySize := "1Z" - crdDiffState := crd.DoguDiffState{ - ReverseProxyConfig: &crd.ReverseProxyConfig{ - MaxBodySize: &wrongBodySize, - }, - } - // when - result, err := convertDoguDiffStateDomain(crdDiffState) - - // then - require.Error(t, err) - assert.Equal(t, domain.DoguDiffState{}, result) - assert.ErrorContains(t, err, "failed to parse maximum proxy body size") - }) - - t.Run("should convert resource config", func(t *testing.T) { - // given - crdDiffState := crd.DoguDiffState{ - ResourceConfig: &crd.ResourceConfig{ - MinVolumeSize: &volumeSizeString, - }, - } - // when - result, err := convertDoguDiffStateDomain(crdDiffState) - - // then - want := domain.DoguDiffState{ - MinVolumeSize: &volumeSize, - } - require.NoError(t, err) - assert.NotNil(t, result.MinVolumeSize) - assert.Empty(t, cmp.Diff(want, result)) - }) - - t.Run("should return error on resource config convert error", func(t *testing.T) { - // given - wrongVolumeSize := "1Gu" - crdDiffState := crd.DoguDiffState{ - ResourceConfig: &crd.ResourceConfig{ - MinVolumeSize: &wrongVolumeSize, - }, - } - // when - result, err := convertDoguDiffStateDomain(crdDiffState) - - // then - require.Error(t, err) - assert.Equal(t, domain.DoguDiffState{}, result) - assert.ErrorContains(t, err, "failed to parse minimum volume size") - }) -} diff --git a/pkg/domain/config.go b/pkg/domain/config.go index 73c23b5f..ba3c44d6 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -178,6 +178,10 @@ func (config ConfigEntry) validate() error { errs = append(errs, fmt.Errorf("config entries can have either a value or a secretRef")) } + if hasSecretRef && !config.Sensitive { + errs = append(errs, fmt.Errorf("config entries with secret references have to be sensitive")) + } + return errors.Join(errs...) } diff --git a/pkg/domain/config_test.go b/pkg/domain/config_test.go index 9d2529c1..24dfd273 100644 --- a/pkg/domain/config_test.go +++ b/pkg/domain/config_test.go @@ -226,6 +226,27 @@ func TestDoguConfig_validate(t *testing.T) { err := config.validate("dogu1") assert.ErrorContains(t, err, "config entries can have either a value or a secretRef") }) + t.Run("No secret without sensitive", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + SecretRef: &SensitiveValueRef{}, + }, + } + err := config.validate("dogu1") + assert.ErrorContains(t, err, "config entries with secret references have to be sensitive") + }) + t.Run("secret with sensitive allowed", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + SecretRef: &SensitiveValueRef{}, + Sensitive: true, + }, + } + err := config.validate("dogu1") + assert.NoError(t, err) + }) } func TestConfig_validate(t *testing.T) { From 710a299b90867bbdfba180b9ed36342e03ac7417 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 10:20:26 +0200 Subject: [PATCH 086/119] #121 remove ReverseProxyConfig-Pointer from domain --- .../blueprintcr/v2/serializer/dogu.go | 15 ++++----- .../blueprintcr/v2/serializer/doguDiff.go | 2 +- .../v2/serializer/doguDiff_test.go | 11 +++---- .../blueprintcr/v2/serializer/dogu_test.go | 12 +++---- .../dogucr/doguInstallationRepo_test.go | 2 +- .../kubernetes/dogucr/doguSerializer.go | 14 +++------ .../kubernetes/dogucr/doguSerializer_test.go | 12 +++---- .../doguInstallationUseCase_test.go | 28 ++++++++--------- pkg/application/stateDiffUseCase_test.go | 4 +-- pkg/domain/blueprint_test.go | 2 +- pkg/domain/dogu.go | 10 +++--- pkg/domain/dogu_test.go | 4 +-- pkg/domain/ecosystem/doguInstallation.go | 15 ++------- pkg/domain/ecosystem/doguInstallation_test.go | 4 +-- pkg/domain/stateDiffDogu.go | 31 +++++-------------- pkg/domain/stateDiffDogu_test.go | 24 +++++++------- 16 files changed, 77 insertions(+), 113 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index dd565791..c590fadf 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -83,7 +83,7 @@ func convertPlatformConfigFromDTOToDomain(dtoDogu *bpv2.Dogu, domainDogu *domain } } - domainDogu.ReverseProxyConfig = &ecosystem.ReverseProxyConfig{ + domainDogu.ReverseProxyConfig = ecosystem.ReverseProxyConfig{ MaxBodySize: maxBodySize, RewriteTarget: dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, AdditionalConfig: dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, @@ -164,7 +164,7 @@ func ConvertToDoguDTOs(dogus []domain.Dogu) []bpv2.Dogu { } func convertPlatformConfigDTO(dogu domain.Dogu) *bpv2.PlatformConfig { - if dogu.ReverseProxyConfig == nil && dogu.MinVolumeSize == nil && len(dogu.AdditionalMounts) == 0 { + if dogu.ReverseProxyConfig.IsEmpty() && dogu.MinVolumeSize == nil && len(dogu.AdditionalMounts) == 0 { return nil } @@ -177,14 +177,11 @@ func convertPlatformConfigDTO(dogu domain.Dogu) *bpv2.PlatformConfig { } func convertReverseProxyConfigDTO(dogu domain.Dogu) *bpv2.ReverseProxyConfig { - config := bpv2.ReverseProxyConfig{} - if dogu.ReverseProxyConfig != nil { - config.RewriteTarget = dogu.ReverseProxyConfig.RewriteTarget - config.AdditionalConfig = dogu.ReverseProxyConfig.AdditionalConfig - config.MaxBodySize = ecosystem.GetQuantityString(dogu.ReverseProxyConfig.MaxBodySize) + return &bpv2.ReverseProxyConfig{ + RewriteTarget: dogu.ReverseProxyConfig.RewriteTarget, + AdditionalConfig: dogu.ReverseProxyConfig.AdditionalConfig, + MaxBodySize: ecosystem.GetQuantityString(dogu.ReverseProxyConfig.MaxBodySize), } - - return &config } func convertResourceConfigDTO(dogu domain.Dogu) *bpv2.ResourceConfig { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index 2be21aac..ac2e7610 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -27,7 +27,7 @@ func convertToDoguDiffStateDTO(domainModel domain.DoguDiffState) crd.DoguDiffSta } var reverseProxyConfig *crd.ReverseProxyConfig - if domainModel.ReverseProxyConfig != nil { + if !domainModel.ReverseProxyConfig.IsEmpty() { reverseProxyConfig = &crd.ReverseProxyConfig{ RewriteTarget: domainModel.ReverseProxyConfig.RewriteTarget, AdditionalConfig: domainModel.ReverseProxyConfig.AdditionalConfig, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index 5793ad6d..9568f918 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -11,24 +11,21 @@ import ( ) func Test_convertToDoguDiffStateDTO(t *testing.T) { - t.Run("should convert empty reverse proxy config", func(t *testing.T) { + t.Run("should convert empty reverse proxy config to nil", func(t *testing.T) { // given domainDiffState := domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{}, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{}, } // when result := convertToDoguDiffStateDTO(domainDiffState) // then - assert.NotNil(t, result.ReverseProxyConfig) - assert.Nil(t, result.ReverseProxyConfig.RewriteTarget) - assert.Nil(t, result.ReverseProxyConfig.AdditionalConfig) - assert.Nil(t, result.ReverseProxyConfig.MaxBodySize) + assert.Nil(t, result.ReverseProxyConfig) }) t.Run("should convert reverse proxy config", func(t *testing.T) { // given domainDiffState := domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go index 44447d02..46bf2856 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go @@ -68,19 +68,19 @@ func TestConvertDogus(t *testing.T) { { name: "dogu with max proxy body size", args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize}}}, wantErr: assert.NoError, }, { name: "dogu with proxy rewrite target", args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}, wantErr: assert.NoError, }, { name: "dogu with proxy additional config", args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}, wantErr: assert.NoError, }, { @@ -263,7 +263,7 @@ func TestConvertToDoguDTOs(t *testing.T) { }, { name: "ok", - args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, + args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, want: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}, ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, }, { @@ -273,7 +273,7 @@ func TestConvertToDoguDTOs(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -319,7 +319,7 @@ func TestConvertToDoguDTOs(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, AdditionalMounts: nil, }}}, want: []bpv2.Dogu{{ diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index 1a09f6c3..af3b5ef5 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -79,7 +79,7 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { UpgradeConfig: ecosystem.UpgradeConfig{}, PersistenceContext: persistenceContext, MinVolumeSize: &quantity2, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index 227e25b4..f1258050 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -80,8 +80,8 @@ func parseAdditionalMounts(mounts []v2.DataMount) []ecosystem.AdditionalMount { return result } -func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) (*ecosystem.ReverseProxyConfig, error) { - reverseProxyConfig := &ecosystem.ReverseProxyConfig{} +func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) (ecosystem.ReverseProxyConfig, error) { + reverseProxyConfig := ecosystem.ReverseProxyConfig{} reverseProxyBodySize, bodySizeOk := annotations[ecosystem.NginxIngressAnnotationBodySize] if bodySizeOk { @@ -91,7 +91,7 @@ func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) // See: [Documentation](https://nginx.org/en/docs/syntax.html) quantity, err := resource.ParseQuantity(reverseProxyBodySize) if err != nil { - return nil, domainservice.NewInternalError(err, "failed to parse quantity %q", reverseProxyBodySize) + return ecosystem.ReverseProxyConfig{}, domainservice.NewInternalError(err, "failed to parse quantity %q", reverseProxyBodySize) } reverseProxyConfig.MaxBodySize = &quantity } @@ -109,10 +109,6 @@ func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) reverseProxyConfig.AdditionalConfig = additionalConfig } - if reverseProxyConfig.IsEmpty() { - return nil, nil - } - return reverseProxyConfig, nil } @@ -176,8 +172,8 @@ func toDoguCRAdditionalMounts(mounts []ecosystem.AdditionalMount) []v2.DataMount return result } -func getNginxIngressAnnotations(config *ecosystem.ReverseProxyConfig) map[string]string { - if config == nil { +func getNginxIngressAnnotations(config ecosystem.ReverseProxyConfig) map[string]string { + if config.IsEmpty() { return nil } diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 7a0ffd90..a2f7d196 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -524,7 +524,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { AllowNamespaceSwitch: true, }, MinVolumeSize: &quantity2, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -588,7 +588,7 @@ func Test_getNginxIngressAnnotations(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, getNginxIngressAnnotations(&tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) + assert.Equalf(t, tt.want, getNginxIngressAnnotations(tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) }) } } @@ -601,7 +601,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { tests := []struct { name string args args - want *ecosystem.ReverseProxyConfig + want ecosystem.ReverseProxyConfig wantErr assert.ErrorAssertionFunc }{ { @@ -613,7 +613,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { "nginx.ingress.kubernetes.io/configuration-snippet": "additional", }, }, - want: &ecosystem.ReverseProxyConfig{ + want: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -629,7 +629,7 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { "nginx.ingress.kubernetes.io/proxy-body-size": "1GG", }, }, - want: &ecosystem.ReverseProxyConfig{}, + want: ecosystem.ReverseProxyConfig{}, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { assert.Error(t, err) assert.ErrorContains(t, err, "failed to parse quantity \"1GG\"") @@ -674,7 +674,7 @@ func Test_getNginxIngressAnnotations1(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, getNginxIngressAnnotations(&tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) + assert.Equalf(t, tt.want, getNginxIngressAnnotations(tt.args.config), "getNginxIngressAnnotations(%v)", tt.args.config) }) } } diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 2bc13887..7024a59e 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -90,7 +90,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT(). Create(testCtx, - ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, &proxyConfig, additionalMounts)). + ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, proxyConfig, additionalMounts)). Return(nil) sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) @@ -110,7 +110,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &proxyConfig, + ReverseProxyConfig: proxyConfig, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -280,14 +280,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedProxyBodySize := resource.MustParse("3G") expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, }, } @@ -303,7 +303,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, }, }, @@ -321,14 +321,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedTarget := &rewriteTarget expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ RewriteTarget: expectedTarget, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ RewriteTarget: nil, }, } @@ -344,7 +344,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ RewriteTarget: expectedTarget, }, }, @@ -362,14 +362,14 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedAdditionalConfig := &additionalConfig expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ AdditionalConfig: expectedAdditionalConfig, }, } dogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ AdditionalConfig: nil, }, } @@ -385,7 +385,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { domain.DoguDiff{ DoguName: "postgresql", Expected: domain.DoguDiffState{ - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ AdditionalConfig: expectedAdditionalConfig, }, }, @@ -480,7 +480,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Name: postgresqlQualifiedName, Version: version3212, MinVolumeSize: &expectedVolumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, RewriteTarget: expectedTarget, AdditionalConfig: expectedAdditionalConfig, @@ -491,7 +491,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Name: postgresqlQualifiedName, Version: version3211, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: nil, AdditionalConfig: nil, @@ -511,7 +511,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Expected: domain.DoguDiffState{ Version: &version3212, MinVolumeSize: &expectedVolumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &expectedProxyBodySize, RewriteTarget: expectedTarget, AdditionalConfig: expectedAdditionalConfig, diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index aa4d96fb..fc30b48a 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -247,7 +247,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Version: mustParseVersionToPtr(t, "2.9.0"), Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, @@ -302,7 +302,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Version: mustParseVersionToPtr(t, "2.9.0"), Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index 1ce6cc76..fc900f93 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -100,7 +100,7 @@ func Test_validateDogus_multipleErrors(t *testing.T) { wrongBodySize := resource.MustParse("1Ki") dogus := []Dogu{ {Name: officialDogu1}, - {Name: officialDogu2, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &wrongBodySize}}, + {Name: officialDogu2, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &wrongBodySize}}, } blueprint := Blueprint{Dogus: dogus} diff --git a/pkg/domain/dogu.go b/pkg/domain/dogu.go index 4e7f244f..a2a31797 100644 --- a/pkg/domain/dogu.go +++ b/pkg/domain/dogu.go @@ -25,7 +25,7 @@ type Dogu struct { // Reducing this value below the actual volume size has no impact as we do not support downsizing. MinVolumeSize *ecosystem.VolumeSize // ReverseProxyConfig defines configuration for the ecosystem reverse proxy. This field is optional. - ReverseProxyConfig *ecosystem.ReverseProxyConfig + ReverseProxyConfig ecosystem.ReverseProxyConfig // AdditionalMounts provides the possibility to mount additional data into the dogu. AdditionalMounts []ecosystem.AdditionalMount } @@ -41,11 +41,9 @@ func (dogu Dogu) validate() error { // minVolumeSize is already checked while unmarshalling json/yaml // Nginx only supports quantities in Decimal SI. This check can be removed if the dogu-operator implements an abstraction for the body size. - if dogu.ReverseProxyConfig != nil { - maxBodySize := dogu.ReverseProxyConfig.MaxBodySize - if maxBodySize != nil && !maxBodySize.IsZero() && maxBodySize.Format != resource.DecimalSI { - errorList = append(errorList, fmt.Errorf("dogu proxy body size is not in Decimal SI (\"M\" or \"G\"): %s", dogu.Name)) - } + maxBodySize := dogu.ReverseProxyConfig.MaxBodySize + if maxBodySize != nil && !maxBodySize.IsZero() && maxBodySize.Format != resource.DecimalSI { + errorList = append(errorList, fmt.Errorf("dogu proxy body size is not in Decimal SI (\"M\" or \"G\"): %s", dogu.Name)) } for _, mount := range dogu.AdditionalMounts { diff --git a/pkg/domain/dogu_test.go b/pkg/domain/dogu_test.go index 5d56aff0..ce7b8a8f 100644 --- a/pkg/domain/dogu_test.go +++ b/pkg/domain/dogu_test.go @@ -39,7 +39,7 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { t.Run("error on invalid proxy body size format", func(t *testing.T) { // given parse := resource.MustParse("1Mi") - dogu := Dogu{Name: officialDogu1, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &parse}} + dogu := Dogu{Name: officialDogu1, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &parse}} // when err := dogu.validate() // then @@ -59,7 +59,7 @@ func Test_TargetDogu_validate_ProxySizeFormat(t *testing.T) { t.Run("no error on zero size quantity", func(t *testing.T) { // given zeroQuantity := resource.MustParse("0") - dogu := Dogu{Name: officialDogu1, Version: &version123, ReverseProxyConfig: &ecosystem.ReverseProxyConfig{MaxBodySize: &zeroQuantity}} + dogu := Dogu{Name: officialDogu1, Version: &version123, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &zeroQuantity}} // when err := dogu.validate() // then diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index d6c91fc5..644b12fa 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -34,7 +34,7 @@ type DoguInstallation struct { // storage is needed. MinVolumeSize *VolumeSize // ReverseProxyConfig defines configuration for the ecosystem reverse proxy. This field is optional. - ReverseProxyConfig *ReverseProxyConfig + ReverseProxyConfig ReverseProxyConfig // AdditionalMounts provides the possibility to mount additional data into the dogu. AdditionalMounts []AdditionalMount } @@ -54,7 +54,7 @@ type ReverseProxyConfig struct { } func (r *ReverseProxyConfig) IsEmpty() bool { - return r == nil || (r.MaxBodySize == nil && r.RewriteTarget == nil && r.AdditionalConfig == nil) + return r.MaxBodySize == nil && r.RewriteTarget == nil && r.AdditionalConfig == nil } // UpgradeConfig contains configuration hints regarding aspects during the upgrade of dogus. @@ -95,7 +95,7 @@ func InstallDogu( name cescommons.QualifiedName, version *core.Version, minVolumeSize *VolumeSize, - reverseProxyConfig *ReverseProxyConfig, + reverseProxyConfig ReverseProxyConfig, additionalMounts []AdditionalMount) *DoguInstallation { doguVersion := core.Version{} @@ -144,9 +144,6 @@ func (dogu *DoguInstallation) SwitchNamespace(newNamespace cescommons.Namespace, } func (dogu *DoguInstallation) UpdateProxyBodySize(value *BodySize) { - if dogu.ReverseProxyConfig == nil { - dogu.ReverseProxyConfig = &ReverseProxyConfig{} - } dogu.ReverseProxyConfig.MaxBodySize = value } @@ -155,16 +152,10 @@ func (dogu *DoguInstallation) UpdateMinVolumeSize(size *VolumeSize) { } func (dogu *DoguInstallation) UpdateProxyRewriteTarget(value RewriteTarget) { - if dogu.ReverseProxyConfig == nil { - dogu.ReverseProxyConfig = &ReverseProxyConfig{} - } dogu.ReverseProxyConfig.RewriteTarget = value } func (dogu *DoguInstallation) UpdateProxyAdditionalConfig(value AdditionalConfig) { - if dogu.ReverseProxyConfig == nil { - dogu.ReverseProxyConfig = &ReverseProxyConfig{} - } dogu.ReverseProxyConfig.AdditionalConfig = value } diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index 432c8472..55ea0495 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -27,7 +27,7 @@ func TestInstallDogu(t *testing.T) { postgresqlQualifiedName, &version1231, &volumeSize, - &ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, []AdditionalMount{ { SourceType: DataSourceConfigMap, @@ -42,7 +42,7 @@ func TestInstallDogu(t *testing.T) { Version: version1231, UpgradeConfig: UpgradeConfig{AllowNamespaceSwitch: false}, MinVolumeSize: &volumeSize, - ReverseProxyConfig: &ReverseProxyConfig{ + ReverseProxyConfig: ReverseProxyConfig{ MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig, diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index 6e219b91..acd2adf6 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -36,7 +36,7 @@ type DoguDiffState struct { Version *core.Version Absent bool MinVolumeSize *ecosystem.VolumeSize - ReverseProxyConfig *ecosystem.ReverseProxyConfig + ReverseProxyConfig ecosystem.ReverseProxyConfig AdditionalMounts []ecosystem.AdditionalMount } @@ -194,19 +194,8 @@ func appendActionForReverseProxyConfig(neededActions []Action, expected DoguDiff neededActions = appendActionForProxyBodySizes(neededActions, exp, act) - switch { - case exp == nil && act == nil: - // both nil → nothing to do - - case exp == nil || act == nil: - // one nil → both fields need updating - neededActions = append(neededActions, - ActionUpdateDoguProxyRewriteTarget, - ActionUpdateDoguProxyAdditionalConfig, - ) - - default: - // both non-nil → compare fields + // both empty → nothing to do + if !(exp.IsEmpty() && act.IsEmpty()) { if exp.RewriteTarget != act.RewriteTarget { neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) } @@ -214,6 +203,7 @@ func appendActionForReverseProxyConfig(neededActions []Action, expected DoguDiff neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) } } + return neededActions } @@ -229,16 +219,11 @@ func appendActionForMinVolumeSize(actions []Action, expectedSize *ecosystem.Volu func appendActionForProxyBodySizes( actions []Action, - expectedReverseProxyConfig *ecosystem.ReverseProxyConfig, - actualReverseProxyConfig *ecosystem.ReverseProxyConfig, + expectedReverseProxyConfig ecosystem.ReverseProxyConfig, + actualReverseProxyConfig ecosystem.ReverseProxyConfig, ) []Action { - var expectedProxyBodySize, actualProxyBodySize *ecosystem.BodySize - if actualReverseProxyConfig != nil { - actualProxyBodySize = actualReverseProxyConfig.MaxBodySize - } - if expectedReverseProxyConfig != nil { - expectedProxyBodySize = expectedReverseProxyConfig.MaxBodySize - } + actualProxyBodySize := actualReverseProxyConfig.MaxBodySize + expectedProxyBodySize := expectedReverseProxyConfig.MaxBodySize if expectedProxyBodySize == nil && actualProxyBodySize == nil { return actions diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index a273a8d2..d2e24309 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -221,7 +221,7 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, AdditionalConfig: &additionalConfig, RewriteTarget: &rewriteConfig, @@ -244,7 +244,7 @@ func Test_determineDoguDiff(t *testing.T) { Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, AdditionalConfig: &additionalConfig, RewriteTarget: &rewriteConfig, @@ -304,14 +304,14 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, @@ -321,14 +321,14 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -341,14 +341,14 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity10MPtr, }, }, @@ -358,14 +358,14 @@ func Test_determineDoguDiff(t *testing.T) { Actual: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity10MPtr, }, }, Expected: DoguDiffState{ Namespace: officialNamespace, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: quantity100MPtr, }, }, @@ -378,14 +378,14 @@ func Test_determineDoguDiff(t *testing.T) { blueprintDogu: &Dogu{ Name: officialNexus, Version: &version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, installedDogu: &ecosystem.DoguInstallation{ Name: officialNexus, Version: version3212, - ReverseProxyConfig: &ecosystem.ReverseProxyConfig{ + ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: nil, }, }, From 9c9200df317aca9e05ff0a9afe11807d67e26b28 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 10:33:45 +0200 Subject: [PATCH 087/119] #121 remove Subfolder-Pointer from domain --- .../v2/serializer/blueprint_test.go | 2 +- .../blueprintcr/v2/serializer/dogu.go | 8 +++- .../blueprintcr/v2/serializer/doguDiff.go | 6 ++- .../blueprintcr/v2/serializer/dogu_test.go | 8 ++-- .../v2/serializer/stateDiff_test.go | 4 +- .../kubernetes/dogucr/doguSerializer.go | 8 +--- .../kubernetes/dogucr/doguSerializer_test.go | 12 ++--- .../doguInstallationUseCase_test.go | 16 +++---- pkg/application/stateDiffUseCase_test.go | 4 +- pkg/domain/blueprintSpec_test.go | 2 +- pkg/domain/dogu.go | 2 +- pkg/domain/dogu_test.go | 6 +-- pkg/domain/ecosystem/doguInstallation.go | 3 +- pkg/domain/ecosystem/doguInstallation_test.go | 4 +- pkg/domain/stateDiffDogu_test.go | 44 +++++++++---------- 15 files changed, 66 insertions(+), 63 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index 1e28b1b0..2b67f274 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -231,7 +231,7 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "config", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index c590fadf..45362ed8 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -140,7 +140,7 @@ func convertAdditionalMountsFromDTOToDomain(mounts []bpv2.AdditionalMount) []eco SourceType: ecosystem.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: m.Subfolder, + Subfolder: ptr.Deref(m.Subfolder, ""), }) } @@ -194,11 +194,15 @@ func convertResourceConfigDTO(dogu domain.Dogu) *bpv2.ResourceConfig { func convertAdditionalMountsConfigDTO(dogu domain.Dogu) []bpv2.AdditionalMount { var config []bpv2.AdditionalMount for _, m := range dogu.AdditionalMounts { + var subfolder *string + if m.Subfolder != "" { + subfolder = &m.Subfolder + } config = append(config, bpv2.AdditionalMount{ SourceType: bpv2.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: m.Subfolder, + Subfolder: subfolder, }) } return config diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index ac2e7610..4747c658 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -59,11 +59,15 @@ func convertAdditionalMountsToDoguDiffDTO(mounts []ecosystem.AdditionalMount) [] } result := make([]crd.AdditionalMount, len(mounts)) for index, mount := range mounts { + var subfolder *string + if mount.Subfolder != "" { + subfolder = &mount.Subfolder + } result[index] = crd.AdditionalMount{ SourceType: crd.DataSourceType(mount.SourceType), Name: mount.Name, Volume: mount.Volume, - Subfolder: mount.Subfolder, + Subfolder: subfolder, } } return result diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go index 46bf2856..91e678bf 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go @@ -144,13 +144,13 @@ func TestConvertDogus(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }}}, wantErr: assert.NoError, @@ -279,13 +279,13 @@ func TestConvertToDoguDTOs(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configMap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "sec", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }}}, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index ede5e0f8..edb39a9e 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -231,7 +231,7 @@ func TestConvertToDTO(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &testSubfolderStr, + Subfolder: testSubfolderStr, }, }, }, @@ -244,7 +244,7 @@ func TestConvertToDTO(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "secret", Volume: "volume2", - Subfolder: &testSubfolderStr2, + Subfolder: testSubfolderStr2, }, }, }, diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index f1258050..4022a375 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -74,7 +74,7 @@ func parseAdditionalMounts(mounts []v2.DataMount) []ecosystem.AdditionalMount { SourceType: ecosystem.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: &m.Subfolder, + Subfolder: m.Subfolder, }) } return result @@ -158,15 +158,11 @@ func toDoguCR(dogu *ecosystem.DoguInstallation) *v2.Dogu { func toDoguCRAdditionalMounts(mounts []ecosystem.AdditionalMount) []v2.DataMount { var result []v2.DataMount for _, m := range mounts { - subfolder := "" - if m.Subfolder != nil { - subfolder = *m.Subfolder - } result = append(result, v2.DataMount{ SourceType: v2.DataSourceType(m.SourceType), Name: m.Name, Volume: m.Volume, - Subfolder: subfolder, + Subfolder: m.Subfolder, }) } return result diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index a2f7d196..811c9c4b 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -209,13 +209,13 @@ func Test_parseDoguCR(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -368,13 +368,13 @@ func Test_toDoguCR(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -530,7 +530,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { AdditionalConfig: &additionalConfig, }, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: subfolder}, }, }, want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"2Gi\"},\"supportMode\":false,\"pauseReconciliation\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":{\"nginx.ingress.kubernetes.io/configuration-snippet\":\"additional\",\"nginx.ingress.kubernetes.io/proxy-body-size\":\"1G\",\"nginx.ingress.kubernetes.io/rewrite-target\":\"/\"},\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", @@ -547,7 +547,7 @@ func Test_toDoguCRPatchBytes(t *testing.T) { AllowNamespaceSwitch: true, }, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: &subfolder}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: subfolder}, }, }, want: "{\"spec\":{\"name\":\"official/postgresql\",\"version\":\"3.2.1-4\",\"resources\":{\"dataVolumeSize\":\"\",\"minDataVolumeSize\":\"0\"},\"supportMode\":false,\"pauseReconciliation\":false,\"upgradeConfig\":{\"allowNamespaceSwitch\":true,\"forceUpgrade\":false},\"additionalIngressAnnotations\":null,\"additionalMounts\":[{\"sourceType\":\"ConfigMap\",\"name\":\"test\",\"volume\":\"volume\",\"subfolder\":\"subfolder\"}]}}", diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 7024a59e..d05d5ded 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -83,7 +83,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, } @@ -116,7 +116,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, @@ -407,13 +407,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, } @@ -425,13 +425,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, } @@ -444,13 +444,13 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index fc30b48a..ade48c78 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -257,7 +257,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, @@ -312,7 +312,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index e7d546fd..1cf116da 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -259,7 +259,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { Version: &version3211, Absent: false, AdditionalMounts: []ecosystem.AdditionalMount{ - {SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", Subfolder: &subfolder}, + {SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", Subfolder: subfolder}, }, }, } diff --git a/pkg/domain/dogu.go b/pkg/domain/dogu.go index a2a31797..751b16ed 100644 --- a/pkg/domain/dogu.go +++ b/pkg/domain/dogu.go @@ -55,7 +55,7 @@ func (dogu Dogu) validate() error { dogu.Name, )) } - if mount.Subfolder != nil && strings.HasPrefix(*mount.Subfolder, "/") { + if strings.HasPrefix(mount.Subfolder, "/") { errorList = append(errorList, fmt.Errorf("dogu additional mounts Subfolder must be a relative path : %s", dogu.Name)) } } diff --git a/pkg/domain/dogu_test.go b/pkg/domain/dogu_test.go index ce7b8a8f..09dfa10e 100644 --- a/pkg/domain/dogu_test.go +++ b/pkg/domain/dogu_test.go @@ -76,7 +76,7 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", - Subfolder: &subfolder, + Subfolder: subfolder, }, }} // when @@ -92,7 +92,7 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { SourceType: "unsupportedType", Name: "html-config", Volume: "customhtml", - Subfolder: &subfolder, + Subfolder: subfolder, }, }} // when @@ -110,7 +110,7 @@ func Test_TargetDogu_validate_AdditionalMounts(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "html-config", Volume: "customhtml", - Subfolder: &absoluteSubfolder, + Subfolder: absoluteSubfolder, }, }} // when diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 644b12fa..9f0b308d 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -86,8 +86,7 @@ type AdditionalMount struct { // Volume is the name of the volume to which the data should be mounted. It is defined in the respective dogu.json. Volume string // Subfolder defines a subfolder in which the data should be put within the volume. - // +optional - Subfolder *string + Subfolder string } // InstallDogu is a factory for new DoguInstallation's. diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index 55ea0495..952a4423 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -33,7 +33,7 @@ func TestInstallDogu(t *testing.T) { SourceType: DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, ) @@ -52,7 +52,7 @@ func TestInstallDogu(t *testing.T) { SourceType: DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, dogu) diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index d2e24309..ef3e5dd6 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -403,13 +403,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -421,13 +421,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -445,13 +445,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, @@ -463,13 +463,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -487,7 +487,7 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, @@ -499,13 +499,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -520,13 +520,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -538,7 +538,7 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, }, }, @@ -556,13 +556,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder3, + Subfolder: subfolder3, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -574,13 +574,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -595,13 +595,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder, + Subfolder: subfolder, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, @@ -613,13 +613,13 @@ func Test_determineDoguDiff(t *testing.T) { SourceType: ecosystem.DataSourceConfigMap, Name: "configmap", Volume: "volume", - Subfolder: &subfolder3, + Subfolder: subfolder3, }, { SourceType: ecosystem.DataSourceSecret, Name: "secret", Volume: "secvolume", - Subfolder: &subfolder2, + Subfolder: subfolder2, }, }, }, From 42904c482bfd0a7cb3ee2c53096e0eced1d7ff74 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 11:24:21 +0200 Subject: [PATCH 088/119] #121 remove rewriteTarget- & additionalConfig-Pointer from domain --- .../blueprintcr/v2/serializer/dogu.go | 15 +++++++--- .../blueprintcr/v2/serializer/doguDiff.go | 11 +++++-- .../v2/serializer/doguDiff_test.go | 4 +-- .../blueprintcr/v2/serializer/dogu_test.go | 10 +++---- .../dogucr/doguInstallationRepo_test.go | 4 +-- .../kubernetes/dogucr/doguSerializer.go | 23 ++++---------- .../kubernetes/dogucr/doguSerializer_test.go | 12 ++++---- .../doguInstallationUseCase_test.go | 30 ++++++++----------- pkg/application/stateDiffUseCase_test.go | 8 ++--- pkg/domain/ecosystem/doguInstallation.go | 2 +- pkg/domain/ecosystem/doguInstallation_test.go | 14 ++++----- pkg/domain/ecosystem/reverseProxyConfig.go | 4 +-- pkg/domain/stateDiffDogu_test.go | 8 ++--- 13 files changed, 71 insertions(+), 74 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go index 45362ed8..bb560faf 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu.go @@ -85,8 +85,8 @@ func convertPlatformConfigFromDTOToDomain(dtoDogu *bpv2.Dogu, domainDogu *domain domainDogu.ReverseProxyConfig = ecosystem.ReverseProxyConfig{ MaxBodySize: maxBodySize, - RewriteTarget: dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, + RewriteTarget: ecosystem.RewriteTarget(ptr.Deref(dtoDogu.PlatformConfig.ReverseProxyConfig.RewriteTarget, "")), + AdditionalConfig: ecosystem.AdditionalConfig(ptr.Deref(dtoDogu.PlatformConfig.ReverseProxyConfig.AdditionalConfig, "")), } } @@ -177,9 +177,16 @@ func convertPlatformConfigDTO(dogu domain.Dogu) *bpv2.PlatformConfig { } func convertReverseProxyConfigDTO(dogu domain.Dogu) *bpv2.ReverseProxyConfig { + var rewriteTarget, additionalConfig *string + if dogu.ReverseProxyConfig.RewriteTarget != "" { + rewriteTarget = (*string)(&dogu.ReverseProxyConfig.RewriteTarget) + } + if dogu.ReverseProxyConfig.AdditionalConfig != "" { + additionalConfig = (*string)(&dogu.ReverseProxyConfig.AdditionalConfig) + } return &bpv2.ReverseProxyConfig{ - RewriteTarget: dogu.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: dogu.ReverseProxyConfig.AdditionalConfig, + RewriteTarget: rewriteTarget, + AdditionalConfig: additionalConfig, MaxBodySize: ecosystem.GetQuantityString(dogu.ReverseProxyConfig.MaxBodySize), } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go index 4747c658..8920241d 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff.go @@ -28,9 +28,16 @@ func convertToDoguDiffStateDTO(domainModel domain.DoguDiffState) crd.DoguDiffSta var reverseProxyConfig *crd.ReverseProxyConfig if !domainModel.ReverseProxyConfig.IsEmpty() { + var rewriteTarget, additionalConfig *string + if domainModel.ReverseProxyConfig.RewriteTarget != "" { + rewriteTarget = (*string)(&domainModel.ReverseProxyConfig.RewriteTarget) + } + if domainModel.ReverseProxyConfig.AdditionalConfig != "" { + additionalConfig = (*string)(&domainModel.ReverseProxyConfig.AdditionalConfig) + } reverseProxyConfig = &crd.ReverseProxyConfig{ - RewriteTarget: domainModel.ReverseProxyConfig.RewriteTarget, - AdditionalConfig: domainModel.ReverseProxyConfig.AdditionalConfig, + RewriteTarget: rewriteTarget, + AdditionalConfig: additionalConfig, MaxBodySize: ecosystem.GetQuantityString(domainModel.ReverseProxyConfig.MaxBodySize), } } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go index 9568f918..5c851d93 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/doguDiff_test.go @@ -27,8 +27,8 @@ func Test_convertToDoguDiffStateDTO(t *testing.T) { domainDiffState := domain.DoguDiffState{ ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, } // when diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go index 91e678bf..907e6c75 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/dogu_test.go @@ -74,13 +74,13 @@ func TestConvertDogus(t *testing.T) { { name: "dogu with proxy rewrite target", args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{RewriteTarget: &rewriteTarget}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{RewriteTarget: ecosystem.RewriteTarget(rewriteTarget)}}}, wantErr: assert.NoError, }, { name: "dogu with proxy additional config", args: args{dogus: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ReverseProxyConfig: &bpv2.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}}}, - want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{AdditionalConfig: &additionalConfig}}}, + want: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, ReverseProxyConfig: ecosystem.ReverseProxyConfig{AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig)}}}, wantErr: assert.NoError, }, { @@ -263,7 +263,7 @@ func TestConvertToDoguDTOs(t *testing.T) { }, { name: "ok", - args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, + args: args{dogus: []domain.Dogu{{Name: cescommons.QualifiedName{Namespace: "official", SimpleName: "postgres"}, Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig)}}}}, want: []bpv2.Dogu{{Name: "official/postgres", Version: &version3211.Raw, Absent: &falseVar, PlatformConfig: &bpv2.PlatformConfig{ResourceConfig: &bpv2.ResourceConfig{MinVolumeSize: &volumeSizeString}, ReverseProxyConfig: &bpv2.ReverseProxyConfig{MaxBodySize: &proxyBodySizeString, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}}}}, }, { @@ -273,7 +273,7 @@ func TestConvertToDoguDTOs(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig)}, AdditionalMounts: []ecosystem.AdditionalMount{ { SourceType: ecosystem.DataSourceConfigMap, @@ -319,7 +319,7 @@ func TestConvertToDoguDTOs(t *testing.T) { Version: &version3211, Absent: false, MinVolumeSize: &volumeSize, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig)}, AdditionalMounts: nil, }}}, want: []bpv2.Dogu{{ diff --git a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go index af3b5ef5..1ad5d0af 100644 --- a/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguInstallationRepo_test.go @@ -81,8 +81,8 @@ func Test_doguInstallationRepo_GetByName(t *testing.T) { MinVolumeSize: &quantity2, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, }, dogu) }) diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer.go b/pkg/adapter/kubernetes/dogucr/doguSerializer.go index 4022a375..54c70171 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer.go @@ -96,18 +96,8 @@ func parseDoguAdditionalIngressAnnotationsCR(annotations v2.IngressAnnotations) reverseProxyConfig.MaxBodySize = &quantity } - var rewriteTarget, additionalConfig *string - if annotations[ecosystem.NginxIngressAnnotationRewriteTarget] != "" { - rewriteTargetString := annotations[ecosystem.NginxIngressAnnotationRewriteTarget] - rewriteTarget = &rewriteTargetString - reverseProxyConfig.RewriteTarget = rewriteTarget - } - - if annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] != "" { - additionalConfigString := annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] - additionalConfig = &additionalConfigString - reverseProxyConfig.AdditionalConfig = additionalConfig - } + reverseProxyConfig.RewriteTarget = ecosystem.RewriteTarget(annotations[ecosystem.NginxIngressAnnotationRewriteTarget]) + reverseProxyConfig.AdditionalConfig = ecosystem.AdditionalConfig(annotations[ecosystem.NginxIngressAnnotationAdditionalConfig]) return reverseProxyConfig, nil } @@ -180,14 +170,13 @@ func getNginxIngressAnnotations(config ecosystem.ReverseProxyConfig) map[string] } rewriteTarget := config.RewriteTarget - if rewriteTarget != nil && *rewriteTarget != "" { - annotations[ecosystem.NginxIngressAnnotationRewriteTarget] = *rewriteTarget - + if rewriteTarget != "" { + annotations[ecosystem.NginxIngressAnnotationRewriteTarget] = string(rewriteTarget) } additionalConfig := config.AdditionalConfig - if additionalConfig != nil && *additionalConfig != "" { - annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] = *additionalConfig + if additionalConfig != "" { + annotations[ecosystem.NginxIngressAnnotationAdditionalConfig] = string(additionalConfig) } // Use nil here to delete existing annotation from the cr. diff --git a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go index 811c9c4b..18714fcc 100644 --- a/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go +++ b/pkg/adapter/kubernetes/dogucr/doguSerializer_test.go @@ -526,8 +526,8 @@ func Test_toDoguCRPatchBytes(t *testing.T) { MinVolumeSize: &quantity2, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, AdditionalMounts: []ecosystem.AdditionalMount{ {SourceType: ecosystem.DataSourceConfigMap, Name: "test", Volume: "volume", Subfolder: subfolder}, @@ -615,8 +615,8 @@ func Test_parseDoguAdditionalIngressAnnotationsCR(t *testing.T) { }, want: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity1, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { return err == nil @@ -662,8 +662,8 @@ func Test_getNginxIngressAnnotations1(t *testing.T) { name: "should parse config", args: args{config: ecosystem.ReverseProxyConfig{ MaxBodySize: &quantity, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }}, want: map[string]string{ "nginx.ingress.kubernetes.io/proxy-body-size": "1M", diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index d05d5ded..4be02427 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -75,8 +75,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { bodySize := resource.MustParse("2G") proxyConfig := ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), } additionalMounts := []ecosystem.AdditionalMount{ { @@ -318,19 +318,17 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { }) t.Run("action update proxy rewrite target", func(t *testing.T) { - expectedTarget := &rewriteTarget + expectedTarget := ecosystem.RewriteTarget(rewriteTarget) expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - RewriteTarget: expectedTarget, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), }, } dogu := &ecosystem.DoguInstallation{ - Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - RewriteTarget: nil, - }, + Name: postgresqlQualifiedName, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{}, } doguRepoMock := newMockDoguInstallationRepository(t) @@ -359,7 +357,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { }) t.Run("action update proxy additional config", func(t *testing.T) { - expectedAdditionalConfig := &additionalConfig + expectedAdditionalConfig := ecosystem.AdditionalConfig(additionalConfig) expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ @@ -368,10 +366,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { } dogu := &ecosystem.DoguInstallation{ - Name: postgresqlQualifiedName, - ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - AdditionalConfig: nil, - }, + Name: postgresqlQualifiedName, + ReverseProxyConfig: ecosystem.ReverseProxyConfig{}, } doguRepoMock := newMockDoguInstallationRepository(t) @@ -474,8 +470,8 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { expectedVolumeSize := resource.MustParse("3Gi") proxyBodySize := resource.MustParse("2G") expectedProxyBodySize := resource.MustParse("3G") - expectedTarget := &rewriteTarget - expectedAdditionalConfig := &additionalConfig + expectedTarget := ecosystem.RewriteTarget(rewriteTarget) + expectedAdditionalConfig := ecosystem.AdditionalConfig(additionalConfig) expectedDogu := &ecosystem.DoguInstallation{ Name: postgresqlQualifiedName, Version: version3212, @@ -492,9 +488,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: version3211, MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ - MaxBodySize: &proxyBodySize, - RewriteTarget: nil, - AdditionalConfig: nil, + MaxBodySize: &proxyBodySize, }, } diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index ade48c78..e54783de 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -249,8 +249,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, AdditionalMounts: []ecosystem.AdditionalMount{ { @@ -304,8 +304,8 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { MinVolumeSize: &volumeSize, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &bodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: ecosystem.RewriteTarget(rewriteTarget), + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), }, AdditionalMounts: []ecosystem.AdditionalMount{ { diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 9f0b308d..5c6f4695 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -54,7 +54,7 @@ type ReverseProxyConfig struct { } func (r *ReverseProxyConfig) IsEmpty() bool { - return r.MaxBodySize == nil && r.RewriteTarget == nil && r.AdditionalConfig == nil + return r.MaxBodySize == nil && r.RewriteTarget == "" && r.AdditionalConfig == "" } // UpgradeConfig contains configuration hints regarding aspects during the upgrade of dogus. diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index 952a4423..399bddaf 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -27,7 +27,7 @@ func TestInstallDogu(t *testing.T) { postgresqlQualifiedName, &version1231, &volumeSize, - ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: &rewriteTarget, AdditionalConfig: &additionalConfig}, + ReverseProxyConfig{MaxBodySize: &proxyBodySize, RewriteTarget: RewriteTarget(rewriteTarget), AdditionalConfig: AdditionalConfig(additionalConfig)}, []AdditionalMount{ { SourceType: DataSourceConfigMap, @@ -44,8 +44,8 @@ func TestInstallDogu(t *testing.T) { MinVolumeSize: &volumeSize, ReverseProxyConfig: ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - RewriteTarget: &rewriteTarget, - AdditionalConfig: &additionalConfig, + RewriteTarget: RewriteTarget(rewriteTarget), + AdditionalConfig: AdditionalConfig(additionalConfig), }, AdditionalMounts: []AdditionalMount{ { @@ -147,10 +147,10 @@ func TestDoguInstallation_UpdateProxyRewriteTarget(t *testing.T) { dogu := DoguInstallation{} // when - dogu.UpdateProxyRewriteTarget(&rewriteTarget) + dogu.UpdateProxyRewriteTarget(RewriteTarget(rewriteTarget)) // then - assert.Equal(t, RewriteTarget(&rewriteTarget), dogu.ReverseProxyConfig.RewriteTarget) + assert.Equal(t, RewriteTarget(rewriteTarget), dogu.ReverseProxyConfig.RewriteTarget) }) } @@ -160,10 +160,10 @@ func TestDoguInstallation_UpdateProxyAdditionalConfig(t *testing.T) { dogu := DoguInstallation{} // when - dogu.UpdateProxyAdditionalConfig(&additionalConfig) + dogu.UpdateProxyAdditionalConfig(AdditionalConfig(additionalConfig)) // then - assert.Equal(t, AdditionalConfig(&additionalConfig), dogu.ReverseProxyConfig.AdditionalConfig) + assert.Equal(t, AdditionalConfig(additionalConfig), dogu.ReverseProxyConfig.AdditionalConfig) }) } diff --git a/pkg/domain/ecosystem/reverseProxyConfig.go b/pkg/domain/ecosystem/reverseProxyConfig.go index 45c49aba..16dc706f 100644 --- a/pkg/domain/ecosystem/reverseProxyConfig.go +++ b/pkg/domain/ecosystem/reverseProxyConfig.go @@ -5,5 +5,5 @@ import ( ) type BodySize = resource.Quantity -type RewriteTarget *string -type AdditionalConfig *string +type RewriteTarget string +type AdditionalConfig string diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index ef3e5dd6..9c81a617 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -223,8 +223,8 @@ func Test_determineDoguDiff(t *testing.T) { Version: &version3212, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - AdditionalConfig: &additionalConfig, - RewriteTarget: &rewriteConfig, + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), + RewriteTarget: ecosystem.RewriteTarget(rewriteConfig), }, MinVolumeSize: &volumeSize2, }, @@ -246,8 +246,8 @@ func Test_determineDoguDiff(t *testing.T) { Version: &version3212, ReverseProxyConfig: ecosystem.ReverseProxyConfig{ MaxBodySize: &proxyBodySize, - AdditionalConfig: &additionalConfig, - RewriteTarget: &rewriteConfig, + AdditionalConfig: ecosystem.AdditionalConfig(additionalConfig), + RewriteTarget: ecosystem.RewriteTarget(rewriteConfig), }, MinVolumeSize: &volumeSize2, }, From e18577ce7a54abf688936718bf649a56f5b34192 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 13:11:19 +0200 Subject: [PATCH 089/119] #121 remove config-Pointer from domain --- .../v2/serializer/blueprint_test.go | 4 +- .../blueprintcr/v2/serializer/config.go | 10 +-- pkg/application/stateDiffUseCase.go | 39 ++++------- pkg/application/stateDiffUseCase_test.go | 64 +++++++++++++++---- pkg/domain/blueprint.go | 5 +- pkg/domain/blueprintSpec.go | 7 +- pkg/domain/blueprintSpec_test.go | 8 +-- pkg/domain/blueprint_test.go | 2 +- pkg/domain/config.go | 4 ++ pkg/domain/effectiveBlueprint.go | 6 +- pkg/domain/stateDiffConfig.go | 16 +++-- pkg/domain/stateDiffConfig_test.go | 31 +++------ 12 files changed, 103 insertions(+), 93 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index 2b67f274..76ee3e03 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -91,7 +91,7 @@ func TestConvertToBlueprintDTO(t *testing.T) { t.Run("convert config", func(t *testing.T) { value42 := "42" blueprint := domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ "my-dogu": { { @@ -246,7 +246,7 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { expected := domain.EffectiveBlueprint{ Dogus: dogus, Components: components, - Config: &domain.Config{ + Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ "my-dogu": { { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go index 64b3c1c1..68f58f6e 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/config.go @@ -9,8 +9,8 @@ import ( "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" ) -func ConvertToConfigDTO(config *domain.Config) *v2.Config { - if config == nil { +func ConvertToConfigDTO(config domain.Config) *v2.Config { + if config.IsEmpty() { return nil } @@ -30,9 +30,9 @@ func ConvertToConfigDTO(config *domain.Config) *v2.Config { } } -func ConvertToConfigDomain(config *v2.Config) *domain.Config { +func ConvertToConfigDomain(config *v2.Config) domain.Config { if config == nil { - return nil + return domain.Config{} } var dogus map[cescommons.SimpleName]domain.DoguConfigEntries // we check for empty values to make good use of default values @@ -44,7 +44,7 @@ func ConvertToConfigDomain(config *v2.Config) *domain.Config { } } - return &domain.Config{ + return domain.Config{ Dogus: dogus, Global: convertToGlobalConfigDomain(config.Global), } diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index a722aa7b..d927d112 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -5,12 +5,9 @@ import ( "errors" "fmt" - cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/cloudogu/k8s-registry-lib/config" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -58,21 +55,17 @@ func (useCase *StateDiffUseCase) DetermineStateDiff(ctx context.Context, bluepri logger.V(2).Info("load referenced sensitive config") // load referenced config before collecting ecosystem state // if an error happens here, we save a lot of heavy work - var referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue - var err error - if blueprint.EffectiveBlueprint.Config != nil { - referencedSensitiveConfig, err = useCase.sensitiveConfigRefReader.GetValues( - ctx, blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), - ) - if err != nil { - err = fmt.Errorf("%s: %w", REFERENCED_CONFIG_NOT_FOUND, err) - blueprint.MissingConfigReferences(err) - updateError := useCase.blueprintSpecRepo.Update(ctx, blueprint) - if updateError != nil { - return errors.Join(updateError, err) - } - return err + referencedSensitiveConfig, err := useCase.sensitiveConfigRefReader.GetValues( + ctx, blueprint.EffectiveBlueprint.Config.GetSensitiveConfigReferences(), + ) + if err != nil { + err = fmt.Errorf("%s: %w", REFERENCED_CONFIG_NOT_FOUND, err) + blueprint.MissingConfigReferences(err) + updateError := useCase.blueprintSpecRepo.Update(ctx, blueprint) + if updateError != nil { + return errors.Join(updateError, err) } + return err } logger.V(2).Info("collect ecosystem state for state diff") @@ -113,15 +106,11 @@ func (useCase *StateDiffUseCase) collectEcosystemState(ctx context.Context, effe logger.V(2).Info("collect needed global config") globalConfig, globalConfigErr := useCase.globalConfigRepo.Get(ctx) - var configByDogu, sensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig - var doguConfigErr, sensitiveConfigErr error - if effectiveBlueprint.Config != nil { - logger.V(2).Info("collect needed dogu config") - configByDogu, doguConfigErr = useCase.doguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedConfig()) + logger.V(2).Info("collect needed dogu config") + configByDogu, doguConfigErr := useCase.doguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedConfig()) - logger.V(2).Info("collect needed sensitive dogu config") - sensitiveConfigByDogu, sensitiveConfigErr = useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) - } + logger.V(2).Info("collect needed sensitive dogu config") + sensitiveConfigByDogu, sensitiveConfigErr := useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) joinedError := errors.Join(doguErr, componentErr, globalConfigErr, doguConfigErr, sensitiveConfigErr) if joinedError != nil { diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index e54783de..ba9e2a73 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -58,8 +58,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -83,8 +91,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -108,8 +124,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, domainservice.NewInternalError(assert.AnError, "internal error")) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -127,7 +151,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { blueprint := &domain.BlueprintSpec{ Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: &domain.Config{}, + Config: domain.Config{}, }, } @@ -167,7 +191,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { }) t.Run("should fail to get sensitive dogu config", func(t *testing.T) { // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{Config: &domain.Config{}}} + blueprint := &domain.BlueprintSpec{Id: "testBlueprint1", EffectiveBlueprint: domain.EffectiveBlueprint{Config: domain.Config{}}} doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) @@ -222,8 +246,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -285,8 +317,16 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { entries, _ := config.MapToEntries(map[string]any{}) globalConfig := config.CreateGlobalConfig(entries) globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) + sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) + configRefReaderMock := newMockSensitiveConfigRefReader(t) + configRefReaderMock.EXPECT(). + GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). + Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, nil, nil, nil) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -341,7 +381,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Global: domain.GlobalConfigEntries{ { Key: "globalKey1", @@ -404,7 +444,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ ldapQualifiedDoguName.SimpleName: { { @@ -490,7 +530,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { Id: "testBlueprint1", Conditions: []domain.Condition{}, EffectiveBlueprint: domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ ldap: { { @@ -597,7 +637,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { t.Run("all ok", func(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Global: domain.GlobalConfigEntries{ { Key: "globalKey1", @@ -681,7 +721,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { t.Run("fail with internalError and notFoundError", func(t *testing.T) { // given effectiveBlueprint := domain.EffectiveBlueprint{ - Config: &domain.Config{ + Config: domain.Config{ Global: domain.GlobalConfigEntries{ { Key: "globalKey1", diff --git a/pkg/domain/blueprint.go b/pkg/domain/blueprint.go index 5aea5c59..d9b008ac 100644 --- a/pkg/domain/blueprint.go +++ b/pkg/domain/blueprint.go @@ -23,7 +23,7 @@ type Blueprint struct { // this blueprint was applied. Optional. Components []Component // Config contains all config entries to set via blueprint. - Config *Config + Config Config } // Validate checks the structure and data of the blueprint statically and returns an error if there are any problems @@ -74,8 +74,5 @@ func (blueprint *Blueprint) validateComponentUniqueness() error { } func (blueprint *Blueprint) validateConfig() error { - if blueprint.Config == nil { - return nil - } return blueprint.Config.validate() } diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 7842c5f4..d8edf044 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -220,10 +220,7 @@ func (spec *BlueprintSpec) calculateEffectiveDogu(dogu Dogu) (Dogu, error) { } // It is not allowed to have config without the corresponding dogu, so this will clean up the unnecessary config. -func (spec *BlueprintSpec) removeConfigForMaskedDogus() *Config { - if spec.Blueprint.Config == nil { - return nil - } +func (spec *BlueprintSpec) removeConfigForMaskedDogus() Config { effectiveDoguConfig := maps.Clone(spec.Blueprint.Config.Dogus) for _, dogu := range spec.BlueprintMask.Dogus { @@ -232,7 +229,7 @@ func (spec *BlueprintSpec) removeConfigForMaskedDogus() *Config { } } - return &Config{ + return Config{ Dogus: effectiveDoguConfig, Global: spec.Blueprint.Config.Global, } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 1cf116da..f527b02f 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -182,7 +182,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } spec := BlueprintSpec{ - Blueprint: Blueprint{Dogus: dogus, Config: &config}, + Blueprint: Blueprint{Dogus: dogus, Config: config}, BlueprintMask: BlueprintMask{Dogus: maskedDogus}, } err := spec.CalculateEffectiveBlueprint() @@ -243,7 +243,7 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } spec := BlueprintSpec{ - Blueprint: Blueprint{Config: &config}, + Blueprint: Blueprint{Config: config}, } err := spec.CalculateEffectiveBlueprint() @@ -320,7 +320,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { EffectiveBlueprint: EffectiveBlueprint{ Dogus: []Dogu{{Name: officialNexus, Version: &version3211}}, Components: []Component{{Name: testComponentName, Version: compVersion3211}}, - Config: &Config{Global: GlobalConfigEntries{{Key: "test", Value: &val1}}}, + Config: Config{Global: GlobalConfigEntries{{Key: "test", Value: &val1}}}, }, } @@ -371,8 +371,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { NeededAction: ConfigActionSet, }, }, - DoguConfigDiffs: map[cescommons.SimpleName]DoguConfigDiffs{}, - SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, } assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index fc900f93..ed9ac883 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -59,7 +59,7 @@ func Test_validate_multipleErrors(t *testing.T) { blueprint := Blueprint{ Dogus: dogus, Components: components, - Config: &Config{ + Config: Config{ Global: GlobalConfigEntries{ { Key: "", diff --git a/pkg/domain/config.go b/pkg/domain/config.go index ba3c44d6..2e5f93a4 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -113,6 +113,10 @@ func (config Config) getDogusWithChangedConfigBySenisitivity(isSensitive bool) [ return dogus } +func (config Config) IsEmpty() bool { + return len(config.Dogus) == 0 && len(config.Global) == 0 +} + func (config Config) validate() error { var errs []error for doguName, doguConfig := range config.Dogus { diff --git a/pkg/domain/effectiveBlueprint.go b/pkg/domain/effectiveBlueprint.go index 5f650425..6f02530a 100644 --- a/pkg/domain/effectiveBlueprint.go +++ b/pkg/domain/effectiveBlueprint.go @@ -19,7 +19,7 @@ type EffectiveBlueprint struct { // this blueprint was applied. Optional. Components []Component // Config contains all config entries to set via blueprint. Optional. - Config *Config + Config Config } // GetWantedDogus returns a list of all dogus which should be installed @@ -35,10 +35,6 @@ func (effectiveBlueprint *EffectiveBlueprint) GetWantedDogus() []Dogu { // validateOnlyConfigForDogusInBlueprint checks that there is only config for dogus to install in the blueprint func (effectiveBlueprint *EffectiveBlueprint) validateOnlyConfigForDogusInBlueprint() error { - if effectiveBlueprint.Config == nil { - return nil - } - wantedDogus := util.Map(effectiveBlueprint.GetWantedDogus(), func(dogu Dogu) cescommons.SimpleName { return dogu.Name.SimpleName }) diff --git a/pkg/domain/stateDiffConfig.go b/pkg/domain/stateDiffConfig.go index b13f6411..a84a6468 100644 --- a/pkg/domain/stateDiffConfig.go +++ b/pkg/domain/stateDiffConfig.go @@ -26,7 +26,7 @@ func countByAction(configActions []ConfigAction) map[ConfigAction]int { } func determineConfigDiffs( - blueprintConfig *Config, + blueprintConfig Config, globalConfig config.GlobalConfig, configByDogu map[cescommons.SimpleName]config.DoguConfig, SensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig, @@ -36,10 +36,6 @@ func determineConfigDiffs( map[cescommons.SimpleName]SensitiveDoguConfigDiffs, GlobalConfigDiffs, ) { - if blueprintConfig == nil { - return nil, nil, nil - } - return determineDogusConfigDiffs(blueprintConfig.Dogus, configByDogu), determineSensitiveDogusConfigDiffs(blueprintConfig.Dogus, SensitiveConfigByDogu, referencedSensitiveConfig), determineGlobalConfigDiffs(blueprintConfig.Global, globalConfig) @@ -49,10 +45,13 @@ func determineDogusConfigDiffs( blueprintDoguConfigs map[cescommons.SimpleName]DoguConfigEntries, configByDogu map[cescommons.SimpleName]config.DoguConfig, ) map[cescommons.SimpleName]DoguConfigDiffs { - diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} + var diffsPerDogu map[cescommons.SimpleName]DoguConfigDiffs for doguName, bluprintDoguConfig := range blueprintDoguConfigs { configDiffs := determineDoguConfigDiffs(doguName, bluprintDoguConfig, configByDogu, false) if len(configDiffs) > 0 { + if diffsPerDogu == nil { + diffsPerDogu = make(map[cescommons.SimpleName]DoguConfigDiffs) + } diffsPerDogu[doguName] = configDiffs } } @@ -64,11 +63,14 @@ func determineSensitiveDogusConfigDiffs( configByDogu map[cescommons.SimpleName]config.DoguConfig, referencedValues map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) map[cescommons.SimpleName]DoguConfigDiffs { - diffsPerDogu := map[cescommons.SimpleName]DoguConfigDiffs{} + var diffsPerDogu map[cescommons.SimpleName]DoguConfigDiffs for doguName, blueprintDoguConfig := range blueprintDoguConfigs { setSensitiveConfigValues(doguName, blueprintDoguConfig, referencedValues) configDiffs := determineDoguConfigDiffs(doguName, blueprintDoguConfig, configByDogu, true) if len(configDiffs) > 0 { + if diffsPerDogu == nil { + diffsPerDogu = make(map[cescommons.SimpleName]DoguConfigDiffs) + } diffsPerDogu[doguName] = configDiffs } } diff --git a/pkg/domain/stateDiffConfig_test.go b/pkg/domain/stateDiffConfig_test.go index 9b9930ea..b9045123 100644 --- a/pkg/domain/stateDiffConfig_test.go +++ b/pkg/domain/stateDiffConfig_test.go @@ -24,32 +24,19 @@ var ( ) func Test_determineConfigDiff(t *testing.T) { - t.Run("nil", func(t *testing.T) { - dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - nil, - config.CreateGlobalConfig(map[config.Key]config.Value{}), - map[cescommons.SimpleName]config.DoguConfig{}, - map[cescommons.SimpleName]config.DoguConfig{}, - map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, - ) - - assert.Nil(t, dogusConfigDiffs) - assert.Nil(t, sensitiveConfigDiffs) - assert.Nil(t, globalConfigDiff) - }) t.Run("empty", func(t *testing.T) { emptyConfig := Config{} dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - &emptyConfig, + emptyConfig, config.CreateGlobalConfig(map[config.Key]config.Value{}), map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}, ) - assert.Equal(t, map[cescommons.SimpleName]DoguConfigDiffs{}, dogusConfigDiffs) - assert.Equal(t, map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, sensitiveConfigDiffs) + assert.Nil(t, dogusConfigDiffs) + assert.Nil(t, sensitiveConfigDiffs) assert.Equal(t, GlobalConfigDiffs(nil), globalConfigDiff) }) t.Run("all actions global config", func(t *testing.T) { @@ -86,7 +73,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - &givenConfig, + givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{}, @@ -94,8 +81,8 @@ func Test_determineConfigDiff(t *testing.T) { ) //then - assert.Equal(t, map[cescommons.SimpleName]DoguConfigDiffs{}, dogusConfigDiffs) - assert.Equal(t, map[cescommons.SimpleName]SensitiveDoguConfigDiffs{}, sensitiveConfigDiffs) + assert.Nil(t, dogusConfigDiffs) + assert.Nil(t, sensitiveConfigDiffs) assert.Equal(t, 2, len(globalConfigDiff)) // only changes hitKeys := make(map[string]bool) for _, diff := range globalConfigDiff { @@ -172,7 +159,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - &givenConfig, + givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{ dogu1: doguConfig, @@ -263,7 +250,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, globalConfigDiff := determineConfigDiffs( - &givenConfig, + givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{}, map[cescommons.SimpleName]config.DoguConfig{ @@ -326,7 +313,7 @@ func Test_determineConfigDiff(t *testing.T) { //when dogusConfigDiffs, sensitiveConfigDiffs, _ := determineConfigDiffs( - &givenConfig, + givenConfig, globalConfig, map[cescommons.SimpleName]config.DoguConfig{ dogu1: doguConfig, From f92b56956a16d82a4942d42b83e064a03c90112d Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 15:56:46 +0200 Subject: [PATCH 090/119] #121 remove components from operator --- k8s/helm/templates/component-editor-role.yaml | 25 - .../component-editor-rolebinding.yaml | 13 - k8s/helm/templates/health-configmap.yaml | 11 - k8s/helm/values.yaml | 8 - .../v2/blueprintSpecCRRepository.go | 1 - .../v2/blueprintSpecCRRepository_test.go | 23 +- .../blueprintcr/v2/serializer/blueprint.go | 26 +- .../v2/serializer/blueprint_test.go | 104 +- .../blueprintcr/v2/serializer/component.go | 75 -- .../v2/serializer/componentDiff.go | 40 - .../v2/serializer/componentDiff_test.go | 56 - .../v2/serializer/component_test.go | 116 -- .../blueprintcr/v2/serializer/stateDiff.go | 6 - .../v2/serializer/stateDiff_test.go | 85 +- .../componentcr/componentInstallationRepo.go | 107 -- .../componentInstallationRepo_test.go | 437 ------- .../componentcr/componentSerializer.go | 201 ---- .../componentcr/componentSerializer_test.go | 352 ------ .../kubernetes/componentcr/interfaces.go | 11 - .../componentcr/mock_componentRepo_test.go | 1049 ----------------- .../kubernetes/componentcr/testdata/testPatch | 1 - pkg/adapter/kubernetes/healthConfig/health.go | 135 --- .../healthConfig/healthSerializer.go | 58 - .../kubernetes/healthConfig/health_test.go | 276 ----- .../kubernetes/healthConfig/interfaces.go | 7 - .../mock_configMapInterface_test.go | 577 --------- pkg/application/applyComponentsUseCase.go | 46 - .../applyComponentsUseCase_test.go | 116 -- pkg/application/blueprintSpecChangeUseCase.go | 25 +- .../blueprintSpecChangeUseCase_test.go | 234 ---- .../completeBlueprintUseCase_test.go | 4 +- .../componentInstallationUseCase.go | 129 -- .../componentInstallationUseCase_test.go | 495 -------- pkg/application/doguInstallationUseCase.go | 34 +- .../doguInstallationUseCase_test.go | 71 +- pkg/application/ecosystemHealthUseCase.go | 29 +- .../ecosystemHealthUseCase_test.go | 129 +- pkg/application/interfaces.go | 33 - .../mock_applyComponentsUseCase_test.go | 94 -- ...ck_componentInstallationRepository_test.go | 298 ----- .../mock_componentInstallationUseCase_test.go | 190 --- .../mock_healthConfigProvider_test.go | 151 --- .../mock_healthWaitConfigProvider_test.go | 93 -- .../mock_requiredComponentsProvider_test.go | 95 -- .../mock_selfUpgradeUseCase_test.go | 84 -- pkg/application/selfUpgradeUseCase.go | 104 -- pkg/application/selfUpgradeUseCase_test.go | 266 ----- pkg/application/stateDiffUseCase.go | 43 +- pkg/application/stateDiffUseCase_test.go | 77 +- pkg/bootstrap.go | 27 +- pkg/domain/blueprint.go | 21 - pkg/domain/blueprintSpec.go | 35 +- pkg/domain/blueprintSpec_test.go | 113 +- pkg/domain/blueprint_test.go | 108 +- pkg/domain/common/componentName.go | 52 - pkg/domain/common/componentName_test.go | 30 - pkg/domain/component.go | 36 - pkg/domain/component_test.go | 79 -- pkg/domain/dogu_test.go | 5 + pkg/domain/ecosystem/EcosystemState.go | 2 - pkg/domain/ecosystem/componentHealth.go | 69 -- pkg/domain/ecosystem/componentHealth_test.go | 185 --- pkg/domain/ecosystem/componentInstallation.go | 69 -- .../ecosystem/componentInstallation_test.go | 102 -- pkg/domain/ecosystem/doguHealth_test.go | 7 +- pkg/domain/ecosystem/ecosystemHealth.go | 15 +- pkg/domain/ecosystem/ecosystemHealth_test.go | 51 +- pkg/domain/effectiveBlueprint.go | 3 - pkg/domain/errors.go | 3 +- pkg/domain/events.go | 58 +- pkg/domain/events_test.go | 42 +- pkg/domain/stateDiff.go | 4 - pkg/domain/stateDiffComponent.go | 238 ---- pkg/domain/stateDiffComponent_test.go | 470 -------- pkg/domainservice/adapterInterfaces.go | 29 - ...ck_ComponentInstallationRepository_test.go | 298 ----- .../mock_HealthWaitConfigProvider_test.go | 93 -- .../mock_RequiredComponentsProvider_test.go | 95 -- 78 files changed, 226 insertions(+), 8753 deletions(-) delete mode 100644 k8s/helm/templates/component-editor-role.yaml delete mode 100644 k8s/helm/templates/component-editor-rolebinding.yaml delete mode 100644 k8s/helm/templates/health-configmap.yaml delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go delete mode 100644 pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go delete mode 100644 pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go delete mode 100644 pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go delete mode 100644 pkg/adapter/kubernetes/componentcr/componentSerializer.go delete mode 100644 pkg/adapter/kubernetes/componentcr/componentSerializer_test.go delete mode 100644 pkg/adapter/kubernetes/componentcr/interfaces.go delete mode 100644 pkg/adapter/kubernetes/componentcr/mock_componentRepo_test.go delete mode 100644 pkg/adapter/kubernetes/componentcr/testdata/testPatch delete mode 100644 pkg/adapter/kubernetes/healthConfig/health.go delete mode 100644 pkg/adapter/kubernetes/healthConfig/healthSerializer.go delete mode 100644 pkg/adapter/kubernetes/healthConfig/health_test.go delete mode 100644 pkg/adapter/kubernetes/healthConfig/interfaces.go delete mode 100644 pkg/adapter/kubernetes/healthConfig/mock_configMapInterface_test.go delete mode 100644 pkg/application/applyComponentsUseCase.go delete mode 100644 pkg/application/applyComponentsUseCase_test.go delete mode 100644 pkg/application/componentInstallationUseCase.go delete mode 100644 pkg/application/componentInstallationUseCase_test.go delete mode 100644 pkg/application/mock_applyComponentsUseCase_test.go delete mode 100644 pkg/application/mock_componentInstallationRepository_test.go delete mode 100644 pkg/application/mock_componentInstallationUseCase_test.go delete mode 100644 pkg/application/mock_healthConfigProvider_test.go delete mode 100644 pkg/application/mock_healthWaitConfigProvider_test.go delete mode 100644 pkg/application/mock_requiredComponentsProvider_test.go delete mode 100644 pkg/application/mock_selfUpgradeUseCase_test.go delete mode 100644 pkg/application/selfUpgradeUseCase.go delete mode 100644 pkg/application/selfUpgradeUseCase_test.go delete mode 100644 pkg/domain/common/componentName.go delete mode 100644 pkg/domain/common/componentName_test.go delete mode 100644 pkg/domain/component.go delete mode 100644 pkg/domain/component_test.go delete mode 100644 pkg/domain/ecosystem/componentHealth.go delete mode 100644 pkg/domain/ecosystem/componentHealth_test.go delete mode 100644 pkg/domain/ecosystem/componentInstallation.go delete mode 100644 pkg/domain/ecosystem/componentInstallation_test.go delete mode 100644 pkg/domain/stateDiffComponent.go delete mode 100644 pkg/domain/stateDiffComponent_test.go delete mode 100644 pkg/domainservice/mock_ComponentInstallationRepository_test.go delete mode 100644 pkg/domainservice/mock_HealthWaitConfigProvider_test.go delete mode 100644 pkg/domainservice/mock_RequiredComponentsProvider_test.go diff --git a/k8s/helm/templates/component-editor-role.yaml b/k8s/helm/templates/component-editor-role.yaml deleted file mode 100644 index 9474e38f..00000000 --- a/k8s/helm/templates/component-editor-role.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} - name: {{ include "k8s-blueprint-operator.name" . }}-component-editor-role -rules: - - apiGroups: - - k8s.cloudogu.com - resources: - - components - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - - apiGroups: - - k8s.cloudogu.com - resources: - - components/status - verbs: - - get \ No newline at end of file diff --git a/k8s/helm/templates/component-editor-rolebinding.yaml b/k8s/helm/templates/component-editor-rolebinding.yaml deleted file mode 100644 index 98ea20aa..00000000 --- a/k8s/helm/templates/component-editor-rolebinding.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} - name: {{ include "k8s-blueprint-operator.name" . }}-component-editor-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "k8s-blueprint-operator.name" . }}-component-editor-role -subjects: - - kind: ServiceAccount - name: {{ include "k8s-blueprint-operator.name" . }}-controller-manager \ No newline at end of file diff --git a/k8s/helm/templates/health-configmap.yaml b/k8s/helm/templates/health-configmap.yaml deleted file mode 100644 index 466c3bb5..00000000 --- a/k8s/helm/templates/health-configmap.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - labels: - {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} - name: {{ include "k8s-blueprint-operator.name" . }}-health-config -data: - components: | - {{- toYaml .Values.healthConfig.components | nindent 4 }} - wait: | - {{- toYaml .Values.healthConfig.wait | nindent 4 }} \ No newline at end of file diff --git a/k8s/helm/values.yaml b/k8s/helm/values.yaml index d6983251..91353a5c 100644 --- a/k8s/helm/values.yaml +++ b/k8s/helm/values.yaml @@ -18,14 +18,6 @@ manager: memory: 105M networkPolicies: enabled: true -healthConfig: - components: - required: - - name: k8s-dogu-operator - - name: k8s-component-operator - wait: - timeout: 10m - interval: 10s doguRegistry: certificate: secret: dogu-registry-cert diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 41ba1233..7d5cea39 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -73,7 +73,6 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) Conditions: conditions, Config: domain.BlueprintConfiguration{ IgnoreDoguHealth: ptr.Deref(blueprintCR.Spec.IgnoreDoguHealth, false), - IgnoreComponentHealth: ptr.Deref(blueprintCR.Spec.IgnoreComponentHealth, false), AllowDoguNamespaceSwitch: ptr.Deref(blueprintCR.Spec.AllowDoguNamespaceSwitch, false), Stopped: ptr.Deref(blueprintCR.Spec.Stopped, false), }, diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index b54a8c58..92f58764 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -207,11 +207,10 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ - Dogus: []bpv2.Dogu{}, - Components: []bpv2.Component{}, - Config: nil, + Dogus: []bpv2.Dogu{}, + Config: nil, }, - StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}}, Conditions: []metav1.Condition{testCondition}, } restClientMock.EXPECT(). @@ -279,11 +278,10 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ - Dogus: []bpv2.Dogu{}, - Components: []bpv2.Component{}, - Config: nil, + Dogus: []bpv2.Dogu{}, + Config: nil, }, - StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}}, Conditions: []metav1.Condition{}, } expectedError := k8sErrors.NewConflict( @@ -322,11 +320,10 @@ func Test_blueprintSpecRepo_Update(t *testing.T) { repo := NewBlueprintSpecRepository(restClientMock, eventRecorderMock) expectedStatus := &bpv2.BlueprintStatus{ EffectiveBlueprint: &bpv2.BlueprintManifest{ - Dogus: []bpv2.Dogu{}, - Components: []bpv2.Component{}, - Config: nil, + Dogus: []bpv2.Dogu{}, + Config: nil, }, - StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}, ComponentDiffs: map[string]bpv2.ComponentDiff{}}, + StateDiff: &bpv2.StateDiff{DoguDiffs: map[string]bpv2.DoguDiff{}}, Conditions: []metav1.Condition{}, } expectedError := fmt.Errorf("test-error") @@ -373,11 +370,9 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { var events []domain.Event events = append(events, domain.StateDiffDoguDeterminedEvent{}, - domain.StateDiffComponentDeterminedEvent{}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffComponentDetermined", "component state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecInvalid", "test-error") // when diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go index 49ffbd1d..d84bec93 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint.go @@ -1,7 +1,6 @@ package serializer import ( - "errors" "fmt" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" @@ -10,17 +9,13 @@ import ( func ConvertToBlueprintDTO(blueprint domain.EffectiveBlueprint) crd.BlueprintManifest { return crd.BlueprintManifest{ - Dogus: ConvertToDoguDTOs(blueprint.Dogus), - Components: ConvertToComponentDTOs(blueprint.Components), - Config: ConvertToConfigDTO(blueprint.Config), + Dogus: ConvertToDoguDTOs(blueprint.Dogus), + Config: ConvertToConfigDTO(blueprint.Config), } } func ConvertToBlueprintDomain(blueprint crd.BlueprintManifest) (domain.Blueprint, error) { - convertedDogus, doguErr := ConvertDogus(blueprint.Dogus) - convertedComponents, compErr := ConvertComponents(blueprint.Components) - - err := errors.Join(doguErr, compErr) + convertedDogus, err := ConvertDogus(blueprint.Dogus) if err != nil { return domain.Blueprint{}, &domain.InvalidBlueprintError{ WrappedError: err, @@ -29,9 +24,8 @@ func ConvertToBlueprintDomain(blueprint crd.BlueprintManifest) (domain.Blueprint } configDomain := ConvertToConfigDomain(blueprint.Config) return domain.Blueprint{ - Dogus: convertedDogus, - Components: convertedComponents, - Config: configDomain, + Dogus: convertedDogus, + Config: configDomain, }, nil } @@ -39,17 +33,13 @@ func ConvertToEffectiveBlueprintDomain(blueprint *crd.BlueprintManifest) (domain if blueprint == nil { return domain.EffectiveBlueprint{}, nil } - convertedDogus, doguErr := ConvertDogus(blueprint.Dogus) - convertedComponents, compErr := ConvertComponents(blueprint.Components) - - err := errors.Join(doguErr, compErr) + convertedDogus, err := ConvertDogus(blueprint.Dogus) if err != nil { return domain.EffectiveBlueprint{}, fmt.Errorf("cannot deserialize effective blueprint: %w", err) } return domain.EffectiveBlueprint{ - Dogus: convertedDogus, - Components: convertedComponents, - Config: ConvertToConfigDomain(blueprint.Config), + Dogus: convertedDogus, + Config: ConvertToConfigDomain(blueprint.Config), }, nil } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go index 76ee3e03..06358ce0 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/blueprint_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - "github.com/Masterminds/semver/v3" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-registry-lib/config" "github.com/google/go-cmp/cmp" @@ -16,7 +15,6 @@ import ( crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" ) var ( @@ -25,14 +23,6 @@ var ( version1233, _ = core.ParseVersion("1.2.3-3") ) -var ( - compVersion1233 = semver.MustParse("1.2.3-3") - compVersion3211 = semver.MustParse("3.2.1-1") - compVersion3212 = semver.MustParse("3.2.1-2") - compVersion1233String = semver.MustParse("1.2.3-3").String() - compVersion3212String = semver.MustParse("3.2.1-2").String() -) - var ( trueVar = true falseVar = false @@ -57,33 +47,7 @@ func TestConvertToBlueprintDTO(t *testing.T) { {Name: "premium/dogu3", Version: &version3212.Raw, Absent: &falseVar}, } expectedManifest := crd.BlueprintManifest{ - Dogus: convertedDogus, - Components: []crd.Component{}, - } - assert.Empty(t, cmp.Diff(expectedManifest, blueprintV2)) - }) - - t.Run("convert components", func(t *testing.T) { - components := []domain.Component{ - {Name: common.QualifiedComponentName{SimpleName: "component1", Namespace: "k8s"}, Version: nil, Absent: true}, - {Name: common.QualifiedComponentName{SimpleName: "component3", Namespace: "k8s-testing"}, Version: compVersion3212, Absent: false}, - } - blueprint := domain.EffectiveBlueprint{ - Components: components, - } - - //when - blueprintV2 := ConvertToBlueprintDTO(blueprint) - - //then - convertedComponents := []crd.Component{ - {Name: "k8s/component1", Absent: &trueVar}, - {Name: "k8s-testing/component3", Version: &compVersion3212String, Absent: &falseVar}, - } - - expectedManifest := crd.BlueprintManifest{ - Dogus: []crd.Dogu{}, - Components: convertedComponents, + Dogus: convertedDogus, } assert.Empty(t, cmp.Diff(expectedManifest, blueprintV2)) }) @@ -119,8 +83,7 @@ func TestConvertToBlueprintDTO(t *testing.T) { blueprintV2 := ConvertToBlueprintDTO(blueprint) expectedManifest := crd.BlueprintManifest{ - Dogus: []crd.Dogu{}, - Components: []crd.Component{}, + Dogus: []crd.Dogu{}, Config: &crd.Config{ Dogus: map[string][]crd.ConfigEntry{ "my-dogu": { @@ -178,17 +141,9 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { }, } - convertedComponents := []crd.Component{ - {Name: "k8s/component1", Version: &version3211.Raw, Absent: &trueVar}, - {Name: "k8s/component2", Absent: &trueVar}, - {Name: "k8s-testing/component3", Version: &version3212.Raw, Absent: &falseVar}, - {Name: "k8s-testing/component4", Version: &version1233.Raw, Absent: &falseVar}, - } - value42 := "42" dto := crd.BlueprintManifest{ - Dogus: convertedDogus, - Components: convertedComponents, + Dogus: convertedDogus, Config: &crd.Config{ Dogus: map[string][]crd.ConfigEntry{ "my-dogu": { @@ -237,15 +192,8 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { }, } - components := []domain.Component{ - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component1"}, Version: compVersion3211, Absent: true}, - {Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "component2"}, Absent: true}, - {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component3"}, Version: compVersion3212, Absent: false}, - {Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "component4"}, Version: compVersion1233}, - } expected := domain.EffectiveBlueprint{ - Dogus: dogus, - Components: components, + Dogus: dogus, Config: domain.Config{ Dogus: map[cescommons.SimpleName]domain.DoguConfigEntries{ "my-dogu": { @@ -301,23 +249,6 @@ func TestConvertToEffectiveBlueprintDomain(t *testing.T) { assert.ErrorContains(t, err, "cannot deserialize effective blueprint") assert.Equal(t, domain.EffectiveBlueprint{}, blueprint) }) - - t.Run("when convert component error return empty effective blueprint and error", func(t *testing.T) { - //given - dto := crd.BlueprintManifest{ - Components: []crd.Component{ - {Name: "k8s/k8s-dogu-operator", Version: &wrongVersion}, // name contains no "/" - }, - } - - //when - blueprint, err := ConvertToEffectiveBlueprintDomain(&dto) - - //then - require.Error(t, err) - assert.ErrorContains(t, err, "cannot deserialize effective blueprint") - assert.Equal(t, domain.EffectiveBlueprint{}, blueprint) - }) } func TestConvertToBlueprintDomain(t *testing.T) { @@ -366,33 +297,6 @@ func TestConvertToBlueprintDomain(t *testing.T) { }, }, converted) }) - - t.Run("convert component", func(t *testing.T) { - given := crd.BlueprintManifest{ - Components: []crd.Component{ - { - Name: "k8s/loki", - Version: &compVersion1233String, - }, - }, - } - converted, err := ConvertToBlueprintDomain(given) - - require.NoError(t, err) - assert.Equal(t, domain.Blueprint{ - Components: []domain.Component{ - { - Name: common.QualifiedComponentName{ - Namespace: "k8s", - SimpleName: "loki", - }, - Version: compVersion1233, - Absent: false, - }, - }, - }, converted) - }) - } func TestConvertToBlueprintMaskDomain(t *testing.T) { diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go deleted file mode 100644 index 08b496ff..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component.go +++ /dev/null @@ -1,75 +0,0 @@ -package serializer - -import ( - "errors" - "fmt" - - v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "k8s.io/utils/ptr" - - "github.com/Masterminds/semver/v3" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" -) - -// ConvertComponents takes a slice of TargetComponent and returns a new slice with their DTO equivalent. -func ConvertComponents(components []v2.Component) ([]domain.Component, error) { - var convertedComponents []domain.Component - var errorList []error - - for _, component := range components { - var version *semver.Version - if component.Version != nil && *component.Version != "" { - var err error - version, err = semver.NewVersion(*component.Version) - if err != nil { - errorList = append(errorList, fmt.Errorf("could not parse version of target component %q: %w", component.Name, err)) - continue - } - } - - name, err := common.QualifiedComponentNameFromString(component.Name) - if err != nil { - errorList = append(errorList, err) - continue - } - - convertedComponents = append(convertedComponents, domain.Component{ - Name: name, - Version: version, - Absent: ptr.Deref(component.Absent, false), - DeployConfig: ecosystem.DeployConfig(component.DeployConfig), - }) - } - - err := errors.Join(errorList...) - if err != nil { - return convertedComponents, fmt.Errorf("cannot convert blueprint components: %w", err) - } - - return convertedComponents, err -} - -// ConvertToComponentDTOs takes a slice of Component DTOs and returns a new slice with their domain equivalent. -func ConvertToComponentDTOs(components []domain.Component) []v2.Component { - converted := util.Map(components, func(component domain.Component) v2.Component { - // convert the distribution namespace back into the name field so the EffectiveBlueprint has the same syntax - // as the original blueprint json from the Blueprint resource. - joinedComponentName := component.Name.String() - var version *string - if !component.Absent && component.Version != nil { - version = ptr.To(component.Version.String()) - } - - return v2.Component{ - Name: joinedComponentName, - Version: version, - Absent: &component.Absent, - DeployConfig: map[string]interface{}(component.DeployConfig), - } - }) - return converted -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go deleted file mode 100644 index d61eb989..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff.go +++ /dev/null @@ -1,40 +0,0 @@ -package serializer - -import ( - crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "k8s.io/utils/ptr" -) - -func convertToComponentDiffDTO(domainModel domain.ComponentDiff) crd.ComponentDiff { - var actualVersion, expectedVersion *string - - if domainModel.Actual.Version != nil { - actualVersion = ptr.To(domainModel.Actual.Version.String()) - } - if domainModel.Expected.Version != nil { - expectedVersion = ptr.To(domainModel.Expected.Version.String()) - } - - neededActions := domainModel.NeededActions - componentActions := make([]crd.ComponentAction, 0, len(neededActions)) - for _, action := range neededActions { - componentActions = append(componentActions, crd.ComponentAction(action)) - } - - return crd.ComponentDiff{ - Actual: crd.ComponentDiffState{ - Namespace: string(domainModel.Actual.Namespace), - Version: actualVersion, - Absent: domainModel.Actual.Absent, - DeployConfig: crd.DeployConfig(domainModel.Actual.DeployConfig), - }, - Expected: crd.ComponentDiffState{ - Namespace: string(domainModel.Expected.Namespace), - Version: expectedVersion, - Absent: domainModel.Expected.Absent, - DeployConfig: crd.DeployConfig(domainModel.Expected.DeployConfig), - }, - NeededActions: componentActions, - } -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go deleted file mode 100644 index b11da904..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/componentDiff_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package serializer - -import ( - "testing" - - crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/stretchr/testify/assert" -) - -var testNamespace = common.ComponentNamespace("k8s") -var testNamespaceString = "k8s" - -var testDeployConfig = map[string]interface{}{"key": "value", "key1": map[string]interface{}{"key": "value2"}} - -func Test_convertToComponentDiffDTO(t *testing.T) { - t.Run("should copy model diff to DTO diff - absent", func(t *testing.T) { - // given - domainDiff := domain.ComponentDiff{ - Name: testComponentName, - Actual: domain.ComponentDiffState{Version: nil, Absent: true}, - Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Namespace: testNamespace, DeployConfig: testDeployConfig}, - NeededActions: []domain.Action{domain.ActionInstall}} - - // when - actual := convertToComponentDiffDTO(domainDiff) - - // then - expected := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Version: nil, Absent: true}, - Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Namespace: testNamespaceString, DeployConfig: testDeployConfig}, - NeededActions: []crd.ComponentAction{domain.ActionInstall}, - } - assert.Equal(t, expected, actual) - }) - t.Run("should copy model diff to DTO diff - present", func(t *testing.T) { - // given - domainDiff := domain.ComponentDiff{ - Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testSemverVersionHigh}, - Expected: domain.ComponentDiffState{Version: nil, Absent: false}, - NeededActions: []domain.Action{domain.ActionUninstall}} - - // when - actual := convertToComponentDiffDTO(domainDiff) - - // then - expected := crd.ComponentDiff{ - Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw}, - Expected: crd.ComponentDiffState{Version: nil, Absent: false}, - NeededActions: []crd.ComponentAction{domain.ActionUninstall}, - } - assert.Equal(t, expected, actual) - }) -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go deleted file mode 100644 index 97a14997..00000000 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/component_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package serializer - -import ( - "fmt" - "testing" - - bpv2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" - - "github.com/stretchr/testify/assert" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" -) - -var ( - k8sK8sDoguOperator = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "k8s-dogu-operator"} -) - -func TestConvertComponents(t *testing.T) { - type args struct { - components []bpv2.Component - } - wrongVersion1 := "1." - version1 := "1.0.0" - tests := []struct { - name string - args args - want []domain.Component - wantErr assert.ErrorAssertionFunc - }{ - { - name: "nil", - args: args{components: nil}, - want: nil, - wantErr: assert.NoError, - }, - { - name: "empty list", - args: args{components: []bpv2.Component{}}, - want: nil, - wantErr: assert.NoError, - }, - { - name: "normal component", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &version3211.Raw, Absent: &falseVar, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}}, - want: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211, DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system", "configOverwrite": map[string]string{"key": "value"}}}}, - wantErr: assert.NoError, - }, - { - name: "absent component", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Absent: &trueVar}}}, - want: []domain.Component{{Name: k8sK8sDoguOperator, Absent: true}}, - wantErr: assert.NoError, - }, - { - name: "unparsable version", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &wrongVersion1}}}, - want: nil, - wantErr: assert.Error, - }, - { - name: "invalid component name", - args: args{components: []bpv2.Component{{Name: "k8s/k8s-dogu-operator/oh/no", Version: &version1}}}, - want: nil, - wantErr: assert.Error, - }, - { - name: "does not contain distribution namespace", - args: args{components: []bpv2.Component{{Name: "k8s-dogu-operator", Version: &version3211.Raw}}}, - want: nil, - wantErr: assert.Error, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ConvertComponents(tt.args.components) - if !tt.wantErr(t, err, fmt.Sprintf("ConvertComponents(%v)", tt.args.components)) { - return - } - assert.Equalf(t, tt.want, got, "ConvertComponents(%v)", tt.args.components) - }) - } -} - -func TestConvertToComponentDTOs(t *testing.T) { - type args struct { - components []domain.Component - } - tests := []struct { - name string - args args - want []bpv2.Component - }{ - { - name: "nil", - args: args{}, - want: []bpv2.Component{}, - }, - { - name: "empty list", - args: args{components: []domain.Component{}}, - want: []bpv2.Component{}, - }, - { - name: "ok", - args: args{components: []domain.Component{{Name: k8sK8sDoguOperator, Version: compVersion3211}}}, - want: []bpv2.Component{{Name: "k8s/k8s-dogu-operator", Version: &version3211.Raw, Absent: &falseVar}}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ConvertToComponentDTOs(tt.args.components) - assert.Equalf(t, tt.want, got, "ConvertToComponentDTOs(%v)", tt.args.components) - }) - } -} diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go index d0c4f89f..bc90bf24 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff.go @@ -14,11 +14,6 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) *crd.StateDiff { doguDiffs[string(doguDiff.DoguName)] = convertToDoguDiffDTO(doguDiff) } - componentDiffs := make(map[string]crd.ComponentDiff, len(domainModel.ComponentDiffs)) - for _, componentDiff := range domainModel.ComponentDiffs { - componentDiffs[string(componentDiff.Name)] = convertToComponentDiffDTO(componentDiff) - } - var dogus []cescommons.SimpleName var combinedConfigDiffs map[string]crd.CombinedDoguConfigDiff var doguConfigDiffByDogu map[cescommons.SimpleName]crd.DoguConfigDiff @@ -49,7 +44,6 @@ func ConvertToStateDiffDTO(domainModel domain.StateDiff) *crd.StateDiff { return &crd.StateDiff{ DoguDiffs: doguDiffs, - ComponentDiffs: componentDiffs, DoguConfigDiffs: combinedConfigDiffs, GlobalConfigDiff: convertToGlobalConfigDiffDTO(domainModel.GlobalConfigDiffs), } diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go index edb39a9e..222c296a 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/serializer/stateDiff_test.go @@ -4,7 +4,6 @@ import ( "reflect" "testing" - "github.com/Masterminds/semver/v3" cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" crd "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" @@ -15,25 +14,19 @@ import ( "github.com/stretchr/testify/assert" ) -const testComponentName = "my-component" - var ( - testSemverVersionLowRaw = "1.2.3" - testSemverVersionLow = semver.MustParse(testSemverVersionLowRaw) - testSemverVersionHighRaw = "2.3.4" - testSemverVersionHigh = semver.MustParse(testSemverVersionHighRaw) - testCoreVersionLow = mustParseVersion("1.1.1-1") - testCoreVersionLowStr = testCoreVersionLow.String() - testCoreVersionHigh = mustParseVersion("1.2.3-1") - testCoreVersionHighStr = testCoreVersionHigh.String() - testDogu = cescommons.SimpleName("testDogu") - testDogu2 = cescommons.SimpleName("testDogu2") - testDoguKey1 = common.DoguConfigKey{DoguName: testDogu, Key: "key1"} - testDoguKey2 = common.DoguConfigKey{DoguName: testDogu2, Key: "key2"} - testFqdn1 = "ces1.example.com" - testFqdn2 = "ces2.example.com" - testSubfolderStr = "subfolder" - testSubfolderStr2 = "different_subfolder" + testCoreVersionLow = mustParseVersion("1.1.1-1") + testCoreVersionLowStr = testCoreVersionLow.String() + testCoreVersionHigh = mustParseVersion("1.2.3-1") + testCoreVersionHighStr = testCoreVersionHigh.String() + testDogu = cescommons.SimpleName("testDogu") + testDogu2 = cescommons.SimpleName("testDogu2") + testDoguKey1 = common.DoguConfigKey{DoguName: testDogu, Key: "key1"} + testDoguKey2 = common.DoguConfigKey{DoguName: testDogu2, Key: "key2"} + testFqdn1 = "ces1.example.com" + testFqdn2 = "ces2.example.com" + testSubfolderStr = "subfolder" + testSubfolderStr2 = "different_subfolder" ) func TestConvertToDTO(t *testing.T) { @@ -72,7 +65,7 @@ func TestConvertToDTO(t *testing.T) { }, NeededActions: []crd.DoguAction{"upgrade"}, }, - }, ComponentDiffs: map[string]crd.ComponentDiff{}}, + }}, }, { name: "should convert multiple dogu diffs", @@ -129,40 +122,7 @@ func TestConvertToDTO(t *testing.T) { }, NeededActions: []crd.DoguAction{"uninstall"}, }, - }, ComponentDiffs: map[string]crd.ComponentDiff{}}, - }, - { - name: "should convert multiple component diffs", - domainModel: domain.StateDiff{ - DoguDiffs: domain.DoguDiffs{}, - ComponentDiffs: []domain.ComponentDiff{ - { - Name: testComponentName, - Actual: domain.ComponentDiffState{Version: testSemverVersionLow, Absent: false}, - Expected: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, - NeededActions: []domain.Action{domain.ActionUpgrade, domain.ActionSwitchComponentNamespace}, - }, - { - Name: "my-component-2", - Actual: domain.ComponentDiffState{Version: testSemverVersionHigh, Absent: false}, - Expected: domain.ComponentDiffState{Absent: true}, - NeededActions: []domain.Action{domain.ActionUninstall}, - }, - }}, - want: &crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{ - testComponentName: { - Actual: crd.ComponentDiffState{Version: &testSemverVersionLowRaw, Absent: false}, - Expected: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, - NeededActions: []crd.ComponentAction{"upgrade", "component namespace switch"}, - }, - "my-component-2": { - Actual: crd.ComponentDiffState{Version: &testSemverVersionHighRaw, Absent: false}, - Expected: crd.ComponentDiffState{Absent: true}, - NeededActions: []crd.ComponentAction{"uninstall"}, - }, - }}, + }}, }, { name: "should convert multiple dogu config diffs", @@ -177,8 +137,7 @@ func TestConvertToDTO(t *testing.T) { }, }, want: &crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguDiffs: map[string]crd.DoguDiff{}, DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ "ldap": {}, "postfix": {}, @@ -202,8 +161,7 @@ func TestConvertToDTO(t *testing.T) { }}, }, want: &crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguDiffs: map[string]crd.DoguDiff{}, GlobalConfigDiff: []crd.ConfigEntryDiff{{ Key: "fqdn", Actual: crd.ConfigValueState{ @@ -251,7 +209,6 @@ func TestConvertToDTO(t *testing.T) { NeededActions: []domain.Action{domain.ActionUpgrade}, }}}, want: &crd.StateDiff{ - ComponentDiffs: map[string]crd.ComponentDiff{}, DoguDiffs: map[string]crd.DoguDiff{ "ldap": { Actual: crd.DoguDiffState{ @@ -316,8 +273,7 @@ func TestConvertToStateDiffDTO(t *testing.T) { { name: "normal dogu config", model: domain.StateDiff{ - DoguDiffs: nil, - ComponentDiffs: nil, + DoguDiffs: nil, DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{ testDogu: { { @@ -351,8 +307,7 @@ func TestConvertToStateDiffDTO(t *testing.T) { GlobalConfigDiffs: nil, }, want: &crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguDiffs: map[string]crd.DoguDiff{}, DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ testDogu.String(): { DoguConfigDiff: crd.DoguConfigDiff{ @@ -394,7 +349,6 @@ func TestConvertToStateDiffDTO(t *testing.T) { name: "censor sensitive config", model: domain.StateDiff{ DoguDiffs: nil, - ComponentDiffs: nil, DoguConfigDiffs: map[cescommons.SimpleName]domain.DoguConfigDiffs{}, SensitiveDoguConfigDiffs: map[cescommons.SimpleName]domain.SensitiveDoguConfigDiffs{ testDogu: { @@ -429,8 +383,7 @@ func TestConvertToStateDiffDTO(t *testing.T) { GlobalConfigDiffs: nil, }, want: &crd.StateDiff{ - DoguDiffs: map[string]crd.DoguDiff{}, - ComponentDiffs: map[string]crd.ComponentDiff{}, + DoguDiffs: map[string]crd.DoguDiff{}, DoguConfigDiffs: map[string]crd.CombinedDoguConfigDiff{ testDogu.String(): { SensitiveDoguConfigDiff: crd.SensitiveDoguConfigDiff{ diff --git a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go deleted file mode 100644 index badd3e6c..00000000 --- a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo.go +++ /dev/null @@ -1,107 +0,0 @@ -package componentcr - -import ( - "context" - "fmt" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/log" - - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - compCli "github.com/cloudogu/k8s-component-operator/pkg/api/ecosystem" -) - -const ( - componentInstallationRepoContextKey = "componentInstallationRepoContext" -) - -type componentInstallationRepoContext struct { - resourceVersion string -} - -type componentInstallationRepo struct { - componentClient compCli.ComponentInterface -} - -// NewComponentInstallationRepo creates a new component repo adapter. -func NewComponentInstallationRepo(componentClient compCli.ComponentInterface) domainservice.ComponentInstallationRepository { - return &componentInstallationRepo{componentClient: componentClient} -} - -// GetByName fetches a named component resource and returns it as ecosystem.ComponentInstallation. -func (repo *componentInstallationRepo) GetByName(ctx context.Context, componentName common.SimpleComponentName) (*ecosystem.ComponentInstallation, error) { - cr, err := repo.componentClient.Get(ctx, string(componentName), metav1.GetOptions{}) - if err != nil { - if k8sErrors.IsNotFound(err) { - return nil, &domainservice.NotFoundError{ - WrappedError: err, - Message: fmt.Sprintf("cannot read component CR %q as it does not exist", componentName), - } - } - return nil, domainservice.NewInternalError(err, "error while reading component CR %q", componentName) - } - - return parseComponentCR(cr) -} - -// GetAll fetches all installed component resources and returns them as list of ecosystem.ComponentInstallation. -func (repo *componentInstallationRepo) GetAll(ctx context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error) { - list, err := repo.componentClient.List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } - - componentInstallations := make(map[common.SimpleComponentName]*ecosystem.ComponentInstallation, len(list.Items)) - for _, componentCr := range list.Items { - cr, err := parseComponentCR(&componentCr) - if err != nil { - return nil, domainservice.NewInternalError(err, "failed to parse component CR %#v", componentCr) - } - componentInstallations[common.SimpleComponentName(componentCr.Name)] = cr - } - - return componentInstallations, nil -} - -func (repo *componentInstallationRepo) Update(ctx context.Context, component *ecosystem.ComponentInstallation) error { - logger := log.FromContext(ctx).WithName("doguInstallationRepo.Update") - patch, err := toComponentCRPatchBytes(component) - if err != nil { - return domainservice.NewInternalError(err, "failed to get patch bytes from component %q", component.Name.SimpleName) - } - - logger.Info("patch component CR", "doguName", component.Name.SimpleName, "doguPatch", string(patch)) - - _, err = repo.componentClient.Patch(ctx, string(component.Name.SimpleName), types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - return domainservice.NewInternalError(err, "failed to patch component %q", component.Name.SimpleName) - } - - return nil -} - -func (repo *componentInstallationRepo) Delete(ctx context.Context, componentName common.SimpleComponentName) error { - err := repo.componentClient.Delete(ctx, string(componentName), metav1.DeleteOptions{}) - if err != nil { - return domainservice.NewInternalError(err, "failed to delete component CR %q", componentName) - } - - return nil -} - -func (repo *componentInstallationRepo) Create(ctx context.Context, component *ecosystem.ComponentInstallation) error { - cr, err := toComponentCR(component) - if err != nil { - return domainservice.NewInternalError(err, "failed to convert component installation %q", component.Name) - } - _, err = repo.componentClient.Create(ctx, cr, metav1.CreateOptions{}) - if err != nil { - return domainservice.NewInternalError(err, "failed to create component CR %q", component.Name.SimpleName) - } - - return nil -} diff --git a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go b/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go deleted file mode 100644 index a9316317..00000000 --- a/pkg/adapter/kubernetes/componentcr/componentInstallationRepo_test.go +++ /dev/null @@ -1,437 +0,0 @@ -package componentcr - -import ( - "context" - "testing" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "k8s.io/apimachinery/pkg/types" - - "github.com/Masterminds/semver/v3" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - - compV1 "github.com/cloudogu/k8s-component-operator/pkg/api/v1" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" -) - -var testCtx = context.Background() - -const ( - testComponentNameRaw = "my-component" - testNamespace = "k8s" -) - -var testComponentName = common.QualifiedComponentName{ - Namespace: testNamespace, - SimpleName: testComponentNameRaw, -} - -func Test_componentInstallationRepo_GetAll(t *testing.T) { - t.Run("should return error when k8s client fails generically", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - mockRepo.EXPECT().List(testCtx, mock.Anything).Return(nil, assert.AnError) - sut := componentInstallationRepo{componentClient: mockRepo} - - // when - _, err := sut.GetAll(testCtx) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - t.Run("should return InternalError when resource parsing fails", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - listWithErroneousElement := &compV1.ComponentList{Items: []compV1.Component{{ - Spec: compV1.ComponentSpec{ - Name: string(testComponentName.SimpleName), - Namespace: testNamespace, - Version: "a-b.c:d@1.2@parse-fail-here", - }, - }}} - mockRepo.EXPECT().List(testCtx, mock.Anything).Return(listWithErroneousElement, nil) - - // when - _, err := sut.GetAll(testCtx) - - // then - require.Error(t, err) - assert.IsType(t, err, &domainservice.InternalError{}) - assert.ErrorContains(t, err, "failed to parse component CR") - - }) - t.Run("should return all existing blueprint resources", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - list := &compV1.ComponentList{Items: []compV1.Component{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: string(testComponentName.SimpleName), - ResourceVersion: "42", - }, - Spec: compV1.ComponentSpec{ - Name: string(testComponentName.SimpleName), - Namespace: string(testComponentName.Namespace), - Version: "1.2.3-4", - }, - Status: compV1.ComponentStatus{ - Status: compV1.ComponentStatusInstalled, - Health: compV1.PendingHealthStatus, - }, - }, - }} - mockRepo.EXPECT().List(testCtx, mock.Anything).Return(list, nil) - - // when - actual, err := sut.GetAll(testCtx) - - // then - require.NoError(t, err) - - expected := map[common.SimpleComponentName]*ecosystem.ComponentInstallation{} - version, _ := semver.NewVersion("1.2.3-4") - expected[testComponentName.SimpleName] = &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: version, - Status: "installed", - Health: "", - PersistenceContext: nil, - } - assert.Equal(t, expected[testComponentName.SimpleName].Name, actual[testComponentName.SimpleName].Name) - assert.Equal(t, expected[testComponentName.SimpleName].Status, actual[testComponentName.SimpleName].Status) - assert.Equal(t, expected[testComponentName.SimpleName].ExpectedVersion, actual[testComponentName.SimpleName].ExpectedVersion) - assert.Equal(t, expected[testComponentName.SimpleName].Health, actual[testComponentName.SimpleName].Health) - // map pointers are hard to compare, test each field individually - assert.Equal(t, - map[string]any{componentInstallationRepoContextKey: componentInstallationRepoContext{resourceVersion: "42"}}, - actual[testComponentName.SimpleName].PersistenceContext) - }) -} - -func Test_componentInstallationRepo_GetByName(t *testing.T) { - t.Run("should return error when k8s client fails generically", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - mockRepo.EXPECT().Get(testCtx, string(testComponentName.SimpleName), metav1.GetOptions{}).Return(nil, assert.AnError) - sut := componentInstallationRepo{componentClient: mockRepo} - - // when - _, err := sut.GetByName(testCtx, testComponentName.SimpleName) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - }) - t.Run("should return InternalError when resource parsing fails", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - erroneousComponent := &compV1.Component{ - Spec: compV1.ComponentSpec{ - Name: string(testComponentName.SimpleName), - Namespace: testNamespace, - Version: "a-b.c:d@1.2@parse-fail-here", - }, - } - mockRepo.EXPECT().Get(testCtx, string(testComponentName.SimpleName), metav1.GetOptions{}).Return(erroneousComponent, nil) - - // when - _, err := sut.GetByName(testCtx, testComponentName.SimpleName) - - // then - require.Error(t, err) - assert.IsType(t, err, &domainservice.InternalError{}) - assert.ErrorContains(t, err, "cannot load component CR as it cannot be parsed correctly") - }) - t.Run("should return InternalError when resource is nil", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - mockRepo.EXPECT().Get(testCtx, string(testComponentName.SimpleName), metav1.GetOptions{}).Return(nil, nil) - - // when - _, err := sut.GetByName(testCtx, testComponentName.SimpleName) - - // then - require.Error(t, err) - assert.IsType(t, err, &domainservice.InternalError{}) - assert.ErrorContains(t, err, "cannot parse component CR as it is nil") - }) - t.Run("should return NotFoundError when resource does not exist", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - errNotFound := errors.NewNotFound( - schema.GroupResource{Group: compV1.GroupVersion.Group, Resource: "component"}, - string(testComponentName.SimpleName)) - mockRepo.EXPECT().Get(testCtx, string(testComponentName.SimpleName), metav1.GetOptions{}).Return(nil, errNotFound) - - // when - _, err := sut.GetByName(testCtx, testComponentName.SimpleName) - - // then - require.Error(t, err) - assert.IsType(t, err, &domainservice.NotFoundError{}) - assert.ErrorContains(t, err, `cannot read component CR "my-component" as it does not exist`) - }) - t.Run("should return all existing blueprint resources", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - sut := componentInstallationRepo{componentClient: mockRepo} - result := &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: string(testComponentName.SimpleName), - ResourceVersion: "42", - }, - Spec: compV1.ComponentSpec{ - Name: string(testComponentName.SimpleName), - Namespace: testNamespace, - Version: "1.2.3-4", - }, - Status: compV1.ComponentStatus{ - Status: compV1.ComponentStatusInstalled, - Health: compV1.PendingHealthStatus, - }, - } - mockRepo.EXPECT().Get(testCtx, string(testComponentName.SimpleName), metav1.GetOptions{}).Return(result, nil) - - // when - actual, err := sut.GetByName(testCtx, testComponentName.SimpleName) - - // then - require.NoError(t, err) - - version, _ := semver.NewVersion("1.2.3-4") - expected := ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: version, - Status: "installed", - Health: "", - PersistenceContext: nil, - } - assert.Equal(t, expected.Name, actual.Name) - assert.Equal(t, expected.Name, testComponentName) - assert.Equal(t, expected.Status, actual.Status) - assert.Equal(t, expected.ExpectedVersion, actual.ExpectedVersion) - assert.Equal(t, expected.Health, actual.Health) - // map pointers are hard to compare, test each field individually - assert.Equal(t, - map[string]any{componentInstallationRepoContextKey: componentInstallationRepoContext{resourceVersion: "42"}}, actual.PersistenceContext) - }) -} - -func TestNewComponentInstallationRepo(t *testing.T) { - t.Run("should return proper repo interface implementation", func(t *testing.T) { - // given - mockRepo := newMockComponentRepo(t) - - // when - actual := NewComponentInstallationRepo(mockRepo) - - // then - assert.Implements(t, (*domainservice.ComponentInstallationRepository)(nil), actual) - }) -} - -func Test_componentInstallationRepo_Update(t *testing.T) { - t.Run("should patch cr on update", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - componentInstallation := &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]interface{}{"key": "value"}, - }, - } - patch := []byte("{\"spec\":{\"namespace\":\"k8s\",\"name\":\"my-component\",\"version\":\"1.0.0-1\",\"deployNamespace\":\"longhorn-system\",\"valuesYamlOverwrite\":\"key: value\\n\"}}") - componentClientMock.EXPECT().Patch(testCtx, string(testComponentName.SimpleName), types.MergePatchType, patch, metav1.PatchOptions{}).Return(nil, nil) - - // when - err := sut.Update(testCtx, componentInstallation) - - // then - require.NoError(t, err) - }) - - t.Run("should return error on patch error", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - componentInstallation := &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - } - patch := []byte("{\"spec\":{\"namespace\":\"k8s\",\"name\":\"my-component\",\"version\":\"1.0.0-1\",\"deployNamespace\":null,\"valuesYamlOverwrite\":null}}") - componentClientMock.EXPECT().Patch(testCtx, string(testComponentName.SimpleName), types.MergePatchType, patch, metav1.PatchOptions{}).Return(nil, assert.AnError) - - // when - err := sut.Update(testCtx, componentInstallation) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to patch component \"my-component\"") - assert.IsType(t, err, &domainservice.InternalError{}) - }) -} - -func Test_componentInstallationRepo_Delete(t *testing.T) { - t.Run("should delete cr on delete", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - - componentClientMock.EXPECT().Delete(testCtx, string(testComponentName.SimpleName), metav1.DeleteOptions{}).Return(nil) - - // when - err := sut.Delete(testCtx, testComponentName.SimpleName) - - // then - require.NoError(t, err) - }) - - t.Run("should return error on delete error", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - - componentClientMock.EXPECT().Delete(testCtx, string(testComponentName.SimpleName), metav1.DeleteOptions{}).Return(assert.AnError) - - // when - err := sut.Delete(testCtx, testComponentName.SimpleName) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to delete component CR \"my-component\"") - assert.IsType(t, err, &domainservice.InternalError{}) - }) -} - -func Test_componentInstallationRepo_Create(t *testing.T) { - t.Run("should create cr on create", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - component := &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - } - expectedCR := &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: string(component.Name.SimpleName), - Labels: map[string]string{ - ComponentNameLabelKey: string(component.Name.SimpleName), - ComponentVersionLabelKey: component.ExpectedVersion.String(), - "app": "ces", - "k8s.cloudogu.com/app": "ces", - "component.name": string(component.Name.SimpleName), - "app.kubernetes.io/name": string(component.Name.SimpleName), - "app.kubernetes.io/version": component.ExpectedVersion.String(), - "app.kubernetes.io/part-of": "ces", - "app.kubernetes.io/managed-by": "k8s-blueprint-operator", - }, - }, - Spec: compV1.ComponentSpec{ - Namespace: string(component.Name.Namespace), - Name: string(component.Name.SimpleName), - Version: component.ExpectedVersion.String(), - }, - } - - componentClientMock.EXPECT().Create(testCtx, expectedCR, metav1.CreateOptions{}).Return(nil, nil) - - // when - err := sut.Create(testCtx, component) - - // then - require.NoError(t, err) - }) - - t.Run("should return error on convert component installation error", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - componentInstallation := &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": map[string]string{"no": "string"}, - }, - } - - // when - err := sut.Create(testCtx, componentInstallation) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to convert component installation \"k8s/my-component\"") - assert.IsType(t, err, &domainservice.InternalError{}) - }) - - t.Run("should return error on patch error", func(t *testing.T) { - // given - componentClientMock := newMockComponentRepo(t) - sut := componentInstallationRepo{ - componentClient: componentClientMock, - } - component := &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - } - expectedCR := &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: string(component.Name.SimpleName), - Labels: map[string]string{ - ComponentNameLabelKey: string(component.Name.SimpleName), - ComponentVersionLabelKey: component.ExpectedVersion.String(), - "app": "ces", - "k8s.cloudogu.com/app": "ces", - "component.name": string(component.Name.SimpleName), - "app.kubernetes.io/name": string(component.Name.SimpleName), - "app.kubernetes.io/version": component.ExpectedVersion.String(), - "app.kubernetes.io/part-of": "ces", - "app.kubernetes.io/managed-by": "k8s-blueprint-operator", - }, - }, - Spec: compV1.ComponentSpec{ - Namespace: string(component.Name.Namespace), - Name: string(component.Name.SimpleName), - Version: component.ExpectedVersion.String(), - }, - } - - componentClientMock.EXPECT().Create(testCtx, expectedCR, metav1.CreateOptions{}).Return(nil, assert.AnError) - - // when - err := sut.Create(testCtx, component) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "failed to create component CR \"my-component\"") - assert.IsType(t, err, &domainservice.InternalError{}) - }) -} diff --git a/pkg/adapter/kubernetes/componentcr/componentSerializer.go b/pkg/adapter/kubernetes/componentcr/componentSerializer.go deleted file mode 100644 index 36fdaba1..00000000 --- a/pkg/adapter/kubernetes/componentcr/componentSerializer.go +++ /dev/null @@ -1,201 +0,0 @@ -package componentcr - -import ( - "encoding/json" - "fmt" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - compV1 "github.com/cloudogu/k8s-component-operator/pkg/api/v1" - "gopkg.in/yaml.v3" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" -) - -const ( - deployConfigKeyDeployNamespace = "deployNamespace" - deployConfigKeyOverwriteConfig = "overwriteConfig" - ComponentNameLabelKey = "k8s.cloudogu.com/component.name" - ComponentVersionLabelKey = "k8s.cloudogu.com/component.version" -) - -func parseComponentCR(cr *compV1.Component) (*ecosystem.ComponentInstallation, error) { - if cr == nil { - return nil, domainservice.NewInternalError(nil, "cannot parse component CR as it is nil") - } - - expectedVersion, err := semver.NewVersion(cr.Spec.Version) - if err != nil { - return nil, domainservice.NewInternalError(err, "cannot load component CR as it cannot be parsed correctly") - } - - // ignore error as the actual version could be not set and a nil pointer as the version is exactly what we want then - actualVersion, _ := semver.NewVersion(cr.Status.InstalledVersion) - - persistenceContext := make(map[string]interface{}, 1) - persistenceContext[componentInstallationRepoContextKey] = componentInstallationRepoContext{ - resourceVersion: cr.GetResourceVersion(), - } - - name, err := common.NewQualifiedComponentName(common.ComponentNamespace(cr.Spec.Namespace), common.SimpleComponentName(cr.Name)) - if err != nil { - return nil, err - } - - componentConfig, err := parseDeployConfig(cr) - if err != nil { - return nil, err - } - - return &ecosystem.ComponentInstallation{ - Name: name, - ExpectedVersion: expectedVersion, - ActualVersion: actualVersion, - Status: cr.Status.Status, - Health: ecosystem.HealthStatus(cr.Status.Health), - PersistenceContext: persistenceContext, - DeployConfig: componentConfig, - }, nil -} - -func parseDeployConfig(cr *compV1.Component) (ecosystem.DeployConfig, error) { - componentConfig := ecosystem.DeployConfig{} - if cr.Spec.DeployNamespace != "" { - componentConfig[deployConfigKeyDeployNamespace] = cr.Spec.DeployNamespace - } - - if cr.Spec.ValuesYamlOverwrite != "" { - valuesYamlOverwrite := map[string]interface{}{} - // We need to use k8syaml here because goyaml unmarshals to map[interface{}]interface {} which is not supported setting in a k8s resource. - err := k8syaml.Unmarshal([]byte(cr.Spec.ValuesYamlOverwrite), &valuesYamlOverwrite) - if err != nil { - return nil, domainservice.NewInternalError(err, "failed to unmarshal values yaml overwrite %q", cr.Spec.ValuesYamlOverwrite) - } - componentConfig[deployConfigKeyOverwriteConfig] = valuesYamlOverwrite - } - - return componentConfig, nil -} - -func toComponentCR(component *ecosystem.ComponentInstallation) (*compV1.Component, error) { - deployNamespace, err := toDeployNamespace(component.DeployConfig) - if err != nil { - return nil, err - } - - valuesYamlOverwrite, err := toValuesYamlOverwrite(component.DeployConfig) - if err != nil { - return nil, err - } - - spec := compV1.ComponentSpec{ - Namespace: string(component.Name.Namespace), - Name: string(component.Name.SimpleName), - Version: component.ExpectedVersion.String(), - } - if deployNamespace != "" { - spec.DeployNamespace = deployNamespace - } - if valuesYamlOverwrite != "" { - spec.ValuesYamlOverwrite = valuesYamlOverwrite - } - - return &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: string(component.Name.SimpleName), - Labels: map[string]string{ - ComponentNameLabelKey: string(component.Name.SimpleName), - ComponentVersionLabelKey: component.ExpectedVersion.String(), - "app": "ces", - "k8s.cloudogu.com/app": "ces", - "component.name": string(component.Name.SimpleName), - "app.kubernetes.io/name": string(component.Name.SimpleName), - "app.kubernetes.io/version": component.ExpectedVersion.String(), - "app.kubernetes.io/part-of": "ces", - "app.kubernetes.io/managed-by": "k8s-blueprint-operator", - }, - }, - Spec: spec, - }, nil -} - -func toDeployNamespace(deployConfig ecosystem.DeployConfig) (string, error) { - deployNamespace, found := deployConfig[deployConfigKeyDeployNamespace] - if !found { - return "", nil - } - deployNamespaceStr, ok := deployNamespace.(string) - if !ok { - return "", fmt.Errorf("deployNamespace is not type of string") - } - - return deployNamespaceStr, nil -} - -func toValuesYamlOverwrite(deployConfig ecosystem.DeployConfig) (string, error) { - in, found := deployConfig[deployConfigKeyOverwriteConfig] - if !found { - return "", nil - } - valuesYamlOverwriteBytes, err := yaml.Marshal(in) - if err != nil { - return "", fmt.Errorf("failed to marshal overwrite config %q", in) - } - - return string(valuesYamlOverwriteBytes), nil -} - -type componentCRPatch struct { - Spec componentSpecPatch `json:"spec"` -} - -type componentSpecPatch struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Version string `json:"version"` - DeployNamespace *string `json:"deployNamespace"` - ValuesYamlOverwrite *string `json:"valuesYamlOverwrite"` -} - -func toComponentCRPatch(component *ecosystem.ComponentInstallation) (*componentCRPatch, error) { - deployNamespace, err := toDeployNamespace(component.DeployConfig) - if err != nil { - return nil, err - } - - valuesYamlOverwrite, err := toValuesYamlOverwrite(component.DeployConfig) - if err != nil { - return nil, err - } - - spec := componentSpecPatch{ - Namespace: string(component.Name.Namespace), - Name: string(component.Name.SimpleName), - Version: component.ExpectedVersion.String(), - } - if deployNamespace != "" { - spec.DeployNamespace = &deployNamespace - } - if valuesYamlOverwrite != "" { - spec.ValuesYamlOverwrite = &valuesYamlOverwrite - } - - return &componentCRPatch{ - Spec: spec, - }, nil -} - -func toComponentCRPatchBytes(component *ecosystem.ComponentInstallation) ([]byte, error) { - crPatch, err := toComponentCRPatch(component) - if err != nil { - return nil, domainservice.NewInternalError(err, "failed to create component CR patch for component %q", component.Name) - } - patch, err := json.Marshal(crPatch) - - if err != nil { - return []byte{}, domainservice.NewInternalError(err, "cannot patch component CR for component %q", component.Name) - } - return patch, nil -} diff --git a/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go b/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go deleted file mode 100644 index d7f4b38e..00000000 --- a/pkg/adapter/kubernetes/componentcr/componentSerializer_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package componentcr - -import ( - _ "embed" - "fmt" - "testing" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - compV1 "github.com/cloudogu/k8s-component-operator/pkg/api/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - testDeployNamespace = "ecosystem" - testStatus = ecosystem.ComponentStatusNotInstalled - testHealthStatus = compV1.AvailableHealthStatus - testResourceVersion = "1" -) - -var ( - //go:embed testdata/testPatch - testPatchBytes []byte - testVersion1, _ = semver.NewVersion("1.0.0-1") -) - -func Test_parseComponentCR(t *testing.T) { - valuesOverwrite := map[string]interface{}{"key": "value", "key1": map[string]string{"key": "value"}} - expectedValuesOverwrite := map[string]interface{}{"key": "value", "key1": map[string]interface{}{"key": "value"}} - valuesOverwriteYAMLBytes, err := yaml.Marshal(valuesOverwrite) - require.NoError(t, err) - - type args struct { - cr *compV1.Component - } - tests := []struct { - name string - args args - want *ecosystem.ComponentInstallation - wantErr assert.ErrorAssertionFunc - }{ - { - name: "success", - args: args{ - cr: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: testComponentNameRaw, - Namespace: testDeployNamespace, - ResourceVersion: testResourceVersion, - }, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - DeployNamespace: "longhorn-system", - ValuesYamlOverwrite: string(valuesOverwriteYAMLBytes), - }, - Status: compV1.ComponentStatus{ - Status: testStatus, - Health: testHealthStatus, - InstalledVersion: testVersion1.String(), - }, - }, - }, - want: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - ActualVersion: testVersion1, - Status: testStatus, - Health: ecosystem.HealthStatus(testHealthStatus), - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": expectedValuesOverwrite, - }, - PersistenceContext: map[string]interface{}{ - componentInstallationRepoContextKey: componentInstallationRepoContext{testResourceVersion}, - }, - }, - wantErr: assert.NoError, - }, - { - name: "should return error on nil component", - args: args{ - cr: nil, - }, - wantErr: assert.Error, - }, - { - name: "should return expected version parse error", - args: args{ - cr: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: testComponentNameRaw, - }, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: "fsdfsd", - }, - }, - }, - wantErr: assert.Error, - }, - { - name: "should return actual version parse error", - args: args{ - cr: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: testComponentNameRaw, - ResourceVersion: testResourceVersion, - }, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - }, - Status: compV1.ComponentStatus{ - InstalledVersion: "fsdfsd", - }, - }, - }, - want: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - ActualVersion: nil, - DeployConfig: map[string]interface{}{}, - PersistenceContext: map[string]interface{}{ - componentInstallationRepoContextKey: componentInstallationRepoContext{testResourceVersion}, - }, - }, - wantErr: assert.NoError, - }, - { - name: "should return error on missing resource name", - args: args{ - cr: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{}, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - }, - }, - }, - wantErr: assert.Error, - }, - { - name: "should return error on wrong package config value", - args: args{ - cr: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: "name", - }, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - DeployNamespace: "longhorn-system", - ValuesYamlOverwrite: "no yaml object", - }, - }, - }, - wantErr: assert.Error, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := parseComponentCR(tt.args.cr) - if !tt.wantErr(t, err, fmt.Sprintf("parseComponentCR(%v)", tt.args.cr)) { - return - } - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_toComponentCR(t *testing.T) { - type args struct { - componentInstallation *ecosystem.ComponentInstallation - } - tests := []struct { - name string - args args - want *compV1.Component - wantErr assert.ErrorAssertionFunc - }{ - { - name: "success", - args: args{ - componentInstallation: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - Status: testStatus, - Health: ecosystem.HealthStatus(testHealthStatus), - PersistenceContext: map[string]interface{}{ - componentInstallationRepoContextKey: componentInstallationRepoContext{testResourceVersion}, - }, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]interface{}{"key": "value"}, - }, - }, - }, - want: &compV1.Component{ - ObjectMeta: metav1.ObjectMeta{ - Name: testComponentNameRaw, - Labels: map[string]string{ - ComponentNameLabelKey: testComponentNameRaw, - ComponentVersionLabelKey: testVersion1.String(), - "app": "ces", - "k8s.cloudogu.com/app": "ces", - "component.name": string(testComponentName.SimpleName), - "app.kubernetes.io/name": string(testComponentName.SimpleName), - "app.kubernetes.io/version": testVersion1.String(), - "app.kubernetes.io/part-of": "ces", - "app.kubernetes.io/managed-by": "k8s-blueprint-operator", - }, - }, - Spec: compV1.ComponentSpec{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - DeployNamespace: "longhorn-system", - ValuesYamlOverwrite: "key: value\n", - }, - }, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := toComponentCR(tt.args.componentInstallation) - if !tt.wantErr(t, err, fmt.Sprintf("toComponentCR(%v)", tt.args.componentInstallation)) { - return - } - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_toComponentCRPatch(t *testing.T) { - testDeployNamespace := "longhorn-system" - testDeployConfig := "key: value\n" - type args struct { - component *ecosystem.ComponentInstallation - } - tests := []struct { - name string - args args - want *componentCRPatch - wantErr assert.ErrorAssertionFunc - }{ - { - name: "success", - args: args{ - component: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]interface{}{"key": "value"}, - }, - }, - }, - want: &componentCRPatch{ - Spec: componentSpecPatch{ - Namespace: testNamespace, - Name: testComponentNameRaw, - Version: testVersion1.String(), - DeployNamespace: &testDeployNamespace, - ValuesYamlOverwrite: &testDeployConfig, - }, - }, - wantErr: assert.NoError, - }, - { - name: "should return error on wrong package config type", - args: args{ - component: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": map[string]interface{}{"no": "string"}, - }, - }, - }, - wantErr: assert.Error, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := toComponentCRPatch(tt.args.component) - if !tt.wantErr(t, err, fmt.Sprintf("toComponentCRPatch(%v)", tt.args.component)) { - return - } - assert.Equal(t, tt.want, got) - }) - } -} - -func Test_toComponentCRPatchBytes(t *testing.T) { - type args struct { - component *ecosystem.ComponentInstallation - } - tests := []struct { - name string - args args - want []byte - wantErr assert.ErrorAssertionFunc - }{ - { - name: "success", - args: args{ - component: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]interface{}{"key": "value"}, - }, - }, - }, - want: testPatchBytes, - wantErr: assert.NoError, - }, - { - name: "should return error on error creating patch", - args: args{ - component: &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{ - "deployNamespace": map[string]interface{}{"no": "string"}, - }, - }, - }, - wantErr: assert.Error, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := toComponentCRPatchBytes(tt.args.component) - if !tt.wantErr(t, err, fmt.Sprintf("toComponentCRPatchBytes(%v)", tt.args.component)) { - return - } - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/adapter/kubernetes/componentcr/interfaces.go b/pkg/adapter/kubernetes/componentcr/interfaces.go deleted file mode 100644 index d21a697d..00000000 --- a/pkg/adapter/kubernetes/componentcr/interfaces.go +++ /dev/null @@ -1,11 +0,0 @@ -package componentcr - -import compCli "github.com/cloudogu/k8s-component-operator/pkg/api/ecosystem" - -// used for mocks - -//nolint:unused -//goland:noinspection GoUnusedType -type componentRepo interface { - compCli.ComponentInterface -} diff --git a/pkg/adapter/kubernetes/componentcr/mock_componentRepo_test.go b/pkg/adapter/kubernetes/componentcr/mock_componentRepo_test.go deleted file mode 100644 index 40d6516c..00000000 --- a/pkg/adapter/kubernetes/componentcr/mock_componentRepo_test.go +++ /dev/null @@ -1,1049 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package componentcr - -import ( - context "context" - - mock "github.com/stretchr/testify/mock" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - types "k8s.io/apimachinery/pkg/types" - - v1 "github.com/cloudogu/k8s-component-operator/pkg/api/v1" - - watch "k8s.io/apimachinery/pkg/watch" -) - -// mockComponentRepo is an autogenerated mock type for the componentRepo type -type mockComponentRepo struct { - mock.Mock -} - -type mockComponentRepo_Expecter struct { - mock *mock.Mock -} - -func (_m *mockComponentRepo) EXPECT() *mockComponentRepo_Expecter { - return &mockComponentRepo_Expecter{mock: &_m.Mock} -} - -// AddFinalizer provides a mock function with given fields: ctx, component, finalizer -func (_m *mockComponentRepo) AddFinalizer(ctx context.Context, component *v1.Component, finalizer string) (*v1.Component, error) { - ret := _m.Called(ctx, component, finalizer) - - if len(ret) == 0 { - panic("no return value specified for AddFinalizer") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, string) (*v1.Component, error)); ok { - return rf(ctx, component, finalizer) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, string) *v1.Component); ok { - r0 = rf(ctx, component, finalizer) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component, string) error); ok { - r1 = rf(ctx, component, finalizer) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_AddFinalizer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddFinalizer' -type mockComponentRepo_AddFinalizer_Call struct { - *mock.Call -} - -// AddFinalizer is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -// - finalizer string -func (_e *mockComponentRepo_Expecter) AddFinalizer(ctx interface{}, component interface{}, finalizer interface{}) *mockComponentRepo_AddFinalizer_Call { - return &mockComponentRepo_AddFinalizer_Call{Call: _e.mock.On("AddFinalizer", ctx, component, finalizer)} -} - -func (_c *mockComponentRepo_AddFinalizer_Call) Run(run func(ctx context.Context, component *v1.Component, finalizer string)) *mockComponentRepo_AddFinalizer_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component), args[2].(string)) - }) - return _c -} - -func (_c *mockComponentRepo_AddFinalizer_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_AddFinalizer_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_AddFinalizer_Call) RunAndReturn(run func(context.Context, *v1.Component, string) (*v1.Component, error)) *mockComponentRepo_AddFinalizer_Call { - _c.Call.Return(run) - return _c -} - -// Create provides a mock function with given fields: ctx, component, opts -func (_m *mockComponentRepo) Create(ctx context.Context, component *v1.Component, opts metav1.CreateOptions) (*v1.Component, error) { - ret := _m.Called(ctx, component, opts) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.CreateOptions) (*v1.Component, error)); ok { - return rf(ctx, component, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.CreateOptions) *v1.Component); ok { - r0 = rf(ctx, component, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component, metav1.CreateOptions) error); ok { - r1 = rf(ctx, component, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type mockComponentRepo_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -// - opts metav1.CreateOptions -func (_e *mockComponentRepo_Expecter) Create(ctx interface{}, component interface{}, opts interface{}) *mockComponentRepo_Create_Call { - return &mockComponentRepo_Create_Call{Call: _e.mock.On("Create", ctx, component, opts)} -} - -func (_c *mockComponentRepo_Create_Call) Run(run func(ctx context.Context, component *v1.Component, opts metav1.CreateOptions)) *mockComponentRepo_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component), args[2].(metav1.CreateOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_Create_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_Create_Call) RunAndReturn(run func(context.Context, *v1.Component, metav1.CreateOptions) (*v1.Component, error)) *mockComponentRepo_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, name, opts -func (_m *mockComponentRepo) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.DeleteOptions) error); ok { - r0 = rf(ctx, name, opts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentRepo_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type mockComponentRepo_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts metav1.DeleteOptions -func (_e *mockComponentRepo_Expecter) Delete(ctx interface{}, name interface{}, opts interface{}) *mockComponentRepo_Delete_Call { - return &mockComponentRepo_Delete_Call{Call: _e.mock.On("Delete", ctx, name, opts)} -} - -func (_c *mockComponentRepo_Delete_Call) Run(run func(ctx context.Context, name string, opts metav1.DeleteOptions)) *mockComponentRepo_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(metav1.DeleteOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_Delete_Call) Return(_a0 error) *mockComponentRepo_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentRepo_Delete_Call) RunAndReturn(run func(context.Context, string, metav1.DeleteOptions) error) *mockComponentRepo_Delete_Call { - _c.Call.Return(run) - return _c -} - -// DeleteCollection provides a mock function with given fields: ctx, opts, listOpts -func (_m *mockComponentRepo) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - ret := _m.Called(ctx, opts, listOpts) - - if len(ret) == 0 { - panic("no return value specified for DeleteCollection") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.DeleteOptions, metav1.ListOptions) error); ok { - r0 = rf(ctx, opts, listOpts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentRepo_DeleteCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCollection' -type mockComponentRepo_DeleteCollection_Call struct { - *mock.Call -} - -// DeleteCollection is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.DeleteOptions -// - listOpts metav1.ListOptions -func (_e *mockComponentRepo_Expecter) DeleteCollection(ctx interface{}, opts interface{}, listOpts interface{}) *mockComponentRepo_DeleteCollection_Call { - return &mockComponentRepo_DeleteCollection_Call{Call: _e.mock.On("DeleteCollection", ctx, opts, listOpts)} -} - -func (_c *mockComponentRepo_DeleteCollection_Call) Run(run func(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions)) *mockComponentRepo_DeleteCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.DeleteOptions), args[2].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_DeleteCollection_Call) Return(_a0 error) *mockComponentRepo_DeleteCollection_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentRepo_DeleteCollection_Call) RunAndReturn(run func(context.Context, metav1.DeleteOptions, metav1.ListOptions) error) *mockComponentRepo_DeleteCollection_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: ctx, name, opts -func (_m *mockComponentRepo) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Component, error) { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.GetOptions) (*v1.Component, error)); ok { - return rf(ctx, name, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.GetOptions) *v1.Component); ok { - r0 = rf(ctx, name, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, metav1.GetOptions) error); ok { - r1 = rf(ctx, name, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type mockComponentRepo_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts metav1.GetOptions -func (_e *mockComponentRepo_Expecter) Get(ctx interface{}, name interface{}, opts interface{}) *mockComponentRepo_Get_Call { - return &mockComponentRepo_Get_Call{Call: _e.mock.On("Get", ctx, name, opts)} -} - -func (_c *mockComponentRepo_Get_Call) Run(run func(ctx context.Context, name string, opts metav1.GetOptions)) *mockComponentRepo_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(metav1.GetOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_Get_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_Get_Call) RunAndReturn(run func(context.Context, string, metav1.GetOptions) (*v1.Component, error)) *mockComponentRepo_Get_Call { - _c.Call.Return(run) - return _c -} - -// List provides a mock function with given fields: ctx, opts -func (_m *mockComponentRepo) List(ctx context.Context, opts metav1.ListOptions) (*v1.ComponentList, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for List") - } - - var r0 *v1.ComponentList - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) (*v1.ComponentList, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) *v1.ComponentList); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.ComponentList) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, metav1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' -type mockComponentRepo_List_Call struct { - *mock.Call -} - -// List is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.ListOptions -func (_e *mockComponentRepo_Expecter) List(ctx interface{}, opts interface{}) *mockComponentRepo_List_Call { - return &mockComponentRepo_List_Call{Call: _e.mock.On("List", ctx, opts)} -} - -func (_c *mockComponentRepo_List_Call) Run(run func(ctx context.Context, opts metav1.ListOptions)) *mockComponentRepo_List_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_List_Call) Return(_a0 *v1.ComponentList, _a1 error) *mockComponentRepo_List_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_List_Call) RunAndReturn(run func(context.Context, metav1.ListOptions) (*v1.ComponentList, error)) *mockComponentRepo_List_Call { - _c.Call.Return(run) - return _c -} - -// Patch provides a mock function with given fields: ctx, name, pt, data, opts, subresources -func (_m *mockComponentRepo) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.Component, error) { - _va := make([]interface{}, len(subresources)) - for _i := range subresources { - _va[_i] = subresources[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, name, pt, data, opts) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Patch") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) (*v1.Component, error)); ok { - return rf(ctx, name, pt, data, opts, subresources...) - } - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) *v1.Component); ok { - r0 = rf(ctx, name, pt, data, opts, subresources...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) error); ok { - r1 = rf(ctx, name, pt, data, opts, subresources...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_Patch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Patch' -type mockComponentRepo_Patch_Call struct { - *mock.Call -} - -// Patch is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - pt types.PatchType -// - data []byte -// - opts metav1.PatchOptions -// - subresources ...string -func (_e *mockComponentRepo_Expecter) Patch(ctx interface{}, name interface{}, pt interface{}, data interface{}, opts interface{}, subresources ...interface{}) *mockComponentRepo_Patch_Call { - return &mockComponentRepo_Patch_Call{Call: _e.mock.On("Patch", - append([]interface{}{ctx, name, pt, data, opts}, subresources...)...)} -} - -func (_c *mockComponentRepo_Patch_Call) Run(run func(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string)) *mockComponentRepo_Patch_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-5) - for i, a := range args[5:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), args[1].(string), args[2].(types.PatchType), args[3].([]byte), args[4].(metav1.PatchOptions), variadicArgs...) - }) - return _c -} - -func (_c *mockComponentRepo_Patch_Call) Return(result *v1.Component, err error) *mockComponentRepo_Patch_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *mockComponentRepo_Patch_Call) RunAndReturn(run func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) (*v1.Component, error)) *mockComponentRepo_Patch_Call { - _c.Call.Return(run) - return _c -} - -// RemoveFinalizer provides a mock function with given fields: ctx, component, finalizer -func (_m *mockComponentRepo) RemoveFinalizer(ctx context.Context, component *v1.Component, finalizer string) (*v1.Component, error) { - ret := _m.Called(ctx, component, finalizer) - - if len(ret) == 0 { - panic("no return value specified for RemoveFinalizer") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, string) (*v1.Component, error)); ok { - return rf(ctx, component, finalizer) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, string) *v1.Component); ok { - r0 = rf(ctx, component, finalizer) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component, string) error); ok { - r1 = rf(ctx, component, finalizer) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_RemoveFinalizer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveFinalizer' -type mockComponentRepo_RemoveFinalizer_Call struct { - *mock.Call -} - -// RemoveFinalizer is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -// - finalizer string -func (_e *mockComponentRepo_Expecter) RemoveFinalizer(ctx interface{}, component interface{}, finalizer interface{}) *mockComponentRepo_RemoveFinalizer_Call { - return &mockComponentRepo_RemoveFinalizer_Call{Call: _e.mock.On("RemoveFinalizer", ctx, component, finalizer)} -} - -func (_c *mockComponentRepo_RemoveFinalizer_Call) Run(run func(ctx context.Context, component *v1.Component, finalizer string)) *mockComponentRepo_RemoveFinalizer_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component), args[2].(string)) - }) - return _c -} - -func (_c *mockComponentRepo_RemoveFinalizer_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_RemoveFinalizer_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_RemoveFinalizer_Call) RunAndReturn(run func(context.Context, *v1.Component, string) (*v1.Component, error)) *mockComponentRepo_RemoveFinalizer_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, component, opts -func (_m *mockComponentRepo) Update(ctx context.Context, component *v1.Component, opts metav1.UpdateOptions) (*v1.Component, error) { - ret := _m.Called(ctx, component, opts) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.UpdateOptions) (*v1.Component, error)); ok { - return rf(ctx, component, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.UpdateOptions) *v1.Component); ok { - r0 = rf(ctx, component, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component, metav1.UpdateOptions) error); ok { - r1 = rf(ctx, component, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type mockComponentRepo_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -// - opts metav1.UpdateOptions -func (_e *mockComponentRepo_Expecter) Update(ctx interface{}, component interface{}, opts interface{}) *mockComponentRepo_Update_Call { - return &mockComponentRepo_Update_Call{Call: _e.mock.On("Update", ctx, component, opts)} -} - -func (_c *mockComponentRepo_Update_Call) Run(run func(ctx context.Context, component *v1.Component, opts metav1.UpdateOptions)) *mockComponentRepo_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component), args[2].(metav1.UpdateOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_Update_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_Update_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_Update_Call) RunAndReturn(run func(context.Context, *v1.Component, metav1.UpdateOptions) (*v1.Component, error)) *mockComponentRepo_Update_Call { - _c.Call.Return(run) - return _c -} - -// UpdateExpectedComponentVersion provides a mock function with given fields: ctx, componentName, version -func (_m *mockComponentRepo) UpdateExpectedComponentVersion(ctx context.Context, componentName string, version string) (*v1.Component, error) { - ret := _m.Called(ctx, componentName, version) - - if len(ret) == 0 { - panic("no return value specified for UpdateExpectedComponentVersion") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (*v1.Component, error)); ok { - return rf(ctx, componentName, version) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) *v1.Component); ok { - r0 = rf(ctx, componentName, version) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, componentName, version) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateExpectedComponentVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateExpectedComponentVersion' -type mockComponentRepo_UpdateExpectedComponentVersion_Call struct { - *mock.Call -} - -// UpdateExpectedComponentVersion is a helper method to define mock.On call -// - ctx context.Context -// - componentName string -// - version string -func (_e *mockComponentRepo_Expecter) UpdateExpectedComponentVersion(ctx interface{}, componentName interface{}, version interface{}) *mockComponentRepo_UpdateExpectedComponentVersion_Call { - return &mockComponentRepo_UpdateExpectedComponentVersion_Call{Call: _e.mock.On("UpdateExpectedComponentVersion", ctx, componentName, version)} -} - -func (_c *mockComponentRepo_UpdateExpectedComponentVersion_Call) Run(run func(ctx context.Context, componentName string, version string)) *mockComponentRepo_UpdateExpectedComponentVersion_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateExpectedComponentVersion_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateExpectedComponentVersion_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateExpectedComponentVersion_Call) RunAndReturn(run func(context.Context, string, string) (*v1.Component, error)) *mockComponentRepo_UpdateExpectedComponentVersion_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatus provides a mock function with given fields: ctx, component, opts -func (_m *mockComponentRepo) UpdateStatus(ctx context.Context, component *v1.Component, opts metav1.UpdateOptions) (*v1.Component, error) { - ret := _m.Called(ctx, component, opts) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatus") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.UpdateOptions) (*v1.Component, error)); ok { - return rf(ctx, component, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component, metav1.UpdateOptions) *v1.Component); ok { - r0 = rf(ctx, component, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component, metav1.UpdateOptions) error); ok { - r1 = rf(ctx, component, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatus' -type mockComponentRepo_UpdateStatus_Call struct { - *mock.Call -} - -// UpdateStatus is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -// - opts metav1.UpdateOptions -func (_e *mockComponentRepo_Expecter) UpdateStatus(ctx interface{}, component interface{}, opts interface{}) *mockComponentRepo_UpdateStatus_Call { - return &mockComponentRepo_UpdateStatus_Call{Call: _e.mock.On("UpdateStatus", ctx, component, opts)} -} - -func (_c *mockComponentRepo_UpdateStatus_Call) Run(run func(ctx context.Context, component *v1.Component, opts metav1.UpdateOptions)) *mockComponentRepo_UpdateStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component), args[2].(metav1.UpdateOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatus_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatus_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatus_Call) RunAndReturn(run func(context.Context, *v1.Component, metav1.UpdateOptions) (*v1.Component, error)) *mockComponentRepo_UpdateStatus_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusDeleting provides a mock function with given fields: ctx, component -func (_m *mockComponentRepo) UpdateStatusDeleting(ctx context.Context, component *v1.Component) (*v1.Component, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusDeleting") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) (*v1.Component, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) *v1.Component); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatusDeleting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusDeleting' -type mockComponentRepo_UpdateStatusDeleting_Call struct { - *mock.Call -} - -// UpdateStatusDeleting is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -func (_e *mockComponentRepo_Expecter) UpdateStatusDeleting(ctx interface{}, component interface{}) *mockComponentRepo_UpdateStatusDeleting_Call { - return &mockComponentRepo_UpdateStatusDeleting_Call{Call: _e.mock.On("UpdateStatusDeleting", ctx, component)} -} - -func (_c *mockComponentRepo_UpdateStatusDeleting_Call) Run(run func(ctx context.Context, component *v1.Component)) *mockComponentRepo_UpdateStatusDeleting_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusDeleting_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatusDeleting_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusDeleting_Call) RunAndReturn(run func(context.Context, *v1.Component) (*v1.Component, error)) *mockComponentRepo_UpdateStatusDeleting_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusInstalled provides a mock function with given fields: ctx, component -func (_m *mockComponentRepo) UpdateStatusInstalled(ctx context.Context, component *v1.Component) (*v1.Component, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusInstalled") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) (*v1.Component, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) *v1.Component); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatusInstalled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusInstalled' -type mockComponentRepo_UpdateStatusInstalled_Call struct { - *mock.Call -} - -// UpdateStatusInstalled is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -func (_e *mockComponentRepo_Expecter) UpdateStatusInstalled(ctx interface{}, component interface{}) *mockComponentRepo_UpdateStatusInstalled_Call { - return &mockComponentRepo_UpdateStatusInstalled_Call{Call: _e.mock.On("UpdateStatusInstalled", ctx, component)} -} - -func (_c *mockComponentRepo_UpdateStatusInstalled_Call) Run(run func(ctx context.Context, component *v1.Component)) *mockComponentRepo_UpdateStatusInstalled_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusInstalled_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatusInstalled_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusInstalled_Call) RunAndReturn(run func(context.Context, *v1.Component) (*v1.Component, error)) *mockComponentRepo_UpdateStatusInstalled_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusInstalling provides a mock function with given fields: ctx, component -func (_m *mockComponentRepo) UpdateStatusInstalling(ctx context.Context, component *v1.Component) (*v1.Component, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusInstalling") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) (*v1.Component, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) *v1.Component); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatusInstalling_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusInstalling' -type mockComponentRepo_UpdateStatusInstalling_Call struct { - *mock.Call -} - -// UpdateStatusInstalling is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -func (_e *mockComponentRepo_Expecter) UpdateStatusInstalling(ctx interface{}, component interface{}) *mockComponentRepo_UpdateStatusInstalling_Call { - return &mockComponentRepo_UpdateStatusInstalling_Call{Call: _e.mock.On("UpdateStatusInstalling", ctx, component)} -} - -func (_c *mockComponentRepo_UpdateStatusInstalling_Call) Run(run func(ctx context.Context, component *v1.Component)) *mockComponentRepo_UpdateStatusInstalling_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusInstalling_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatusInstalling_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusInstalling_Call) RunAndReturn(run func(context.Context, *v1.Component) (*v1.Component, error)) *mockComponentRepo_UpdateStatusInstalling_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusNotInstalled provides a mock function with given fields: ctx, component -func (_m *mockComponentRepo) UpdateStatusNotInstalled(ctx context.Context, component *v1.Component) (*v1.Component, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusNotInstalled") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) (*v1.Component, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) *v1.Component); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatusNotInstalled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusNotInstalled' -type mockComponentRepo_UpdateStatusNotInstalled_Call struct { - *mock.Call -} - -// UpdateStatusNotInstalled is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -func (_e *mockComponentRepo_Expecter) UpdateStatusNotInstalled(ctx interface{}, component interface{}) *mockComponentRepo_UpdateStatusNotInstalled_Call { - return &mockComponentRepo_UpdateStatusNotInstalled_Call{Call: _e.mock.On("UpdateStatusNotInstalled", ctx, component)} -} - -func (_c *mockComponentRepo_UpdateStatusNotInstalled_Call) Run(run func(ctx context.Context, component *v1.Component)) *mockComponentRepo_UpdateStatusNotInstalled_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusNotInstalled_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatusNotInstalled_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusNotInstalled_Call) RunAndReturn(run func(context.Context, *v1.Component) (*v1.Component, error)) *mockComponentRepo_UpdateStatusNotInstalled_Call { - _c.Call.Return(run) - return _c -} - -// UpdateStatusUpgrading provides a mock function with given fields: ctx, component -func (_m *mockComponentRepo) UpdateStatusUpgrading(ctx context.Context, component *v1.Component) (*v1.Component, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for UpdateStatusUpgrading") - } - - var r0 *v1.Component - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) (*v1.Component, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.Component) *v1.Component); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*v1.Component) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.Component) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_UpdateStatusUpgrading_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateStatusUpgrading' -type mockComponentRepo_UpdateStatusUpgrading_Call struct { - *mock.Call -} - -// UpdateStatusUpgrading is a helper method to define mock.On call -// - ctx context.Context -// - component *v1.Component -func (_e *mockComponentRepo_Expecter) UpdateStatusUpgrading(ctx interface{}, component interface{}) *mockComponentRepo_UpdateStatusUpgrading_Call { - return &mockComponentRepo_UpdateStatusUpgrading_Call{Call: _e.mock.On("UpdateStatusUpgrading", ctx, component)} -} - -func (_c *mockComponentRepo_UpdateStatusUpgrading_Call) Run(run func(ctx context.Context, component *v1.Component)) *mockComponentRepo_UpdateStatusUpgrading_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.Component)) - }) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusUpgrading_Call) Return(_a0 *v1.Component, _a1 error) *mockComponentRepo_UpdateStatusUpgrading_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_UpdateStatusUpgrading_Call) RunAndReturn(run func(context.Context, *v1.Component) (*v1.Component, error)) *mockComponentRepo_UpdateStatusUpgrading_Call { - _c.Call.Return(run) - return _c -} - -// Watch provides a mock function with given fields: ctx, opts -func (_m *mockComponentRepo) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for Watch") - } - - var r0 watch.Interface - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) (watch.Interface, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) watch.Interface); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(watch.Interface) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, metav1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentRepo_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch' -type mockComponentRepo_Watch_Call struct { - *mock.Call -} - -// Watch is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.ListOptions -func (_e *mockComponentRepo_Expecter) Watch(ctx interface{}, opts interface{}) *mockComponentRepo_Watch_Call { - return &mockComponentRepo_Watch_Call{Call: _e.mock.On("Watch", ctx, opts)} -} - -func (_c *mockComponentRepo_Watch_Call) Run(run func(ctx context.Context, opts metav1.ListOptions)) *mockComponentRepo_Watch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockComponentRepo_Watch_Call) Return(_a0 watch.Interface, _a1 error) *mockComponentRepo_Watch_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentRepo_Watch_Call) RunAndReturn(run func(context.Context, metav1.ListOptions) (watch.Interface, error)) *mockComponentRepo_Watch_Call { - _c.Call.Return(run) - return _c -} - -// newMockComponentRepo creates a new instance of mockComponentRepo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockComponentRepo(t interface { - mock.TestingT - Cleanup(func()) -}) *mockComponentRepo { - mock := &mockComponentRepo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/adapter/kubernetes/componentcr/testdata/testPatch b/pkg/adapter/kubernetes/componentcr/testdata/testPatch deleted file mode 100644 index 094025be..00000000 --- a/pkg/adapter/kubernetes/componentcr/testdata/testPatch +++ /dev/null @@ -1 +0,0 @@ -{"spec":{"namespace":"k8s","name":"my-component","version":"1.0.0-1","deployNamespace":"longhorn-system","valuesYamlOverwrite":"key: value\n"}} \ No newline at end of file diff --git a/pkg/adapter/kubernetes/healthConfig/health.go b/pkg/adapter/kubernetes/healthConfig/health.go deleted file mode 100644 index 61ad81c5..00000000 --- a/pkg/adapter/kubernetes/healthConfig/health.go +++ /dev/null @@ -1,135 +0,0 @@ -package healthconfig - -import ( - "context" - "errors" - "fmt" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "time" - - "k8s.io/api/core/v1" - k8sErr "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/yaml" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" -) - -const ( - healthConfigMapName = "k8s-blueprint-operator-health-config" - componentHealthConfigKey = "components" - waitHealthConfigKey = "wait" -) - -var defaultHealthConfig = healthConfig{ - Components: componentHealthConfig{ - Required: nil, - }, - Wait: waitHealthConfig{ - Timeout: duration{10 * time.Minute}, - Interval: duration{10 * time.Second}, - }, -} - -type HealthConfigProvider struct { - cmClient configMapInterface -} - -func NewHealthConfigProvider(cmClient corev1.ConfigMapInterface) *HealthConfigProvider { - return &HealthConfigProvider{cmClient: cmClient} -} - -func (h *HealthConfigProvider) GetWaitConfig(ctx context.Context) (ecosystem.WaitConfig, error) { - config, err := h.getAll(ctx) - if err != nil { - return ecosystem.WaitConfig{}, err - } - - return convertToWaitConfigDomain(config.Wait), nil -} - -func convertToWaitConfigDomain(config waitHealthConfig) ecosystem.WaitConfig { - return ecosystem.WaitConfig{ - Timeout: config.Timeout.Duration, - Interval: config.Interval.Duration, - } -} - -func (h *HealthConfigProvider) GetRequiredComponents(ctx context.Context) ([]ecosystem.RequiredComponent, error) { - config, err := h.getAll(ctx) - if err != nil { - return nil, err - } - - return util.Map(config.Components.Required, convertToRequiredComponentDomain), nil -} - -func convertToRequiredComponentDomain(component requiredComponent) ecosystem.RequiredComponent { - return ecosystem.RequiredComponent{Name: common.SimpleComponentName(component.Name)} -} - -func (h *HealthConfigProvider) getAll(ctx context.Context) (healthConfig, error) { - configMap, err := h.cmClient.Get(ctx, healthConfigMapName, metav1.GetOptions{}) - if k8sErr.IsNotFound(err) { - return defaultHealthConfig, nil - } else if err != nil { - return healthConfig{}, &domainservice.InternalError{ - WrappedError: err, - Message: fmt.Sprintf("failed to get config map %q", healthConfigMapName), - } - } - - components, componentsErr := parseComponentConfig(configMap) - wait, waitErr := parseWaitConfig(configMap) - - return healthConfig{ - Components: components, - Wait: wait, - }, errors.Join(componentsErr, waitErr) -} - -func parseWaitConfig(configMap *v1.ConfigMap) (waitHealthConfig, error) { - waitConfigStr, exists := configMap.Data[waitHealthConfigKey] - if !exists { - return defaultHealthConfig.Wait, nil - } - - var wait waitHealthConfig - err := yaml.Unmarshal([]byte(waitConfigStr), &wait) - if err != nil { - return waitHealthConfig{}, &domainservice.InternalError{ - WrappedError: err, - Message: "failed to parse wait health config", - } - } - - if wait.Interval.Duration == 0 { - wait.Interval = defaultHealthConfig.Wait.Interval - } - - if wait.Timeout.Duration == 0 { - wait.Timeout = defaultHealthConfig.Wait.Timeout - } - - return wait, nil -} - -func parseComponentConfig(configMap *v1.ConfigMap) (componentHealthConfig, error) { - componentHealthConfigStr, exists := configMap.Data[componentHealthConfigKey] - if !exists { - return defaultHealthConfig.Components, nil - } - - var components componentHealthConfig - err := yaml.Unmarshal([]byte(componentHealthConfigStr), &components) - if err != nil { - return componentHealthConfig{}, &domainservice.InternalError{ - WrappedError: err, - Message: "failed to parse component health config", - } - } - return components, nil -} diff --git a/pkg/adapter/kubernetes/healthConfig/healthSerializer.go b/pkg/adapter/kubernetes/healthConfig/healthSerializer.go deleted file mode 100644 index 1446453f..00000000 --- a/pkg/adapter/kubernetes/healthConfig/healthSerializer.go +++ /dev/null @@ -1,58 +0,0 @@ -package healthconfig - -import ( - "encoding/json" - "fmt" - "time" -) - -type healthConfig struct { - // Components contains configuration concerning the health-check of components. - Components componentHealthConfig `yaml:"components,omitempty" json:"components,omitempty"` - // Wait contains configuration concerning how the stand-by-period for the ecosystem to become healthy. - Wait waitHealthConfig `yaml:"wait,omitempty" json:"wait,omitempty"` -} - -type componentHealthConfig struct { - // Required is a list of components that have to be installed for the health-check to succeed. - Required []requiredComponent `yaml:"required,omitempty" json:"required,omitempty"` -} - -type requiredComponent struct { - // Name identifies the component. - Name string `yaml:"name" json:"name"` -} - -type waitHealthConfig struct { - // Timeout is the maximum time to wait for the ecosystem to become healthy. - Timeout duration `yaml:"timeout,omitempty" json:"timeout,omitempty"` - // Interval is the time to wait between health checks. - Interval duration `yaml:"interval,omitempty" json:"interval,omitempty"` -} - -// duration is a wrapper for time.Duration so that it can be marshalled and unmarshalled to JSON -// using time.Duration's string formatting. -type duration struct { - time.Duration -} - -// UnmarshalJSON expects either a JSON string formatted to be parseable by time.ParseDuration or -// a JSON integer number that represents the duration in nanoseconds. -func (d *duration) UnmarshalJSON(b []byte) (err error) { - if b[0] == '"' { - sd := string(b[1 : len(b)-1]) - d.Duration, err = time.ParseDuration(sd) - return err - } - - var id int64 - id, err = json.Number(b).Int64() - d.Duration = time.Duration(id) - - return err -} - -// MarshalJSON returns the duration as a JSON string formatted with time.Duration's string formatting -func (d *duration) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"%s"`, d.String())), nil -} diff --git a/pkg/adapter/kubernetes/healthConfig/health_test.go b/pkg/adapter/kubernetes/healthConfig/health_test.go deleted file mode 100644 index 6bd468d0..00000000 --- a/pkg/adapter/kubernetes/healthConfig/health_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package healthconfig - -import ( - "context" - "math/rand" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/stretchr/testify/assert" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" -) - -var testCtx = context.Background() - -func TestHealthConfigRepository_GetRequiredComponents(t *testing.T) { - notFoundErr := errors.NewNotFound(schema.GroupResource{ - Group: "v1", - Resource: "ConfigMap", - }, "k8s-blueprint-operator-health-config") - tests := []struct { - name string - cmClientFn func(t *testing.T) configMapInterface - want []ecosystem.RequiredComponent - wantErr assert.ErrorAssertionFunc - }{ - { - name: "should return default on not found error", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(nil, notFoundErr) - return cmMock - }, - want: make([]ecosystem.RequiredComponent, 0), - wantErr: assert.NoError, - }, - { - name: "should fail to get configmap due to other error", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(nil, assert.AnError) - return cmMock - }, - want: nil, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "failed to get config map \"k8s-blueprint-operator-health-config\"", i) - }, - }, - { - name: "should return default if config key does not exist", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: []ecosystem.RequiredComponent{}, - wantErr: assert.NoError, - }, - { - name: "should fail to parse component config", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"components": `{"required": [{]}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: nil, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse component health config", i) - }, - }, - { - name: "should succeed", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"components": `{"required": [{"name": "k8s-dogu-operator"}, {"name": "k8s-etcd"}]}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: []ecosystem.RequiredComponent{{Name: "k8s-dogu-operator"}, {Name: "k8s-etcd"}}, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &HealthConfigProvider{ - cmClient: tt.cmClientFn(t), - } - got, err := h.GetRequiredComponents(testCtx) - tt.wantErr(t, err) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestHealthConfigRepository_GetWaitConfig(t *testing.T) { - notFoundErr := errors.NewNotFound(schema.GroupResource{ - Group: "v1", - Resource: "ConfigMap", - }, "k8s-blueprint-operator-health-config") - tests := []struct { - name string - cmClientFn func(t *testing.T) configMapInterface - want ecosystem.WaitConfig - wantErr assert.ErrorAssertionFunc - }{ - { - name: "should return default on not found error", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(nil, notFoundErr) - return cmMock - }, - want: ecosystem.WaitConfig{ - Timeout: 10 * time.Minute, - Interval: 10 * time.Second, - }, - wantErr: assert.NoError, - }, - { - name: "should fail to get config map due to other error", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(nil, assert.AnError) - return cmMock - }, - want: ecosystem.WaitConfig{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "failed to get config map \"k8s-blueprint-operator-health-config\"", i) - }, - }, - { - name: "should return default if config key does not exist", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: ecosystem.WaitConfig{ - Timeout: 10 * time.Minute, - Interval: 10 * time.Second, - }, - wantErr: assert.NoError, - }, - { - name: "should fail to parse wait config", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"wait": `{"timeout": {[[{{}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: ecosystem.WaitConfig{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "failed to parse wait health config", i) - }, - }, - { - name: "should return default timeout if empty", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"wait": `{"interval": 2}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: ecosystem.WaitConfig{ - Timeout: 10 * time.Minute, - Interval: 2, - }, - wantErr: assert.NoError, - }, - { - name: "should return default interval if empty", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"wait": `{"timeout": "50s"}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: ecosystem.WaitConfig{ - Timeout: 50 * time.Second, - Interval: 10 * time.Second, - }, - wantErr: assert.NoError, - }, - { - name: "should succeed", - cmClientFn: func(t *testing.T) configMapInterface { - cmMock := newMockConfigMapInterface(t) - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "k8s-blueprint-operator-health-config"}, - Data: map[string]string{"wait": `{"timeout": "120s", "interval": "13s"}`}, - } - cmMock.EXPECT().Get(testCtx, "k8s-blueprint-operator-health-config", metav1.GetOptions{}). - Return(configMap, nil) - return cmMock - }, - want: ecosystem.WaitConfig{ - Timeout: 120 * time.Second, - Interval: 13 * time.Second, - }, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - h := &HealthConfigProvider{ - cmClient: tt.cmClientFn(t), - } - got, err := h.GetWaitConfig(testCtx) - tt.wantErr(t, err) - assert.Equal(t, tt.want, got) - }) - } -} - -func TestNewHealthConfigRepository(t *testing.T) { - mock := newMockConfigMapInterface(t) - provider := NewHealthConfigProvider(mock) - assert.Same(t, mock, provider.cmClient) -} - -func Test_duration_Marshal_Unmarshal_JSON(t *testing.T) { - // we provide a seed to produce always the exact same sequence of values - rng := rand.New(rand.NewSource(42)) - for i := 0; i < 100; i++ { - d1 := duration{time.Duration(rng.Int63())} - json, err := d1.MarshalJSON() - assert.NoErrorf(t, err, "failed to marshal duration %q to json", d1) - - var d2 duration - err = d2.UnmarshalJSON(json) - assert.NoErrorf(t, err, "failed to unmarshal json %q (original: %q)", string(json), d1) - } -} diff --git a/pkg/adapter/kubernetes/healthConfig/interfaces.go b/pkg/adapter/kubernetes/healthConfig/interfaces.go deleted file mode 100644 index f00b3266..00000000 --- a/pkg/adapter/kubernetes/healthConfig/interfaces.go +++ /dev/null @@ -1,7 +0,0 @@ -package healthconfig - -import corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - -type configMapInterface interface { - corev1.ConfigMapInterface -} diff --git a/pkg/adapter/kubernetes/healthConfig/mock_configMapInterface_test.go b/pkg/adapter/kubernetes/healthConfig/mock_configMapInterface_test.go deleted file mode 100644 index 3bcde17b..00000000 --- a/pkg/adapter/kubernetes/healthConfig/mock_configMapInterface_test.go +++ /dev/null @@ -1,577 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package healthconfig - -import ( - context "context" - - corev1 "k8s.io/api/core/v1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - mock "github.com/stretchr/testify/mock" - - types "k8s.io/apimachinery/pkg/types" - - v1 "k8s.io/client-go/applyconfigurations/core/v1" - - watch "k8s.io/apimachinery/pkg/watch" -) - -// mockConfigMapInterface is an autogenerated mock type for the configMapInterface type -type mockConfigMapInterface struct { - mock.Mock -} - -type mockConfigMapInterface_Expecter struct { - mock *mock.Mock -} - -func (_m *mockConfigMapInterface) EXPECT() *mockConfigMapInterface_Expecter { - return &mockConfigMapInterface_Expecter{mock: &_m.Mock} -} - -// Apply provides a mock function with given fields: ctx, configMap, opts -func (_m *mockConfigMapInterface) Apply(ctx context.Context, configMap *v1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (*corev1.ConfigMap, error) { - ret := _m.Called(ctx, configMap, opts) - - if len(ret) == 0 { - panic("no return value specified for Apply") - } - - var r0 *corev1.ConfigMap - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *v1.ConfigMapApplyConfiguration, metav1.ApplyOptions) (*corev1.ConfigMap, error)); ok { - return rf(ctx, configMap, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *v1.ConfigMapApplyConfiguration, metav1.ApplyOptions) *corev1.ConfigMap); ok { - r0 = rf(ctx, configMap, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMap) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *v1.ConfigMapApplyConfiguration, metav1.ApplyOptions) error); ok { - r1 = rf(ctx, configMap, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Apply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Apply' -type mockConfigMapInterface_Apply_Call struct { - *mock.Call -} - -// Apply is a helper method to define mock.On call -// - ctx context.Context -// - configMap *v1.ConfigMapApplyConfiguration -// - opts metav1.ApplyOptions -func (_e *mockConfigMapInterface_Expecter) Apply(ctx interface{}, configMap interface{}, opts interface{}) *mockConfigMapInterface_Apply_Call { - return &mockConfigMapInterface_Apply_Call{Call: _e.mock.On("Apply", ctx, configMap, opts)} -} - -func (_c *mockConfigMapInterface_Apply_Call) Run(run func(ctx context.Context, configMap *v1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions)) *mockConfigMapInterface_Apply_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*v1.ConfigMapApplyConfiguration), args[2].(metav1.ApplyOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Apply_Call) Return(result *corev1.ConfigMap, err error) *mockConfigMapInterface_Apply_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *mockConfigMapInterface_Apply_Call) RunAndReturn(run func(context.Context, *v1.ConfigMapApplyConfiguration, metav1.ApplyOptions) (*corev1.ConfigMap, error)) *mockConfigMapInterface_Apply_Call { - _c.Call.Return(run) - return _c -} - -// Create provides a mock function with given fields: ctx, configMap, opts -func (_m *mockConfigMapInterface) Create(ctx context.Context, configMap *corev1.ConfigMap, opts metav1.CreateOptions) (*corev1.ConfigMap, error) { - ret := _m.Called(ctx, configMap, opts) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 *corev1.ConfigMap - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *corev1.ConfigMap, metav1.CreateOptions) (*corev1.ConfigMap, error)); ok { - return rf(ctx, configMap, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *corev1.ConfigMap, metav1.CreateOptions) *corev1.ConfigMap); ok { - r0 = rf(ctx, configMap, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMap) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *corev1.ConfigMap, metav1.CreateOptions) error); ok { - r1 = rf(ctx, configMap, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type mockConfigMapInterface_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - configMap *corev1.ConfigMap -// - opts metav1.CreateOptions -func (_e *mockConfigMapInterface_Expecter) Create(ctx interface{}, configMap interface{}, opts interface{}) *mockConfigMapInterface_Create_Call { - return &mockConfigMapInterface_Create_Call{Call: _e.mock.On("Create", ctx, configMap, opts)} -} - -func (_c *mockConfigMapInterface_Create_Call) Run(run func(ctx context.Context, configMap *corev1.ConfigMap, opts metav1.CreateOptions)) *mockConfigMapInterface_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*corev1.ConfigMap), args[2].(metav1.CreateOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Create_Call) Return(_a0 *corev1.ConfigMap, _a1 error) *mockConfigMapInterface_Create_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockConfigMapInterface_Create_Call) RunAndReturn(run func(context.Context, *corev1.ConfigMap, metav1.CreateOptions) (*corev1.ConfigMap, error)) *mockConfigMapInterface_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, name, opts -func (_m *mockConfigMapInterface) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.DeleteOptions) error); ok { - r0 = rf(ctx, name, opts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockConfigMapInterface_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type mockConfigMapInterface_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts metav1.DeleteOptions -func (_e *mockConfigMapInterface_Expecter) Delete(ctx interface{}, name interface{}, opts interface{}) *mockConfigMapInterface_Delete_Call { - return &mockConfigMapInterface_Delete_Call{Call: _e.mock.On("Delete", ctx, name, opts)} -} - -func (_c *mockConfigMapInterface_Delete_Call) Run(run func(ctx context.Context, name string, opts metav1.DeleteOptions)) *mockConfigMapInterface_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(metav1.DeleteOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Delete_Call) Return(_a0 error) *mockConfigMapInterface_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockConfigMapInterface_Delete_Call) RunAndReturn(run func(context.Context, string, metav1.DeleteOptions) error) *mockConfigMapInterface_Delete_Call { - _c.Call.Return(run) - return _c -} - -// DeleteCollection provides a mock function with given fields: ctx, opts, listOpts -func (_m *mockConfigMapInterface) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - ret := _m.Called(ctx, opts, listOpts) - - if len(ret) == 0 { - panic("no return value specified for DeleteCollection") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.DeleteOptions, metav1.ListOptions) error); ok { - r0 = rf(ctx, opts, listOpts) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockConfigMapInterface_DeleteCollection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteCollection' -type mockConfigMapInterface_DeleteCollection_Call struct { - *mock.Call -} - -// DeleteCollection is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.DeleteOptions -// - listOpts metav1.ListOptions -func (_e *mockConfigMapInterface_Expecter) DeleteCollection(ctx interface{}, opts interface{}, listOpts interface{}) *mockConfigMapInterface_DeleteCollection_Call { - return &mockConfigMapInterface_DeleteCollection_Call{Call: _e.mock.On("DeleteCollection", ctx, opts, listOpts)} -} - -func (_c *mockConfigMapInterface_DeleteCollection_Call) Run(run func(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions)) *mockConfigMapInterface_DeleteCollection_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.DeleteOptions), args[2].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_DeleteCollection_Call) Return(_a0 error) *mockConfigMapInterface_DeleteCollection_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockConfigMapInterface_DeleteCollection_Call) RunAndReturn(run func(context.Context, metav1.DeleteOptions, metav1.ListOptions) error) *mockConfigMapInterface_DeleteCollection_Call { - _c.Call.Return(run) - return _c -} - -// Get provides a mock function with given fields: ctx, name, opts -func (_m *mockConfigMapInterface) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.ConfigMap, error) { - ret := _m.Called(ctx, name, opts) - - if len(ret) == 0 { - panic("no return value specified for Get") - } - - var r0 *corev1.ConfigMap - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.GetOptions) (*corev1.ConfigMap, error)); ok { - return rf(ctx, name, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, string, metav1.GetOptions) *corev1.ConfigMap); ok { - r0 = rf(ctx, name, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMap) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, metav1.GetOptions) error); ok { - r1 = rf(ctx, name, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' -type mockConfigMapInterface_Get_Call struct { - *mock.Call -} - -// Get is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - opts metav1.GetOptions -func (_e *mockConfigMapInterface_Expecter) Get(ctx interface{}, name interface{}, opts interface{}) *mockConfigMapInterface_Get_Call { - return &mockConfigMapInterface_Get_Call{Call: _e.mock.On("Get", ctx, name, opts)} -} - -func (_c *mockConfigMapInterface_Get_Call) Run(run func(ctx context.Context, name string, opts metav1.GetOptions)) *mockConfigMapInterface_Get_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(metav1.GetOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Get_Call) Return(_a0 *corev1.ConfigMap, _a1 error) *mockConfigMapInterface_Get_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockConfigMapInterface_Get_Call) RunAndReturn(run func(context.Context, string, metav1.GetOptions) (*corev1.ConfigMap, error)) *mockConfigMapInterface_Get_Call { - _c.Call.Return(run) - return _c -} - -// List provides a mock function with given fields: ctx, opts -func (_m *mockConfigMapInterface) List(ctx context.Context, opts metav1.ListOptions) (*corev1.ConfigMapList, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for List") - } - - var r0 *corev1.ConfigMapList - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) (*corev1.ConfigMapList, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) *corev1.ConfigMapList); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMapList) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, metav1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List' -type mockConfigMapInterface_List_Call struct { - *mock.Call -} - -// List is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.ListOptions -func (_e *mockConfigMapInterface_Expecter) List(ctx interface{}, opts interface{}) *mockConfigMapInterface_List_Call { - return &mockConfigMapInterface_List_Call{Call: _e.mock.On("List", ctx, opts)} -} - -func (_c *mockConfigMapInterface_List_Call) Run(run func(ctx context.Context, opts metav1.ListOptions)) *mockConfigMapInterface_List_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_List_Call) Return(_a0 *corev1.ConfigMapList, _a1 error) *mockConfigMapInterface_List_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockConfigMapInterface_List_Call) RunAndReturn(run func(context.Context, metav1.ListOptions) (*corev1.ConfigMapList, error)) *mockConfigMapInterface_List_Call { - _c.Call.Return(run) - return _c -} - -// Patch provides a mock function with given fields: ctx, name, pt, data, opts, subresources -func (_m *mockConfigMapInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*corev1.ConfigMap, error) { - _va := make([]interface{}, len(subresources)) - for _i := range subresources { - _va[_i] = subresources[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, name, pt, data, opts) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for Patch") - } - - var r0 *corev1.ConfigMap - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) (*corev1.ConfigMap, error)); ok { - return rf(ctx, name, pt, data, opts, subresources...) - } - if rf, ok := ret.Get(0).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) *corev1.ConfigMap); ok { - r0 = rf(ctx, name, pt, data, opts, subresources...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMap) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) error); ok { - r1 = rf(ctx, name, pt, data, opts, subresources...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Patch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Patch' -type mockConfigMapInterface_Patch_Call struct { - *mock.Call -} - -// Patch is a helper method to define mock.On call -// - ctx context.Context -// - name string -// - pt types.PatchType -// - data []byte -// - opts metav1.PatchOptions -// - subresources ...string -func (_e *mockConfigMapInterface_Expecter) Patch(ctx interface{}, name interface{}, pt interface{}, data interface{}, opts interface{}, subresources ...interface{}) *mockConfigMapInterface_Patch_Call { - return &mockConfigMapInterface_Patch_Call{Call: _e.mock.On("Patch", - append([]interface{}{ctx, name, pt, data, opts}, subresources...)...)} -} - -func (_c *mockConfigMapInterface_Patch_Call) Run(run func(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string)) *mockConfigMapInterface_Patch_Call { - _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]string, len(args)-5) - for i, a := range args[5:] { - if a != nil { - variadicArgs[i] = a.(string) - } - } - run(args[0].(context.Context), args[1].(string), args[2].(types.PatchType), args[3].([]byte), args[4].(metav1.PatchOptions), variadicArgs...) - }) - return _c -} - -func (_c *mockConfigMapInterface_Patch_Call) Return(result *corev1.ConfigMap, err error) *mockConfigMapInterface_Patch_Call { - _c.Call.Return(result, err) - return _c -} - -func (_c *mockConfigMapInterface_Patch_Call) RunAndReturn(run func(context.Context, string, types.PatchType, []byte, metav1.PatchOptions, ...string) (*corev1.ConfigMap, error)) *mockConfigMapInterface_Patch_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, configMap, opts -func (_m *mockConfigMapInterface) Update(ctx context.Context, configMap *corev1.ConfigMap, opts metav1.UpdateOptions) (*corev1.ConfigMap, error) { - ret := _m.Called(ctx, configMap, opts) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 *corev1.ConfigMap - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *corev1.ConfigMap, metav1.UpdateOptions) (*corev1.ConfigMap, error)); ok { - return rf(ctx, configMap, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, *corev1.ConfigMap, metav1.UpdateOptions) *corev1.ConfigMap); ok { - r0 = rf(ctx, configMap, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*corev1.ConfigMap) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, *corev1.ConfigMap, metav1.UpdateOptions) error); ok { - r1 = rf(ctx, configMap, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type mockConfigMapInterface_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - configMap *corev1.ConfigMap -// - opts metav1.UpdateOptions -func (_e *mockConfigMapInterface_Expecter) Update(ctx interface{}, configMap interface{}, opts interface{}) *mockConfigMapInterface_Update_Call { - return &mockConfigMapInterface_Update_Call{Call: _e.mock.On("Update", ctx, configMap, opts)} -} - -func (_c *mockConfigMapInterface_Update_Call) Run(run func(ctx context.Context, configMap *corev1.ConfigMap, opts metav1.UpdateOptions)) *mockConfigMapInterface_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*corev1.ConfigMap), args[2].(metav1.UpdateOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Update_Call) Return(_a0 *corev1.ConfigMap, _a1 error) *mockConfigMapInterface_Update_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockConfigMapInterface_Update_Call) RunAndReturn(run func(context.Context, *corev1.ConfigMap, metav1.UpdateOptions) (*corev1.ConfigMap, error)) *mockConfigMapInterface_Update_Call { - _c.Call.Return(run) - return _c -} - -// Watch provides a mock function with given fields: ctx, opts -func (_m *mockConfigMapInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - ret := _m.Called(ctx, opts) - - if len(ret) == 0 { - panic("no return value specified for Watch") - } - - var r0 watch.Interface - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) (watch.Interface, error)); ok { - return rf(ctx, opts) - } - if rf, ok := ret.Get(0).(func(context.Context, metav1.ListOptions) watch.Interface); ok { - r0 = rf(ctx, opts) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(watch.Interface) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, metav1.ListOptions) error); ok { - r1 = rf(ctx, opts) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockConfigMapInterface_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch' -type mockConfigMapInterface_Watch_Call struct { - *mock.Call -} - -// Watch is a helper method to define mock.On call -// - ctx context.Context -// - opts metav1.ListOptions -func (_e *mockConfigMapInterface_Expecter) Watch(ctx interface{}, opts interface{}) *mockConfigMapInterface_Watch_Call { - return &mockConfigMapInterface_Watch_Call{Call: _e.mock.On("Watch", ctx, opts)} -} - -func (_c *mockConfigMapInterface_Watch_Call) Run(run func(ctx context.Context, opts metav1.ListOptions)) *mockConfigMapInterface_Watch_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(metav1.ListOptions)) - }) - return _c -} - -func (_c *mockConfigMapInterface_Watch_Call) Return(_a0 watch.Interface, _a1 error) *mockConfigMapInterface_Watch_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockConfigMapInterface_Watch_Call) RunAndReturn(run func(context.Context, metav1.ListOptions) (watch.Interface, error)) *mockConfigMapInterface_Watch_Call { - _c.Call.Return(run) - return _c -} - -// newMockConfigMapInterface creates a new instance of mockConfigMapInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockConfigMapInterface(t interface { - mock.TestingT - Cleanup(func()) -}) *mockConfigMapInterface { - mock := &mockConfigMapInterface{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/applyComponentsUseCase.go b/pkg/application/applyComponentsUseCase.go deleted file mode 100644 index e9099a24..00000000 --- a/pkg/application/applyComponentsUseCase.go +++ /dev/null @@ -1,46 +0,0 @@ -package application - -import ( - "context" - "errors" - "fmt" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" -) - -// ApplyComponentsUseCase can handle component installations, updates and deletions. -type ApplyComponentsUseCase struct { - repo blueprintSpecRepository - componentUseCase componentInstallationUseCase -} - -func NewApplyComponentsUseCase( - repo blueprintSpecRepository, - componentUseCase componentInstallationUseCase, -) *ApplyComponentsUseCase { - return &ApplyComponentsUseCase{ - repo: repo, - componentUseCase: componentUseCase, - } -} - -// ApplyComponents applies components if necessary. -// The conditions in the blueprint will be set accordingly. -// returns domainservice.ConflictError if there was a concurrent update to the blueprint or -// returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. -func (useCase *ApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { - err := useCase.componentUseCase.ApplyComponentStates(ctx, blueprint) - isComponentsApplied := blueprint.StateDiff.ComponentDiffs.HasChanges() && err == nil - if isComponentsApplied { - blueprint.Events = append(blueprint.Events, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}) - } - conditionChanged := blueprint.SetLastApplySucceededConditionOnError(domain.ReasonLastApplyErrorAtComponents, err) - - if isComponentsApplied || conditionChanged { - updateErr := useCase.repo.Update(ctx, blueprint) - if updateErr != nil { - return isComponentsApplied, fmt.Errorf("cannot update status while applying components: %w", errors.Join(updateErr, err)) - } - } - return isComponentsApplied, err -} diff --git a/pkg/application/applyComponentsUseCase_test.go b/pkg/application/applyComponentsUseCase_test.go deleted file mode 100644 index 8fcb9005..00000000 --- a/pkg/application/applyComponentsUseCase_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package application - -import ( - "testing" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/meta" -) - -func TestApplyComponentsUseCase_ApplyComponents(t *testing.T) { - t.Run("ok", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - { - Name: "k8s-dogu-operator", - NeededActions: []domain.Action{ - domain.ActionUpgrade, - }, - }, - }, - }, - Conditions: []domain.Condition{}, - } - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - - changed, err := useCase.ApplyComponents(testCtx, blueprint) - - require.NoError(t, err) - assert.True(t, changed) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) - require.Empty(t, blueprint.Conditions) - }) - - t.Run("no update without condition change", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{}, - Conditions: []domain.Condition{}, - } - - repoMock := newMockBlueprintSpecRepository(t) - // Here is the important part: we expect the update to be called only once - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError).Twice() - useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - - componentsApplied, err := useCase.ApplyComponents(testCtx, blueprint) - require.Error(t, err) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - assert.False(t, componentsApplied) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) - // second apply - componentsApplied, err = useCase.ApplyComponents(testCtx, blueprint) - require.Error(t, err) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - assert.False(t, componentsApplied) - require.Equal(t, 1, len(blueprint.Events)) - }) - - t.Run("fail to apply components", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - } - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(nil) - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(assert.AnError) - useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - - changed, err := useCase.ApplyComponents(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionLastApplySucceeded)) - assert.False(t, changed) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.NewExecutionFailedEvent(err), blueprint.Events[0]) - }) - - t.Run("fail to update blueprint", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - StateDiff: domain.StateDiff{ - ComponentDiffs: domain.ComponentDiffs{ - { - NeededActions: []domain.Action{domain.ActionInstall}, - }, - }, - }, - } - - repoMock := newMockBlueprintSpecRepository(t) - repoMock.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - componentInstallUseCaseMock := newMockComponentInstallationUseCase(t) - componentInstallUseCaseMock.EXPECT().ApplyComponentStates(testCtx, blueprint).Return(nil) - useCase := NewApplyComponentsUseCase(repoMock, componentInstallUseCaseMock) - - changed, err := useCase.ApplyComponents(testCtx, blueprint) - - require.ErrorIs(t, err, assert.AnError) - assert.Empty(t, blueprint.Conditions) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, domain.ComponentsAppliedEvent{Diffs: blueprint.StateDiff.ComponentDiffs}, blueprint.Events[0]) - assert.True(t, changed) - }) -} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 7fc276fe..e4fb06fb 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -35,8 +35,6 @@ func NewBlueprintPreparationUseCases( type BlueprintApplyUseCases struct { completeUseCase completeBlueprintUseCase ecosystemConfigUseCase ecosystemConfigUseCase - selfUpgradeUseCase selfUpgradeUseCase - applyComponentUseCase applyComponentsUseCase applyDogusUseCase applyDogusUseCase healthUseCase ecosystemHealthUseCase dogusUpToDateUseCase dogusUpToDateUseCase @@ -45,8 +43,6 @@ type BlueprintApplyUseCases struct { func NewBlueprintApplyUseCases( completeUseCase completeBlueprintUseCase, ecosystemConfigUseCase ecosystemConfigUseCase, - selfUpgradeUseCase selfUpgradeUseCase, - applyComponentUseCase applyComponentsUseCase, applyDogusUseCase applyDogusUseCase, healthUseCase ecosystemHealthUseCase, dogusUpToDateUseCase dogusUpToDateUseCase, @@ -54,8 +50,6 @@ func NewBlueprintApplyUseCases( return BlueprintApplyUseCases{ completeUseCase: completeUseCase, ecosystemConfigUseCase: ecosystemConfigUseCase, - selfUpgradeUseCase: selfUpgradeUseCase, - applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDogusUseCase, healthUseCase: healthUseCase, dogusUpToDateUseCase: dogusUpToDateUseCase, @@ -159,26 +153,11 @@ func (useCase *BlueprintPreparationUseCases) prepareBlueprint(ctx context.Contex } func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) - if err != nil { - // could be a domain.AwaitSelfUpgradeError to trigger another reconcile - return err - } - err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) - if err != nil { - return err - } - changedComponents, err := useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) + err := useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) if err != nil { return err } - // check after applying components - if changedComponents { - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err - } - } + changedDogus, err := useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) if err != nil { return err diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 70caa11b..542d0617 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -25,8 +25,6 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { stateDiffUseCaseMock := newMockStateDiffUseCase(t) completeUseCaseMock := newMockCompleteBlueprintUseCase(t) ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCaseMock := newMockSelfUpgradeUseCase(t) - applyComponentUseCaseMock := newMockApplyComponentsUseCase(t) applyDoguUseCaseMock := newMockApplyDogusUseCase(t) ecosystemHealthUseCaseMock := newMockEcosystemHealthUseCase(t) dogusUpToDateUseCaseMock := newMockDogusUpToDateUseCase(t) @@ -42,8 +40,6 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { applyUseCases := NewBlueprintApplyUseCases( completeUseCaseMock, ecosystemConfigUseCaseMock, - selfUpgradeUseCaseMock, - applyComponentUseCaseMock, applyDoguUseCaseMock, ecosystemHealthUseCaseMock, dogusUpToDateUseCaseMock, @@ -62,8 +58,6 @@ func TestNewBlueprintSpecChangeUseCase(t *testing.T) { assert.Equal(t, ecosystemHealthUseCaseMock, result.preparationUseCases.healthUseCase) assert.Equal(t, ecosystemConfigUseCaseMock, result.applyUseCases.ecosystemConfigUseCase) // apply use cases - assert.Equal(t, selfUpgradeUseCaseMock, result.applyUseCases.selfUpgradeUseCase) - assert.Equal(t, applyComponentUseCaseMock, result.applyUseCases.applyComponentUseCase) assert.Equal(t, applyDoguUseCaseMock, result.applyUseCases.applyDogusUseCase) assert.Equal(t, completeUseCaseMock, result.applyUseCases.completeUseCase) assert.Equal(t, ecosystemHealthUseCaseMock, result.applyUseCases.healthUseCase) @@ -88,8 +82,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { stateDiff func(t *testing.T) stateDiffUseCase applyUseCase func(t *testing.T) completeBlueprintUseCase ecosystemConfigUseCase func(t *testing.T) ecosystemConfigUseCase - selfUpgradeUseCase func(t *testing.T) selfUpgradeUseCase - applyComponentUseCase func(t *testing.T) applyComponentsUseCase applyDogusUseCase func(t *testing.T) applyDogusUseCase healthUseCase func(t *testing.T) ecosystemHealthUseCase upToDateUseCase func(t *testing.T) dogusUpToDateUseCase @@ -352,52 +344,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { args: testArgs, wantErr: assert.NoError, }, - { - name: "should return error on error handle self upgrade", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) - }, - }, { name: "should return error on error apply config", fields: fields{ @@ -433,11 +379,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(assert.AnError) @@ -449,119 +390,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { return assert.Error(t, err) }, }, - { - name: "should return error on error apply components", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) - }, - }, - { - name: "should return error on error check health after component apply", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError).Times(1) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(true, nil) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) - }, - }, { name: "should return error on error apply dogus", fields: fields{ @@ -597,21 +425,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) return m }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, applyDogusUseCase: func(t *testing.T) applyDogusUseCase { m := newMockApplyDogusUseCase(t) m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) @@ -659,21 +477,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) return m }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, applyDogusUseCase: func(t *testing.T) applyDogusUseCase { m := newMockApplyDogusUseCase(t) m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(true, nil) @@ -720,21 +528,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) return m }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, applyDogusUseCase: func(t *testing.T) applyDogusUseCase { m := newMockApplyDogusUseCase(t) m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) @@ -786,21 +584,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) return m }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, applyDogusUseCase: func(t *testing.T) applyDogusUseCase { m := newMockApplyDogusUseCase(t) m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) @@ -857,21 +645,11 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { m := newMockEcosystemConfigUseCase(t) m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) return m }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, applyDogusUseCase: func(t *testing.T) applyDogusUseCase { m := newMockApplyDogusUseCase(t) m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) @@ -929,16 +707,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { ecoConfigUseCase = tt.fields.ecosystemConfigUseCase(t) } - var selfUpgrade selfUpgradeUseCase - if tt.fields.selfUpgradeUseCase != nil { - selfUpgrade = tt.fields.selfUpgradeUseCase(t) - } - - var applyComponentUseCase applyComponentsUseCase - if tt.fields.applyComponentUseCase != nil { - applyComponentUseCase = tt.fields.applyComponentUseCase(t) - } - var applyDoguUseCase applyDogusUseCase if tt.fields.applyDogusUseCase != nil { applyDoguUseCase = tt.fields.applyDogusUseCase(t) @@ -966,8 +734,6 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { applyUseCases: BlueprintApplyUseCases{ completeUseCase: completeUseCase, ecosystemConfigUseCase: ecoConfigUseCase, - selfUpgradeUseCase: selfUpgrade, - applyComponentUseCase: applyComponentUseCase, applyDogusUseCase: applyDoguUseCase, healthUseCase: ecoHealthUseCase, dogusUpToDateUseCase: upToDateUseCase, diff --git a/pkg/application/completeBlueprintUseCase_test.go b/pkg/application/completeBlueprintUseCase_test.go index 8969248d..d152651a 100644 --- a/pkg/application/completeBlueprintUseCase_test.go +++ b/pkg/application/completeBlueprintUseCase_test.go @@ -37,9 +37,9 @@ func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ + DoguDiffs: []domain.DoguDiff{ { - Name: "k8s-dogu-operator", + DoguName: "ldap", NeededActions: []domain.Action{domain.ActionUpgrade}, }, }, diff --git a/pkg/application/componentInstallationUseCase.go b/pkg/application/componentInstallationUseCase.go deleted file mode 100644 index 6555c699..00000000 --- a/pkg/application/componentInstallationUseCase.go +++ /dev/null @@ -1,129 +0,0 @@ -package application - -import ( - "context" - "errors" - "fmt" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -const ( - noDowngradesExplanationTextFmt = "downgrades are not allowed as the data model of the %s could have changed and " + - "doing rollbacks to older models is not supported. " + - "You can downgrade %s by restoring a backup. " + - "If you want an 'allow-downgrades' flag, issue a feature request" - noDistributionNamespaceSwitchExplanationText = "switching distribution namespace of components is not allowed. If you want an " + - "`allow-switch-distribution-namespace` flag, issue a feature request" -) - -type ComponentInstallationUseCase struct { - blueprintSpecRepo domainservice.BlueprintSpecRepository - componentRepo domainservice.ComponentInstallationRepository - healthConfigProvider healthConfigProvider -} - -func NewComponentInstallationUseCase( - blueprintSpecRepo domainservice.BlueprintSpecRepository, - componentRepo domainservice.ComponentInstallationRepository, - healthConfigProvider healthConfigProvider, -) *ComponentInstallationUseCase { - return &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepo, - componentRepo: componentRepo, - healthConfigProvider: healthConfigProvider, - } -} - -func (useCase *ComponentInstallationUseCase) CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.CheckComponentHealth") - logger.V(2).Info("check component health...") - installedComponents, err := useCase.componentRepo.GetAll(ctx) - if err != nil { - return ecosystem.ComponentHealthResult{}, fmt.Errorf("cannot retrieve installed components: %w", err) - } - - requiredComponents, err := useCase.healthConfigProvider.GetRequiredComponents(ctx) - if err != nil { - return ecosystem.ComponentHealthResult{}, fmt.Errorf("cannot retrieve required components: %w", err) - } - - return ecosystem.CalculateComponentHealthResult(installedComponents, requiredComponents), nil -} - -// ApplyComponentStates applies the expected component state from the Blueprint to the ecosystem. -// Fail-fast here, so that the possible damage is as small as possible. -func (useCase *ComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.ApplyComponentStates") - - if len(blueprint.StateDiff.ComponentDiffs) == 0 { - logger.V(2).Info("apply no components because blueprint has no component state differences") - return nil - } - - // ComponentDiff contains all installed components anyway (but some with action none) so we can load them all at once - components, err := useCase.componentRepo.GetAll(ctx) - if err != nil { - return fmt.Errorf("cannot load component installations to apply component state: %w", err) - } - - for _, componentDiff := range blueprint.StateDiff.ComponentDiffs { - err = useCase.applyComponentState(ctx, componentDiff, components[componentDiff.Name]) - if err != nil { - return fmt.Errorf("an error occurred while applying component state to the ecosystem: %w", err) - } - } - return nil -} - -func (useCase *ComponentInstallationUseCase) applyComponentState( - ctx context.Context, - componentDiff domain.ComponentDiff, - componentInstallation *ecosystem.ComponentInstallation, -) error { - logger := log.FromContext(ctx). - WithName("ComponentInstallationUseCase.applyComponentState"). - WithValues("component", componentDiff.Name, "diff", componentDiff.String()) - - for _, action := range componentDiff.NeededActions { - switch action { - case domain.ActionInstall: - logger.Info("install component") - newComponent := ecosystem.InstallComponent(common.QualifiedComponentName{ - Namespace: componentDiff.Expected.Namespace, - SimpleName: componentDiff.Name, - }, componentDiff.Expected.Version, componentDiff.Expected.DeployConfig) - return useCase.componentRepo.Create(ctx, newComponent) - case domain.ActionUninstall: - if componentInstallation == nil { - return &domainservice.NotFoundError{Message: fmt.Sprintf("component %q not found", componentDiff.Name)} - } - logger.Info("uninstall component") - return useCase.componentRepo.Delete(ctx, componentInstallation.Name.SimpleName) - case domain.ActionUpgrade: - componentInstallation.Upgrade(componentDiff.Expected.Version) - case domain.ActionUpdateComponentDeployConfig: - componentInstallation.UpdateDeployConfig(componentDiff.Expected.DeployConfig) - case domain.ActionSwitchComponentNamespace: - logger.Info("switch distribution namespace") - return errors.New(noDistributionNamespaceSwitchExplanationText) - case domain.ActionDowngrade: - logger.Info("downgrade component") - return fmt.Errorf(noDowngradesExplanationTextFmt, "components", "components") - default: - return fmt.Errorf("cannot perform unknown action %q", action) - } - } - - // If this routine did not terminate until this point, it is always an update. - if len(componentDiff.NeededActions) > 0 { - logger.Info("upgrade component") - return useCase.componentRepo.Update(ctx, componentInstallation) - } - - return nil -} diff --git a/pkg/application/componentInstallationUseCase_test.go b/pkg/application/componentInstallationUseCase_test.go deleted file mode 100644 index cf98ba69..00000000 --- a/pkg/application/componentInstallationUseCase_test.go +++ /dev/null @@ -1,495 +0,0 @@ -package application - -import ( - "fmt" - "testing" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - componentName1 = "operator1" - testNamespace = "k8s" -) - -var ( - semVer3212, _ = semver.NewVersion("3.2.1-2") -) - -func TestNewComponentInstallationUseCase(t *testing.T) { - t.Run("success", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - healthConfigRepo := newMockHealthConfigProvider(t) - - // when - useCase := NewComponentInstallationUseCase(blueprintSpecRepoMock, componentRepoMock, healthConfigRepo) - - // then - assert.Equal(t, blueprintSpecRepoMock, useCase.blueprintSpecRepo) - assert.Equal(t, componentRepoMock, useCase.componentRepo) - assert.Same(t, healthConfigRepo, useCase.healthConfigProvider) - }) -} - -func TestComponentInstallationUseCase_ApplyComponentStates(t *testing.T) { - t.Run("success with no needed action", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - { - Name: componentName1, - NeededActions: []domain.Action{}, - Actual: domain.ComponentDiffState{ - Version: semVer3212, - }, - Expected: domain.ComponentDiffState{ - Version: semVer3212, - }, - }, - }, - }, - } - - allComponents := map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - componentName1: nil, - } - - componentRepoMock.EXPECT().GetAll(testCtx).Return(allComponents, nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.ApplyComponentStates(testCtx, blueprint) - - // then - require.NoError(t, err) - }) - - t.Run("should return nil and do nothing with no component diffs", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{} - sut := &ComponentInstallationUseCase{} - - // when - err := sut.ApplyComponentStates(testCtx, blueprint) - - // then - require.NoError(t, err) - }) - - t.Run("should return error getting all components", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - {}, - }, - }, - } - - componentRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.ApplyComponentStates(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot load component installations to apply component state") - }) - - t.Run("should return error with unknown action", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - blueprint := &domain.BlueprintSpec{ - StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - { - Name: componentName1, - NeededActions: []domain.Action{"unknown"}, - }, - }, - }, - } - - allComponents := map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - componentName1: nil, - } - - componentRepoMock.EXPECT().GetAll(testCtx).Return(allComponents, nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.ApplyComponentStates(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, "cannot perform unknown action \"unknown\"") - }) -} - -func TestComponentInstallationUseCase_applyComponentState(t *testing.T) { - t.Run("should create component on action install", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{domain.ActionInstall}, - Expected: domain.ComponentDiffState{ - Namespace: testNamespace, - Version: semVer3212, - }, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - ExpectedVersion: semVer3212, - } - - componentRepoMock.EXPECT().Create(testCtx, componentInstallation).Return(nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.NoError(t, err) - }) - - t.Run("should delete component on action uninstall", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{domain.ActionUninstall}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - } - - componentRepoMock.EXPECT().Delete(testCtx, componentInstallation.Name.SimpleName).Return(nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.NoError(t, err) - }) - - t.Run("should throw NotFoundError on action uninstall when component not found", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{domain.ActionUninstall}, - } - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, nil) - - // then - require.Error(t, err) - var targetError *domainservice.NotFoundError - assert.ErrorAs(t, err, &targetError) - }) - - t.Run("should update component on action upgrade", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - Expected: domain.ComponentDiffState{ - Namespace: testNamespace, - Version: semVer3212, - }, - NeededActions: []domain.Action{domain.ActionUpgrade}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - ExpectedVersion: semVer3212, - } - - componentRepoMock.EXPECT().Update(testCtx, componentInstallation).Return(nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.NoError(t, err) - }) - - t.Run("should update component with multiple actions", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - Expected: domain.ComponentDiffState{ - Namespace: testNamespace, - Version: semVer3212, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]string{ - "key": "value", - }, - }, - }, - NeededActions: []domain.Action{domain.ActionUpgrade, domain.ActionUpdateComponentDeployConfig}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - ExpectedVersion: semVer3212, - DeployConfig: map[string]interface{}{ - "deployNamespace": "longhorn-system", - "overwriteConfig": map[string]string{ - "key": "value", - }, - }, - } - - componentRepoMock.EXPECT().Update(testCtx, componentInstallation).Return(nil) - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.NoError(t, err) - }) - - t.Run("should return error on action downgrade", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{domain.ActionDowngrade}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - } - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, fmt.Sprintf(noDowngradesExplanationTextFmt, "components", "components")) - }) - - t.Run("should return error on action distribution namespace switch", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{domain.ActionSwitchComponentNamespace}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - } - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.Error(t, err) - assert.ErrorContains(t, err, noDistributionNamespaceSwitchExplanationText) - }) - - t.Run("should return no error on empty actions in diff", func(t *testing.T) { - // given - blueprintSpecRepoMock := newMockBlueprintSpecRepository(t) - componentRepoMock := newMockComponentInstallationRepository(t) - - componentDiff := domain.ComponentDiff{ - Name: componentName1, - NeededActions: []domain.Action{}, - } - - componentInstallation := &ecosystem.ComponentInstallation{ - Name: common.QualifiedComponentName{SimpleName: componentName1, Namespace: testNamespace}, - } - - sut := &ComponentInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepoMock, - componentRepo: componentRepoMock, - } - - // when - err := sut.applyComponentState(testCtx, componentDiff, componentInstallation) - - // then - require.NoError(t, err) - }) -} - -func TestComponentInstallationUseCase_CheckComponentHealth(t *testing.T) { - type fields struct { - componentRepoFn func(t *testing.T) componentInstallationRepository - healthConfigRepoFn func(t *testing.T) healthConfigProvider - } - tests := []struct { - name string - fields fields - want ecosystem.ComponentHealthResult - wantErr assert.ErrorAssertionFunc - }{ - { - name: "should fail to get installed components", - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - componentMock := newMockComponentInstallationRepository(t) - componentMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) - return componentMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - return newMockHealthConfigProvider(t) - }, - }, - want: ecosystem.ComponentHealthResult{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "cannot retrieve installed components", i) - }, - }, - { - name: "should fail to get required components", - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - componentMock := newMockComponentInstallationRepository(t) - componentMock.EXPECT().GetAll(testCtx). - Return(map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, nil) - return componentMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - healthConfigMock := newMockHealthConfigProvider(t) - healthConfigMock.EXPECT().GetRequiredComponents(testCtx). - Return(nil, assert.AnError) - return healthConfigMock - }, - }, - want: ecosystem.ComponentHealthResult{}, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorIs(t, err, assert.AnError, i) && - assert.ErrorContains(t, err, "cannot retrieve required components", i) - }, - }, - { - name: "should succeed", - fields: fields{ - componentRepoFn: func(t *testing.T) componentInstallationRepository { - componentMock := newMockComponentInstallationRepository(t) - componentMock.EXPECT().GetAll(testCtx). - Return(map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - "k8s-component-operator": { - Name: common.QualifiedComponentName{SimpleName: "k8s-component-operator", Namespace: testNamespace}, - Health: ecosystem.UnavailableHealthStatus}, - }, nil) - return componentMock - }, - healthConfigRepoFn: func(t *testing.T) healthConfigProvider { - healthConfigMock := newMockHealthConfigProvider(t) - healthConfigMock.EXPECT().GetRequiredComponents(testCtx). - Return([]ecosystem.RequiredComponent{{Name: "k8s-dogu-operator"}}, nil) - return healthConfigMock - }, - }, - want: ecosystem.ComponentHealthResult{ - ComponentsByStatus: map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"k8s-component-operator"}, - }}, - wantErr: assert.NoError, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - useCase := &ComponentInstallationUseCase{ - componentRepo: tt.fields.componentRepoFn(t), - healthConfigProvider: tt.fields.healthConfigRepoFn(t), - } - got, err := useCase.CheckComponentHealth(testCtx) - tt.wantErr(t, err) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index f06bdea1..7d504fa1 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -2,6 +2,7 @@ package application import ( "context" + "errors" "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -12,27 +13,29 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) +const noDowngradesExplanationTextFmt = "downgrades are not allowed as the data model of the %s could have changed and " + + "doing rollbacks to older models is not supported. " + + "You can downgrade %s by restoring a backup. " + + "If you want an 'allow-downgrades' flag, issue a feature request" + type DoguInstallationUseCase struct { - blueprintSpecRepo blueprintSpecRepository - doguRepo doguInstallationRepository - waitConfigProvider healthWaitConfigProvider - doguConfigRepo doguConfigRepository - globalConfigRepo globalConfigRepository + blueprintSpecRepo blueprintSpecRepository + doguRepo doguInstallationRepository + doguConfigRepo doguConfigRepository + globalConfigRepo globalConfigRepository } func NewDoguInstallationUseCase( blueprintSpecRepo domainservice.BlueprintSpecRepository, doguRepo domainservice.DoguInstallationRepository, - waitConfigProvider domainservice.HealthWaitConfigProvider, doguConfigRepo doguConfigRepository, globalConfigRepo globalConfigRepository, ) *DoguInstallationUseCase { return &DoguInstallationUseCase{ - blueprintSpecRepo: blueprintSpecRepo, - doguRepo: doguRepo, - waitConfigProvider: waitConfigProvider, - doguConfigRepo: doguConfigRepo, - globalConfigRepo: globalConfigRepo, + blueprintSpecRepo: blueprintSpecRepo, + doguRepo: doguRepo, + doguConfigRepo: doguConfigRepo, + globalConfigRepo: globalConfigRepo, } } @@ -72,6 +75,15 @@ func (useCase *DoguInstallationUseCase) CheckDogusUpToDate(ctx context.Context) doguConfig, err := useCase.doguConfigRepo.Get(ctx, doguName) if err != nil { + // If error is a NotFoundError here, the dogu might be in the process of being deleted. Throw an internal error to reconcile this case. + var notFoundError *domainservice.NotFoundError + if errors.As(err, ¬FoundError) { + return nil, domainservice.NewInternalError( + errors.Unwrap(err), + "dogu config does not exist for dogu %q. This might be, because the dogu is currently deleted. Retry Later", + doguName, + ) + } return nil, err } doguConfigUpdateTime := doguConfig.LastUpdated diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 4be02427..8f884e36 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -45,7 +45,7 @@ var ( func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action none", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil) // when err := sut.applyDoguState(testCtx, domain.DoguDiff{ @@ -93,7 +93,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { ecosystem.InstallDogu(postgresqlQualifiedName, &version3211, &volumeSize, proxyConfig, additionalMounts)). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -136,7 +136,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Delete(testCtx, cescommons.SimpleName("postgresql")). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -159,7 +159,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("action uninstall throws NotFoundError when dogu not found", func(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -188,7 +188,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Update(testCtx, dogu). Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) dogu.PauseReconciliation = true // test if it gets reset on update (the dogu in the EXPECT Update call has this to false) @@ -218,7 +218,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: version3212, } - sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -255,7 +255,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -295,7 +295,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -334,7 +334,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -373,7 +373,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -456,7 +456,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState(testCtx, diff, dogu, domain.BlueprintConfiguration{}) @@ -495,7 +495,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, expectedDogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -533,7 +533,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { Version: version3212, } - sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -563,7 +563,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().Update(testCtx, dogu).Return(nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.applyDoguState( @@ -588,7 +588,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("unknown action", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -610,7 +610,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { t.Run("should no fail with no actions", func(t *testing.T) { // given - sut := NewDoguInstallationUseCase(nil, nil, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, nil, nil, nil) // when err := sut.applyDoguState( @@ -637,7 +637,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.ApplyDoguStates(testCtx, &domain.BlueprintSpec{}) @@ -665,7 +665,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { doguRepoMock := newMockDoguInstallationRepository(t) doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(blueprintSpecRepoMock, doguRepoMock, nil, nil) // when err := sut.ApplyDoguStates(testCtx, blueprint) @@ -697,7 +697,7 @@ func TestDoguInstallationUseCase_ApplyDoguStates(t *testing.T) { }, }, nil) - sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil, nil) + sut := NewDoguInstallationUseCase(nil, doguRepoMock, nil, nil) // when err := sut.ApplyDoguStates(testCtx, blueprint) @@ -1013,4 +1013,37 @@ func TestDoguInstallationUseCase_CheckDogusUpToDate(t *testing.T) { require.Error(t, err) require.Nil(t, dogusNotUpToDate) }) + t.Run("internal error on dogu config Get not found error", func(t *testing.T) { + // given + doguRepoMock := newMockDoguInstallationRepository(t) + doguRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{ + "postgresql": { + Name: postgresqlQualifiedName, + Version: version3211, + InstalledVersion: version3211, + StartedAt: timeJuly, + }, + }, nil) + + globalConfigRepoMock := newMockGlobalConfigRepository(t) + globalConfigRepoMock.EXPECT().Get(testCtx).Return(config.GlobalConfig{}, nil) + doguConfigRepoMock := newMockDoguConfigRepository(t) + notFoundErr := domainservice.NewNotFoundError(assert.AnError, "testerror") + doguConfigRepoMock.EXPECT().Get(testCtx, postgresqlQualifiedName.SimpleName).Return(config.DoguConfig{}, notFoundErr) + + useCase := &DoguInstallationUseCase{ + doguRepo: doguRepoMock, + doguConfigRepo: doguConfigRepoMock, + globalConfigRepo: globalConfigRepoMock, + } + + // when + dogusNotUpToDate, err := useCase.CheckDogusUpToDate(testCtx) + // then + require.Error(t, err) + require.Nil(t, dogusNotUpToDate) + var internalError *domainservice.InternalError + assert.ErrorAs(t, err, &internalError) + assert.ErrorContains(t, err, "dogu config does not exist for dogu \"postgresql\". This might be, because the dogu is currently deleted. Retry Later") + }) } diff --git a/pkg/application/ecosystemHealthUseCase.go b/pkg/application/ecosystemHealthUseCase.go index 9535646e..033c3ceb 100644 --- a/pkg/application/ecosystemHealthUseCase.go +++ b/pkg/application/ecosystemHealthUseCase.go @@ -11,20 +11,14 @@ import ( ) type EcosystemHealthUseCase struct { - doguUseCase doguInstallationUseCase - componentUseCase componentInstallationUseCase - blueprintRepo blueprintSpecRepository + doguUseCase doguInstallationUseCase + blueprintRepo blueprintSpecRepository } -func NewEcosystemHealthUseCase( - doguUseCase doguInstallationUseCase, - componentUseCase componentInstallationUseCase, - blueprintRepo blueprintSpecRepository, -) *EcosystemHealthUseCase { +func NewEcosystemHealthUseCase(doguUseCase doguInstallationUseCase, blueprintRepo blueprintSpecRepository) *EcosystemHealthUseCase { return &EcosystemHealthUseCase{ - doguUseCase: doguUseCase, - componentUseCase: componentUseCase, - blueprintRepo: blueprintRepo, + doguUseCase: doguUseCase, + blueprintRepo: blueprintRepo, } } @@ -40,7 +34,6 @@ func (useCase *EcosystemHealthUseCase) CheckEcosystemHealth( health, determineHealthError := useCase.getEcosystemHealth( ctx, blueprint.Config.IgnoreDoguHealth, - blueprint.Config.IgnoreComponentHealth, ) healthChanged := blueprint.HandleHealthResult(health, determineHealthError) if healthChanged { @@ -65,7 +58,6 @@ func (useCase *EcosystemHealthUseCase) CheckEcosystemHealth( func (useCase *EcosystemHealthUseCase) getEcosystemHealth( ctx context.Context, ignoreDoguHealth bool, - ignoreComponentHealth bool, ) (ecosystem.HealthResult, error) { logger := log.FromContext(ctx).WithName("EcosystemHealthUseCase.getEcosystemHealth") logger.V(1).Info("check ecosystem health...") @@ -75,14 +67,7 @@ func (useCase *EcosystemHealthUseCase) getEcosystemHealth( doguHealth, doguHealthErr = useCase.doguUseCase.CheckDoguHealth(ctx) } - var componentHealth ecosystem.ComponentHealthResult - var componentHealthErr error - if !ignoreComponentHealth { - componentHealth, componentHealthErr = useCase.componentUseCase.CheckComponentHealth(ctx) - } - return ecosystem.HealthResult{ - DoguHealth: doguHealth, - ComponentHealth: componentHealth, - }, errors.Join(doguHealthErr, componentHealthErr) + DoguHealth: doguHealth, + }, doguHealthErr } diff --git a/pkg/application/ecosystemHealthUseCase_test.go b/pkg/application/ecosystemHealthUseCase_test.go index e14d5720..8f0d6c6f 100644 --- a/pkg/application/ecosystemHealthUseCase_test.go +++ b/pkg/application/ecosystemHealthUseCase_test.go @@ -5,7 +5,6 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -24,25 +23,14 @@ var ( ecosystem.UnavailableHealthStatus: {"postfix"}, ecosystem.PendingHealthStatus: {"scm"}, } - healthyComponent = map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, - } - mixedComponentHealth = map[ecosystem.HealthStatus][]common.SimpleComponentName{ - ecosystem.NotInstalledHealthStatus: {"k8s-dogu-operator"}, - ecosystem.UnavailableHealthStatus: {"k8s-etcd"}, - ecosystem.PendingHealthStatus: {"k8s-service-discovery"}, - ecosystem.AvailableHealthStatus: {"k8s-component-operator"}, - } ) func TestNewEcosystemHealthUseCase(t *testing.T) { doguUseCase := newMockDoguInstallationUseCase(t) - componentUseCase := newMockComponentInstallationUseCase(t) blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) assert.Same(t, doguUseCase, useCase.doguUseCase) - assert.Same(t, componentUseCase, useCase.componentUseCase) } func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { @@ -50,29 +38,23 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: false, - IgnoreComponentHealth: false, + IgnoreDoguHealth: false, }, } doguHealth := ecosystem.DoguHealthResult{ DogusByStatus: healthyDogu, } - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: healthyComponent, - } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) blueprintRepo := newMockBlueprintSpecRepository(t) blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) require.NoError(t, err) - assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) + assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth}, health) assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionEcosystemHealthy)) }) @@ -80,32 +62,25 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: false, - IgnoreComponentHealth: false, + IgnoreDoguHealth: false, }, } doguHealth := ecosystem.DoguHealthResult{ DogusByStatus: mixedDoguHealth, } - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) blueprintRepo := newMockBlueprintSpecRepository(t) blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) assert.Error(t, err) assert.ErrorContains(t, err, "ecosystem is unhealthy") assert.ErrorContains(t, err, "2 dogu(s) are unhealthy: postfix, scm") - assert.ErrorContains(t, err, "3 component(s) are unhealthy: k8s-dogu-operator, k8s-etcd, k8s-service-discovery") - assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) + assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth}, health) assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionEcosystemHealthy)) }) @@ -113,24 +88,18 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: false, - IgnoreComponentHealth: false, + IgnoreDoguHealth: false, }, } doguHealth := ecosystem.DoguHealthResult{ DogusByStatus: mixedDoguHealth, } - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) blueprintRepo := newMockBlueprintSpecRepository(t) blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) health, err := useCase.CheckEcosystemHealth(testCtx, blueprint) @@ -143,21 +112,17 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: false, - IgnoreComponentHealth: false, + IgnoreDoguHealth: false, }, } doguHealth := ecosystem.DoguHealthResult{} - componentHealth := ecosystem.ComponentHealthResult{} doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, assert.AnError) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, assert.AnError) blueprintRepo := newMockBlueprintSpecRepository(t) blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) _, err := useCase.CheckEcosystemHealth(testCtx, blueprint) @@ -171,24 +136,18 @@ func TestEcosystemHealthUseCase_CheckEcosystemHealth(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, Config: domain.BlueprintConfiguration{ - IgnoreDoguHealth: false, - IgnoreComponentHealth: false, + IgnoreDoguHealth: false, }, } doguHealth := ecosystem.DoguHealthResult{ DogusByStatus: mixedDoguHealth, } - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil).Twice() - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil).Twice() blueprintRepo := newMockBlueprintSpecRepository(t) blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) _, err := useCase.CheckEcosystemHealth(testCtx, blueprint) assert.ErrorContains(t, err, "ecosystem is unhealthy") @@ -204,80 +163,34 @@ func TestEcosystemHealthUseCase_getEcosystemHealth(t *testing.T) { doguHealth := ecosystem.DoguHealthResult{ DogusByStatus: mixedDoguHealth, } - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) - health, err := useCase.getEcosystemHealth(testCtx, false, false) + health, err := useCase.getEcosystemHealth(testCtx, false) require.NoError(t, err) - assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth, ComponentHealth: componentHealth}, health) + assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth}, health) }) t.Run("ok, ignore dogu health", func(t *testing.T) { - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) - blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(nil, componentUseCase, blueprintRepo) - - health, err := useCase.getEcosystemHealth(testCtx, true, false) - - require.NoError(t, err) - assert.Equal(t, ecosystem.HealthResult{ComponentHealth: componentHealth}, health) - }) - - t.Run("ok, ignore component health", func(t *testing.T) { - doguHealth := ecosystem.DoguHealthResult{ - DogusByStatus: mixedDoguHealth, - } - doguUseCase := newMockDoguInstallationUseCase(t) - doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, nil, blueprintRepo) + useCase := NewEcosystemHealthUseCase(nil, blueprintRepo) - health, err := useCase.getEcosystemHealth(testCtx, false, true) + health, err := useCase.getEcosystemHealth(testCtx, true) require.NoError(t, err) - assert.Equal(t, ecosystem.HealthResult{DoguHealth: doguHealth}, health) + assert.Equal(t, ecosystem.HealthResult{}, health) }) t.Run("error checking dogu health", func(t *testing.T) { - componentHealth := ecosystem.ComponentHealthResult{ - ComponentsByStatus: mixedComponentHealth, - } - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(componentHealth, nil) doguUseCase := newMockDoguInstallationUseCase(t) doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(ecosystem.DoguHealthResult{}, assert.AnError) blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) - - _, err := useCase.getEcosystemHealth(testCtx, false, false) - - require.ErrorIs(t, err, assert.AnError) - }) - - t.Run("error checking component health", func(t *testing.T) { - doguHealth := ecosystem.DoguHealthResult{ - DogusByStatus: mixedDoguHealth, - } - doguUseCase := newMockDoguInstallationUseCase(t) - doguUseCase.EXPECT().CheckDoguHealth(mock.Anything).Return(doguHealth, nil) - componentUseCase := newMockComponentInstallationUseCase(t) - componentUseCase.EXPECT().CheckComponentHealth(testCtx).Return(ecosystem.ComponentHealthResult{}, assert.AnError) - blueprintRepo := newMockBlueprintSpecRepository(t) - useCase := NewEcosystemHealthUseCase(doguUseCase, componentUseCase, blueprintRepo) + useCase := NewEcosystemHealthUseCase(doguUseCase, blueprintRepo) - _, err := useCase.getEcosystemHealth(testCtx, false, false) + _, err := useCase.getEcosystemHealth(testCtx, false) require.ErrorIs(t, err, assert.AnError) }) diff --git a/pkg/application/interfaces.go b/pkg/application/interfaces.go index d4cec8da..6f52d3b0 100644 --- a/pkg/application/interfaces.go +++ b/pkg/application/interfaces.go @@ -36,16 +36,6 @@ type applyDogusUseCase interface { ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) } -type applyComponentsUseCase interface { - ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) -} - -type componentInstallationUseCase interface { - ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error - CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) - applyComponentState(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error -} - type completeBlueprintUseCase interface { CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -58,10 +48,6 @@ type dogusUpToDateUseCase interface { CheckDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error } -type selfUpgradeUseCase interface { - HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error -} - type ecosystemConfigUseCase interface { ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error } @@ -70,31 +56,12 @@ type doguInstallationRepository interface { domainservice.DoguInstallationRepository } -//nolint:unused -//goland:noinspection GoUnusedType -type componentInstallationRepository interface { - domainservice.ComponentInstallationRepository -} - //nolint:unused //goland:noinspection GoUnusedType type blueprintSpecRepository interface { domainservice.BlueprintSpecRepository } -type requiredComponentsProvider interface { - domainservice.RequiredComponentsProvider -} - -type healthWaitConfigProvider interface { - domainservice.HealthWaitConfigProvider -} - -type healthConfigProvider interface { - requiredComponentsProvider - healthWaitConfigProvider -} - // interface duplication for mocks //nolint:unused diff --git a/pkg/application/mock_applyComponentsUseCase_test.go b/pkg/application/mock_applyComponentsUseCase_test.go deleted file mode 100644 index cc06f1a6..00000000 --- a/pkg/application/mock_applyComponentsUseCase_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - mock "github.com/stretchr/testify/mock" -) - -// mockApplyComponentsUseCase is an autogenerated mock type for the applyComponentsUseCase type -type mockApplyComponentsUseCase struct { - mock.Mock -} - -type mockApplyComponentsUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockApplyComponentsUseCase) EXPECT() *mockApplyComponentsUseCase_Expecter { - return &mockApplyComponentsUseCase_Expecter{mock: &_m.Mock} -} - -// ApplyComponents provides a mock function with given fields: ctx, blueprint -func (_m *mockApplyComponentsUseCase) ApplyComponents(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for ApplyComponents") - } - - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) (bool, error)); ok { - return rf(ctx, blueprint) - } - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) bool); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Get(0).(bool) - } - - if rf, ok := ret.Get(1).(func(context.Context, *domain.BlueprintSpec) error); ok { - r1 = rf(ctx, blueprint) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockApplyComponentsUseCase_ApplyComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponents' -type mockApplyComponentsUseCase_ApplyComponents_Call struct { - *mock.Call -} - -// ApplyComponents is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockApplyComponentsUseCase_Expecter) ApplyComponents(ctx interface{}, blueprint interface{}) *mockApplyComponentsUseCase_ApplyComponents_Call { - return &mockApplyComponentsUseCase_ApplyComponents_Call{Call: _e.mock.On("ApplyComponents", ctx, blueprint)} -} - -func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockApplyComponentsUseCase_ApplyComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) Return(_a0 bool, _a1 error) *mockApplyComponentsUseCase_ApplyComponents_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockApplyComponentsUseCase_ApplyComponents_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) (bool, error)) *mockApplyComponentsUseCase_ApplyComponents_Call { - _c.Call.Return(run) - return _c -} - -// newMockApplyComponentsUseCase creates a new instance of mockApplyComponentsUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockApplyComponentsUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockApplyComponentsUseCase { - mock := &mockApplyComponentsUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_componentInstallationRepository_test.go b/pkg/application/mock_componentInstallationRepository_test.go deleted file mode 100644 index d5be824a..00000000 --- a/pkg/application/mock_componentInstallationRepository_test.go +++ /dev/null @@ -1,298 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - common "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - - mock "github.com/stretchr/testify/mock" -) - -// mockComponentInstallationRepository is an autogenerated mock type for the componentInstallationRepository type -type mockComponentInstallationRepository struct { - mock.Mock -} - -type mockComponentInstallationRepository_Expecter struct { - mock *mock.Mock -} - -func (_m *mockComponentInstallationRepository) EXPECT() *mockComponentInstallationRepository_Expecter { - return &mockComponentInstallationRepository_Expecter{mock: &_m.Mock} -} - -// Create provides a mock function with given fields: ctx, component -func (_m *mockComponentInstallationRepository) Create(ctx context.Context, component *ecosystem.ComponentInstallation) error { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *ecosystem.ComponentInstallation) error); ok { - r0 = rf(ctx, component) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentInstallationRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type mockComponentInstallationRepository_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - component *ecosystem.ComponentInstallation -func (_e *mockComponentInstallationRepository_Expecter) Create(ctx interface{}, component interface{}) *mockComponentInstallationRepository_Create_Call { - return &mockComponentInstallationRepository_Create_Call{Call: _e.mock.On("Create", ctx, component)} -} - -func (_c *mockComponentInstallationRepository_Create_Call) Run(run func(ctx context.Context, component *ecosystem.ComponentInstallation)) *mockComponentInstallationRepository_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*ecosystem.ComponentInstallation)) - }) - return _c -} - -func (_c *mockComponentInstallationRepository_Create_Call) Return(_a0 error) *mockComponentInstallationRepository_Create_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentInstallationRepository_Create_Call) RunAndReturn(run func(context.Context, *ecosystem.ComponentInstallation) error) *mockComponentInstallationRepository_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, componentName -func (_m *mockComponentInstallationRepository) Delete(ctx context.Context, componentName common.SimpleComponentName) error { - ret := _m.Called(ctx, componentName) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) error); ok { - r0 = rf(ctx, componentName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentInstallationRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type mockComponentInstallationRepository_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - componentName common.SimpleComponentName -func (_e *mockComponentInstallationRepository_Expecter) Delete(ctx interface{}, componentName interface{}) *mockComponentInstallationRepository_Delete_Call { - return &mockComponentInstallationRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, componentName)} -} - -func (_c *mockComponentInstallationRepository_Delete_Call) Run(run func(ctx context.Context, componentName common.SimpleComponentName)) *mockComponentInstallationRepository_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.SimpleComponentName)) - }) - return _c -} - -func (_c *mockComponentInstallationRepository_Delete_Call) Return(_a0 error) *mockComponentInstallationRepository_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentInstallationRepository_Delete_Call) RunAndReturn(run func(context.Context, common.SimpleComponentName) error) *mockComponentInstallationRepository_Delete_Call { - _c.Call.Return(run) - return _c -} - -// GetAll provides a mock function with given fields: ctx -func (_m *mockComponentInstallationRepository) GetAll(ctx context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetAll") - } - - var r0 map[common.SimpleComponentName]*ecosystem.ComponentInstallation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) map[common.SimpleComponentName]*ecosystem.ComponentInstallation); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[common.SimpleComponentName]*ecosystem.ComponentInstallation) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentInstallationRepository_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' -type mockComponentInstallationRepository_GetAll_Call struct { - *mock.Call -} - -// GetAll is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockComponentInstallationRepository_Expecter) GetAll(ctx interface{}) *mockComponentInstallationRepository_GetAll_Call { - return &mockComponentInstallationRepository_GetAll_Call{Call: _e.mock.On("GetAll", ctx)} -} - -func (_c *mockComponentInstallationRepository_GetAll_Call) Run(run func(ctx context.Context)) *mockComponentInstallationRepository_GetAll_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockComponentInstallationRepository_GetAll_Call) Return(_a0 map[common.SimpleComponentName]*ecosystem.ComponentInstallation, _a1 error) *mockComponentInstallationRepository_GetAll_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentInstallationRepository_GetAll_Call) RunAndReturn(run func(context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error)) *mockComponentInstallationRepository_GetAll_Call { - _c.Call.Return(run) - return _c -} - -// GetByName provides a mock function with given fields: ctx, componentName -func (_m *mockComponentInstallationRepository) GetByName(ctx context.Context, componentName common.SimpleComponentName) (*ecosystem.ComponentInstallation, error) { - ret := _m.Called(ctx, componentName) - - if len(ret) == 0 { - panic("no return value specified for GetByName") - } - - var r0 *ecosystem.ComponentInstallation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) (*ecosystem.ComponentInstallation, error)); ok { - return rf(ctx, componentName) - } - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) *ecosystem.ComponentInstallation); ok { - r0 = rf(ctx, componentName) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*ecosystem.ComponentInstallation) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, common.SimpleComponentName) error); ok { - r1 = rf(ctx, componentName) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentInstallationRepository_GetByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByName' -type mockComponentInstallationRepository_GetByName_Call struct { - *mock.Call -} - -// GetByName is a helper method to define mock.On call -// - ctx context.Context -// - componentName common.SimpleComponentName -func (_e *mockComponentInstallationRepository_Expecter) GetByName(ctx interface{}, componentName interface{}) *mockComponentInstallationRepository_GetByName_Call { - return &mockComponentInstallationRepository_GetByName_Call{Call: _e.mock.On("GetByName", ctx, componentName)} -} - -func (_c *mockComponentInstallationRepository_GetByName_Call) Run(run func(ctx context.Context, componentName common.SimpleComponentName)) *mockComponentInstallationRepository_GetByName_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.SimpleComponentName)) - }) - return _c -} - -func (_c *mockComponentInstallationRepository_GetByName_Call) Return(_a0 *ecosystem.ComponentInstallation, _a1 error) *mockComponentInstallationRepository_GetByName_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentInstallationRepository_GetByName_Call) RunAndReturn(run func(context.Context, common.SimpleComponentName) (*ecosystem.ComponentInstallation, error)) *mockComponentInstallationRepository_GetByName_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, component -func (_m *mockComponentInstallationRepository) Update(ctx context.Context, component *ecosystem.ComponentInstallation) error { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *ecosystem.ComponentInstallation) error); ok { - r0 = rf(ctx, component) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentInstallationRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type mockComponentInstallationRepository_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - component *ecosystem.ComponentInstallation -func (_e *mockComponentInstallationRepository_Expecter) Update(ctx interface{}, component interface{}) *mockComponentInstallationRepository_Update_Call { - return &mockComponentInstallationRepository_Update_Call{Call: _e.mock.On("Update", ctx, component)} -} - -func (_c *mockComponentInstallationRepository_Update_Call) Run(run func(ctx context.Context, component *ecosystem.ComponentInstallation)) *mockComponentInstallationRepository_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*ecosystem.ComponentInstallation)) - }) - return _c -} - -func (_c *mockComponentInstallationRepository_Update_Call) Return(_a0 error) *mockComponentInstallationRepository_Update_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentInstallationRepository_Update_Call) RunAndReturn(run func(context.Context, *ecosystem.ComponentInstallation) error) *mockComponentInstallationRepository_Update_Call { - _c.Call.Return(run) - return _c -} - -// newMockComponentInstallationRepository creates a new instance of mockComponentInstallationRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockComponentInstallationRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *mockComponentInstallationRepository { - mock := &mockComponentInstallationRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_componentInstallationUseCase_test.go b/pkg/application/mock_componentInstallationUseCase_test.go deleted file mode 100644 index 8b7f2e4a..00000000 --- a/pkg/application/mock_componentInstallationUseCase_test.go +++ /dev/null @@ -1,190 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - - mock "github.com/stretchr/testify/mock" -) - -// mockComponentInstallationUseCase is an autogenerated mock type for the componentInstallationUseCase type -type mockComponentInstallationUseCase struct { - mock.Mock -} - -type mockComponentInstallationUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockComponentInstallationUseCase) EXPECT() *mockComponentInstallationUseCase_Expecter { - return &mockComponentInstallationUseCase_Expecter{mock: &_m.Mock} -} - -// ApplyComponentStates provides a mock function with given fields: ctx, blueprint -func (_m *mockComponentInstallationUseCase) ApplyComponentStates(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for ApplyComponentStates") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentInstallationUseCase_ApplyComponentStates_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ApplyComponentStates' -type mockComponentInstallationUseCase_ApplyComponentStates_Call struct { - *mock.Call -} - -// ApplyComponentStates is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockComponentInstallationUseCase_Expecter) ApplyComponentStates(ctx interface{}, blueprint interface{}) *mockComponentInstallationUseCase_ApplyComponentStates_Call { - return &mockComponentInstallationUseCase_ApplyComponentStates_Call{Call: _e.mock.On("ApplyComponentStates", ctx, blueprint)} -} - -func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockComponentInstallationUseCase_ApplyComponentStates_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) Return(_a0 error) *mockComponentInstallationUseCase_ApplyComponentStates_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentInstallationUseCase_ApplyComponentStates_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockComponentInstallationUseCase_ApplyComponentStates_Call { - _c.Call.Return(run) - return _c -} - -// CheckComponentHealth provides a mock function with given fields: ctx -func (_m *mockComponentInstallationUseCase) CheckComponentHealth(ctx context.Context) (ecosystem.ComponentHealthResult, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for CheckComponentHealth") - } - - var r0 ecosystem.ComponentHealthResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.ComponentHealthResult, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.ComponentHealthResult); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.ComponentHealthResult) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockComponentInstallationUseCase_CheckComponentHealth_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckComponentHealth' -type mockComponentInstallationUseCase_CheckComponentHealth_Call struct { - *mock.Call -} - -// CheckComponentHealth is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockComponentInstallationUseCase_Expecter) CheckComponentHealth(ctx interface{}) *mockComponentInstallationUseCase_CheckComponentHealth_Call { - return &mockComponentInstallationUseCase_CheckComponentHealth_Call{Call: _e.mock.On("CheckComponentHealth", ctx)} -} - -func (_c *mockComponentInstallationUseCase_CheckComponentHealth_Call) Run(run func(ctx context.Context)) *mockComponentInstallationUseCase_CheckComponentHealth_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockComponentInstallationUseCase_CheckComponentHealth_Call) Return(_a0 ecosystem.ComponentHealthResult, _a1 error) *mockComponentInstallationUseCase_CheckComponentHealth_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockComponentInstallationUseCase_CheckComponentHealth_Call) RunAndReturn(run func(context.Context) (ecosystem.ComponentHealthResult, error)) *mockComponentInstallationUseCase_CheckComponentHealth_Call { - _c.Call.Return(run) - return _c -} - -// applyComponentState provides a mock function with given fields: _a0, _a1, _a2 -func (_m *mockComponentInstallationUseCase) applyComponentState(_a0 context.Context, _a1 domain.ComponentDiff, _a2 *ecosystem.ComponentInstallation) error { - ret := _m.Called(_a0, _a1, _a2) - - if len(ret) == 0 { - panic("no return value specified for applyComponentState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error); ok { - r0 = rf(_a0, _a1, _a2) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockComponentInstallationUseCase_applyComponentState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'applyComponentState' -type mockComponentInstallationUseCase_applyComponentState_Call struct { - *mock.Call -} - -// applyComponentState is a helper method to define mock.On call -// - _a0 context.Context -// - _a1 domain.ComponentDiff -// - _a2 *ecosystem.ComponentInstallation -func (_e *mockComponentInstallationUseCase_Expecter) applyComponentState(_a0 interface{}, _a1 interface{}, _a2 interface{}) *mockComponentInstallationUseCase_applyComponentState_Call { - return &mockComponentInstallationUseCase_applyComponentState_Call{Call: _e.mock.On("applyComponentState", _a0, _a1, _a2)} -} - -func (_c *mockComponentInstallationUseCase_applyComponentState_Call) Run(run func(_a0 context.Context, _a1 domain.ComponentDiff, _a2 *ecosystem.ComponentInstallation)) *mockComponentInstallationUseCase_applyComponentState_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(domain.ComponentDiff), args[2].(*ecosystem.ComponentInstallation)) - }) - return _c -} - -func (_c *mockComponentInstallationUseCase_applyComponentState_Call) Return(_a0 error) *mockComponentInstallationUseCase_applyComponentState_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockComponentInstallationUseCase_applyComponentState_Call) RunAndReturn(run func(context.Context, domain.ComponentDiff, *ecosystem.ComponentInstallation) error) *mockComponentInstallationUseCase_applyComponentState_Call { - _c.Call.Return(run) - return _c -} - -// newMockComponentInstallationUseCase creates a new instance of mockComponentInstallationUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockComponentInstallationUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockComponentInstallationUseCase { - mock := &mockComponentInstallationUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_healthConfigProvider_test.go b/pkg/application/mock_healthConfigProvider_test.go deleted file mode 100644 index 8891b9c8..00000000 --- a/pkg/application/mock_healthConfigProvider_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - mock "github.com/stretchr/testify/mock" -) - -// mockHealthConfigProvider is an autogenerated mock type for the healthConfigProvider type -type mockHealthConfigProvider struct { - mock.Mock -} - -type mockHealthConfigProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *mockHealthConfigProvider) EXPECT() *mockHealthConfigProvider_Expecter { - return &mockHealthConfigProvider_Expecter{mock: &_m.Mock} -} - -// GetRequiredComponents provides a mock function with given fields: ctx -func (_m *mockHealthConfigProvider) GetRequiredComponents(ctx context.Context) ([]ecosystem.RequiredComponent, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetRequiredComponents") - } - - var r0 []ecosystem.RequiredComponent - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]ecosystem.RequiredComponent, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []ecosystem.RequiredComponent); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]ecosystem.RequiredComponent) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockHealthConfigProvider_GetRequiredComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequiredComponents' -type mockHealthConfigProvider_GetRequiredComponents_Call struct { - *mock.Call -} - -// GetRequiredComponents is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockHealthConfigProvider_Expecter) GetRequiredComponents(ctx interface{}) *mockHealthConfigProvider_GetRequiredComponents_Call { - return &mockHealthConfigProvider_GetRequiredComponents_Call{Call: _e.mock.On("GetRequiredComponents", ctx)} -} - -func (_c *mockHealthConfigProvider_GetRequiredComponents_Call) Run(run func(ctx context.Context)) *mockHealthConfigProvider_GetRequiredComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockHealthConfigProvider_GetRequiredComponents_Call) Return(_a0 []ecosystem.RequiredComponent, _a1 error) *mockHealthConfigProvider_GetRequiredComponents_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockHealthConfigProvider_GetRequiredComponents_Call) RunAndReturn(run func(context.Context) ([]ecosystem.RequiredComponent, error)) *mockHealthConfigProvider_GetRequiredComponents_Call { - _c.Call.Return(run) - return _c -} - -// GetWaitConfig provides a mock function with given fields: ctx -func (_m *mockHealthConfigProvider) GetWaitConfig(ctx context.Context) (ecosystem.WaitConfig, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetWaitConfig") - } - - var r0 ecosystem.WaitConfig - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.WaitConfig, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.WaitConfig); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.WaitConfig) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockHealthConfigProvider_GetWaitConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWaitConfig' -type mockHealthConfigProvider_GetWaitConfig_Call struct { - *mock.Call -} - -// GetWaitConfig is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockHealthConfigProvider_Expecter) GetWaitConfig(ctx interface{}) *mockHealthConfigProvider_GetWaitConfig_Call { - return &mockHealthConfigProvider_GetWaitConfig_Call{Call: _e.mock.On("GetWaitConfig", ctx)} -} - -func (_c *mockHealthConfigProvider_GetWaitConfig_Call) Run(run func(ctx context.Context)) *mockHealthConfigProvider_GetWaitConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockHealthConfigProvider_GetWaitConfig_Call) Return(_a0 ecosystem.WaitConfig, _a1 error) *mockHealthConfigProvider_GetWaitConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockHealthConfigProvider_GetWaitConfig_Call) RunAndReturn(run func(context.Context) (ecosystem.WaitConfig, error)) *mockHealthConfigProvider_GetWaitConfig_Call { - _c.Call.Return(run) - return _c -} - -// newMockHealthConfigProvider creates a new instance of mockHealthConfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockHealthConfigProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *mockHealthConfigProvider { - mock := &mockHealthConfigProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_healthWaitConfigProvider_test.go b/pkg/application/mock_healthWaitConfigProvider_test.go deleted file mode 100644 index 529218a4..00000000 --- a/pkg/application/mock_healthWaitConfigProvider_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - mock "github.com/stretchr/testify/mock" -) - -// mockHealthWaitConfigProvider is an autogenerated mock type for the healthWaitConfigProvider type -type mockHealthWaitConfigProvider struct { - mock.Mock -} - -type mockHealthWaitConfigProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *mockHealthWaitConfigProvider) EXPECT() *mockHealthWaitConfigProvider_Expecter { - return &mockHealthWaitConfigProvider_Expecter{mock: &_m.Mock} -} - -// GetWaitConfig provides a mock function with given fields: ctx -func (_m *mockHealthWaitConfigProvider) GetWaitConfig(ctx context.Context) (ecosystem.WaitConfig, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetWaitConfig") - } - - var r0 ecosystem.WaitConfig - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.WaitConfig, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.WaitConfig); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.WaitConfig) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockHealthWaitConfigProvider_GetWaitConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWaitConfig' -type mockHealthWaitConfigProvider_GetWaitConfig_Call struct { - *mock.Call -} - -// GetWaitConfig is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockHealthWaitConfigProvider_Expecter) GetWaitConfig(ctx interface{}) *mockHealthWaitConfigProvider_GetWaitConfig_Call { - return &mockHealthWaitConfigProvider_GetWaitConfig_Call{Call: _e.mock.On("GetWaitConfig", ctx)} -} - -func (_c *mockHealthWaitConfigProvider_GetWaitConfig_Call) Run(run func(ctx context.Context)) *mockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockHealthWaitConfigProvider_GetWaitConfig_Call) Return(_a0 ecosystem.WaitConfig, _a1 error) *mockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockHealthWaitConfigProvider_GetWaitConfig_Call) RunAndReturn(run func(context.Context) (ecosystem.WaitConfig, error)) *mockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Return(run) - return _c -} - -// newMockHealthWaitConfigProvider creates a new instance of mockHealthWaitConfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockHealthWaitConfigProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *mockHealthWaitConfigProvider { - mock := &mockHealthWaitConfigProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_requiredComponentsProvider_test.go b/pkg/application/mock_requiredComponentsProvider_test.go deleted file mode 100644 index f7e874fc..00000000 --- a/pkg/application/mock_requiredComponentsProvider_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - mock "github.com/stretchr/testify/mock" -) - -// mockRequiredComponentsProvider is an autogenerated mock type for the requiredComponentsProvider type -type mockRequiredComponentsProvider struct { - mock.Mock -} - -type mockRequiredComponentsProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *mockRequiredComponentsProvider) EXPECT() *mockRequiredComponentsProvider_Expecter { - return &mockRequiredComponentsProvider_Expecter{mock: &_m.Mock} -} - -// GetRequiredComponents provides a mock function with given fields: ctx -func (_m *mockRequiredComponentsProvider) GetRequiredComponents(ctx context.Context) ([]ecosystem.RequiredComponent, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetRequiredComponents") - } - - var r0 []ecosystem.RequiredComponent - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]ecosystem.RequiredComponent, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []ecosystem.RequiredComponent); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]ecosystem.RequiredComponent) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// mockRequiredComponentsProvider_GetRequiredComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequiredComponents' -type mockRequiredComponentsProvider_GetRequiredComponents_Call struct { - *mock.Call -} - -// GetRequiredComponents is a helper method to define mock.On call -// - ctx context.Context -func (_e *mockRequiredComponentsProvider_Expecter) GetRequiredComponents(ctx interface{}) *mockRequiredComponentsProvider_GetRequiredComponents_Call { - return &mockRequiredComponentsProvider_GetRequiredComponents_Call{Call: _e.mock.On("GetRequiredComponents", ctx)} -} - -func (_c *mockRequiredComponentsProvider_GetRequiredComponents_Call) Run(run func(ctx context.Context)) *mockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *mockRequiredComponentsProvider_GetRequiredComponents_Call) Return(_a0 []ecosystem.RequiredComponent, _a1 error) *mockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *mockRequiredComponentsProvider_GetRequiredComponents_Call) RunAndReturn(run func(context.Context) ([]ecosystem.RequiredComponent, error)) *mockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Return(run) - return _c -} - -// newMockRequiredComponentsProvider creates a new instance of mockRequiredComponentsProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockRequiredComponentsProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *mockRequiredComponentsProvider { - mock := &mockRequiredComponentsProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/mock_selfUpgradeUseCase_test.go b/pkg/application/mock_selfUpgradeUseCase_test.go deleted file mode 100644 index 453ca93a..00000000 --- a/pkg/application/mock_selfUpgradeUseCase_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package application - -import ( - context "context" - - domain "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - mock "github.com/stretchr/testify/mock" -) - -// mockSelfUpgradeUseCase is an autogenerated mock type for the selfUpgradeUseCase type -type mockSelfUpgradeUseCase struct { - mock.Mock -} - -type mockSelfUpgradeUseCase_Expecter struct { - mock *mock.Mock -} - -func (_m *mockSelfUpgradeUseCase) EXPECT() *mockSelfUpgradeUseCase_Expecter { - return &mockSelfUpgradeUseCase_Expecter{mock: &_m.Mock} -} - -// HandleSelfUpgrade provides a mock function with given fields: ctx, blueprint -func (_m *mockSelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ret := _m.Called(ctx, blueprint) - - if len(ret) == 0 { - panic("no return value specified for HandleSelfUpgrade") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *domain.BlueprintSpec) error); ok { - r0 = rf(ctx, blueprint) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// mockSelfUpgradeUseCase_HandleSelfUpgrade_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HandleSelfUpgrade' -type mockSelfUpgradeUseCase_HandleSelfUpgrade_Call struct { - *mock.Call -} - -// HandleSelfUpgrade is a helper method to define mock.On call -// - ctx context.Context -// - blueprint *domain.BlueprintSpec -func (_e *mockSelfUpgradeUseCase_Expecter) HandleSelfUpgrade(ctx interface{}, blueprint interface{}) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { - return &mockSelfUpgradeUseCase_HandleSelfUpgrade_Call{Call: _e.mock.On("HandleSelfUpgrade", ctx, blueprint)} -} - -func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) Run(run func(ctx context.Context, blueprint *domain.BlueprintSpec)) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*domain.BlueprintSpec)) - }) - return _c -} - -func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) Return(_a0 error) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call) RunAndReturn(run func(context.Context, *domain.BlueprintSpec) error) *mockSelfUpgradeUseCase_HandleSelfUpgrade_Call { - _c.Call.Return(run) - return _c -} - -// newMockSelfUpgradeUseCase creates a new instance of mockSelfUpgradeUseCase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockSelfUpgradeUseCase(t interface { - mock.TestingT - Cleanup(func()) -}) *mockSelfUpgradeUseCase { - mock := &mockSelfUpgradeUseCase{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/application/selfUpgradeUseCase.go b/pkg/application/selfUpgradeUseCase.go deleted file mode 100644 index f7dee243..00000000 --- a/pkg/application/selfUpgradeUseCase.go +++ /dev/null @@ -1,104 +0,0 @@ -package application - -import ( - "context" - "fmt" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "sigs.k8s.io/controller-runtime/pkg/log" -) - -const awaitSelfUpgradeErrorMsg = "await self upgrade" - -type SelfUpgradeUseCase struct { - blueprintRepo domainservice.BlueprintSpecRepository - componentRepo componentInstallationRepository - componentUseCase componentInstallationUseCase - blueprintOperatorName common.SimpleComponentName -} - -func NewSelfUpgradeUseCase( - blueprintRepo domainservice.BlueprintSpecRepository, - componentRepo componentInstallationRepository, - componentUseCase componentInstallationUseCase, - blueprintOperatorName common.SimpleComponentName, -) *SelfUpgradeUseCase { - return &SelfUpgradeUseCase{ - blueprintRepo: blueprintRepo, - componentRepo: componentRepo, - componentUseCase: componentUseCase, - blueprintOperatorName: blueprintOperatorName, - } -} - -// HandleSelfUpgrade checks if a self upgrade is necessary, executes all needed steps and -// can check if the self upgrade was successful after a restart. -// It always sets the fitting status in the blueprint spec. -func (useCase *SelfUpgradeUseCase) HandleSelfUpgrade(ctx context.Context, blueprint *domain.BlueprintSpec) error { - ownDiff := blueprint.StateDiff.ComponentDiffs.GetComponentDiffByName(useCase.blueprintOperatorName) - ownComponent, err := useCase.componentRepo.GetByName(ctx, useCase.blueprintOperatorName) - - if err != nil && !domainservice.IsNotFoundError(err) { - // ignore not found errors as this is ok if the component was not installed via a component CR before - // only return if other errors happen, e.g. InternalError - return fmt.Errorf("cannot load component installation for %q from ecosystem: %w", useCase.blueprintOperatorName, err) - } - - needsToApply, isCompleted := checkStateOfSelfUpgrade(ownDiff, ownComponent) - - if isCompleted { - blueprint.MarkSelfUpgradeCompleted() - err = useCase.blueprintRepo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot save blueprint spec %q after self upgrading the operator: %w", blueprint.Id, err) - } - return nil - } - // if not done, set conditions accordingly - blueprint.MarkWaitingForSelfUpgrade() - err = useCase.blueprintRepo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot persist blueprint spec %q to mark it waiting for self upgrade: %w", blueprint.Id, err) - } - - if needsToApply { - return useCase.doSelfUpgrade(ctx, ownDiff, ownComponent) - } - return &domain.AwaitSelfUpgradeError{Message: awaitSelfUpgradeErrorMsg} -} - -func checkStateOfSelfUpgrade(ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) (needsToApply, isCompleted bool) { - // use extra vars to avoid nil pointer dereferences of the component - var versionSetForInstallation, installedVersion *semver.Version - if ownComponent != nil { - versionSetForInstallation = ownComponent.ExpectedVersion - installedVersion = ownComponent.ActualVersion - } - - if ownDiff.IsExpectedVersion(installedVersion) { - // if component CR status.installedVersion already says: our wanted version is installed - return false, true - } - if ownDiff.IsExpectedVersion(versionSetForInstallation) { - // update is already triggered but not done - return false, false - } else { - // no update triggered yet - // trigger update and trigger later reconciliation via error - return true, false - } -} - -func (useCase *SelfUpgradeUseCase) doSelfUpgrade(ctx context.Context, ownDiff domain.ComponentDiff, ownComponent *ecosystem.ComponentInstallation) error { - logger := log.FromContext(ctx).WithName("ComponentInstallationUseCase.doSelfUpgrade") - logger.Info("self upgrade needed, apply self upgrade") - err := useCase.componentUseCase.applyComponentState(ctx, ownDiff, ownComponent) - if err != nil { - return fmt.Errorf("an error occurred while applying the self-upgrade to the ecosystem: %w", err) - } - return &domain.AwaitSelfUpgradeError{Message: awaitSelfUpgradeErrorMsg} -} diff --git a/pkg/application/selfUpgradeUseCase_test.go b/pkg/application/selfUpgradeUseCase_test.go deleted file mode 100644 index 4e8a893f..00000000 --- a/pkg/application/selfUpgradeUseCase_test.go +++ /dev/null @@ -1,266 +0,0 @@ -package application - -import ( - "context" - "testing" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "k8s.io/apimachinery/pkg/api/meta" -) - -func TestSelfUpgradeUseCase_HandleSelfUpgrade(t *testing.T) { - var blueprintOperatorName = common.SimpleComponentName("k8s-blueprint-operator") - blueprintId := "myBlueprint" - testCtx := context.TODO() - version1, _ := semver.NewVersion("1.0") - version2, _ := semver.NewVersion("2.0") - internalTestError := domainservice.NewInternalError(assert.AnError, "internal error") - - NoActionV2ComponentDiff := domain.ComponentDiff{ - Name: blueprintOperatorName, - Actual: domain.ComponentDiffState{ - Version: version2, - }, - Expected: domain.ComponentDiffState{ - Version: version2, - }, - NeededActions: []domain.Action{}, - } - NoActionV2StateDiff := domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - NoActionV2ComponentDiff, - }, - } - UpgradeToV2ComponentDiff := domain.ComponentDiff{ - Name: blueprintOperatorName, - Actual: domain.ComponentDiffState{ - Version: version1, - }, - Expected: domain.ComponentDiffState{ - Version: version2, - }, - NeededActions: []domain.Action{domain.ActionUpgrade}, - } - upgradeToV2StateDiff := domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - UpgradeToV2ComponentDiff, - }, - } - InstallV2ComponentDiff := domain.ComponentDiff{ - Name: blueprintOperatorName, - Actual: domain.ComponentDiffState{}, - Expected: domain.ComponentDiffState{ - Version: version2, - }, - NeededActions: []domain.Action{domain.ActionInstall}, - } - InstallV2StateDiff := domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - InstallV2ComponentDiff, - }, - } - - t.Run("apply upgrade and trigger reconcile", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - Conditions: []domain.Condition{}, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version1, - ActualVersion: version1, - } - - // only once as the operator will terminate and will set status completed later. - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil).Once() - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() - componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, component).Return(nil) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - var awaitError *domain.AwaitSelfUpgradeError - assert.ErrorAs(t, err, &awaitError) - assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) - }) - - t.Run("apply upgrade with missing component cr", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: InstallV2StateDiff, - Conditions: []domain.Condition{}, - } - var nilComponent *ecosystem.ComponentInstallation - - // only once as the operator will terminate and will set status completed later. - blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil).Once() - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(nil, domainservice.NewNotFoundError(assert.AnError, "test-error")).Once() - componentUseCase.EXPECT().applyComponentState(testCtx, InstallV2ComponentDiff, nilComponent).Return(nil) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - var awaitError *domain.AwaitSelfUpgradeError - assert.ErrorAs(t, err, &awaitError) - assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) - }) - - t.Run("check if self-upgrade is done -> not yet", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: NoActionV2StateDiff, - Conditions: []domain.Condition{}, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version1, - } - - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() - blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - var awaitError *domain.AwaitSelfUpgradeError - assert.ErrorAs(t, err, &awaitError) - assert.ErrorContains(t, err, awaitSelfUpgradeErrorMsg) - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) - }) - - t.Run("check if self-upgrade is done -> done", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: NoActionV2StateDiff, - Conditions: []domain.Condition{}, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version2, - } - - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() - blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(nil) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.NoError(t, err) - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionSelfUpgradeCompleted)) - }) - - t.Run("cannot load component", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - } - - componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(nil, internalTestError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.ErrorIs(t, err, internalTestError) - assert.ErrorContains(t, err, "cannot load component installation for \""+string(blueprintOperatorName)+"\" from ecosystem") - }) - - t.Run("cannot save blueprint in doSelfUpgrade", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - StateDiff: upgradeToV2StateDiff, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version1, - } - - componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(component, nil) - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(internalTestError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.ErrorIs(t, err, internalTestError) - assert.ErrorContains(t, err, "cannot persist blueprint spec \""+blueprintId+"\" to mark it waiting for self upgrade") - }) - - t.Run("cannot apply self upgrade", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - StateDiff: upgradeToV2StateDiff, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version1, - } - - componentRepo.EXPECT().GetByName(mock.Anything, blueprintOperatorName).Return(component, nil) - blueprintRepo.EXPECT().Update(mock.Anything, blueprint).Return(nil) - componentUseCase.EXPECT().applyComponentState(mock.Anything, UpgradeToV2ComponentDiff, component).Return(internalTestError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.ErrorIs(t, err, internalTestError) - assert.ErrorContains(t, err, "an error occurred while applying the self-upgrade to the ecosystem") - }) - - t.Run("cannot save blueprint to complete self upgrade", func(t *testing.T) { - blueprintRepo := newMockBlueprintSpecRepository(t) - componentRepo := newMockComponentInstallationRepository(t) - componentUseCase := newMockComponentInstallationUseCase(t) - useCase := NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentUseCase, blueprintOperatorName) - - blueprint := &domain.BlueprintSpec{ - Id: blueprintId, - StateDiff: NoActionV2StateDiff, - Conditions: []domain.Condition{}, - } - - component := &ecosystem.ComponentInstallation{ - ExpectedVersion: version2, - ActualVersion: version2, - } - - componentRepo.EXPECT().GetByName(testCtx, blueprintOperatorName).Return(component, nil).Once() // no reload for actual version check - blueprintRepo.EXPECT().Update(testCtx, blueprint).Return(assert.AnError) - - err := useCase.HandleSelfUpgrade(testCtx, blueprint) - - assert.ErrorIs(t, err, assert.AnError) - assert.ErrorContains(t, err, "cannot save blueprint spec \"myBlueprint\" after self upgrading the operator") - }) -} diff --git a/pkg/application/stateDiffUseCase.go b/pkg/application/stateDiffUseCase.go index d927d112..9f084174 100644 --- a/pkg/application/stateDiffUseCase.go +++ b/pkg/application/stateDiffUseCase.go @@ -14,32 +14,22 @@ import ( const REFERENCED_CONFIG_NOT_FOUND = "could not load referenced sensitive config" type StateDiffUseCase struct { - blueprintSpecRepo blueprintSpecRepository - doguInstallationRepo doguInstallationRepository - componentInstallationRepo componentInstallationRepository - globalConfigRepo globalConfigRepository - doguConfigRepo doguConfigRepository - sensitiveDoguConfigRepo sensitiveDoguConfigRepository - sensitiveConfigRefReader sensitiveConfigRefReader + blueprintSpecRepo blueprintSpecRepository + doguInstallationRepo doguInstallationRepository + globalConfigRepo globalConfigRepository + doguConfigRepo doguConfigRepository + sensitiveDoguConfigRepo sensitiveDoguConfigRepository + sensitiveConfigRefReader sensitiveConfigRefReader } -func NewStateDiffUseCase( - blueprintSpecRepo domainservice.BlueprintSpecRepository, - doguInstallationRepo domainservice.DoguInstallationRepository, - componentInstallationRepo domainservice.ComponentInstallationRepository, - globalConfigRepo domainservice.GlobalConfigRepository, - doguConfigRepo domainservice.DoguConfigRepository, - sensitiveDoguConfigRepo domainservice.SensitiveDoguConfigRepository, - sensitiveConfigRefReader domainservice.SensitiveConfigRefReader, -) *StateDiffUseCase { +func NewStateDiffUseCase(blueprintSpecRepo domainservice.BlueprintSpecRepository, doguInstallationRepo domainservice.DoguInstallationRepository, globalConfigRepo domainservice.GlobalConfigRepository, doguConfigRepo domainservice.DoguConfigRepository, sensitiveDoguConfigRepo domainservice.SensitiveDoguConfigRepository, sensitiveConfigRefReader domainservice.SensitiveConfigRefReader) *StateDiffUseCase { return &StateDiffUseCase{ - blueprintSpecRepo: blueprintSpecRepo, - doguInstallationRepo: doguInstallationRepo, - componentInstallationRepo: componentInstallationRepo, - globalConfigRepo: globalConfigRepo, - doguConfigRepo: doguConfigRepo, - sensitiveDoguConfigRepo: sensitiveDoguConfigRepo, - sensitiveConfigRefReader: sensitiveConfigRefReader, + blueprintSpecRepo: blueprintSpecRepo, + doguInstallationRepo: doguInstallationRepo, + globalConfigRepo: globalConfigRepo, + doguConfigRepo: doguConfigRepo, + sensitiveDoguConfigRepo: sensitiveDoguConfigRepo, + sensitiveConfigRefReader: sensitiveConfigRefReader, } } @@ -97,11 +87,9 @@ func (useCase *StateDiffUseCase) collectEcosystemState(ctx context.Context, effe logger := log.FromContext(ctx).WithName("StateDiffUseCase.collectEcosystemState") // TODO: collect ecosystem state in parallel (like for ecosystem health) if we have time - // load current dogus and components + // load current dogus logger.V(2).Info("collect installed dogus") installedDogus, doguErr := useCase.doguInstallationRepo.GetAll(ctx) - logger.V(2).Info("collect installed components") - installedComponents, componentErr := useCase.componentInstallationRepo.GetAll(ctx) // load current config logger.V(2).Info("collect needed global config") globalConfig, globalConfigErr := useCase.globalConfigRepo.Get(ctx) @@ -112,14 +100,13 @@ func (useCase *StateDiffUseCase) collectEcosystemState(ctx context.Context, effe logger.V(2).Info("collect needed sensitive dogu config") sensitiveConfigByDogu, sensitiveConfigErr := useCase.sensitiveDoguConfigRepo.GetAllExisting(ctx, effectiveBlueprint.Config.GetDogusWithChangedSensitiveConfig()) - joinedError := errors.Join(doguErr, componentErr, globalConfigErr, doguConfigErr, sensitiveConfigErr) + joinedError := errors.Join(doguErr, globalConfigErr, doguConfigErr, sensitiveConfigErr) if joinedError != nil { return ecosystem.EcosystemState{}, fmt.Errorf("could not collect ecosystem state: %w", joinedError) } return ecosystem.EcosystemState{ InstalledDogus: installedDogus, - InstalledComponents: installedComponents, GlobalConfig: globalConfig, ConfigByDogu: configByDogu, SensitiveConfigByDogu: sensitiveConfigByDogu, diff --git a/pkg/application/stateDiffUseCase_test.go b/pkg/application/stateDiffUseCase_test.go index ba9e2a73..1a6b9b45 100644 --- a/pkg/application/stateDiffUseCase_test.go +++ b/pkg/application/stateDiffUseCase_test.go @@ -51,8 +51,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, internalTestError) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -67,40 +65,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) - - // when - err := sut.DetermineStateDiff(testCtx, blueprint) - - // then - require.Error(t, err) - assert.ErrorIs(t, err, internalTestError) - assert.ErrorContains(t, err, "could not determine state diff") - assert.ErrorContains(t, err, "could not collect ecosystem state") - }) - t.Run("should fail to get installed components", func(t *testing.T) { - // given - blueprint := &domain.BlueprintSpec{Id: "testBlueprint1"} - - doguInstallRepoMock := newMockDoguInstallationRepository(t) - doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, internalTestError) - - globalConfigRepoMock := newMockGlobalConfigRepository(t) - entries, _ := config.MapToEntries(map[string]any{}) - globalConfig := config.CreateGlobalConfig(entries) - globalConfigRepoMock.EXPECT().Get(testCtx).Return(globalConfig, nil) - doguConfigRepoMock := newMockDoguConfigRepository(t) - doguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - sensitiveDoguConfigRepoMock := newMockSensitiveDoguConfigRepository(t) - sensitiveDoguConfigRepoMock.EXPECT().GetAllExisting(testCtx, nilDoguNameList).Return(map[cescommons.SimpleName]config.DoguConfig{}, nil) - configRefReaderMock := newMockSensitiveConfigRefReader(t) - configRefReaderMock.EXPECT(). - GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). - Return(map[common.DoguConfigKey]config.Value{}, nil) - - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -117,8 +82,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -133,7 +96,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -157,8 +120,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -176,7 +137,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -195,8 +156,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -214,7 +173,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -239,8 +198,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -255,7 +212,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -310,8 +267,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { "ldap": {Name: ldapQualifiedDoguName, Version: mustParseVersion(t, "1.1.1")}, } doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -326,7 +281,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -402,8 +357,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) installedDogus := map[cescommons.SimpleName]*ecosystem.DoguInstallation{} doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -419,7 +372,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -469,8 +422,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { "ldap": {Name: ldapQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, } doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -498,7 +449,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { GetValues(testCtx, map[common.DoguConfigKey]domain.SensitiveValueRef{}). Return(map[common.DoguConfigKey]config.Value{}, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -560,8 +511,6 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { "ldap": {Name: ldapQualifiedDoguName, Version: mustParseVersion(t, "1.8.6")}, } doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(installedDogus, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -594,7 +543,7 @@ func TestStateDiffUseCase_DetermineStateDiff(t *testing.T) { ldapConfigKeyNginxKey1: config.Value(val3), }, nil) - sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(blueprintRepoMock, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when err := sut.DetermineStateDiff(testCtx, blueprint) @@ -678,8 +627,6 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -702,7 +649,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { }, nil) configRefReaderMock := newMockSensitiveConfigRefReader(t) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when ecosystemState, err := sut.collectEcosystemState(testCtx, effectiveBlueprint) @@ -764,8 +711,6 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { doguInstallRepoMock := newMockDoguInstallationRepository(t) doguInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) - componentInstallRepoMock := newMockComponentInstallationRepository(t) - componentInstallRepoMock.EXPECT().GetAll(testCtx).Return(nil, nil) globalConfigRepoMock := newMockGlobalConfigRepository(t) entries, _ := config.MapToEntries(map[string]any{}) @@ -782,7 +727,7 @@ func TestStateDiffUseCase_collectEcosystemState(t *testing.T) { Return(map[cescommons.SimpleName]config.DoguConfig{}, internalTestError) configRefReaderMock := newMockSensitiveConfigRefReader(t) - sut := NewStateDiffUseCase(nil, doguInstallRepoMock, componentInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) + sut := NewStateDiffUseCase(nil, doguInstallRepoMock, globalConfigRepoMock, doguConfigRepoMock, sensitiveDoguConfigRepoMock, configRefReaderMock) // when ecosystemState, err := sut.collectEcosystemState(testCtx, effectiveBlueprint) diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index a3effe87..bfe0707d 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -10,22 +10,17 @@ import ( "github.com/cloudogu/k8s-registry-lib/repository" remotedogudescriptor "github.com/cloudogu/remote-dogu-descriptor-lib/repository" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" adapterk8s "github.com/cloudogu/k8s-blueprint-lib/v2/client" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/doguregistry" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/componentcr" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/dogucr" - adapterhealthconfig "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/kubernetes/healthConfig" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/adapter/reconciler" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/application" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/config" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" - componentEcoClient "github.com/cloudogu/k8s-component-operator/pkg/api/ecosystem" doguEcoClient "github.com/cloudogu/k8s-dogu-lib/v2/client" ) @@ -34,11 +29,6 @@ type ApplicationContext struct { Reconciler *reconciler.BlueprintReconciler } -var blueprintOperatorName = common.QualifiedComponentName{ - Namespace: "k8s", - SimpleName: "k8s-blueprint-operator", -} - // Bootstrap creates the ApplicationContext and does all dependency injection of the whole application. func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, namespace string) (*ApplicationContext, error) { ecosystemClientSet, err := createEcosystemClientSet(restConfig) @@ -50,10 +40,6 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name if err != nil { return nil, fmt.Errorf("failed to create dogus interface: %w", err) } - componentsInterface, err := componentEcoClient.NewForConfig(restConfig) - if err != nil { - return nil, fmt.Errorf("failed to create components interface: %w", err) - } blueprintRepo := v2.NewBlueprintSpecRepository( ecosystemClientSet.EcosystemV1Alpha1().Blueprints(namespace), eventRecorder, @@ -73,23 +59,18 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name globalConfigRepoAdapter := adapterconfigk8s.NewGlobalConfigRepository(*k8sGlobalConfigRepo) doguRepo := dogucr.NewDoguInstallationRepo(dogusInterface.Dogus(namespace)) - componentRepo := componentcr.NewComponentInstallationRepo(componentsInterface.Components(namespace)) - healthConfigRepo := adapterhealthconfig.NewHealthConfigProvider(ecosystemClientSet.CoreV1().ConfigMaps(namespace)) initialBlueprintStateUseCase := application.NewInitiateBlueprintStatusUseCase(blueprintRepo) validateDependenciesUseCase := domainservice.NewValidateDependenciesDomainUseCase(remoteDoguRegistry) validateMountsUseCase := domainservice.NewValidateAdditionalMountsDomainUseCase(remoteDoguRegistry) blueprintValidationUseCase := application.NewBlueprintSpecValidationUseCase(blueprintRepo, validateDependenciesUseCase, validateMountsUseCase) effectiveBlueprintUseCase := application.NewEffectiveBlueprintUseCase(blueprintRepo) - stateDiffUseCase := application.NewStateDiffUseCase(blueprintRepo, doguRepo, componentRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) - doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, healthConfigRepo, doguConfigRepo, globalConfigRepoAdapter) - componentInstallationUseCase := application.NewComponentInstallationUseCase(blueprintRepo, componentRepo, healthConfigRepo) - ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, componentInstallationUseCase, blueprintRepo) + stateDiffUseCase := application.NewStateDiffUseCase(blueprintRepo, doguRepo, globalConfigRepoAdapter, doguConfigRepo, sensitiveDoguConfigRepo, sensitiveConfigRefReader) + doguInstallationUseCase := application.NewDoguInstallationUseCase(blueprintRepo, doguRepo, doguConfigRepo, globalConfigRepoAdapter) + ecosystemHealthUseCase := application.NewEcosystemHealthUseCase(doguInstallationUseCase, blueprintRepo) completeBlueprintSpecUseCase := application.NewCompleteBlueprintUseCase(blueprintRepo) - applyComponentUseCase := application.NewApplyComponentsUseCase(blueprintRepo, componentInstallationUseCase) applyDogusUseCase := application.NewApplyDogusUseCase(blueprintRepo, doguInstallationUseCase) ConfigUseCase := application.NewEcosystemConfigUseCase(blueprintRepo, doguConfigRepo, sensitiveDoguConfigRepo, globalConfigRepoAdapter, doguRepo) - selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) dogusUpToDateUseCase := application.NewDogusUpToDateUseCase(blueprintRepo, doguInstallationUseCase) preparationUseCases := application.NewBlueprintPreparationUseCases( @@ -102,8 +83,6 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name applyUseCases := application.NewBlueprintApplyUseCases( completeBlueprintSpecUseCase, ConfigUseCase, - selfUpgradeUseCase, - applyComponentUseCase, applyDogusUseCase, ecosystemHealthUseCase, dogusUpToDateUseCase, diff --git a/pkg/domain/blueprint.go b/pkg/domain/blueprint.go index d9b008ac..4b6cdcab 100644 --- a/pkg/domain/blueprint.go +++ b/pkg/domain/blueprint.go @@ -5,7 +5,6 @@ import ( "fmt" cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" ) @@ -19,9 +18,6 @@ type Blueprint struct { // Dogus contains a set of exact dogu versions which should be present or absent in the CES instance after which this // blueprint was applied. Optional. Dogus []Dogu - // Components contains a set of exact components versions which should be present or absent in the CES instance after which - // this blueprint was applied. Optional. - Components []Component // Config contains all config entries to set via blueprint. Config Config } @@ -31,8 +27,6 @@ func (blueprint *Blueprint) Validate() error { errorList := []error{ blueprint.validateDogus(), blueprint.validateDoguUniqueness(), - blueprint.validateComponents(), - blueprint.validateComponentUniqueness(), blueprint.validateConfig(), } @@ -58,21 +52,6 @@ func (blueprint *Blueprint) validateDoguUniqueness() error { return nil } -func (blueprint *Blueprint) validateComponents() error { - errorList := util.Map(blueprint.Components, func(component Component) error { return component.Validate() }) - return errors.Join(errorList...) -} - -// validateComponentUniqueness checks if components exist twice in the blueprint and returns an error if it's so. -func (blueprint *Blueprint) validateComponentUniqueness() error { - componentNames := util.Map(blueprint.Components, func(component Component) common.SimpleComponentName { return component.Name.SimpleName }) - duplicates := util.GetDuplicates(componentNames) - if len(duplicates) != 0 { - return fmt.Errorf("there are duplicate components: %v", duplicates) - } - return nil -} - func (blueprint *Blueprint) validateConfig() error { return blueprint.Config.validate() } diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index d8edf044..d72146cc 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -41,15 +41,13 @@ const ( ConditionCompleted = "Completed" ConditionLastApplySucceeded = "LastApplySucceeded" - ReasonLastApplyErrorAtComponents = "ComponentApplyFailure" - ReasonLastApplyErrorAtDogus = "DoguApplyFailure" - ReasonLastApplyErrorAtConfig = "ConfigApplyFailure" + ReasonLastApplyErrorAtDogus = "DoguApplyFailure" + ReasonLastApplyErrorAtConfig = "ConfigApplyFailure" ) var ( BlueprintConditions = []string{ConditionValid, ConditionExecutable, ConditionEcosystemHealthy, ConditionSelfUpgradeCompleted, ConditionCompleted, ConditionLastApplySucceeded} - notAllowedComponentActions = []Action{ActionDowngrade, ActionSwitchComponentNamespace} // ActionSwitchDoguNamespace is an exception and should be handled with the blueprint config. notAllowedDoguActions = []Action{ActionDowngrade, ActionSwitchDoguNamespace} ) @@ -57,8 +55,6 @@ var ( type BlueprintConfiguration struct { // IgnoreDoguHealth forces blueprint upgrades even if dogus are unhealthy IgnoreDoguHealth bool - // IgnoreComponentHealth forces blueprint upgrades even if components are unhealthy - IgnoreComponentHealth bool // AllowDoguNamespaceSwitch allows the blueprint upgrade to switch a dogus namespace AllowDoguNamespaceSwitch bool // Stopped lets the user test a blueprint run to check if all attributes of the blueprint are correct and avoid a result with a failure state. @@ -158,9 +154,8 @@ func (spec *BlueprintSpec) CalculateEffectiveBlueprint() error { effectiveConfig := spec.removeConfigForMaskedDogus() spec.EffectiveBlueprint = EffectiveBlueprint{ - Dogus: effectiveDogus, - Components: spec.Blueprint.Components, - Config: effectiveConfig, + Dogus: effectiveDogus, + Config: effectiveConfig, } validationError := spec.EffectiveBlueprint.validateOnlyConfigForDogusInBlueprint() if validationError != nil { @@ -251,7 +246,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( referencedSensitiveConfig map[common.DoguConfigKey]common.SensitiveDoguConfigValue, ) error { doguDiffs := determineDoguDiffs(spec.EffectiveBlueprint.Dogus, ecosystemState.InstalledDogus) - compDiffs := determineComponentDiffs(spec.EffectiveBlueprint.Components, ecosystemState.InstalledComponents) doguConfigDiffs, sensitiveDoguConfigDiffs, globalConfigDiffs := determineConfigDiffs( spec.EffectiveBlueprint.Config, ecosystemState.GlobalConfig, @@ -262,7 +256,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( spec.StateDiff = StateDiff{ DoguDiffs: doguDiffs, - ComponentDiffs: compDiffs, DoguConfigDiffs: doguConfigDiffs, SensitiveDoguConfigDiffs: sensitiveDoguConfigDiffs, GlobalConfigDiffs: globalConfigDiffs, @@ -272,9 +265,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( if spec.StateDiff.DoguDiffs.HasChanges() { spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) } - if spec.StateDiff.ComponentDiffs.HasChanges() { - spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) - } if spec.StateDiff.HasConfigChanges() { spec.Events = append(spec.Events, NewConfigDiffDeterminedEvent(spec.StateDiff)) } @@ -343,8 +333,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul if healthResult.AllHealthy() { event := EcosystemHealthyEvent{ - doguHealthIgnored: spec.Config.IgnoreDoguHealth, - componentHealthIgnored: spec.Config.IgnoreComponentHealth, + doguHealthIgnored: spec.Config.IgnoreDoguHealth, } conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, @@ -425,10 +414,6 @@ func (spec *BlueprintSpec) validateStateDiff() error { invalidBlueprintErrors = append(invalidBlueprintErrors, spec.validateDoguDiffActions(diff)...) } - for _, diff := range spec.StateDiff.ComponentDiffs { - invalidBlueprintErrors = append(invalidBlueprintErrors, spec.validateComponentDiffActions(diff)...) - } - return errors.Join(invalidBlueprintErrors...) } @@ -446,16 +431,6 @@ func (spec *BlueprintSpec) validateDoguDiffActions(diff DoguDiff) []error { }) } -func (spec *BlueprintSpec) validateComponentDiffActions(diff ComponentDiff) []error { - return util.Map(diff.NeededActions, func(action Action) error { - if slices.Contains(notAllowedComponentActions, action) { - return getActionNotAllowedError(action) - } - - return nil - }) -} - func getActionNotAllowedError(action Action) *InvalidBlueprintError { return &InvalidBlueprintError{ Message: fmt.Sprintf("action %q is not allowed", action), diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index f527b02f..32c89c1a 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -21,11 +21,6 @@ var version3212, _ = core.ParseVersion("3.2.1-2") var version3213, _ = core.ParseVersion("3.2.1-3") var subfolder = "subfolder" -const ( - testDistributionNamespace = "k8s" - testChangeDistributionNamespace = "k8s-testing" -) - var k8sNginxStatic = cescommons.QualifiedName{Namespace: "k8s", SimpleName: "nginx-static"} var officialNexus = cescommons.QualifiedName{ Namespace: "official", @@ -289,14 +284,12 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // given spec := BlueprintSpec{ EffectiveBlueprint: EffectiveBlueprint{ - Dogus: []Dogu{}, - Components: []Component{}, + Dogus: []Dogu{}, }, } clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, + InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, } // when @@ -304,8 +297,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then stateDiff := StateDiff{ - DoguDiffs: DoguDiffs{}, - ComponentDiffs: ComponentDiffs{}, + DoguDiffs: DoguDiffs{}, } assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) @@ -318,15 +310,13 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // given spec := BlueprintSpec{ EffectiveBlueprint: EffectiveBlueprint{ - Dogus: []Dogu{{Name: officialNexus, Version: &version3211}}, - Components: []Component{{Name: testComponentName, Version: compVersion3211}}, - Config: Config{Global: GlobalConfigEntries{{Key: "test", Value: &val1}}}, + Dogus: []Dogu{{Name: officialNexus, Version: &version3211}}, + Config: Config{Global: GlobalConfigEntries{{Key: "test", Value: &val1}}}, }, } clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, + InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, } // when @@ -347,19 +337,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { NeededActions: []Action{ActionInstall}, }, }, - ComponentDiffs: ComponentDiffs{ - { - Name: "my-component", - Actual: ComponentDiffState{ - Absent: true, - }, - Expected: ComponentDiffState{ - Namespace: "k8s", - Version: compVersion3211, - }, - NeededActions: []Action{ActionInstall}, - }, - }, GlobalConfigDiffs: GlobalConfigDiffs{ { Key: "test", @@ -375,10 +352,9 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) require.NoError(t, err) - require.Equal(t, 3, len(spec.Events)) + require.Equal(t, 2, len(spec.Events)) assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) - assert.Equal(t, newStateDiffComponentEvent(stateDiff.ComponentDiffs), spec.Events[1]) - assert.Equal(t, NewConfigDiffDeterminedEvent(stateDiff), spec.Events[2]) + assert.Equal(t, NewConfigDiffDeterminedEvent(stateDiff), spec.Events[1]) assert.Empty(t, cmp.Diff(stateDiff, spec.StateDiff)) }) @@ -407,7 +383,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { SimpleName: "name", }}, }, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, } // when @@ -443,7 +418,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { SimpleName: "name", }}, }, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, } // when @@ -454,74 +428,6 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { require.Error(t, err) assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") }) - - t.Run("should return error with not allowed component namespace switch action", func(t *testing.T) { - // given - spec := BlueprintSpec{ - EffectiveBlueprint: EffectiveBlueprint{ - Components: []Component{ - { - Name: common.QualifiedComponentName{ - Namespace: testChangeDistributionNamespace, - SimpleName: testComponentName.SimpleName, - }, - Version: compVersion3211, - }, - }, - }, - } - clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - testComponentName.SimpleName: { - Name: testComponentName, - ExpectedVersion: compVersion3211, - }, - }, - } - - // when - err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) - - // then - assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) - require.Error(t, err) - assert.ErrorContains(t, err, "action \"component namespace switch\" is not allowed") - }) - - t.Run("should return error with not allowed component downgrade action", func(t *testing.T) { - // given - spec := BlueprintSpec{ - EffectiveBlueprint: EffectiveBlueprint{ - Components: []Component{ - { - Name: common.QualifiedComponentName{ - Namespace: testComponentName.Namespace, - SimpleName: testComponentName.SimpleName, - }, - Version: compVersion3210, - }, - }, - }, - } - clusterState := ecosystem.EcosystemState{ - InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{}, - InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - testComponentName.SimpleName: { - Name: testComponentName, - ExpectedVersion: compVersion3211, - }, - }, - } - - // when - err := spec.DetermineStateDiff(clusterState, map[common.DoguConfigKey]common.SensitiveDoguConfigValue{}) - - // then - assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) - require.Error(t, err) - assert.ErrorContains(t, err, "action \"downgrade\" is not allowed") - }) } func TestBlueprintSpec_CompletePostProcessing(t *testing.T) { @@ -742,7 +648,7 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { condition := meta.FindStatusCondition(blueprint.Conditions, ConditionEcosystemHealthy) assert.Equal(t, metav1.ConditionTrue, condition.Status) assert.Equal(t, "Healthy", condition.Reason) - assert.Equal(t, "dogu health ignored: false; component health ignored: false", condition.Message) + assert.Equal(t, "dogu health ignored: false", condition.Message) }) t.Run("unhealthy", func(t *testing.T) { @@ -763,7 +669,6 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { assert.Equal(t, "Unhealthy", condition.Reason) assert.Contains(t, condition.Message, "ecosystem health:") assert.Contains(t, condition.Message, "1 dogu(s) are unhealthy: ldap") - assert.Contains(t, condition.Message, "0 component(s) are unhealthy:") }) t.Run("error given, condition Unknown", func(t *testing.T) { diff --git a/pkg/domain/blueprint_test.go b/pkg/domain/blueprint_test.go index ed9ac883..4a4e4773 100644 --- a/pkg/domain/blueprint_test.go +++ b/pkg/domain/blueprint_test.go @@ -3,9 +3,7 @@ package domain import ( "testing" - "github.com/Masterminds/semver/v3" "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" @@ -15,17 +13,6 @@ import ( var version3_2_1_4, _ = core.ParseVersion("3.2.1-4") -var ( - compVersion3210 = semver.MustParse("3.2.1-0") - compVersion3212 = semver.MustParse("3.2.1-2") - compVersion3213 = semver.MustParse("3.2.1-3") - - testComponentName1 = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "my-component1"} - testComponentName2 = common.QualifiedComponentName{Namespace: "official", SimpleName: "my-component2"} - testComponentName3 = common.QualifiedComponentName{Namespace: "testing", SimpleName: "my-component3"} - testComponentName4 = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "my-component4"} -) - func Test_validate_ok(t *testing.T) { dogus := []Dogu{ {Name: officialDogu1, Version: &version3_2_1_0, Absent: true}, @@ -34,13 +21,7 @@ func Test_validate_ok(t *testing.T) { {Name: officialNexus, Version: &version3213}, } - components := []Component{ - {Name: testComponentName1, Version: compVersion3210, Absent: true}, - {Name: testComponentName2, Absent: true}, - {Name: testComponentName3, Version: compVersion3212, Absent: false}, - {Name: testComponentName4, Version: compVersion3213}, - } - blueprint := Blueprint{Dogus: dogus, Components: components} + blueprint := Blueprint{Dogus: dogus} err := blueprint.Validate() @@ -52,13 +33,8 @@ func Test_validate_multipleErrors(t *testing.T) { dogus := []Dogu{ {Version: nil}, } - components := []Component{ - {Version: compVersion3212}, - {Name: testComponentName, Version: compVersion3212}, - } blueprint := Blueprint{ - Dogus: dogus, - Components: components, + Dogus: dogus, Config: Config{ Global: GlobalConfigEntries{ { @@ -75,8 +51,6 @@ func Test_validate_multipleErrors(t *testing.T) { assert.ErrorContains(t, err, "blueprint is invalid") assert.ErrorContains(t, err, "dogu is invalid") assert.ErrorContains(t, err, "dogu version must not be empty") - assert.ErrorContains(t, err, "component name must not be empty") - assert.ErrorContains(t, err, `namespace of component "" must not be empty`) assert.ErrorContains(t, err, `key for global config should not be empty`) } @@ -111,44 +85,6 @@ func Test_validateDogus_multipleErrors(t *testing.T) { assert.Contains(t, err.Error(), "dogu version must not be empty") } -func Test_validateComponents_ok(t *testing.T) { - components := []Component{ - { - Name: common.QualifiedComponentName{ - Namespace: "k8s", - SimpleName: "absent-component", - }, - Absent: true, - }, - { - Name: common.QualifiedComponentName{ - SimpleName: "present-component", - Namespace: "k8s", - }, - Version: compVersion3212, - Absent: false, - }, - } - blueprint := Blueprint{Components: components} - - err := blueprint.validateComponents() - - require.NoError(t, err) -} - -func Test_validateComponents_multipleErrors(t *testing.T) { - components := []Component{ - {Name: testComponentName}, - {Version: compVersion3212}, - } - blueprint := Blueprint{Components: components} - err := blueprint.validateComponents() - - require.Error(t, err) - assert.Contains(t, err.Error(), "component name must not be empty") - assert.Contains(t, err.Error(), `version of component "k8s/my-component" must not be empty`) -} - func Test_validateDoguUniqueness(t *testing.T) { dogus := []Dogu{ {Name: officialDogu1, Version: &version3_2_1_0, Absent: false}, @@ -166,43 +102,3 @@ func Test_validateDoguUniqueness(t *testing.T) { assert.Contains(t, err.Error(), "dogu1") assert.Contains(t, err.Error(), "dogu2") } - -func Test_validateComponentUniqueness(t *testing.T) { - components := []Component{ - { - Name: common.QualifiedComponentName{ - Namespace: "present", - SimpleName: "component1", - }, - Version: compVersion3210, - Absent: false, - }, - { - Name: common.QualifiedComponentName{ - Namespace: "present", - SimpleName: "component1", - }, - Version: compVersion3213}, - { - Name: common.QualifiedComponentName{ - Namespace: "present", - SimpleName: "component2", - }, - Version: compVersion3213}, - { - Name: common.QualifiedComponentName{ - Namespace: "present", - SimpleName: "component2", - }, - Version: compVersion3213}, - } - - blueprint := Blueprint{Components: components} - - err := blueprint.validateComponentUniqueness() - - require.Error(t, err) - assert.Contains(t, err.Error(), "there are duplicate components") - assert.Contains(t, err.Error(), "component1") - assert.Contains(t, err.Error(), "component2") -} diff --git a/pkg/domain/common/componentName.go b/pkg/domain/common/componentName.go deleted file mode 100644 index 59502251..00000000 --- a/pkg/domain/common/componentName.go +++ /dev/null @@ -1,52 +0,0 @@ -package common - -import ( - "errors" - "fmt" - "strings" -) - -type QualifiedComponentName struct { - Namespace ComponentNamespace - SimpleName SimpleComponentName -} - -type ComponentNamespace string -type SimpleComponentName string - -func NewQualifiedComponentName(namespace ComponentNamespace, simpleName SimpleComponentName) (QualifiedComponentName, error) { - componentName := QualifiedComponentName{Namespace: namespace, SimpleName: simpleName} - err := componentName.Validate() - if err != nil { - return QualifiedComponentName{}, err - } - return QualifiedComponentName{Namespace: namespace, SimpleName: simpleName}, nil -} - -func (componentName QualifiedComponentName) Validate() error { - var errorList []error - if componentName.Namespace == "" { - errorList = append(errorList, fmt.Errorf("namespace of component %q must not be empty", componentName.SimpleName)) - } - if componentName.SimpleName == "" { - errorList = append(errorList, fmt.Errorf("component name must not be empty: '%s/%s'", componentName.Namespace, componentName.SimpleName)) - } - return errors.Join(errorList...) -} - -// String returns the component name with namespace, e.g. k8s/k8s-dogu-operator -func (componentName QualifiedComponentName) String() string { - return fmt.Sprintf("%s/%s", componentName.Namespace, componentName.SimpleName) -} - -// QualifiedComponentNameFromString converts a qualified component as a string, e.g. "k8s/k8s-dogu-operator", to a dedicated QualifiedComponentName or raises an error if this is not possible. -func QualifiedComponentNameFromString(qualifiedName string) (QualifiedComponentName, error) { - splitName := strings.Split(qualifiedName, "/") - if len(splitName) != 2 { - return QualifiedComponentName{}, fmt.Errorf("component name needs to be in the form 'namespace/component' but is '%s'", qualifiedName) - } - return NewQualifiedComponentName( - ComponentNamespace(splitName[0]), - SimpleComponentName(splitName[1]), - ) -} diff --git a/pkg/domain/common/componentName_test.go b/pkg/domain/common/componentName_test.go deleted file mode 100644 index 55f5b981..00000000 --- a/pkg/domain/common/componentName_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package common - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestQualifiedComponentNameFromString(t *testing.T) { - tests := []struct { - test string - given string - expected QualifiedComponentName - wantErr assert.ErrorAssertionFunc - }{ - {test: "ok", given: "k8s/k8s-dogu-operator", expected: QualifiedComponentName{ComponentNamespace("k8s"), SimpleComponentName("k8s-dogu-operator")}, wantErr: assert.NoError}, - {test: "no ns", given: "k8s-dogu-operator", expected: QualifiedComponentName{}, wantErr: assert.Error}, - {test: "no name", given: "k8s/", expected: QualifiedComponentName{}, wantErr: assert.Error}, - {test: "double namespace", given: "k8s/test/k8s-dogu-operator", expected: QualifiedComponentName{}, wantErr: assert.Error}, - } - for _, tt := range tests { - t.Run(tt.test, func(t *testing.T) { - got, err := QualifiedComponentNameFromString(tt.given) - if !tt.wantErr(t, err, fmt.Sprintf("TestQualifiedComponentNameFromString(%v)", tt.given)) { - return - } - assert.Equalf(t, tt.expected, got, "TestQualifiedComponentNameFromString(%v)", tt.given) - }) - } -} diff --git a/pkg/domain/component.go b/pkg/domain/component.go deleted file mode 100644 index 7a56f55e..00000000 --- a/pkg/domain/component.go +++ /dev/null @@ -1,36 +0,0 @@ -package domain - -import ( - "errors" - "fmt" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" -) - -// Component represents a CES component (e.g. operators), its version, and the installation state in which it is supposed to be -// after a blueprint was applied. -type Component struct { - // Name defines the name and namespace of the component. Must not be empty. - Name common.QualifiedComponentName - // Version defines the version of the package that is to be installed. Must not be empty if the targetState is - // "present"; otherwise it is optional and is not going to be interpreted. - Version *semver.Version - // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. - Absent bool - // DeployConfig defines generic properties for the component. This field is optional. - DeployConfig ecosystem.DeployConfig -} - -// Validate checks if the component is semantically correct. -func (component *Component) Validate() error { - nameError := component.Name.Validate() - - var versionErr error - if !component.Absent && component.Version == nil { - versionErr = fmt.Errorf("version of component %q must not be empty", component.Name) - } - - return errors.Join(versionErr, nameError) -} diff --git a/pkg/domain/component_test.go b/pkg/domain/component_test.go deleted file mode 100644 index 53901ed8..00000000 --- a/pkg/domain/component_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package domain - -import ( - "testing" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/cesapp-lib/core" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - compVersion123 = semver.MustParse("1.2.3") - version123, _ = core.ParseVersion("1.2.3") -) - -func TestComponent_Validate(t *testing.T) { - t.Run("errorOnMissingComponentVersion", func(t *testing.T) { - component := Component{Name: testComponentName, Absent: false} - - err := component.Validate() - - require.Error(t, err) - assert.Contains(t, err.Error(), `version of component "k8s/my-component" must not be empty`) - }) - - t.Run("errorOnEmptyComponentVersion", func(t *testing.T) { - component := Component{Name: testComponentName, Version: nil, Absent: false} - - err := component.Validate() - - require.Error(t, err) - assert.Contains(t, err.Error(), "version of component \"k8s/my-component\" must not be empty") - }) - - t.Run("errorOnMissingComponentName", func(t *testing.T) { - component := Component{Version: compVersion123, Absent: false} - - err := component.Validate() - - require.Error(t, err) - assert.Contains(t, err.Error(), "component name must not be empty") - }) - - t.Run("errorOnEmptyComponentNamespace", func(t *testing.T) { - component := Component{Name: common.QualifiedComponentName{Namespace: "", SimpleName: "test"}, Version: compVersion123, Absent: false} - err := component.Validate() - - require.Error(t, err) - assert.Contains(t, err.Error(), "namespace of component \"test\" must not be empty") - }) - - t.Run("errorOnEmptyComponentName", func(t *testing.T) { - component := Component{Name: common.QualifiedComponentName{Namespace: "k8s"}, Version: compVersion123, Absent: false} - - err := component.Validate() - - require.Error(t, err) - assert.Contains(t, err.Error(), "component name must not be empty") - }) - - t.Run("emptyComponentStateDefaultsToPresent", func(t *testing.T) { - component := Component{Name: testComponentName, Version: compVersion123} - - err := component.Validate() - - require.NoError(t, err) - assert.False(t, component.Absent) - }) - - t.Run("missingComponentVersionOkayForAbsent", func(t *testing.T) { - component := Component{Name: testComponentName, Absent: true} - - err := component.Validate() - - require.NoError(t, err) - }) -} diff --git a/pkg/domain/dogu_test.go b/pkg/domain/dogu_test.go index 09dfa10e..24ddca04 100644 --- a/pkg/domain/dogu_test.go +++ b/pkg/domain/dogu_test.go @@ -3,12 +3,17 @@ package domain import ( "testing" + "github.com/cloudogu/cesapp-lib/core" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/resource" ) +var ( + version123, _ = core.ParseVersion("1.2.3-4") +) + func Test_TargetDogu_validate_errorOnMissingVersionForPresentDogu(t *testing.T) { dogu := Dogu{Name: officialDogu1, Absent: false} diff --git a/pkg/domain/ecosystem/EcosystemState.go b/pkg/domain/ecosystem/EcosystemState.go index 1c699d75..9afab76a 100644 --- a/pkg/domain/ecosystem/EcosystemState.go +++ b/pkg/domain/ecosystem/EcosystemState.go @@ -2,14 +2,12 @@ package ecosystem import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-registry-lib/config" ) // EcosystemState describes the actual state of the ecosystem, which is used to compare it with the expected state in the state diff. type EcosystemState struct { InstalledDogus map[cescommons.SimpleName]*DoguInstallation - InstalledComponents map[common.SimpleComponentName]*ComponentInstallation GlobalConfig config.GlobalConfig ConfigByDogu map[cescommons.SimpleName]config.DoguConfig SensitiveConfigByDogu map[cescommons.SimpleName]config.DoguConfig diff --git a/pkg/domain/ecosystem/componentHealth.go b/pkg/domain/ecosystem/componentHealth.go deleted file mode 100644 index 2fbabd99..00000000 --- a/pkg/domain/ecosystem/componentHealth.go +++ /dev/null @@ -1,69 +0,0 @@ -package ecosystem - -import ( - "fmt" - "slices" - "strings" - "time" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" -) - -type RequiredComponent struct { - Name common.SimpleComponentName -} - -type WaitConfig struct { - Timeout time.Duration - Interval time.Duration -} - -// ComponentHealthResult is a snapshot of all components' health states. -type ComponentHealthResult struct { - ComponentsByStatus map[HealthStatus][]common.SimpleComponentName -} - -func (result ComponentHealthResult) getUnhealthyComponents() []common.SimpleComponentName { - var unhealthyComponents []common.SimpleComponentName - for healthState, componentNames := range result.ComponentsByStatus { - if healthState != AvailableHealthStatus { - unhealthyComponents = append(unhealthyComponents, componentNames...) - } - } - return unhealthyComponents -} - -func (result ComponentHealthResult) String() string { - unhealthyComponents := util.Map(result.getUnhealthyComponents(), func(dogu common.SimpleComponentName) string { return string(dogu) }) - slices.Sort(unhealthyComponents) - return fmt.Sprintf("%d component(s) are unhealthy: %s", len(unhealthyComponents), strings.Join(unhealthyComponents, ", ")) -} - -// CalculateComponentHealthResult checks if all required components are installed, -// collects the health states from ComponentInstallation and creates a ComponentHealthResult. -func CalculateComponentHealthResult(installedComponents map[common.SimpleComponentName]*ComponentInstallation, requiredComponents []RequiredComponent) ComponentHealthResult { - result := ComponentHealthResult{ - ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{}, - } - for _, required := range requiredComponents { - _, installed := installedComponents[required.Name] - if !installed { - result.ComponentsByStatus[NotInstalledHealthStatus] = append(result.ComponentsByStatus[NotInstalledHealthStatus], required.Name) - } - } - for _, component := range installedComponents { - result.ComponentsByStatus[component.Health] = append(result.ComponentsByStatus[component.Health], component.Name.SimpleName) - } - return result -} - -func (result ComponentHealthResult) AllHealthy() bool { - for healthState, componentNames := range result.ComponentsByStatus { - if healthState != AvailableHealthStatus && len(componentNames) != 0 { - return false - } - } - return true -} diff --git a/pkg/domain/ecosystem/componentHealth_test.go b/pkg/domain/ecosystem/componentHealth_test.go deleted file mode 100644 index 1cc06d75..00000000 --- a/pkg/domain/ecosystem/componentHealth_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package ecosystem - -import ( - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/stretchr/testify/assert" - "testing" -) - -var ( - k8sK8sBlueprintOperator = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "k8s-blueprint-operator"} - k8sK8sDoguOperator = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "k8s-dogu-operator"} - k8sK8sLonghorn = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "k8s-longhorn"} - k8sK8sVelero = common.QualifiedComponentName{Namespace: "k8s", SimpleName: "k8s-velero"} -) - -func TestComponentHealthResult_String(t *testing.T) { - tests := []struct { - name string - healthStates map[HealthStatus][]common.SimpleComponentName - contains []string - notContains []string - }{ - { - name: "no components should result in 0 components unhealthy", - healthStates: map[HealthStatus][]common.SimpleComponentName{}, - contains: []string{"0 component(s) are unhealthy: "}, - }, - { - name: "only available components should result in 0 components unhealthy", - healthStates: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-dogu-operator"}, - }, - contains: []string{"0 component(s) are unhealthy: "}, - notContains: []string{"k8s-dogu-operator"}, - }, - { - name: "any components not available should be unhealthy", - healthStates: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator"}, - UnavailableHealthStatus: {"k8s-etcd", "k8s-dogu-operator"}, - NotInstalledHealthStatus: {"k8s-service-discovery"}, - "other": {"k8s-component-operator"}, - }, - contains: []string{ - "4 component(s) are unhealthy: ", - "k8s-etcd", - "k8s-dogu-operator", - "k8s-service-discovery", - "k8s-component-operator", - }, - notContains: []string{"ks8-blueprint-operator"}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := ComponentHealthResult{ComponentsByStatus: tt.healthStates} - actual := result.String() - for _, contains := range tt.contains { - assert.Contains(t, actual, contains) - } - for _, notContains := range tt.notContains { - assert.NotContains(t, actual, notContains) - } - }) - } -} - -func TestComponentHealthResult_AllHealthy(t *testing.T) { - tests := []struct { - name string - healthStates map[HealthStatus][]common.SimpleComponentName - want bool - }{ - { - name: "should be healthy if empty", - healthStates: map[HealthStatus][]common.SimpleComponentName{}, - want: true, - }, - { - name: "should be healthy if all are available", - healthStates: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator", "k8s-etcd", "k8s-service-discovery"}, - }, - want: true, - }, - { - name: "should not be healthy if one is not available", - healthStates: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator", "k8s-etcd", "k8s-service-discovery"}, - UnavailableHealthStatus: {"k8s-dogu-operator"}, - }, - want: false, - }, - { - name: "should not be healthy if multiple are not available", - healthStates: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator", "k8s-etcd", "k8s-service-discovery"}, - UnavailableHealthStatus: {"k8s-dogu-operator", "k8s-component-operator"}, - PendingHealthStatus: {"k8s-longhorn"}, - NotInstalledHealthStatus: {"k8s-backup-operator"}, - "other": {"k8s-velero"}, - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := ComponentHealthResult{ComponentsByStatus: tt.healthStates} - assert.Equalf(t, tt.want, result.AllHealthy(), "AllHealthy()") - }) - } -} - -func TestCalculateComponentHealthResult(t *testing.T) { - type args struct { - installedComponents map[common.SimpleComponentName]*ComponentInstallation - requiredComponents []RequiredComponent - } - tests := []struct { - name string - args args - want ComponentHealthResult - }{ - { - name: "result should be empty for no required and no installed components", - args: args{ - installedComponents: map[common.SimpleComponentName]*ComponentInstallation{}, - requiredComponents: []RequiredComponent{}, - }, - want: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{}}, - }, - { - name: "result should contain components that are not installed but required", - args: args{ - installedComponents: map[common.SimpleComponentName]*ComponentInstallation{}, - requiredComponents: []RequiredComponent{{Name: "k8s-etcd"}, {Name: "k8s-service-discovery"}}, - }, - want: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{ - NotInstalledHealthStatus: {"k8s-etcd", "k8s-service-discovery"}, - }}, - }, - { - name: "result should contain any components with their health state", - args: args{ - installedComponents: map[common.SimpleComponentName]*ComponentInstallation{ - "k8s-blueprint-operator": {Name: k8sK8sBlueprintOperator, Health: AvailableHealthStatus}, - "k8s-dogu-operator": {Name: k8sK8sDoguOperator, Health: UnavailableHealthStatus}, - "k8s-longhorn": {Name: k8sK8sLonghorn, Health: PendingHealthStatus}, - "k8s-velero": {Name: k8sK8sVelero, Health: "other"}, - }, - requiredComponents: []RequiredComponent{}, - }, - want: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator"}, - UnavailableHealthStatus: {"k8s-dogu-operator"}, - PendingHealthStatus: {"k8s-longhorn"}, - "other": {"k8s-velero"}, - }}, - }, - { - name: "result should contain any components with their health state and components that are not installed but required", - args: args{ - installedComponents: map[common.SimpleComponentName]*ComponentInstallation{ - "k8s-blueprint-operator": {Name: k8sK8sBlueprintOperator, Health: AvailableHealthStatus}, - "k8s-dogu-operator": {Name: k8sK8sDoguOperator, Health: UnavailableHealthStatus}, - "k8s-longhorn": {Name: k8sK8sLonghorn, Health: PendingHealthStatus}, - "k8s-velero": {Name: k8sK8sVelero, Health: "other"}, - }, - requiredComponents: []RequiredComponent{{Name: "k8s-etcd"}, {Name: "k8s-service-discovery"}}, - }, - want: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{ - AvailableHealthStatus: {"k8s-blueprint-operator"}, - UnavailableHealthStatus: {"k8s-dogu-operator"}, - PendingHealthStatus: {"k8s-longhorn"}, - "other": {"k8s-velero"}, - NotInstalledHealthStatus: {"k8s-etcd", "k8s-service-discovery"}, - }}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, CalculateComponentHealthResult(tt.args.installedComponents, tt.args.requiredComponents), "CalculateComponentHealthResult(%v, %v)", tt.args.installedComponents, tt.args.requiredComponents) - }) - } -} diff --git a/pkg/domain/ecosystem/componentInstallation.go b/pkg/domain/ecosystem/componentInstallation.go deleted file mode 100644 index 3e91e5e6..00000000 --- a/pkg/domain/ecosystem/componentInstallation.go +++ /dev/null @@ -1,69 +0,0 @@ -package ecosystem - -import ( - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" -) - -// ComponentInstallation represents an installed or to be installed component in the ecosystem. -type ComponentInstallation struct { - // Name identifies the component by simple dogu name and namespace, e.g 'k8s/k8s-dogu-operator'. - Name common.QualifiedComponentName - // ExpectedVersion is the version of the component which should be installed - ExpectedVersion *semver.Version - // ActualVersion is the version of the component which is actually installed - ActualVersion *semver.Version - // Status is the installation status of the component in the ecosystem - Status string - // Health is the current health status of the component in the ecosystem - Health HealthStatus - // PersistenceContext can hold generic values needed for persistence with repositories, e.g. version counters or transaction contexts. - // This field has a generic map type as the values within it highly depend on the used type of repository. - // This field should be ignored in the whole domain. - PersistenceContext map[string]interface{} - DeployConfig DeployConfig -} - -// TODO: Unused constants needed? -const ( - // ComponentStatusNotInstalled represents a status for a component that is not installed - ComponentStatusNotInstalled = "" - // ComponentStatusInstalling represents a status for a component that is currently being installed - ComponentStatusInstalling = "installing" - // ComponentStatusUpgrading represents a status for a component that is currently being upgraded - ComponentStatusUpgrading = "upgrading" - // ComponentStatusDeleting represents a status for a component that is currently being deleted - ComponentStatusDeleting = "deleting" - ComponentStatusIgnored = "ignored" - // ComponentStatusInstalled represents a status for a component that was successfully installed - ComponentStatusInstalled = "installed" - // ComponentStatusTryToInstall represents a status for a component that is not installed but its install process is in requeue loop. - ComponentStatusTryToInstall = "tryToInstall" - // ComponentStatusTryToUpgrade represents a status for a component that is installed but its actual upgrade process is in requeue loop. - // In this state the component can be healthy but the version in the spec is not installed. - ComponentStatusTryToUpgrade = "tryToUpgrade" - // ComponentStatusTryToDelete represents a status for a component that is installed but its delete process is in requeue loop. - // In this state the component can be healthy. - ComponentStatusTryToDelete = "tryToDelete" -) - -// InstallComponent is a factory for new ComponentInstallation's. -func InstallComponent( - componentName common.QualifiedComponentName, - expectedVersion *semver.Version, - deployConfig DeployConfig, -) *ComponentInstallation { - return &ComponentInstallation{ - Name: componentName, - ExpectedVersion: expectedVersion, - DeployConfig: deployConfig, - } -} - -func (ci *ComponentInstallation) Upgrade(expectedVersion *semver.Version) { - ci.ExpectedVersion = expectedVersion -} - -func (ci *ComponentInstallation) UpdateDeployConfig(deployConfig DeployConfig) { - ci.DeployConfig = deployConfig -} diff --git a/pkg/domain/ecosystem/componentInstallation_test.go b/pkg/domain/ecosystem/componentInstallation_test.go deleted file mode 100644 index 207bc910..00000000 --- a/pkg/domain/ecosystem/componentInstallation_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package ecosystem - -import ( - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/stretchr/testify/assert" - "testing" -) - -var testComponentName = k8sK8sDoguOperator - -var ( - testVersion1, _ = semver.NewVersion("1.0.0") - testVersion2, _ = semver.NewVersion("2.0.0") -) - -func TestInstallComponent(t *testing.T) { - type args struct { - componentName common.QualifiedComponentName - version *semver.Version - deployConfig DeployConfig - } - tests := []struct { - name string - args args - want *ComponentInstallation - }{ - { - name: "success", - args: args{ - componentName: testComponentName, - version: testVersion1, - deployConfig: map[string]interface{}{"deployNamespace": "longhorn-system"}, - }, - want: &ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: testVersion1, - DeployConfig: map[string]interface{}{"deployNamespace": "longhorn-system"}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, InstallComponent(tt.args.componentName, tt.args.version, tt.args.deployConfig), "InstallComponent(%v, %v, %v)", tt.args.componentName, tt.args.version) - }) - } -} - -func TestComponentInstallation_Upgrade(t *testing.T) { - type fields struct { - Name common.QualifiedComponentName - Version *semver.Version - Status string - PersistenceContext map[string]interface{} - Health HealthStatus - } - type args struct { - version *semver.Version - } - tests := []struct { - name string - fields fields - args args - }{ - { - name: "should set the version parameter in struct", - fields: fields{ - Version: testVersion1, - }, - args: args{ - version: testVersion2, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ci := &ComponentInstallation{ - Name: tt.fields.Name, - ExpectedVersion: tt.fields.Version, - Status: tt.fields.Status, - PersistenceContext: tt.fields.PersistenceContext, - Health: tt.fields.Health, - } - ci.Upgrade(tt.args.version) - assert.Equal(t, tt.args.version, ci.ExpectedVersion) - }) - } -} - -func TestComponentInstallation_UpdateDeployConfig(t *testing.T) { - t.Run("should set config", func(t *testing.T) { - // given - sut := ComponentInstallation{} - config := map[string]interface{}{"key": "value"} - - // when - sut.UpdateDeployConfig(config) - - // then - assert.Equal(t, DeployConfig(config), sut.DeployConfig) - }) -} diff --git a/pkg/domain/ecosystem/doguHealth_test.go b/pkg/domain/ecosystem/doguHealth_test.go index 8a87e2a2..9a655470 100644 --- a/pkg/domain/ecosystem/doguHealth_test.go +++ b/pkg/domain/ecosystem/doguHealth_test.go @@ -1,9 +1,10 @@ package ecosystem import ( + "testing" + cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/stretchr/testify/assert" - "testing" ) const ( @@ -74,12 +75,12 @@ func TestDoguHealthResult_String(t *testing.T) { notContains []string }{ { - name: "no dogus should result in 0 components unhealthy", + name: "no dogus should result in 0 dogus unhealthy", healthStates: map[HealthStatus][]cescommons.SimpleName{}, contains: []string{"0 dogu(s) are unhealthy: "}, }, { - name: "only available dogus should result in 0 components unhealthy", + name: "only available dogus should result in 0 dogus unhealthy", healthStates: map[HealthStatus][]cescommons.SimpleName{ AvailableHealthStatus: {"nginx-ingress"}, }, diff --git a/pkg/domain/ecosystem/ecosystemHealth.go b/pkg/domain/ecosystem/ecosystemHealth.go index 4517ae04..47d93bdb 100644 --- a/pkg/domain/ecosystem/ecosystemHealth.go +++ b/pkg/domain/ecosystem/ecosystemHealth.go @@ -7,23 +7,20 @@ import ( type HealthStatus = string const ( - PendingHealthStatus HealthStatus = "" - AvailableHealthStatus HealthStatus = "available" - UnavailableHealthStatus HealthStatus = "unavailable" - NotInstalledHealthStatus HealthStatus = "not installed" + PendingHealthStatus HealthStatus = "" + AvailableHealthStatus HealthStatus = "available" + UnavailableHealthStatus HealthStatus = "unavailable" ) // HealthResult is a snapshot of the health states of all relevant parts of the running ecosystem. type HealthResult struct { - DoguHealth DoguHealthResult - ComponentHealth ComponentHealthResult + DoguHealth DoguHealthResult } func (result HealthResult) String() string { - return fmt.Sprintf("ecosystem health:\n %s\n %s", result.DoguHealth, result.ComponentHealth) + return fmt.Sprintf("ecosystem health:\n %s", result.DoguHealth) } func (result HealthResult) AllHealthy() bool { - return result.DoguHealth.AllHealthy() && - result.ComponentHealth.AllHealthy() + return result.DoguHealth.AllHealthy() } diff --git a/pkg/domain/ecosystem/ecosystemHealth_test.go b/pkg/domain/ecosystem/ecosystemHealth_test.go index 3a908ca6..32fc315b 100644 --- a/pkg/domain/ecosystem/ecosystemHealth_test.go +++ b/pkg/domain/ecosystem/ecosystemHealth_test.go @@ -4,14 +4,12 @@ import ( "testing" cescommons "github.com/cloudogu/ces-commons-lib/dogu" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/stretchr/testify/assert" ) func TestHealthResult_String(t *testing.T) { type fields struct { - DoguHealth DoguHealthResult - ComponentHealth ComponentHealthResult + DoguHealth DoguHealthResult } tests := []struct { name string @@ -19,27 +17,24 @@ func TestHealthResult_String(t *testing.T) { want string }{ { - name: "should print dogu and component health results with no unhealthy", + name: "should print dogu health results with no unhealthy", fields: fields{ - DoguHealth: DoguHealthResult{}, - ComponentHealth: ComponentHealthResult{}, + DoguHealth: DoguHealthResult{}, }, - want: "ecosystem health:\n 0 dogu(s) are unhealthy: \n 0 component(s) are unhealthy: ", + want: "ecosystem health:\n 0 dogu(s) are unhealthy: ", }, { - name: "should print dogu and component health results with unhealthy", + name: "should print dogu health results with unhealthy", fields: fields{ - DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, - ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{UnavailableHealthStatus: {"k8s-etcd"}}}, + DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, }, - want: "ecosystem health:\n 1 dogu(s) are unhealthy: nginx-ingress\n 1 component(s) are unhealthy: k8s-etcd", + want: "ecosystem health:\n 1 dogu(s) are unhealthy: nginx-ingress", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := HealthResult{ - DoguHealth: tt.fields.DoguHealth, - ComponentHealth: tt.fields.ComponentHealth, + DoguHealth: tt.fields.DoguHealth, } assert.Equalf(t, tt.want, result.String(), "String()") }) @@ -48,8 +43,7 @@ func TestHealthResult_String(t *testing.T) { func TestHealthResult_AllHealthy(t *testing.T) { type fields struct { - DoguHealth DoguHealthResult - ComponentHealth ComponentHealthResult + DoguHealth DoguHealthResult } tests := []struct { name string @@ -57,34 +51,16 @@ func TestHealthResult_AllHealthy(t *testing.T) { want bool }{ { - name: "should be healthy if no dogus or components are unavailable", + name: "should be healthy if no dogus are unavailable", fields: fields{ - DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{AvailableHealthStatus: {"nginx-ingress"}}}, - ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{AvailableHealthStatus: {"k8s-etcd"}}}, + DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{AvailableHealthStatus: {"nginx-ingress"}}}, }, want: true, }, { name: "should be unhealthy if dogus are unavailable", fields: fields{ - DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, - ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{AvailableHealthStatus: {"k8s-etcd"}}}, - }, - want: false, - }, - { - name: "should be unhealthy if components are unavailable", - fields: fields{ - DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{AvailableHealthStatus: {"nginx-ingress"}}}, - ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{UnavailableHealthStatus: {"k8s-etcd"}}}, - }, - want: false, - }, - { - name: "should be unhealthy if dogus and components are unavailable", - fields: fields{ - DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, - ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{UnavailableHealthStatus: {"k8s-etcd"}}}, + DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, }, want: false, }, @@ -92,8 +68,7 @@ func TestHealthResult_AllHealthy(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := HealthResult{ - DoguHealth: tt.fields.DoguHealth, - ComponentHealth: tt.fields.ComponentHealth, + DoguHealth: tt.fields.DoguHealth, } assert.Equalf(t, tt.want, result.AllHealthy(), "AllHealthy()") }) diff --git a/pkg/domain/effectiveBlueprint.go b/pkg/domain/effectiveBlueprint.go index 6f02530a..025565d4 100644 --- a/pkg/domain/effectiveBlueprint.go +++ b/pkg/domain/effectiveBlueprint.go @@ -15,9 +15,6 @@ type EffectiveBlueprint struct { // Dogus contains a set of exact dogu versions which should be present or absent in the CES instance after which this // blueprint was applied. Optional. Dogus []Dogu - // Components contains a set of exact components versions which should be present or absent in the CES instance after which - // this blueprint was applied. Optional. - Components []Component // Config contains all config entries to set via blueprint. Optional. Config Config } diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index d85816f5..a7d09d44 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -32,9 +32,8 @@ type UnhealthyEcosystemError struct { } func (e *UnhealthyEcosystemError) Error() string { - unhealthyComponentsText := e.healthResult.ComponentHealth.String() unhealthyDogusText := e.healthResult.DoguHealth.String() - combinedMessage := fmt.Sprintf("%s - %s - %s", e.Message, unhealthyDogusText, unhealthyComponentsText) + combinedMessage := fmt.Sprintf("%s - %s", e.Message, unhealthyDogusText) if e.WrappedError != nil { return fmt.Errorf("%s: %w", combinedMessage, e.WrappedError).Error() } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index e5c7cf9f..0b4e958d 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -93,36 +93,6 @@ func (e ConfigDiffDeterminedEvent) generateConfigChangeCounter() string { return fmt.Sprintf("%d changes (%s)", actionsCounter, strings.Join(stringPerAction, ", ")) } -// StateDiffComponentDeterminedEvent provides event information over detected changes regarding components. -type StateDiffComponentDeterminedEvent struct { - componentDiffs []ComponentDiff -} - -func newStateDiffComponentEvent(componentDiffs ComponentDiffs) StateDiffComponentDeterminedEvent { - return StateDiffComponentDeterminedEvent{ - componentDiffs: componentDiffs, - } -} - -// Name contains the StateDiffComponentDeterminedEvent display name. -func (s StateDiffComponentDeterminedEvent) Name() string { - return "StateDiffComponentDetermined" -} - -// Message contains the StateDiffComponentDeterminedEvent's statistics message. -func (s StateDiffComponentDeterminedEvent) Message() string { - var amountActions = map[Action]int{} - for _, diff := range s.componentDiffs { - for _, action := range diff.NeededActions { - amountActions[action]++ - } - } - - message, amount := getActionAmountMessage(amountActions) - - return fmt.Sprintf("component state diff determined: %d actions (%s)", amount, message) -} - func getActionAmountMessage(amountActions map[Action]int) (message string, totalAmount int) { var messages []string for action, amount := range amountActions { @@ -171,8 +141,7 @@ func (s StateDiffDoguDeterminedEvent) Message() string { } type EcosystemHealthyEvent struct { - doguHealthIgnored bool - componentHealthIgnored bool + doguHealthIgnored bool } func (d EcosystemHealthyEvent) Name() string { @@ -180,7 +149,7 @@ func (d EcosystemHealthyEvent) Name() string { } func (d EcosystemHealthyEvent) Message() string { - return fmt.Sprintf("dogu health ignored: %t; component health ignored: %t", d.doguHealthIgnored, d.componentHealthIgnored) + return fmt.Sprintf("dogu health ignored: %t", d.doguHealthIgnored) } type EcosystemUnhealthyEvent struct { @@ -205,29 +174,6 @@ func (b BlueprintDryRunEvent) Message() string { return "Executed blueprint in dry run mode. Remove flag to continue" } -type ComponentsAppliedEvent struct { - Diffs ComponentDiffs -} - -func (e ComponentsAppliedEvent) Name() string { - return "ComponentsApplied" -} - -func (e ComponentsAppliedEvent) Message() string { - var buffer bytes.Buffer - buffer.WriteString("components applied: ") - var details []string - for _, diff := range e.Diffs { - actionsAsStrings := util.Map(diff.NeededActions, func(action Action) string { - return string(action) - }) - actions := strings.Join(actionsAsStrings, ", ") - details = append(details, fmt.Sprintf("%q: [%v]", diff.Name, actions)) - } - buffer.WriteString(strings.Join(details, ", ")) - return buffer.String() -} - type DogusAppliedEvent struct { Diffs DoguDiffs } diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index ba6a9ddd..69f1fe13 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -32,19 +32,13 @@ func TestEvents(t *testing.T) { name: "ecosystem healthy", event: EcosystemHealthyEvent{}, expectedName: "EcosystemHealthy", - expectedMessage: "dogu health ignored: false; component health ignored: false", + expectedMessage: "dogu health ignored: false", }, { name: "ignore dogu health", event: EcosystemHealthyEvent{doguHealthIgnored: true}, expectedName: "EcosystemHealthy", - expectedMessage: "dogu health ignored: true; component health ignored: false", - }, - { - name: "ignore component health", - event: EcosystemHealthyEvent{componentHealthIgnored: true}, - expectedName: "EcosystemHealthy", - expectedMessage: "dogu health ignored: false; component health ignored: true", + expectedMessage: "dogu health ignored: true", }, { name: "ecosystem unhealthy upfront", @@ -60,7 +54,7 @@ func TestEvents(t *testing.T) { }, }, expectedName: "EcosystemUnhealthy", - expectedMessage: "ecosystem health:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", + expectedMessage: "ecosystem health:\n 2 dogu(s) are unhealthy: admin, ldap", }, { name: "dogu state diff determined", @@ -77,21 +71,6 @@ func TestEvents(t *testing.T) { expectedName: "StateDiffDoguDetermined", expectedMessage: "dogu state diff determined: 11 actions (\"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update resource minimum volume size\": 1, \"update reverse proxy\": 3, \"upgrade\": 1)", }, - { - name: "component state diff determined", - event: newStateDiffComponentEvent( - ComponentDiffs{ - {NeededActions: []Action{ActionInstall}}, - {NeededActions: []Action{ActionUninstall}}, - {NeededActions: []Action{ActionInstall}}, - {NeededActions: []Action{ActionUninstall}}, - {NeededActions: []Action{ActionUninstall}}, - {NeededActions: []Action{ActionUpgrade, ActionUpdateComponentDeployConfig, ActionSwitchComponentNamespace}}, - {NeededActions: []Action{ActionDowngrade}}, - }), - expectedName: "StateDiffComponentDetermined", - expectedMessage: "component state diff determined: 9 actions (\"component namespace switch\": 1, \"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update component package config\": 1, \"upgrade\": 1)", - }, { name: "config diff determined", event: ConfigDiffDeterminedEvent{ @@ -127,21 +106,6 @@ func TestEvents(t *testing.T) { expectedName: "MissingConfigReferences", expectedMessage: assert.AnError.Error(), }, - { - name: "components applied", - event: ComponentsAppliedEvent{ - Diffs: ComponentDiffs{ - { - Name: "dogu-operator", - NeededActions: []Action{ - ActionUpgrade, ActionSwitchComponentNamespace, - }, - }, - }, - }, - expectedName: "ComponentsApplied", - expectedMessage: "components applied: \"dogu-operator\": [upgrade, component namespace switch]", - }, { name: "dogus applied", event: DogusAppliedEvent{ diff --git a/pkg/domain/stateDiff.go b/pkg/domain/stateDiff.go index fc9b777e..17024339 100644 --- a/pkg/domain/stateDiff.go +++ b/pkg/domain/stateDiff.go @@ -8,7 +8,6 @@ import ( // If there is a state in the ecosystem, which is not represented in the effective blueprint, then the expected state is the actual state. type StateDiff struct { DoguDiffs DoguDiffs - ComponentDiffs ComponentDiffs DoguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs SensitiveDoguConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs GlobalConfigDiffs GlobalConfigDiffs @@ -27,8 +26,6 @@ const ( ActionUpdateDoguProxyRewriteTarget = "update proxy rewrite target" ActionUpdateDoguProxyAdditionalConfig = "update proxy additional config" ActionUpdateDoguResourceMinVolumeSize = "update resource minimum volume size" - ActionSwitchComponentNamespace = "component namespace switch" - ActionUpdateComponentDeployConfig = "update component package config" ActionUpdateAdditionalMounts = "update additional mounts" ) @@ -38,7 +35,6 @@ func (a Action) IsDoguProxyAction() bool { func (diff StateDiff) HasChanges() bool { return diff.DoguDiffs.HasChanges() || - diff.ComponentDiffs.HasChanges() || diff.HasConfigChanges() } diff --git a/pkg/domain/stateDiffComponent.go b/pkg/domain/stateDiffComponent.go deleted file mode 100644 index f07a7082..00000000 --- a/pkg/domain/stateDiffComponent.go +++ /dev/null @@ -1,238 +0,0 @@ -package domain - -import ( - "fmt" - "reflect" - - "github.com/Masterminds/semver/v3" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - "golang.org/x/exp/maps" -) - -// ComponentDiffs contains the differences for all expected Components to the current ecosystem.ComponentInstallations. -type ComponentDiffs []ComponentDiff - -// GetComponentDiffByName returns the diff for the given component name or an empty struct if it was not found. -func (diffs ComponentDiffs) GetComponentDiffByName(name common.SimpleComponentName) ComponentDiff { - for _, diff := range diffs { - if diff.Name == name { - return diff - } - } - return ComponentDiff{} -} - -func (diffs ComponentDiffs) HasChanges() bool { - for _, diff := range diffs { - if diff.HasChanges() { - return true - } - } - return false -} - -// ComponentDiff represents the Diff for a single expected Component to the current ecosystem.ComponentInstallation. -type ComponentDiff struct { - // Name contains the component's name. - Name common.SimpleComponentName - // Actual contains that state of a component how it is currently found in the system. - Actual ComponentDiffState - // Expected contains that desired state of a component how it is supposed to be. - Expected ComponentDiffState - // NeededActions hints how the component should be handled by the application change automaton in order to reconcile - // differences between Actual and Expected in the current system. - NeededActions []Action -} - -// ComponentDiffState contains all fields to make a diff for components in respect to another ComponentDiffState. -type ComponentDiffState struct { - // Namespace is part of the address under which the component will be obtained. This namespace must NOT - // to be confused with the K8s cluster namespace. - Namespace common.ComponentNamespace - // Version contains the component's version. - Version *semver.Version - // Absent defines if the dogu should be absent in the ecosystem. Defaults to false. - Absent bool - // DeployConfig contains generic properties for the component. - DeployConfig ecosystem.DeployConfig -} - -// IsExpectedVersion checks if the given version es equal to the expected version -func (diff ComponentDiff) IsExpectedVersion(actualVersion *semver.Version) bool { - // expected is nil if the component is not in the blueprint, therefore no upgrade needs to happen - if diff.Expected.Version == nil { - return true - } - // actualVersion is nil if there is no component or no actual version in it yet. - if actualVersion == nil { - return false - } - return diff.Expected.Version.Equal(actualVersion) -} - -func (diff ComponentDiff) HasChanges() bool { - return len(diff.NeededActions) != 0 -} - -// String returns a string representation of the ComponentDiff. -func (diff *ComponentDiff) String() string { - return fmt.Sprintf( - "{Name: %q, Actual: %s, Expected: %s, NeededActions: %q}", - diff.Name, - diff.Actual.String(), - diff.Expected.String(), - diff.NeededActions, - ) -} - -// String returns a string representation of the ComponentDiffState. -func (diff *ComponentDiffState) String() string { - return fmt.Sprintf( - "{Namespace: %q, Version: %q, Absent: %t}", - diff.Namespace, - diff.getSafeVersionString(), - diff.Absent, - ) -} - -func (diff *ComponentDiffState) getSafeVersionString() string { - if diff.Version != nil { - return diff.Version.String() - } else { - return "" - } -} - -// determineComponentDiffs creates ComponentDiffs for all components in the blueprint and all installed components as well. -func determineComponentDiffs(blueprintComponents []Component, installedComponents map[common.SimpleComponentName]*ecosystem.ComponentInstallation) []ComponentDiff { - var componentDiffs = map[common.SimpleComponentName]ComponentDiff{} - for _, blueprintComponent := range blueprintComponents { - installedComponent := installedComponents[blueprintComponent.Name.SimpleName] - compDiff := determineComponentDiff(&blueprintComponent, installedComponent) - // only add changes to diff - if compDiff != nil { - componentDiffs[blueprintComponent.Name.SimpleName] = *compDiff - } - } - - for _, installedComponent := range installedComponents { - _, found := findComponentByName(blueprintComponents, installedComponent.Name.SimpleName) - // Only create ComponentDiff if the installed component is not found in the blueprint. - // If the installed component is in blueprint the ComponentDiff was already determined above. - if !found { - compDiff := determineComponentDiff(nil, installedComponent) - // only add changes to diff - if compDiff != nil { - componentDiffs[installedComponent.Name.SimpleName] = *compDiff - } - } - } - return maps.Values(componentDiffs) -} - -// determineComponentDiff creates a ComponentDiff out of a Component from the blueprint and the ecosystem.ComponentInstallation in the ecosystem. -// If the Component is nil (was not in the blueprint), the actual state is also the expected state. -// If the installedComponent is nil, it is considered to be not installed currently. -func determineComponentDiff(blueprintComponent *Component, installedComponent *ecosystem.ComponentInstallation) *ComponentDiff { - var expectedState, actualState ComponentDiffState - componentName := common.SimpleComponentName("") // either blueprintComponent or installedComponent could be nil - - if installedComponent == nil { - actualState = ComponentDiffState{ - Absent: true, - } - } else { - componentName = installedComponent.Name.SimpleName - actualState = ComponentDiffState{ - Namespace: installedComponent.Name.Namespace, - Version: installedComponent.ExpectedVersion, - DeployConfig: installedComponent.DeployConfig, - } - } - - if blueprintComponent == nil { - expectedState = actualState - } else { - componentName = blueprintComponent.Name.SimpleName - expectedState = ComponentDiffState{ - Namespace: blueprintComponent.Name.Namespace, - Version: blueprintComponent.Version, - Absent: blueprintComponent.Absent, - DeployConfig: blueprintComponent.DeployConfig, - } - } - - nextActions := getComponentActions(expectedState, actualState) - - if len(nextActions) == 0 { - return nil - } - - return &ComponentDiff{ - Name: componentName, - Expected: expectedState, - Actual: actualState, - NeededActions: nextActions, - } -} - -func findComponentByName(components []Component, name common.SimpleComponentName) (Component, bool) { - for _, component := range components { - if component.Name.SimpleName == name { - return component, true - } - } - return Component{}, false -} - -func getComponentActions(expected ComponentDiffState, actual ComponentDiffState) []Action { - if expected.Absent == actual.Absent { - return decideOnEqualState(expected, actual) - } - - return decideOnDifferentState(expected) -} - -func decideOnEqualState(expected ComponentDiffState, actual ComponentDiffState) []Action { - var neededActions []Action - - if expected.Absent { - return neededActions - } else { - return getActionsForEqualPresentState(expected, actual) - } -} - -func getActionsForEqualPresentState(expected ComponentDiffState, actual ComponentDiffState) []Action { - var neededActions []Action - - if expected.Namespace != actual.Namespace { - neededActions = append(neededActions, ActionSwitchComponentNamespace) - } - - if !reflect.DeepEqual(expected.DeployConfig, actual.DeployConfig) { - // Do update only if any DeployConfig contains data. - // A nil DeployConfig and an empty DeployConfig are not deeply equal. But in this case we do not want to update the DeployConfig. - if len(expected.DeployConfig) != 0 || len(actual.DeployConfig) != 0 { - neededActions = append(neededActions, ActionUpdateComponentDeployConfig) - } - } - - if expected.Version.GreaterThan(actual.Version) { - neededActions = append(neededActions, ActionUpgrade) - } else if actual.Version.GreaterThan(expected.Version) { - neededActions = append(neededActions, ActionDowngrade) - } - - return neededActions -} - -func decideOnDifferentState(expected ComponentDiffState) []Action { - // at this place, the actual state is always the opposite to the expected state so just follow the expected state. - if expected.Absent { - return []Action{ActionUninstall} - } else { - return []Action{ActionInstall} - } -} diff --git a/pkg/domain/stateDiffComponent_test.go b/pkg/domain/stateDiffComponent_test.go deleted file mode 100644 index c91721c6..00000000 --- a/pkg/domain/stateDiffComponent_test.go +++ /dev/null @@ -1,470 +0,0 @@ -package domain - -import ( - "testing" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - - "github.com/go-logr/logr" - "github.com/stretchr/testify/assert" - - "github.com/Masterminds/semver/v3" - - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" -) - -var ( - testComponentName = common.QualifiedComponentName{ - Namespace: "k8s", - SimpleName: "my-component", - } - blueprintOperatorSimpleName = common.SimpleComponentName("k8s-blueprint-operator") -) - -var ( - compVersion3211 = semver.MustParse("3.2.1-1") -) - -func Test_determineComponentDiff(t *testing.T) { - type args struct { - logger logr.Logger - blueprintComponent *Component - installedComponent *ecosystem.ComponentInstallation - } - tests := []struct { - name string - args args - want *ComponentDiff - }{ - { - name: "equal, no action", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, false, nil), - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: nil, - }, - { - name: "install", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, false, nil), - installedComponent: nil, - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState("", nil, true, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - NeededActions: []Action{ActionInstall}, - }, - }, - { - name: "uninstall", - args: args{ - blueprintComponent: mockTargetComponent(nil, true, nil), - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, nil, true, nil), - NeededActions: []Action{ActionUninstall}, - }, - }, - { - name: "upgrade", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3212, false, nil), - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), - NeededActions: []Action{ActionUpgrade}, - }, - }, - { - name: "update package config", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), - NeededActions: []Action{ActionUpdateComponentDeployConfig}, - }, - }, - { - name: "update package config and upgrade", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, map[string]interface{}{"deployNamespace": "k8s-longhorn"}), - NeededActions: []Action{ActionUpdateComponentDeployConfig, ActionUpgrade}, - }, - }, - { - name: "downgrade", - args: args{ - blueprintComponent: mockTargetComponent(compVersion3211, false, nil), - installedComponent: mockComponentInstallation(compVersion3212), - }, - want: &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: mockComponentDiffState(testDistributionNamespace, compVersion3212, false, nil), - Expected: mockComponentDiffState(testDistributionNamespace, compVersion3211, false, nil), - NeededActions: []Action{ActionDowngrade}, - }, - }, - { - name: "ignore present component, no action", - args: args{ - blueprintComponent: nil, - installedComponent: mockComponentInstallation(compVersion3211), - }, - want: nil, - }, - { - name: "should stay absent, no action", // this is empty set comparison is weird and should basically not occur - args: args{ - blueprintComponent: nil, - installedComponent: nil, - }, - want: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - compDiff := determineComponentDiff(tt.args.blueprintComponent, tt.args.installedComponent) - assert.Equalf(t, tt.want, compDiff, "determineComponentDiff(%v, %v, %v)", tt.args.logger, tt.args.blueprintComponent, tt.args.installedComponent) - }) - } -} - -func TestComponentDiff_String(t *testing.T) { - actual := ComponentDiffState{ - Version: compVersion3211, - Absent: false, - } - expected := ComponentDiffState{ - Version: compVersion3212, - Absent: false, - } - diff := &ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: actual, - Expected: expected, - NeededActions: []Action{ActionInstall}, - } - - assert.Equal(t, "{"+ - "Name: \"my-component\", "+ - "Actual: {Namespace: \"\", Version: \"3.2.1-1\", Absent: false}, "+ - "Expected: {Namespace: \"\", Version: \"3.2.1-2\", Absent: false}, "+ - "NeededActions: [\"install\"]"+ - "}", diff.String()) -} - -func TestComponentDiffState_String(t *testing.T) { - diff := &ComponentDiffState{ - Namespace: "k8s", - Version: compVersion3211, - Absent: false, - } - - assert.Equal(t, `{Namespace: "k8s", Version: "3.2.1-1", Absent: false}`, diff.String()) -} - -func mockTargetComponent(version *semver.Version, absent bool, deployConfig ecosystem.DeployConfig) *Component { - return &Component{ - Name: testComponentName, - Version: version, - Absent: absent, - DeployConfig: deployConfig, - } -} - -func mockComponentInstallation(version *semver.Version) *ecosystem.ComponentInstallation { - return &ecosystem.ComponentInstallation{ - Name: testComponentName, - ExpectedVersion: version, - } -} - -func mockComponentDiffState(namespace common.ComponentNamespace, version *semver.Version, absent bool, deployConfig ecosystem.DeployConfig) ComponentDiffState { - return ComponentDiffState{ - Namespace: namespace, - Version: version, - Absent: absent, - DeployConfig: deployConfig, - } -} - -func Test_determineComponentDiffs(t *testing.T) { - type args struct { - logger logr.Logger - blueprintComponents []Component - installedComponents map[common.SimpleComponentName]*ecosystem.ComponentInstallation - } - tests := []struct { - name string - args args - want []ComponentDiff - }{ - { - name: "no components", - args: args{ - blueprintComponents: nil, - installedComponents: nil, - }, - want: []ComponentDiff{}, - }, - { - name: "a not installed component in the blueprint", - args: args{ - blueprintComponents: []Component{ - { - Name: testComponentName, - Version: compVersion3211, - Absent: false, - }, - }, - installedComponents: nil, - }, - want: []ComponentDiff{ - { - Name: testComponentName.SimpleName, - Actual: ComponentDiffState{ - Absent: true, - }, - Expected: ComponentDiffState{ - Namespace: testComponentName.Namespace, - Version: compVersion3211, - Absent: false, - }, - NeededActions: []Action{ActionInstall}, - }, - }, - }, - { - name: "an installed component which is not in the blueprint", - args: args{ - blueprintComponents: nil, - installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - testComponentName.SimpleName: { - Name: testComponentName, - ExpectedVersion: compVersion3211, - }, - }, - }, - want: []ComponentDiff{}, - }, - { - name: "determine distribution namespace switch", - args: args{ - blueprintComponents: []Component{ - { - Name: common.QualifiedComponentName{Namespace: "k8s-testing", SimpleName: "my-component"}, - Version: compVersion3211, - Absent: false, - }, - }, - installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - testComponentName.SimpleName: { - Name: common.QualifiedComponentName{Namespace: "k8s", SimpleName: "my-component"}, - ExpectedVersion: compVersion3211, - }, - }, - }, - want: []ComponentDiff{ - { - Name: testComponentName.SimpleName, - Actual: ComponentDiffState{ - Version: compVersion3211, - Absent: false, - Namespace: testDistributionNamespace, - }, - Expected: ComponentDiffState{ - Version: compVersion3211, - Absent: false, - Namespace: testChangeDistributionNamespace, - }, - NeededActions: []Action{ActionSwitchComponentNamespace}, - }, - }, - }, - { - name: "determine upgrade for an installed component which is also in the blueprint", - args: args{ - blueprintComponents: []Component{ - { - Name: testComponentName, - Version: compVersion3212, - Absent: false, - }, - }, - installedComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{ - testComponentName.SimpleName: { - Name: testComponentName, - ExpectedVersion: compVersion3211, - }, - }, - }, - want: []ComponentDiff{ - { - Name: testComponentName.SimpleName, - Actual: ComponentDiffState{ - Version: compVersion3211, - Namespace: testComponentName.Namespace, - Absent: false, - }, - Expected: ComponentDiffState{ - Version: compVersion3212, - Namespace: testComponentName.Namespace, - Absent: false, - }, - NeededActions: []Action{ActionUpgrade}, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - compDiffs := determineComponentDiffs(tt.args.blueprintComponents, tt.args.installedComponents) - assert.Equalf(t, tt.want, compDiffs, "determineComponentDiffs(%v, %v, %v)", tt.args.logger, tt.args.blueprintComponents, tt.args.installedComponents) - }) - } -} - -func TestComponentDiffState_getSafeVersionString(t *testing.T) { - version1, _ := semver.NewVersion("1.0.0") - - type fields struct { - Namespace common.ComponentNamespace - Version *semver.Version - } - tests := []struct { - name string - fields fields - want string - }{ - { - name: "success", - fields: fields{Version: version1}, - want: "1.0.0", - }, - { - name: "should return empty string and no panic on nil version", - fields: fields{Version: nil}, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - diff := &ComponentDiffState{ - Namespace: tt.fields.Namespace, - Version: tt.fields.Version, - } - assert.Equalf(t, tt.want, diff.getSafeVersionString(), "getSafeVersionString()") - }) - } -} - -func TestComponentDiffs_GetComponentDiffByName(t *testing.T) { - t.Run("find diff", func(t *testing.T) { - blueprintOpDiff := ComponentDiff{ - Name: blueprintOperatorSimpleName, - Actual: ComponentDiffState{}, - Expected: ComponentDiffState{}, - NeededActions: []Action{ActionUninstall}, - } - diffs := ComponentDiffs{ - blueprintOpDiff, - ComponentDiff{ - Name: testComponentName.SimpleName, - Actual: ComponentDiffState{}, - Expected: ComponentDiffState{}, - NeededActions: []Action{ActionUninstall}, - }, - } - - foundDiff := diffs.GetComponentDiffByName(blueprintOperatorSimpleName) - - assert.Equal(t, blueprintOpDiff, foundDiff) - }) - - t.Run("don't find diff", func(t *testing.T) { - diffs := ComponentDiffs{} - - foundDiff := diffs.GetComponentDiffByName(blueprintOperatorSimpleName) - - assert.Equal(t, ComponentDiff{}, foundDiff) - }) -} - -func TestComponentDiff_HasChanges(t *testing.T) { - t.Run("no change", func(t *testing.T) { - diff := ComponentDiff{ - NeededActions: []Action{}, - } - assert.False(t, diff.HasChanges()) - }) - t.Run("change for any", func(t *testing.T) { - diff := ComponentDiff{ - NeededActions: []Action{ActionInstall}, - } - assert.True(t, diff.HasChanges()) - }) -} - -func TestComponentDiff_IsExpectedVersion(t *testing.T) { - tests := []struct { - name string - expected *semver.Version - actual *semver.Version - want bool - }{ - { - name: "equal", - expected: semver.MustParse("1.0"), - actual: semver.MustParse("1.0"), - want: true, - }, - { - name: "equal dev versions", - expected: semver.MustParse("0.2.0-dev"), - actual: semver.MustParse("0.2.0-dev"), - want: true, - }, - { - name: "higher expected", - expected: semver.MustParse("1.1"), - actual: semver.MustParse("1.0"), - want: false, - }, - { - name: "nothing expected", - expected: nil, - actual: semver.MustParse("1.0"), - want: true, - }, - { - name: "nothing installed", - expected: semver.MustParse("1.0"), - actual: nil, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - diff := ComponentDiff{Expected: ComponentDiffState{Version: tt.expected}} - assert.Equalf(t, tt.want, diff.IsExpectedVersion(tt.actual), "{version:%v}.IsExpectedVersion(%v)", tt.expected, tt.actual) - }) - } -} diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index d45f1062..23fba150 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -38,35 +38,6 @@ type DoguInstallationRepository interface { Delete(ctx context.Context, doguName cescommons.SimpleName) error } -type ComponentInstallationRepository interface { - // GetByName loads an installed component from the ecosystem and returns - // - the ecosystem.ComponentInstallation or - // - a NotFoundError if the component is not installed or - // - an InternalError if there is any other error. - GetByName(ctx context.Context, componentName common.SimpleComponentName) (*ecosystem.ComponentInstallation, error) - // GetAll returns - // - the installation info of all installed components or - // - an InternalError if there is any other error. - GetAll(ctx context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error) - // Delete deletes the component by name from the ecosystem. - // returns an InternalError if there is an error. - Delete(ctx context.Context, componentName common.SimpleComponentName) error - // Create creates the ecosystem.ComponentInstallation in the ecosystem. - // returns an InternalError if there is an error. - Create(ctx context.Context, component *ecosystem.ComponentInstallation) error - // Update updates the ecosystem.ComponentInstallation in the ecosystem. - // returns an InternalError if anything went wrong. - Update(ctx context.Context, component *ecosystem.ComponentInstallation) error -} - -type RequiredComponentsProvider interface { - GetRequiredComponents(ctx context.Context) ([]ecosystem.RequiredComponent, error) -} - -type HealthWaitConfigProvider interface { - GetWaitConfig(ctx context.Context) (ecosystem.WaitConfig, error) -} - type BlueprintSpecRepository interface { // GetById returns a BlueprintSpec identified by its ID or // a NotFoundError if the BlueprintSpec was not found or diff --git a/pkg/domainservice/mock_ComponentInstallationRepository_test.go b/pkg/domainservice/mock_ComponentInstallationRepository_test.go deleted file mode 100644 index d269ae54..00000000 --- a/pkg/domainservice/mock_ComponentInstallationRepository_test.go +++ /dev/null @@ -1,298 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package domainservice - -import ( - context "context" - - common "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - - mock "github.com/stretchr/testify/mock" -) - -// MockComponentInstallationRepository is an autogenerated mock type for the ComponentInstallationRepository type -type MockComponentInstallationRepository struct { - mock.Mock -} - -type MockComponentInstallationRepository_Expecter struct { - mock *mock.Mock -} - -func (_m *MockComponentInstallationRepository) EXPECT() *MockComponentInstallationRepository_Expecter { - return &MockComponentInstallationRepository_Expecter{mock: &_m.Mock} -} - -// Create provides a mock function with given fields: ctx, component -func (_m *MockComponentInstallationRepository) Create(ctx context.Context, component *ecosystem.ComponentInstallation) error { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for Create") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *ecosystem.ComponentInstallation) error); ok { - r0 = rf(ctx, component) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockComponentInstallationRepository_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create' -type MockComponentInstallationRepository_Create_Call struct { - *mock.Call -} - -// Create is a helper method to define mock.On call -// - ctx context.Context -// - component *ecosystem.ComponentInstallation -func (_e *MockComponentInstallationRepository_Expecter) Create(ctx interface{}, component interface{}) *MockComponentInstallationRepository_Create_Call { - return &MockComponentInstallationRepository_Create_Call{Call: _e.mock.On("Create", ctx, component)} -} - -func (_c *MockComponentInstallationRepository_Create_Call) Run(run func(ctx context.Context, component *ecosystem.ComponentInstallation)) *MockComponentInstallationRepository_Create_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*ecosystem.ComponentInstallation)) - }) - return _c -} - -func (_c *MockComponentInstallationRepository_Create_Call) Return(_a0 error) *MockComponentInstallationRepository_Create_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockComponentInstallationRepository_Create_Call) RunAndReturn(run func(context.Context, *ecosystem.ComponentInstallation) error) *MockComponentInstallationRepository_Create_Call { - _c.Call.Return(run) - return _c -} - -// Delete provides a mock function with given fields: ctx, componentName -func (_m *MockComponentInstallationRepository) Delete(ctx context.Context, componentName common.SimpleComponentName) error { - ret := _m.Called(ctx, componentName) - - if len(ret) == 0 { - panic("no return value specified for Delete") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) error); ok { - r0 = rf(ctx, componentName) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockComponentInstallationRepository_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type MockComponentInstallationRepository_Delete_Call struct { - *mock.Call -} - -// Delete is a helper method to define mock.On call -// - ctx context.Context -// - componentName common.SimpleComponentName -func (_e *MockComponentInstallationRepository_Expecter) Delete(ctx interface{}, componentName interface{}) *MockComponentInstallationRepository_Delete_Call { - return &MockComponentInstallationRepository_Delete_Call{Call: _e.mock.On("Delete", ctx, componentName)} -} - -func (_c *MockComponentInstallationRepository_Delete_Call) Run(run func(ctx context.Context, componentName common.SimpleComponentName)) *MockComponentInstallationRepository_Delete_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.SimpleComponentName)) - }) - return _c -} - -func (_c *MockComponentInstallationRepository_Delete_Call) Return(_a0 error) *MockComponentInstallationRepository_Delete_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockComponentInstallationRepository_Delete_Call) RunAndReturn(run func(context.Context, common.SimpleComponentName) error) *MockComponentInstallationRepository_Delete_Call { - _c.Call.Return(run) - return _c -} - -// GetAll provides a mock function with given fields: ctx -func (_m *MockComponentInstallationRepository) GetAll(ctx context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetAll") - } - - var r0 map[common.SimpleComponentName]*ecosystem.ComponentInstallation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) map[common.SimpleComponentName]*ecosystem.ComponentInstallation); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(map[common.SimpleComponentName]*ecosystem.ComponentInstallation) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockComponentInstallationRepository_GetAll_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAll' -type MockComponentInstallationRepository_GetAll_Call struct { - *mock.Call -} - -// GetAll is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockComponentInstallationRepository_Expecter) GetAll(ctx interface{}) *MockComponentInstallationRepository_GetAll_Call { - return &MockComponentInstallationRepository_GetAll_Call{Call: _e.mock.On("GetAll", ctx)} -} - -func (_c *MockComponentInstallationRepository_GetAll_Call) Run(run func(ctx context.Context)) *MockComponentInstallationRepository_GetAll_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockComponentInstallationRepository_GetAll_Call) Return(_a0 map[common.SimpleComponentName]*ecosystem.ComponentInstallation, _a1 error) *MockComponentInstallationRepository_GetAll_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockComponentInstallationRepository_GetAll_Call) RunAndReturn(run func(context.Context) (map[common.SimpleComponentName]*ecosystem.ComponentInstallation, error)) *MockComponentInstallationRepository_GetAll_Call { - _c.Call.Return(run) - return _c -} - -// GetByName provides a mock function with given fields: ctx, componentName -func (_m *MockComponentInstallationRepository) GetByName(ctx context.Context, componentName common.SimpleComponentName) (*ecosystem.ComponentInstallation, error) { - ret := _m.Called(ctx, componentName) - - if len(ret) == 0 { - panic("no return value specified for GetByName") - } - - var r0 *ecosystem.ComponentInstallation - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) (*ecosystem.ComponentInstallation, error)); ok { - return rf(ctx, componentName) - } - if rf, ok := ret.Get(0).(func(context.Context, common.SimpleComponentName) *ecosystem.ComponentInstallation); ok { - r0 = rf(ctx, componentName) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*ecosystem.ComponentInstallation) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, common.SimpleComponentName) error); ok { - r1 = rf(ctx, componentName) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockComponentInstallationRepository_GetByName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetByName' -type MockComponentInstallationRepository_GetByName_Call struct { - *mock.Call -} - -// GetByName is a helper method to define mock.On call -// - ctx context.Context -// - componentName common.SimpleComponentName -func (_e *MockComponentInstallationRepository_Expecter) GetByName(ctx interface{}, componentName interface{}) *MockComponentInstallationRepository_GetByName_Call { - return &MockComponentInstallationRepository_GetByName_Call{Call: _e.mock.On("GetByName", ctx, componentName)} -} - -func (_c *MockComponentInstallationRepository_GetByName_Call) Run(run func(ctx context.Context, componentName common.SimpleComponentName)) *MockComponentInstallationRepository_GetByName_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(common.SimpleComponentName)) - }) - return _c -} - -func (_c *MockComponentInstallationRepository_GetByName_Call) Return(_a0 *ecosystem.ComponentInstallation, _a1 error) *MockComponentInstallationRepository_GetByName_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockComponentInstallationRepository_GetByName_Call) RunAndReturn(run func(context.Context, common.SimpleComponentName) (*ecosystem.ComponentInstallation, error)) *MockComponentInstallationRepository_GetByName_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with given fields: ctx, component -func (_m *MockComponentInstallationRepository) Update(ctx context.Context, component *ecosystem.ComponentInstallation) error { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for Update") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *ecosystem.ComponentInstallation) error); ok { - r0 = rf(ctx, component) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockComponentInstallationRepository_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type MockComponentInstallationRepository_Update_Call struct { - *mock.Call -} - -// Update is a helper method to define mock.On call -// - ctx context.Context -// - component *ecosystem.ComponentInstallation -func (_e *MockComponentInstallationRepository_Expecter) Update(ctx interface{}, component interface{}) *MockComponentInstallationRepository_Update_Call { - return &MockComponentInstallationRepository_Update_Call{Call: _e.mock.On("Update", ctx, component)} -} - -func (_c *MockComponentInstallationRepository_Update_Call) Run(run func(ctx context.Context, component *ecosystem.ComponentInstallation)) *MockComponentInstallationRepository_Update_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(*ecosystem.ComponentInstallation)) - }) - return _c -} - -func (_c *MockComponentInstallationRepository_Update_Call) Return(_a0 error) *MockComponentInstallationRepository_Update_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockComponentInstallationRepository_Update_Call) RunAndReturn(run func(context.Context, *ecosystem.ComponentInstallation) error) *MockComponentInstallationRepository_Update_Call { - _c.Call.Return(run) - return _c -} - -// NewMockComponentInstallationRepository creates a new instance of MockComponentInstallationRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockComponentInstallationRepository(t interface { - mock.TestingT - Cleanup(func()) -}) *MockComponentInstallationRepository { - mock := &MockComponentInstallationRepository{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/domainservice/mock_HealthWaitConfigProvider_test.go b/pkg/domainservice/mock_HealthWaitConfigProvider_test.go deleted file mode 100644 index b3e12ccc..00000000 --- a/pkg/domainservice/mock_HealthWaitConfigProvider_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package domainservice - -import ( - context "context" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - mock "github.com/stretchr/testify/mock" -) - -// MockHealthWaitConfigProvider is an autogenerated mock type for the HealthWaitConfigProvider type -type MockHealthWaitConfigProvider struct { - mock.Mock -} - -type MockHealthWaitConfigProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *MockHealthWaitConfigProvider) EXPECT() *MockHealthWaitConfigProvider_Expecter { - return &MockHealthWaitConfigProvider_Expecter{mock: &_m.Mock} -} - -// GetWaitConfig provides a mock function with given fields: ctx -func (_m *MockHealthWaitConfigProvider) GetWaitConfig(ctx context.Context) (ecosystem.WaitConfig, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetWaitConfig") - } - - var r0 ecosystem.WaitConfig - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ecosystem.WaitConfig, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ecosystem.WaitConfig); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ecosystem.WaitConfig) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockHealthWaitConfigProvider_GetWaitConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWaitConfig' -type MockHealthWaitConfigProvider_GetWaitConfig_Call struct { - *mock.Call -} - -// GetWaitConfig is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockHealthWaitConfigProvider_Expecter) GetWaitConfig(ctx interface{}) *MockHealthWaitConfigProvider_GetWaitConfig_Call { - return &MockHealthWaitConfigProvider_GetWaitConfig_Call{Call: _e.mock.On("GetWaitConfig", ctx)} -} - -func (_c *MockHealthWaitConfigProvider_GetWaitConfig_Call) Run(run func(ctx context.Context)) *MockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockHealthWaitConfigProvider_GetWaitConfig_Call) Return(_a0 ecosystem.WaitConfig, _a1 error) *MockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockHealthWaitConfigProvider_GetWaitConfig_Call) RunAndReturn(run func(context.Context) (ecosystem.WaitConfig, error)) *MockHealthWaitConfigProvider_GetWaitConfig_Call { - _c.Call.Return(run) - return _c -} - -// NewMockHealthWaitConfigProvider creates a new instance of MockHealthWaitConfigProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockHealthWaitConfigProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *MockHealthWaitConfigProvider { - mock := &MockHealthWaitConfigProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/pkg/domainservice/mock_RequiredComponentsProvider_test.go b/pkg/domainservice/mock_RequiredComponentsProvider_test.go deleted file mode 100644 index c4b4c949..00000000 --- a/pkg/domainservice/mock_RequiredComponentsProvider_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. - -package domainservice - -import ( - context "context" - - ecosystem "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" - mock "github.com/stretchr/testify/mock" -) - -// MockRequiredComponentsProvider is an autogenerated mock type for the RequiredComponentsProvider type -type MockRequiredComponentsProvider struct { - mock.Mock -} - -type MockRequiredComponentsProvider_Expecter struct { - mock *mock.Mock -} - -func (_m *MockRequiredComponentsProvider) EXPECT() *MockRequiredComponentsProvider_Expecter { - return &MockRequiredComponentsProvider_Expecter{mock: &_m.Mock} -} - -// GetRequiredComponents provides a mock function with given fields: ctx -func (_m *MockRequiredComponentsProvider) GetRequiredComponents(ctx context.Context) ([]ecosystem.RequiredComponent, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetRequiredComponents") - } - - var r0 []ecosystem.RequiredComponent - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) ([]ecosystem.RequiredComponent, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) []ecosystem.RequiredComponent); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]ecosystem.RequiredComponent) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockRequiredComponentsProvider_GetRequiredComponents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRequiredComponents' -type MockRequiredComponentsProvider_GetRequiredComponents_Call struct { - *mock.Call -} - -// GetRequiredComponents is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockRequiredComponentsProvider_Expecter) GetRequiredComponents(ctx interface{}) *MockRequiredComponentsProvider_GetRequiredComponents_Call { - return &MockRequiredComponentsProvider_GetRequiredComponents_Call{Call: _e.mock.On("GetRequiredComponents", ctx)} -} - -func (_c *MockRequiredComponentsProvider_GetRequiredComponents_Call) Run(run func(ctx context.Context)) *MockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockRequiredComponentsProvider_GetRequiredComponents_Call) Return(_a0 []ecosystem.RequiredComponent, _a1 error) *MockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockRequiredComponentsProvider_GetRequiredComponents_Call) RunAndReturn(run func(context.Context) ([]ecosystem.RequiredComponent, error)) *MockRequiredComponentsProvider_GetRequiredComponents_Call { - _c.Call.Return(run) - return _c -} - -// NewMockRequiredComponentsProvider creates a new instance of MockRequiredComponentsProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockRequiredComponentsProvider(t interface { - mock.TestingT - Cleanup(func()) -}) *MockRequiredComponentsProvider { - mock := &MockRequiredComponentsProvider{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} From 6a905db7820a36608186703bbe8a3389aad40b72 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 26 Sep 2025 15:59:04 +0200 Subject: [PATCH 091/119] #121 add changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce0145f..30475705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - remove dependency to k8s-service-discovery (maintenance-mode was the reason for this dependency) - [#121] *breaking* dogus will not be restarted by the blueprint operator anymore - this is now the responsibility of the dogu operator +- [#121] *breaking* remove the ability to apply components (including self upgrade) + - Ecosystem-Core-Chart has now the resonsibility of components +- [#121] *breaking* remove watching the health state of components in the ecosystem ## [v2.8.0] - 2025-09-15 ### Changed From ece4020ba11c1767f0b2a3054eca2daf89d818d8 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 10:00:56 +0200 Subject: [PATCH 092/119] #121 allow empty values in config --- pkg/domain/config.go | 9 +-------- pkg/domain/config_test.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/domain/config.go b/pkg/domain/config.go index 2e5f93a4..ef1bcf05 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -177,8 +177,7 @@ func (config ConfigEntry) validate() error { hasValue := config.Value != nil hasSecretRef := config.SecretRef != nil - // TODO: See test, is both nil allowed? - if hasValue == hasSecretRef { + if hasValue && hasSecretRef { errs = append(errs, fmt.Errorf("config entries can have either a value or a secretRef")) } @@ -220,11 +219,5 @@ func (config ConfigEntry) validateGlobal() error { return errors.Join(errs...) } - // For present entries, a Value must be set - // TODO: See test, is empty string allowed? - //if config.Value == nil { - // errs = append(errs, fmt.Errorf("config entries must have a value")) - //} - return errors.Join(errs...) } diff --git a/pkg/domain/config_test.go b/pkg/domain/config_test.go index 24dfd273..649062f4 100644 --- a/pkg/domain/config_test.go +++ b/pkg/domain/config_test.go @@ -173,6 +173,16 @@ func TestDoguConfig_validate(t *testing.T) { assert.ErrorContains(t, err, "key for config should not be empty") }) + t.Run("empty value is allowed", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + }, + } + + err := config.validate("dogu1") + assert.NoError(t, err) + }) t.Run("combine errors", func(t *testing.T) { config := DoguConfigEntries{ { From 1e094a228276f73cc2f33404a3b5d29afec06da1 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 10:19:10 +0200 Subject: [PATCH 093/119] #121 remove Todo --- pkg/domain/blueprintSpec.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index d8edf044..ffd9ac23 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -314,16 +314,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( // I am not sure with this yet. // Con: a central stateDiff could be problematic because we then check every state and // we cannot optimize it if we know, which watch triggered the reconciliation. - - //TODO: The state diff will be generated at every run and will be written on the blueprint-CR.Status. - // After every apply, the state diff will be empty and therefore will be deleted on the blueprint-CR. - // It is only useful for dry-run or error states, where no further work happens. - // Maybe we just not write it in the status anymore? How to debug then? - // The old classic blueprint-process was hard to understand because there was no overview. - // A separate BlueprintExecution-CR could be a solution but i am not sure, if we have enough time for that and if it is the right choice. - // CR could have the diff as it's spec. - // It is a single execution like k8s-jobs. - // The operator will always spawn more blueprintExecutions if they fail or there is a diff left after applying. return nil } From 7d8145ac8038c35833996076e05fd51e6f6f6dca Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 14:32:17 +0200 Subject: [PATCH 094/119] #121 combine stateDiff events and throw ecosystemUnhealty only once --- .../v2/blueprintSpecCRRepository_test.go | 4 +- pkg/domain/blueprintSpec.go | 29 ++--- pkg/domain/blueprintSpec_test.go | 5 +- pkg/domain/ecosystem/ecosystemHealth.go | 2 +- pkg/domain/ecosystem/ecosystemHealth_test.go | 4 +- pkg/domain/events.go | 106 ++++++++---------- pkg/domain/events_test.go | 37 ++++-- 7 files changed, 85 insertions(+), 102 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go index b54a8c58..73d0e2fe 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository_test.go @@ -372,11 +372,11 @@ func Test_blueprintSpecRepo_Update_publishEvents(t *testing.T) { var events []domain.Event events = append(events, - domain.StateDiffDoguDeterminedEvent{}, + domain.StateDiffDeterminedEvent{}, domain.StateDiffComponentDeterminedEvent{}, domain.BlueprintSpecInvalidEvent{ValidationError: errors.New("test-error")}, ) - eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDoguDetermined", "dogu state diff determined: 0 actions ()") + eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffDetermined", "state diff determined:\n 0 config changes ()\n 0 dogu actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "StateDiffComponentDetermined", "component state diff determined: 0 actions ()") eventRecorderMock.EXPECT().Event(mock.Anything, corev1.EventTypeNormal, "BlueprintSpecInvalid", "test-error") diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index ffd9ac23..6635ed99 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -270,14 +270,11 @@ func (spec *BlueprintSpec) DetermineStateDiff( spec.resetCompletedConditionAfterStateDiff() if spec.StateDiff.DoguDiffs.HasChanges() { - spec.Events = append(spec.Events, newStateDiffDoguEvent(spec.StateDiff.DoguDiffs)) + spec.Events = append(spec.Events, newStateDiffEvent(spec.StateDiff)) } if spec.StateDiff.ComponentDiffs.HasChanges() { spec.Events = append(spec.Events, newStateDiffComponentEvent(spec.StateDiff.ComponentDiffs)) } - if spec.StateDiff.HasConfigChanges() { - spec.Events = append(spec.Events, NewConfigDiffDeterminedEvent(spec.StateDiff)) - } invalidBlueprintError := spec.validateStateDiff() if invalidBlueprintError != nil { @@ -298,22 +295,6 @@ func (spec *BlueprintSpec) DetermineStateDiff( Status: metav1.ConditionTrue, Reason: "Executable", }) - //TODO: we cannot just deduplicate the events here by detecting a condition change, - // because the blueprint could be executable even after a change of the blueprint. - // Therefore, a check with "conditionChanged" is not enough to prevent, that we regenerate all events on every reconcile. - - //TODO: We could set all diff-related conditions here - // Con: this could override Reason and Message. - // They will be set again when we try to apply. - // We also could check, if there is a reason and a message and just change nothing then if there is still a diff. - // Pro: we don't need to update the blueprint so often -> big update here and only setting conditionTrue while applying things - // Con: StateDiff as it is could problematic because it hides conflict errors - // (is this a real problem if we set it anyways in the next run?). - // The idea is, that we just check the needed cluster-state when we try to apply. - // Then there is no central stateDiff anymore, just some independent ApplyUseCases. - // I am not sure with this yet. - // Con: a central stateDiff could be problematic because we then check every state and - // we cannot optimize it if we know, which watch triggered the reconciliation. return nil } @@ -351,13 +332,17 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul event := EcosystemUnhealthyEvent{ HealthResult: healthResult, } + oldHealthyCondition := meta.FindStatusCondition(spec.Conditions, ConditionEcosystemHealthy) + // determine here, because the condition is a pointer and will change with the SetStatusCondition call below + isConditionStatusChanged := oldHealthyCondition == nil || oldHealthyCondition.Status == metav1.ConditionTrue conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ Type: ConditionEcosystemHealthy, Status: metav1.ConditionFalse, Reason: "Unhealthy", - Message: event.Message(), + Message: "ecosystem health:\n " + healthResult.String(), }) - if conditionChanged { + // only throw an event the first unhealthy time to avoid having too many events + if isConditionStatusChanged { spec.Events = append(spec.Events, event) } return conditionChanged diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index f527b02f..7a332a6e 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -375,10 +375,9 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(spec.Conditions, ConditionExecutable)) require.NoError(t, err) - require.Equal(t, 3, len(spec.Events)) - assert.Equal(t, newStateDiffDoguEvent(stateDiff.DoguDiffs), spec.Events[0]) + require.Equal(t, 2, len(spec.Events)) + assert.Equal(t, newStateDiffEvent(stateDiff), spec.Events[0]) assert.Equal(t, newStateDiffComponentEvent(stateDiff.ComponentDiffs), spec.Events[1]) - assert.Equal(t, NewConfigDiffDeterminedEvent(stateDiff), spec.Events[2]) assert.Empty(t, cmp.Diff(stateDiff, spec.StateDiff)) }) diff --git a/pkg/domain/ecosystem/ecosystemHealth.go b/pkg/domain/ecosystem/ecosystemHealth.go index 4517ae04..0a354d4b 100644 --- a/pkg/domain/ecosystem/ecosystemHealth.go +++ b/pkg/domain/ecosystem/ecosystemHealth.go @@ -20,7 +20,7 @@ type HealthResult struct { } func (result HealthResult) String() string { - return fmt.Sprintf("ecosystem health:\n %s\n %s", result.DoguHealth, result.ComponentHealth) + return fmt.Sprintf("%s\n %s", result.DoguHealth, result.ComponentHealth) } func (result HealthResult) AllHealthy() bool { diff --git a/pkg/domain/ecosystem/ecosystemHealth_test.go b/pkg/domain/ecosystem/ecosystemHealth_test.go index 3a908ca6..cbb4604b 100644 --- a/pkg/domain/ecosystem/ecosystemHealth_test.go +++ b/pkg/domain/ecosystem/ecosystemHealth_test.go @@ -24,7 +24,7 @@ func TestHealthResult_String(t *testing.T) { DoguHealth: DoguHealthResult{}, ComponentHealth: ComponentHealthResult{}, }, - want: "ecosystem health:\n 0 dogu(s) are unhealthy: \n 0 component(s) are unhealthy: ", + want: "0 dogu(s) are unhealthy: \n 0 component(s) are unhealthy: ", }, { name: "should print dogu and component health results with unhealthy", @@ -32,7 +32,7 @@ func TestHealthResult_String(t *testing.T) { DoguHealth: DoguHealthResult{DogusByStatus: map[HealthStatus][]cescommons.SimpleName{UnavailableHealthStatus: {"nginx-ingress"}}}, ComponentHealth: ComponentHealthResult{ComponentsByStatus: map[HealthStatus][]common.SimpleComponentName{UnavailableHealthStatus: {"k8s-etcd"}}}, }, - want: "ecosystem health:\n 1 dogu(s) are unhealthy: nginx-ingress\n 1 component(s) are unhealthy: k8s-etcd", + want: "1 dogu(s) are unhealthy: nginx-ingress\n 1 component(s) are unhealthy: k8s-etcd", }, } for _, tt := range tests { diff --git a/pkg/domain/events.go b/pkg/domain/events.go index e5c7cf9f..5833f5af 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -28,28 +28,6 @@ func (b BlueprintSpecInvalidEvent) Message() string { return b.ValidationError.Error() } -type ConfigDiffDeterminedEvent struct { - GlobalConfigDiffs GlobalConfigDiffs - DoguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs - SensitiveConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs -} - -func NewConfigDiffDeterminedEvent(ConfigDiffs StateDiff) ConfigDiffDeterminedEvent { - return ConfigDiffDeterminedEvent{ - DoguConfigDiffs: ConfigDiffs.DoguConfigDiffs, - GlobalConfigDiffs: ConfigDiffs.GlobalConfigDiffs, - SensitiveConfigDiffs: ConfigDiffs.SensitiveDoguConfigDiffs, - } -} - -func (e ConfigDiffDeterminedEvent) Name() string { - return "ConfigDiffDetermined" -} - -func (e ConfigDiffDeterminedEvent) Message() string { - return fmt.Sprintf("config diff determined: %s", e.generateConfigChangeCounter()) -} - type MissingConfigReferencesEvent struct { err error } @@ -66,33 +44,6 @@ func (e MissingConfigReferencesEvent) Message() string { return e.err.Error() } -func (e ConfigDiffDeterminedEvent) generateConfigChangeCounter() string { - configActions := util.Map(e.GlobalConfigDiffs, func(entryDiff GlobalConfigEntryDiff) ConfigAction { - return entryDiff.NeededAction - }) - for _, doguDiff := range e.DoguConfigDiffs { - configActions = append(configActions, util.Map(doguDiff, func(entryDiff DoguConfigEntryDiff) ConfigAction { - return entryDiff.NeededAction - })...) - } - for _, doguDiff := range e.SensitiveConfigDiffs { - configActions = append(configActions, util.Map(doguDiff, func(entryDiff SensitiveDoguConfigEntryDiff) ConfigAction { - return entryDiff.NeededAction - })...) - } - - var stringPerAction []string - var actionsCounter int - for action, amount := range countByAction(configActions) { - stringPerAction = append(stringPerAction, fmt.Sprintf("%q: %d", action, amount)) - if action != ConfigActionNone { - actionsCounter += amount - } - } - slices.Sort(stringPerAction) - return fmt.Sprintf("%d changes (%s)", actionsCounter, strings.Join(stringPerAction, ", ")) -} - // StateDiffComponentDeterminedEvent provides event information over detected changes regarding components. type StateDiffComponentDeterminedEvent struct { componentDiffs []ComponentDiff @@ -134,26 +85,32 @@ func getActionAmountMessage(amountActions map[Action]int) (message string, total return } -// StateDiffDoguDeterminedEvent provides event information over detected changes regarding dogus. -type StateDiffDoguDeterminedEvent struct { - doguDiffs DoguDiffs +// StateDiffDeterminedEvent provides event information over detected changes regarding dogus. +type StateDiffDeterminedEvent struct { + doguDiffs DoguDiffs + GlobalConfigDiffs GlobalConfigDiffs + DoguConfigDiffs map[cescommons.SimpleName]DoguConfigDiffs + SensitiveConfigDiffs map[cescommons.SimpleName]SensitiveDoguConfigDiffs } -func newStateDiffDoguEvent(doguDiffs DoguDiffs) StateDiffDoguDeterminedEvent { - return StateDiffDoguDeterminedEvent{ - doguDiffs: doguDiffs, +func newStateDiffEvent(stateDiff StateDiff) StateDiffDeterminedEvent { + return StateDiffDeterminedEvent{ + doguDiffs: stateDiff.DoguDiffs, + DoguConfigDiffs: stateDiff.DoguConfigDiffs, + GlobalConfigDiffs: stateDiff.GlobalConfigDiffs, + SensitiveConfigDiffs: stateDiff.SensitiveDoguConfigDiffs, } } // Name contains the StateDiffDoguDeterminedEvent display name. -func (s StateDiffDoguDeterminedEvent) Name() string { - return "StateDiffDoguDetermined" +func (s StateDiffDeterminedEvent) Name() string { + return "StateDiffDetermined" } const groupedDoguProxyAction = "update reverse proxy" // Message contains the StateDiffDoguDeterminedEvent's statistics message. -func (s StateDiffDoguDeterminedEvent) Message() string { +func (s StateDiffDeterminedEvent) Message() string { var amountActions = map[Action]int{} for _, diff := range s.doguDiffs { for _, action := range diff.NeededActions { @@ -165,9 +122,36 @@ func (s StateDiffDoguDeterminedEvent) Message() string { } } - message, amount := getActionAmountMessage(amountActions) + doguMessage, doguAmount := getActionAmountMessage(amountActions) + + return fmt.Sprintf("state diff determined:\n %s\n %d dogu actions (%s)", s.generateConfigChangeCounter(), doguAmount, doguMessage) +} + +func (s StateDiffDeterminedEvent) generateConfigChangeCounter() string { + configActions := util.Map(s.GlobalConfigDiffs, func(entryDiff GlobalConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + }) + for _, doguDiff := range s.DoguConfigDiffs { + configActions = append(configActions, util.Map(doguDiff, func(entryDiff DoguConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + })...) + } + for _, doguDiff := range s.SensitiveConfigDiffs { + configActions = append(configActions, util.Map(doguDiff, func(entryDiff SensitiveDoguConfigEntryDiff) ConfigAction { + return entryDiff.NeededAction + })...) + } - return fmt.Sprintf("dogu state diff determined: %d actions (%s)", amount, message) + var stringPerAction []string + var actionsCounter int + for action, amount := range countByAction(configActions) { + stringPerAction = append(stringPerAction, fmt.Sprintf("%q: %d", action, amount)) + if action != ConfigActionNone { + actionsCounter += amount + } + } + slices.Sort(stringPerAction) + return fmt.Sprintf("%d config changes (%s)", actionsCounter, strings.Join(stringPerAction, ", ")) } type EcosystemHealthyEvent struct { @@ -192,7 +176,7 @@ func (d EcosystemUnhealthyEvent) Name() string { } func (d EcosystemUnhealthyEvent) Message() string { - return d.HealthResult.String() + return "Ecosystem became unhealthy. Reason:\n " + d.HealthResult.String() } type BlueprintDryRunEvent struct{} diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index ba6a9ddd..fd5585d0 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -60,12 +60,12 @@ func TestEvents(t *testing.T) { }, }, expectedName: "EcosystemUnhealthy", - expectedMessage: "ecosystem health:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", + expectedMessage: "Ecosystem became unhealthy. Reason:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", }, { name: "dogu state diff determined", - event: newStateDiffDoguEvent( - DoguDiffs{ + event: newStateDiffEvent( + StateDiff{DoguDiffs: DoguDiffs{ {NeededActions: []Action{ActionInstall}}, {NeededActions: []Action{ActionUninstall}}, {NeededActions: []Action{ActionInstall}}, @@ -73,9 +73,9 @@ func TestEvents(t *testing.T) { {NeededActions: []Action{ActionUninstall}}, {NeededActions: []Action{ActionUpgrade, ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguProxyBodySize, ActionUpdateDoguProxyRewriteTarget, ActionUpdateDoguProxyAdditionalConfig}}, {NeededActions: []Action{ActionDowngrade}}, - }), - expectedName: "StateDiffDoguDetermined", - expectedMessage: "dogu state diff determined: 11 actions (\"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update resource minimum volume size\": 1, \"update reverse proxy\": 3, \"upgrade\": 1)", + }}), + expectedName: "StateDiffDetermined", + expectedMessage: "state diff determined:\n 0 config changes ()\n 11 dogu actions (\"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update resource minimum volume size\": 1, \"update reverse proxy\": 3, \"upgrade\": 1)", }, { name: "component state diff determined", @@ -94,7 +94,7 @@ func TestEvents(t *testing.T) { }, { name: "config diff determined", - event: ConfigDiffDeterminedEvent{ + event: newStateDiffEvent(StateDiff{ GlobalConfigDiffs: GlobalConfigDiffs{ {NeededAction: ConfigActionNone}, {NeededAction: ConfigActionNone}, @@ -108,16 +108,31 @@ func TestEvents(t *testing.T) { {NeededAction: ConfigActionRemove}, }, }, - SensitiveConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ + SensitiveDoguConfigDiffs: map[cescommons.SimpleName]SensitiveDoguConfigDiffs{ "dogu1": []SensitiveDoguConfigEntryDiff{ {NeededAction: ConfigActionNone}, {NeededAction: ConfigActionSet}, {NeededAction: ConfigActionRemove}, }, }, - }, - expectedName: "ConfigDiffDetermined", - expectedMessage: "config diff determined: 6 changes (\"none\": 4, \"remove\": 3, \"set\": 3)", + }), + expectedName: "StateDiffDetermined", + expectedMessage: "state diff determined:\n 6 config changes (\"none\": 4, \"remove\": 3, \"set\": 3)\n 0 dogu actions ()", + }, + { + name: "config and dogu diff determined", + event: newStateDiffEvent(StateDiff{ + DoguDiffs: DoguDiffs{ + {NeededActions: []Action{ActionInstall}}, + {NeededActions: []Action{ActionUninstall}}, + }, + GlobalConfigDiffs: GlobalConfigDiffs{ + {NeededAction: ConfigActionSet}, + {NeededAction: ConfigActionRemove}, + }, + }), + expectedName: "StateDiffDetermined", + expectedMessage: "state diff determined:\n 2 config changes (\"remove\": 1, \"set\": 1)\n 2 dogu actions (\"install\": 1, \"uninstall\": 1)", }, { name: "config references missing", From 14e31e42ce44a04be87efc3692be15aec4e8a67f Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 15:23:42 +0200 Subject: [PATCH 095/119] #121 check ecosystem health before stateDiff to avoid overhead --- pkg/application/blueprintSpecChangeUseCase.go | 12 +- .../blueprintSpecChangeUseCase_test.go | 122 +++++++++--------- pkg/application/ecosystemConfigUseCase.go | 14 +- pkg/domain/events.go | 2 +- pkg/domain/events_test.go | 2 +- 5 files changed, 77 insertions(+), 75 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 7fc276fe..3f4740e8 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -143,18 +143,18 @@ func (useCase *BlueprintPreparationUseCases) prepareBlueprint(ctx context.Contex if err != nil { return err } - err = useCase.stateDiff.DetermineStateDiff(ctx, blueprint) - if err != nil { - // error could be either a technical error from a repository or an InvalidBlueprintError from the domain - // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. - return err - } // always check health here, even if we already know here, that we don't need to apply anything // because we need to update the health condition _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) if err != nil { return err } + err = useCase.stateDiff.DetermineStateDiff(ctx, blueprint) + if err != nil { + // error could be either a technical error from a repository or an InvalidBlueprintError from the domain + // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. + return err + } return nil } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 70caa11b..3634f53c 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -237,7 +237,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { }, }, { - name: "should return error on error determining state diff", + name: "should return error on error checking ecosystem health", fields: fields{ repo: func(t *testing.T) blueprintSpecRepository { m := newMockBlueprintSpecRepository(t) @@ -261,9 +261,9 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(assert.AnError) + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) return m }, }, @@ -273,7 +273,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { }, }, { - name: "should return error on error checking ecosystem health", + name: "should return error on error determining state diff", fields: fields{ repo: func(t *testing.T) blueprintSpecRepository { m := newMockBlueprintSpecRepository(t) @@ -297,14 +297,14 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(assert.AnError) return m }, }, @@ -338,16 +338,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testDryRunBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testDryRunBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testDryRunBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testDryRunBlueprintSpec).Return(nil) + return m + }, }, args: testArgs, wantErr: assert.NoError, @@ -377,16 +377,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(assert.AnError) @@ -423,16 +423,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -474,16 +474,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -530,17 +530,17 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError).Times(1) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -587,16 +587,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -648,17 +648,17 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -710,16 +710,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -776,16 +776,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) @@ -847,16 +847,16 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) return m }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) + return m + }, selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { m := newMockSelfUpgradeUseCase(t) m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 4753304c..79be5ada 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -54,13 +54,15 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not apply global config: %w", err)) } - blueprint.Events = append(blueprint.Events, domain.EcosystemConfigAppliedEvent{}) - repoErr := useCase.blueprintRepository.Update(ctx, blueprint) + if blueprint.StateDiff.HasConfigChanges() { + blueprint.Events = append(blueprint.Events, domain.EcosystemConfigAppliedEvent{}) + repoErr := useCase.blueprintRepository.Update(ctx, blueprint) - if repoErr != nil { - repoErr = errors.Join(repoErr, err) - logger.Error(repoErr, "cannot update blueprint events") - return fmt.Errorf("cannot update blueprint events: %w", repoErr) + if repoErr != nil { + repoErr = errors.Join(repoErr, err) + logger.Error(repoErr, "cannot update blueprint events") + return fmt.Errorf("cannot update blueprint events: %w", repoErr) + } } return nil } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 5833f5af..7a0bed11 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -176,7 +176,7 @@ func (d EcosystemUnhealthyEvent) Name() string { } func (d EcosystemUnhealthyEvent) Message() string { - return "Ecosystem became unhealthy. Reason:\n " + d.HealthResult.String() + return "Ecosystem became unhealthy (up-to-date list is in the EcosystemHealthy condition):\n " + d.HealthResult.String() } type BlueprintDryRunEvent struct{} diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index fd5585d0..d7495219 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -60,7 +60,7 @@ func TestEvents(t *testing.T) { }, }, expectedName: "EcosystemUnhealthy", - expectedMessage: "Ecosystem became unhealthy. Reason:\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", + expectedMessage: "Ecosystem became unhealthy (up-to-date list is in the EcosystemHealthy condition):\n 2 dogu(s) are unhealthy: admin, ldap\n 0 component(s) are unhealthy: ", }, { name: "dogu state diff determined", From 001afe3a6699ec8345d28bd1d6242289218d1c08 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 15:28:54 +0200 Subject: [PATCH 096/119] #121 upgrade makefiles to 10.4.0 --- CHANGELOG.md | 2 +- Makefile | 2 +- build/make/static-analysis.mk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30475705..ff3e21be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#121] *breaking* events were reworked, some events are now more general, some events got removed completely - Note, that events are for humans. You should not compute them for automation as they have no consistency guarantees. - [#121] Upgrade to Golang v1.25.1 -- [#121] Upgrade Makefiles to v10.3.0 +- [#121] Upgrade Makefiles to v10.4.0 ### Removed - [#119] *breaking* no support for v1 blueprint CRs anymore diff --git a/Makefile b/Makefile index 2d16030c..7358960a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ARTIFACT_ID=k8s-blueprint-operator VERSION=2.8.0 IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} GOTAG=1.25.1 -MAKEFILES_VERSION=10.3.0 +MAKEFILES_VERSION=10.4.0 STAGE?=production diff --git a/build/make/static-analysis.mk b/build/make/static-analysis.mk index a01371af..262bc0d2 100644 --- a/build/make/static-analysis.mk +++ b/build/make/static-analysis.mk @@ -7,7 +7,7 @@ CUSTOM_GO_MOUNT?=-v /tmp:/tmp REVIEW_DOG=$(TMP_DIR)/bin/reviewdog LINT=$(TMP_DIR)/bin/golangci-lint -LINT_VERSION?=v2.1.6 +LINT_VERSION?=v2.5.0 # ignore tests and mocks LINTFLAGS=--tests=false --timeout 10m --issues-exit-code 0 ADDITIONAL_LINTER=-E bodyclose -E containedctx -E contextcheck -E decorder -E dupl -E errname -E forcetypeassert -E funlen -E unparam From 89fba7e325c4c96c06611306076fc23646b02a95 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 15:29:24 +0200 Subject: [PATCH 097/119] #121 upgrade makefiles to 10.4.0 --- CHANGELOG.md | 2 +- Makefile | 2 +- build/make/static-analysis.mk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce0145f..aa2b5476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#121] *breaking* events were reworked, some events are now more general, some events got removed completely - Note, that events are for humans. You should not compute them for automation as they have no consistency guarantees. - [#121] Upgrade to Golang v1.25.1 -- [#121] Upgrade Makefiles to v10.3.0 +- [#121] Upgrade Makefiles to v10.4.0 ### Removed - [#119] *breaking* no support for v1 blueprint CRs anymore diff --git a/Makefile b/Makefile index 2d16030c..7358960a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ARTIFACT_ID=k8s-blueprint-operator VERSION=2.8.0 IMAGE=cloudogu/${ARTIFACT_ID}:${VERSION} GOTAG=1.25.1 -MAKEFILES_VERSION=10.3.0 +MAKEFILES_VERSION=10.4.0 STAGE?=production diff --git a/build/make/static-analysis.mk b/build/make/static-analysis.mk index a01371af..262bc0d2 100644 --- a/build/make/static-analysis.mk +++ b/build/make/static-analysis.mk @@ -7,7 +7,7 @@ CUSTOM_GO_MOUNT?=-v /tmp:/tmp REVIEW_DOG=$(TMP_DIR)/bin/reviewdog LINT=$(TMP_DIR)/bin/golangci-lint -LINT_VERSION?=v2.1.6 +LINT_VERSION?=v2.5.0 # ignore tests and mocks LINTFLAGS=--tests=false --timeout 10m --issues-exit-code 0 ADDITIONAL_LINTER=-E bodyclose -E containedctx -E contextcheck -E decorder -E dupl -E errname -E forcetypeassert -E funlen -E unparam From 099c4dd48fd780c197309d9970918f4bcc7e4a87 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 15:48:24 +0200 Subject: [PATCH 098/119] #121 apply cesmarvin review --- pkg/domain/stateDiffDogu.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index acd2adf6..47da9356 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -192,16 +192,19 @@ func appendActionForReverseProxyConfig(neededActions []Action, expected DoguDiff exp := expected.ReverseProxyConfig act := actual.ReverseProxyConfig + // both empty → nothing to do + if exp.IsEmpty() && act.IsEmpty() { + return neededActions + } + neededActions = appendActionForProxyBodySizes(neededActions, exp, act) - // both empty → nothing to do - if !(exp.IsEmpty() && act.IsEmpty()) { - if exp.RewriteTarget != act.RewriteTarget { - neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) - } - if exp.AdditionalConfig != act.AdditionalConfig { - neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) - } + if exp.RewriteTarget != act.RewriteTarget { + neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) + } + + if exp.AdditionalConfig != act.AdditionalConfig { + neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) } return neededActions From 102480429df0343c96703da4329e7a7d80466873 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 16:10:52 +0200 Subject: [PATCH 099/119] #121 apply cesmarvin review --- pkg/application/ecosystemConfigUseCase.go | 38 +++++++++++-------- .../ecosystemConfigUseCase_test.go | 18 ++++++--- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 79be5ada..075d5b4e 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -10,6 +10,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/cloudogu/k8s-registry-lib/config" "sigs.k8s.io/controller-runtime/pkg/log" @@ -37,7 +38,7 @@ func NewEcosystemConfigUseCase(blueprintRepository blueprintSpecRepository, dogu func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprint *domain.BlueprintSpec) error { logger := log.FromContext(ctx).WithName("EcosystemConfigUseCase.ApplyConfig") - err := pauseReconciliationForDogus(ctx, useCase.doguInstallationRepository, blueprint.StateDiff) + err := useCase.pauseReconciliationForDogus(ctx, blueprint.StateDiff) if err != nil { return useCase.handleFailedApplyEcosystemConfig(ctx, blueprint, fmt.Errorf("could not pause reconciliation for some dogus: %w", err)) } @@ -67,32 +68,37 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin return nil } -func pauseReconciliationForDogus(ctx context.Context, repository doguInstallationRepository, diff domain.StateDiff) error { - allDogus, err := repository.GetAll(ctx) +func (useCase *EcosystemConfigUseCase) pauseReconciliationForDogus(ctx context.Context, diff domain.StateDiff) error { + allDogus, err := useCase.doguInstallationRepository.GetAll(ctx) if err != nil { return fmt.Errorf("error while attempting to load dogus: %w", err) } globalConfigChanges := diff.GlobalConfigDiffs.HasChanges() for _, dogu := range allDogus { - for _, doguDiff := range diff.DoguDiffs { - if doguDiff.DoguName != dogu.Name.SimpleName { - continue - } - if slices.Contains(doguDiff.NeededActions, domain.ActionUpgrade) && - (globalConfigChanges || - diff.DoguConfigDiffs[dogu.Name.SimpleName].HasChanges() || - diff.SensitiveDoguConfigDiffs[dogu.Name.SimpleName].HasChanges()) { - dogu.PauseReconciliation = true - err = repository.Update(ctx, dogu) - if err != nil { - return fmt.Errorf("could not pause reconciliation for dogu: %w", err) - } + doguDiff := findDoguDiff(diff, dogu) + if slices.Contains(doguDiff.NeededActions, domain.ActionUpgrade) && + (globalConfigChanges || + diff.DoguConfigDiffs[dogu.Name.SimpleName].HasChanges() || + diff.SensitiveDoguConfigDiffs[dogu.Name.SimpleName].HasChanges()) { + dogu.PauseReconciliation = true + err = useCase.doguInstallationRepository.Update(ctx, dogu) + if err != nil { + return fmt.Errorf("could not pause reconciliation for dogu: %w", err) } } } return nil } +func findDoguDiff(diff domain.StateDiff, dogu *ecosystem.DoguInstallation) domain.DoguDiff { + for _, doguDiff := range diff.DoguDiffs { + if doguDiff.DoguName == dogu.Name.SimpleName { + return doguDiff + } + } + return domain.DoguDiff{} +} + func (useCase *EcosystemConfigUseCase) applyGlobalConfigDiffs(ctx context.Context, globalConfigDiffsByAction map[domain.ConfigAction][]domain.GlobalConfigEntryDiff) error { var errs []error diff --git a/pkg/application/ecosystemConfigUseCase_test.go b/pkg/application/ecosystemConfigUseCase_test.go index f11ff0d1..e9e18ab6 100644 --- a/pkg/application/ecosystemConfigUseCase_test.go +++ b/pkg/application/ecosystemConfigUseCase_test.go @@ -378,7 +378,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { }).Return(nil).Times(2) // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, stateDiff) // then require.NoError(t, err) @@ -426,7 +427,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { }).Return(nil).Times(2) // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, stateDiff) // then require.NoError(t, err) @@ -466,7 +468,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { }).Return(nil).Times(2) // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, stateDiff) // then require.NoError(t, err) @@ -501,7 +504,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { // No Update calls // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, stateDiff) // then require.NoError(t, err) @@ -513,7 +517,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { doguInstallaltionRepoMock.EXPECT().GetAll(testCtx).Return(nil, assert.AnError) // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, domain.StateDiff{}) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, domain.StateDiff{}) // then require.Error(t, err) @@ -541,7 +546,8 @@ func TestEcosystemConfigUseCase_pauseReconciliationForDogus(t *testing.T) { doguInstallaltionRepoMock.EXPECT().Update(testCtx, mock.Anything).Return(assert.AnError) // when - err := pauseReconciliationForDogus(testCtx, doguInstallaltionRepoMock, stateDiff) + sut := NewEcosystemConfigUseCase(nil, nil, nil, nil, doguInstallaltionRepoMock) + err := sut.pauseReconciliationForDogus(testCtx, stateDiff) // then require.Error(t, err) From ca875ca52715e7a1a924dff959d6041775e9c2da Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 16:42:11 +0200 Subject: [PATCH 100/119] #121 update docs --- docs/operations/apply_blueprints_de.md | 37 ++++++++++++------------- docs/operations/apply_blueprints_en.md | 38 ++++++++++++-------------- docs/operations/health_checks_de.md | 4 +-- docs/operations/health_checks_en.md | 4 +-- 4 files changed, 37 insertions(+), 46 deletions(-) diff --git a/docs/operations/apply_blueprints_de.md b/docs/operations/apply_blueprints_de.md index 165972ef..80f60bfc 100644 --- a/docs/operations/apply_blueprints_de.md +++ b/docs/operations/apply_blueprints_de.md @@ -3,35 +3,32 @@ Sie können einen Blueprint anwenden, indem Sie eine `Blueprint`-Ressource auf den Cluster-Namespace anwenden, in dem das Cloudogu MultiNode EcoSystem läuft: ```yaml -apiVersion: k8s.cloudogu.com/v1 +apiVersion: k8s.cloudogu.com/v2 kind: Blueprint metadata: + labels: + app: ces + app.kubernetes.io/name: k8s-blueprint-lib name: my-blueprint spec: - # fügen Sie die blueprint.json hier ein - blueprint: | - { - "blueprintApi": "v2", - "dogus": [ ... ], - "components": [ ... ], - "config": { - "global": { ... }, - "dogus": { ... } - } - } - # fügen Sie hier die blueprint-mask.json ein - blueprintMask: | - { - "blueprintMaskApi": "v1", - "blueprintMaskId": "my-blueprint-mask", - "dogus": [ ... ] - } + displayName: "Blueprint Sample v6.834" + # fügen Sie die blueprint hier ein + blueprint: + dogus: ... + components: ... + config: + global: ... + dogus: ... + # fügen Sie hier die blueprint-mask ein + blueprintMask: + dogus: ... ``` Das Dokument [Blueprint-Format](https://github.com/cloudogu/k8s-blueprint-lib/blob/develop/docs/operations/blueprintV2_format_de.md) beschreibt die Struktur des Blueprint im Detail. Blueprint-CR-Beispiele können dem [Sample-Repository](https://github.com/cloudogu/k8s-ecosystem-samples/tree/main/blueprints) entnommen werden. Wenn `k8s-blueprint-operator` korrekt installiert wurde, lässt sich dies z. B. so auf den Cluster anwenden: ```bash -kubectl apply -n ecosystem -f k8s_v1_blueprint.yaml +kubectl apply -n ecosystem -f k8s_v2_blueprint.yaml ``` +**Hinweis:** Pro Namespace ist nur ein Blueprint zulässig. Ändern Sie entweder das vorhandene Blueprint oder wenden Sie erneut ein `kubectl apply` mit demselben Blueprint-Namen an, um es zu aktualisieren. diff --git a/docs/operations/apply_blueprints_en.md b/docs/operations/apply_blueprints_en.md index 77ef453f..bf66ec74 100644 --- a/docs/operations/apply_blueprints_en.md +++ b/docs/operations/apply_blueprints_en.md @@ -3,33 +3,31 @@ You can apply a blueprint by applying a `Blueprint` resource to the cluster namespace where the Cloudogu MultiNode EcoSystem is running in: ```yaml -apiVersion: k8s.cloudogu.com/v1 +apiVersion: k8s.cloudogu.com/v2 kind: Blueprint metadata: + labels: + app: ces + app.kubernetes.io/name: k8s-blueprint-lib name: my-blueprint spec: - # put your blueprint.json here - blueprint: | - { - "blueprintApi": "v2", - "dogus": [ ... ], - "components": [ ... ], - "config": { - "global": { ... }, - "dogus": { ... } - } - } - # put your blueprint-mask.json here - blueprintMask: | - { - "blueprintMaskApi": "v1", - "blueprintMaskId": "my-blueprint-mask", - "dogus": [ ... ] - } + displayName: "Blueprint Sample v6.834" + # put your blueprint here + blueprint: + dogus: ... + components: ... + config: + global: ... + dogus: ... + # put your blueprint-mask here + blueprintMask: + dogus: ... ``` The document [blueprint format](https://github.com/cloudogu/k8s-blueprint-lib/blob/develop/docs/operations/blueprintV2_format_en.md) describes the structure of the Blueprint in detail. You may see examples of Blueprint-CRs in the [sample repository](https://github.com/cloudogu/k8s-ecosystem-samples/tree/main/blueprints). With `k8s-blueprint-operator` properly being installed, you can apply it to the cluster like this: ```bash -kubectl apply -n ecosystem -f k8s_v1_blueprint.yaml +kubectl apply -n ecosystem -f k8s_v2_blueprint.yaml ``` + +**Note:** Only one blueprint is permitted per namespace. Either change the existing one or apply with the same name to update it. diff --git a/docs/operations/health_checks_de.md b/docs/operations/health_checks_de.md index 990c2af8..f01ba4d2 100644 --- a/docs/operations/health_checks_de.md +++ b/docs/operations/health_checks_de.md @@ -5,9 +5,7 @@ Dabei wird folgendes geprüft: - Health aller Dogus anhand der Dogu-CRs - Health aller Components anhand der Component-CRs - Überprüfung, ob alle notwendigen Components installiert sind, die für das Blueprint gebraucht werden - -Die Health-Checks verwenden einen eingebauten Retry. -Timeout und Check-Interval lassen sich dafür in der [Health-Config](#health-config) festlegen. +- Überprüfung, ob alle Dogus bereits die neueste Version und Konfiguration verwenden ## Health ignorieren diff --git a/docs/operations/health_checks_en.md b/docs/operations/health_checks_en.md index 7ee804f5..d62bff31 100644 --- a/docs/operations/health_checks_en.md +++ b/docs/operations/health_checks_en.md @@ -5,9 +5,7 @@ The following is checked: - Health of all Dogus based on the Dogu-CRs - Health of all components based on the component CRs - Check whether all necessary components required for the blueprint are installed - -The health checks use a built-in retry. -The timeout and check interval can be defined in the [Health-Config](#health-config). +- Check whether all Dogus already use the latest version and configuration ## Ignoring health From 7000a74ebe8a4e7c9cb184821b8c87b32c2a9dc5 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Mon, 29 Sep 2025 16:45:16 +0200 Subject: [PATCH 101/119] #121 remove components from docs --- docs/operations/apply_blueprints_de.md | 1 - docs/operations/apply_blueprints_en.md | 1 - docs/operations/health_checks_de.md | 21 --------------------- docs/operations/health_checks_en.md | 21 --------------------- 4 files changed, 44 deletions(-) diff --git a/docs/operations/apply_blueprints_de.md b/docs/operations/apply_blueprints_de.md index 80f60bfc..c7088e2b 100644 --- a/docs/operations/apply_blueprints_de.md +++ b/docs/operations/apply_blueprints_de.md @@ -15,7 +15,6 @@ spec: # fügen Sie die blueprint hier ein blueprint: dogus: ... - components: ... config: global: ... dogus: ... diff --git a/docs/operations/apply_blueprints_en.md b/docs/operations/apply_blueprints_en.md index bf66ec74..0e08b906 100644 --- a/docs/operations/apply_blueprints_en.md +++ b/docs/operations/apply_blueprints_en.md @@ -15,7 +15,6 @@ spec: # put your blueprint here blueprint: dogus: ... - components: ... config: global: ... dogus: ... diff --git a/docs/operations/health_checks_de.md b/docs/operations/health_checks_de.md index f01ba4d2..28c1d06b 100644 --- a/docs/operations/health_checks_de.md +++ b/docs/operations/health_checks_de.md @@ -3,34 +3,13 @@ Vor und nach dem Anwenden des Blueprints wird gewartet, dass das Ecosystem healthy ist. Dabei wird folgendes geprüft: - Health aller Dogus anhand der Dogu-CRs -- Health aller Components anhand der Component-CRs -- Überprüfung, ob alle notwendigen Components installiert sind, die für das Blueprint gebraucht werden - Überprüfung, ob alle Dogus bereits die neueste Version und Konfiguration verwenden ## Health ignorieren Die Health-Checks vor der Ausführung des Blueprints können deaktiviert werden: - für Dogus, wenn `spec.ignoreDoguHealth` auf `true` gesetzt wird, -- für Components, wenn `spec.ignoreComponentHealth` auf `true` gesetzt wird. So ist es möglich, per Blueprint Fehler an Dogus und Komponenten zu beheben. Für ein Dogu-Upgrade muss ein Dogu allerdings healthy sein, um Pre-Upgrade-Skripte ausführen zu können. Das Ignorieren der Dogu-Health kann also zu Folgefehlern während der Ausführung des Blueprints führen. - -## Health-Config - -Die Health-Konfiguration kann im Feld `valuesYamlOverwrite` der Komponenten-CR des Blueprint-Operators überschrieben werden. -Folgendes Beispiel zeigt die möglichen Einstellungen mit ihrer Default-Konfiguration: - -```yaml -valuesYamlOverwrite: | - healthConfig: - components: - required: # These components are required for health checks to succeed. - - name: k8s-dogu-operator - - name: k8s-service-discovery - - name: k8s-component-operator - wait: # Define timeout and check-interval for the ecosystem to become healthy. - timeout: 10m - interval: 10s -``` \ No newline at end of file diff --git a/docs/operations/health_checks_en.md b/docs/operations/health_checks_en.md index d62bff31..597876a7 100644 --- a/docs/operations/health_checks_en.md +++ b/docs/operations/health_checks_en.md @@ -3,34 +3,13 @@ Before and after applying the blueprint, the ecosystem is checked to ensure that it is healthy. The following is checked: - Health of all Dogus based on the Dogu-CRs -- Health of all components based on the component CRs -- Check whether all necessary components required for the blueprint are installed - Check whether all Dogus already use the latest version and configuration ## Ignoring health Upfront health checks can be deactivated: - for Dogus, if `spec.ignoreDoguHealth` is set to `true`, -- for components, if `spec.ignoreComponentHealth` is set to `true`. This makes it possible to fix errors on Dogus and components via Blueprint. For a Dogu upgrade, however, a Dogu must be healthy in order to be able to execute pre-upgrade scripts. Ignoring the dogu health can therefore lead to subsequent errors during the execution of the blueprint. - -## Health-Config - -The health configuration can be overwritten in the `valuesYamlOverwrite` field of the component CR of the blueprint operator. -The following example shows the possible settings with their default configuration: - -```yaml -valuesYamlOverwrite: | - healthConfig: - components: - required: # These components are required for health checks to succeed. - - name: k8s-dogu-operator - - name: k8s-service-discovery - - name: k8s-component-operator - wait: # Define timeout and check-interval for the ecosystem to become healthy. - timeout: 10m - interval: 10s -``` \ No newline at end of file From 42762fffb00ae60a727e4d56f8a3211b58af8303 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 30 Sep 2025 11:07:05 +0200 Subject: [PATCH 102/119] #121 add stopped event and log --- pkg/application/blueprintSpecChangeUseCase.go | 15 +++++++++++-- .../blueprintSpecChangeUseCase_test.go | 22 +++++++++++-------- pkg/domain/blueprintSpec.go | 2 +- pkg/domain/blueprintSpec_test.go | 2 +- pkg/domain/events.go | 20 ++++++++--------- pkg/domain/events_test.go | 12 +++++----- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 3f4740e8..192fe1d5 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" + "github.com/go-logr/logr" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -112,8 +113,8 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } if !blueprint.ShouldBeApplied() { - // just stop the loop here on dry run or early exit - return nil + // stop the loop here on stopped-flag or early exit + return useCase.handleShouldNotBeApplied(ctx, logger, blueprint) } // === Apply from here on === @@ -126,6 +127,16 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C return nil } +func (useCase *BlueprintSpecChangeUseCase) handleShouldNotBeApplied(ctx context.Context, logger logr.Logger, blueprint *domain.BlueprintSpec) error { + logger.Info("blueprint is currently set as stopped and will not be applied") + blueprint.Events = append(blueprint.Events, domain.BlueprintStoppedEvent{}) + err := useCase.repo.Update(ctx, blueprint) + if err != nil { + return fmt.Errorf("cannot update status to set stopped event: %w", err) + } + return nil +} + func (useCase *BlueprintPreparationUseCases) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { err := useCase.initialStatus.InitateConditions(ctx, blueprint) if err != nil { diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 3634f53c..f7669a6e 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -75,7 +75,7 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { Id: testBlueprintId, StateDiff: domain.StateDiff{DoguDiffs: domain.DoguDiffs{{NeededActions: []domain.Action{domain.ActionInstall}}}}, } - testDryRunBlueprintSpec := &domain.BlueprintSpec{ + testStoppedBlueprintSpec := &domain.BlueprintSpec{ Id: testBlueprintId, Config: domain.BlueprintConfiguration{Stopped: true}, } @@ -314,38 +314,42 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { }, }, { - name: "should return nil and do nothing on dry run", + name: "should return nil and throw stopped event on being stopped", fields: fields{ repo: func(t *testing.T) blueprintSpecRepository { m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testDryRunBlueprintSpec, nil) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testStoppedBlueprintSpec, nil) + m.EXPECT().Update(mock.Anything, mock.Anything).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + assert.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.BlueprintStoppedEvent{}.Name(), blueprint.Events[0].Name()) + }).Return(nil) return m }, initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testDryRunBlueprintSpec).Return(nil) + m.EXPECT().InitateConditions(mock.Anything, testStoppedBlueprintSpec).Return(nil) return m }, validation: func(t *testing.T) blueprintSpecValidationUseCase { m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testDryRunBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testDryRunBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testStoppedBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testStoppedBlueprintSpec).Return(nil) return m }, effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testDryRunBlueprintSpec).Return(nil) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testStoppedBlueprintSpec).Return(nil) return m }, healthUseCase: func(t *testing.T) ecosystemHealthUseCase { m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testDryRunBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testStoppedBlueprintSpec).Return(ecosystem.HealthResult{}, nil) return m }, stateDiff: func(t *testing.T) stateDiffUseCase { m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testDryRunBlueprintSpec).Return(nil) + m.EXPECT().DetermineStateDiff(mock.Anything, testStoppedBlueprintSpec).Return(nil) return m }, }, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 6635ed99..a4c10dfa 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -348,7 +348,7 @@ func (spec *BlueprintSpec) HandleHealthResult(healthResult ecosystem.HealthResul return conditionChanged } -// ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while dry run. +// ShouldBeApplied returns true if the blueprint should be applied or an early-exit should happen, e.g. while being stopped. func (spec *BlueprintSpec) ShouldBeApplied() bool { if spec.Config.Stopped { return false diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 7a332a6e..9290d57d 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -678,7 +678,7 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { } assert.Falsef(t, spec.ShouldBeApplied(), "ShouldBeApplied()") }) - t.Run("should not be applied due to dry run", func(t *testing.T) { + t.Run("should not be applied due to being stopped", func(t *testing.T) { spec := &BlueprintSpec{ Config: BlueprintConfiguration{ Stopped: true, diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 7a0bed11..26c7e2ea 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -179,16 +179,6 @@ func (d EcosystemUnhealthyEvent) Message() string { return "Ecosystem became unhealthy (up-to-date list is in the EcosystemHealthy condition):\n " + d.HealthResult.String() } -type BlueprintDryRunEvent struct{} - -func (b BlueprintDryRunEvent) Name() string { - return "BlueprintDryRun" -} - -func (b BlueprintDryRunEvent) Message() string { - return "Executed blueprint in dry run mode. Remove flag to continue" -} - type ComponentsAppliedEvent struct { Diffs ComponentDiffs } @@ -259,6 +249,16 @@ func (e BlueprintAppliedEvent) Message() string { return "waiting for ecosystem health" } +type BlueprintStoppedEvent struct{} + +func (e BlueprintStoppedEvent) Name() string { + return "BlueprintStopped" +} + +func (e BlueprintStoppedEvent) Message() string { + return "Blueprint is set as stopped and will not be applied. Remove flag to continue" +} + type ExecutionFailedEvent struct { err error } diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index d7495219..8961bbb6 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -16,12 +16,6 @@ func TestEvents(t *testing.T) { expectedName string expectedMessage string }{ - { - name: "blueprint dry run", - event: BlueprintDryRunEvent{}, - expectedName: "BlueprintDryRun", - expectedMessage: "Executed blueprint in dry run mode. Remove flag to continue", - }, { name: "blueprint spec invalid", event: BlueprintSpecInvalidEvent{ValidationError: assert.AnError}, @@ -46,6 +40,12 @@ func TestEvents(t *testing.T) { expectedName: "EcosystemHealthy", expectedMessage: "dogu health ignored: false; component health ignored: true", }, + { + name: "blueprint stopped", + event: BlueprintStoppedEvent{}, + expectedName: "BlueprintStopped", + expectedMessage: "Blueprint is set as stopped and will not be applied. Remove flag to continue", + }, { name: "ecosystem unhealthy upfront", event: EcosystemUnhealthyEvent{ From 583b781dad504fce2018f166ba1ceec618fdf308 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 30 Sep 2025 12:07:45 +0200 Subject: [PATCH 103/119] #121 apply ces-marvin review --- go.mod | 72 +------- go.sum | 231 ------------------------ pkg/domain/ecosystem/ecosystemHealth.go | 6 +- 3 files changed, 4 insertions(+), 305 deletions(-) diff --git a/go.mod b/go.mod index 9301109c..d3945c1d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/cloudogu/ces-commons-lib v0.2.0 github.com/cloudogu/cesapp-lib v0.18.1 github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b - github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0 github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 @@ -17,46 +16,28 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 golang.org/x/net v0.44.0 - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.34.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d sigs.k8s.io/controller-runtime v0.22.1 ) require ( dario.cat/mergo v1.0.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect - github.com/BurntSushi/toml v1.5.0 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.3.0 // indirect - github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chai2010/gettext-go v1.0.3 // indirect github.com/cloudogu/retry-lib v0.1.0 // indirect - github.com/containerd/containerd v1.7.28 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/docker/docker v27.4.1+incompatible // indirect github.com/eapache/go-resiliency v1.7.0 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect - github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gammazero/toposort v0.1.1 // indirect - github.com/go-errors/errors v1.5.1 // indirect - github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.22.0 // indirect github.com/go-openapi/jsonreference v0.21.1 // indirect @@ -73,71 +54,34 @@ require ( github.com/go-openapi/swag/typeutils v0.24.0 // indirect github.com/go-openapi/swag/yamlutils v0.24.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect - github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/gosuri/uitable v0.0.4 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/huandu/xstrings v1.5.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.9.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.5.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/onsi/gomega v1.38.2 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.17.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/rubenv/sql-migrate v1.8.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect - github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/spf13/cast v1.9.2 // indirect - github.com/spf13/cobra v1.10.1 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/metric v1.37.0 // indirect - go.opentelemetry.io/otel/sdk v1.37.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.42.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect @@ -145,25 +89,15 @@ require ( golang.org/x/text v0.29.0 // indirect golang.org/x/time v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - helm.sh/helm/v3 v3.19.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.34.1 // indirect - k8s.io/apiserver v0.34.1 // indirect - k8s.io/cli-runtime v0.34.1 // indirect - k8s.io/component-base v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/kubectl v0.34.0 // indirect - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect - oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/cluster-api v1.11.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect diff --git a/go.sum b/go.sum index a1c6f3ae..653640af 100644 --- a/go.sum +++ b/go.sum @@ -1,57 +1,25 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= -github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= -github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= -github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= -github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= -github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/cloudogu/ces-commons-lib v0.2.0 h1:yOEZWFl4W9N3J/6fok4svE3UufK5GQQtyxvwtIF5AdM= github.com/cloudogu/ces-commons-lib v0.2.0/go.mod h1:4rvR2RTDDaz5a6OZ1fW27G0MOnl5I3ackeiHxt4gn3o= github.com/cloudogu/cesapp-lib v0.18.1 h1:LMdGktIefm/PuhdPqpLTPvjY1smO06EEGBbRSAaYi7U= github.com/cloudogu/cesapp-lib v0.18.1/go.mod h1:J05eXFxnz4enZblABlmiVTZaUtJ+LIhlJ2UF6l9jpDw= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1 h1:SjKSO5/5fNKQ6Z9v7t9laD3u62rl6Tr9o/0h8Ik6LrE= -github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250922184523-850dede012d1/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b h1:1VRisF6h7o/B8fOtPIRuMApMe4PQKiCgiYDLD2tyScw= github.com/cloudogu/k8s-blueprint-lib/v2 v2.0.0-20250925091741-275e54da827b/go.mod h1:yNtYKIfJkJyMCQ5srtWspl4CDIu3pCvfNC4Yci1XVtQ= -github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c h1:zgDSZ1LqdqwRabR7KEaWZlwZkAFG/qsM0uIJTuzoodc= -github.com/cloudogu/k8s-component-operator v0.0.0-20250923121356-c1fb311e229c/go.mod h1:1NmERGUjXJdMHGMOb2CP6U9Y8NRt7I8O7juUNTIDxKM= -github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96 h1:3BzzLzUliyWFwqgQn4Mp3/rW3cYeQqv0en6HFn6SVsk= -github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250919084717-d3c544ee7c96/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= -github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250923124722-b9569070ec13 h1:8/rEusXXKvbi+dLfUENsAvN84ItU0q6rFJDHKwh4BCQ= -github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250923124722-b9569070ec13/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0 h1:ohitXnpL655pmZOO0LVjW/weVi1ftFKSXG7jllbRQag= github.com/cloudogu/k8s-dogu-lib/v2 v2.0.0-20250924122244-01339f784cd0/go.mod h1:2xkJ1TI7fuBRcqIpHUlmQ6CJAtzlOvyUbwPktIVq6K8= github.com/cloudogu/k8s-registry-lib v0.2.2-0.20250818112109-cfd93e57e9f6 h1:OYMO6DYX6rtzo2a88hXXTzwf8+aXt7uV+BTaEmvlls4= @@ -60,46 +28,22 @@ github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1 h1:NWoBvBwiE8yO8HqSId0+nOT github.com/cloudogu/remote-dogu-descriptor-lib v0.1.1/go.mod h1:AGZ3bihl/sUUJ6iEtcxtALIt/UlkfUdK7HXJ6DtBivE= github.com/cloudogu/retry-lib v0.1.0 h1:gaAmtyjUqgHbxfCWMeUn0qnGbDH4TtZVSQkbZ1Nq6eI= github.com/cloudogu/retry-lib v0.1.0/go.mod h1:iG9y6zx8oJZT5ULtl9koZkYJLRsqam/2mTU+rgjxQ0g= -github.com/containerd/containerd v1.7.28 h1:Nsgm1AtcmEh4AHAJ4gGlNSaKgXiNccU270Dnf81FQ3c= -github.com/containerd/containerd v1.7.28/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= -github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA= @@ -110,26 +54,14 @@ github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= -github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gammazero/toposort v0.1.1 h1:OivGxsWxF3U3+U80VoLJ+f50HcPU1MIqE1JlKzoJ2Eg= github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw= -github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= -github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= -github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -166,18 +98,12 @@ github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zib github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= @@ -191,33 +117,6 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= -github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= -github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= -github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= -github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= -github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= -github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -232,42 +131,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN3opGfRpoQgAVqr6/A5+qRTi67VUZY= github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= @@ -282,15 +155,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.25.1 h1:Fwp6crTREKM+oA6Cz4MsO8RhKQzs2/gOIVOUscMAfZY= github.com/onsi/ginkgo/v2 v2.25.1/go.mod h1:ppTWQ1dh9KM/F1XgpeRqelR+zHVwV81DGRSDnFxK7Sk= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= @@ -299,10 +167,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -310,8 +174,6 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= -github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -320,38 +182,14 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= -github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= -github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= -github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= -github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -359,9 +197,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -378,58 +214,20 @@ github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYg github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= -github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= -go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= -go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= -go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= -go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= -go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -451,8 +249,6 @@ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -469,9 +265,7 @@ golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= @@ -494,13 +288,6 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -510,47 +297,29 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnf gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.19.0 h1:krVyCGa8fa/wzTZgqw0DUiXuRT5BPdeqE/sQXujQ22k= -helm.sh/helm/v3 v3.19.0/go.mod h1:Lk/SfzN0w3a3C3o+TdAKrLwJ0wcZ//t1/SDXAvfgDdc= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= -k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= -k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= -k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= -oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/cluster-api v1.11.1 h1:7CyGCTxv1p3Y2kRe1ljTj/w4TcdIdWNj0CTBc4i1aBo= sigs.k8s.io/cluster-api v1.11.1/go.mod h1:zyrjgJ5RbXhwKcAdUlGPNK5YOHpcmxXvur+5I8lkMUQ= sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= diff --git a/pkg/domain/ecosystem/ecosystemHealth.go b/pkg/domain/ecosystem/ecosystemHealth.go index 3d9f86b3..add1c479 100644 --- a/pkg/domain/ecosystem/ecosystemHealth.go +++ b/pkg/domain/ecosystem/ecosystemHealth.go @@ -1,9 +1,5 @@ package ecosystem -import ( - "fmt" -) - type HealthStatus = string const ( @@ -18,7 +14,7 @@ type HealthResult struct { } func (result HealthResult) String() string { - return fmt.Sprintf("%s", result.DoguHealth) + return result.DoguHealth.String() } func (result HealthResult) AllHealthy() bool { From 81a97d2c2171cdaec2332543fc649deaf73352cb Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 30 Sep 2025 13:52:40 +0200 Subject: [PATCH 104/119] #121 validate that sensitive config is not allowed to have normal values --- pkg/domain/config.go | 4 ++++ pkg/domain/config_test.go | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/pkg/domain/config.go b/pkg/domain/config.go index ef1bcf05..398149b7 100644 --- a/pkg/domain/config.go +++ b/pkg/domain/config.go @@ -185,6 +185,10 @@ func (config ConfigEntry) validate() error { errs = append(errs, fmt.Errorf("config entries with secret references have to be sensitive")) } + if hasValue && config.Sensitive { + errs = append(errs, fmt.Errorf("sensitive config entries are not allowed to have normal values")) + } + return errors.Join(errs...) } diff --git a/pkg/domain/config_test.go b/pkg/domain/config_test.go index 649062f4..28e54b10 100644 --- a/pkg/domain/config_test.go +++ b/pkg/domain/config_test.go @@ -246,6 +246,17 @@ func TestDoguConfig_validate(t *testing.T) { err := config.validate("dogu1") assert.ErrorContains(t, err, "config entries with secret references have to be sensitive") }) + t.Run("No sensitive with normal value", func(t *testing.T) { + config := DoguConfigEntries{ + { + Key: "my/key1", + Sensitive: true, + Value: &confgiVal1, + }, + } + err := config.validate("dogu1") + assert.ErrorContains(t, err, "sensitive config entries are not allowed to have normal values") + }) t.Run("secret with sensitive allowed", func(t *testing.T) { config := DoguConfigEntries{ { From a206826373b328878fbaae335a6ec947f2b188f1 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 1 Oct 2025 10:39:01 +0200 Subject: [PATCH 105/119] #121 only log stopped + event when actually stopped --- pkg/application/blueprintSpecChangeUseCase.go | 13 +++-- .../blueprintSpecChangeUseCase_test.go | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 192fe1d5..e1806fef 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -128,11 +128,14 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } func (useCase *BlueprintSpecChangeUseCase) handleShouldNotBeApplied(ctx context.Context, logger logr.Logger, blueprint *domain.BlueprintSpec) error { - logger.Info("blueprint is currently set as stopped and will not be applied") - blueprint.Events = append(blueprint.Events, domain.BlueprintStoppedEvent{}) - err := useCase.repo.Update(ctx, blueprint) - if err != nil { - return fmt.Errorf("cannot update status to set stopped event: %w", err) + // post event and log only if blueprint is stopped, all other cases are just NoOps + if blueprint.Config.Stopped { + logger.Info("blueprint is currently set as stopped and will not be applied") + blueprint.Events = append(blueprint.Events, domain.BlueprintStoppedEvent{}) + err := useCase.repo.Update(ctx, blueprint) + if err != nil { + return fmt.Errorf("cannot update status to set stopped event: %w", err) + } } return nil } diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index f7669a6e..40f35a0f 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var testCtx = context.Background() @@ -79,6 +80,13 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { Id: testBlueprintId, Config: domain.BlueprintConfiguration{Stopped: true}, } + testCompletedBlueprintSpec := &domain.BlueprintSpec{ + Id: testBlueprintId, + Conditions: []domain.Condition{{ + Type: domain.ConditionCompleted, + Status: metav1.ConditionTrue, + }}, + } type fields struct { repo func(t *testing.T) blueprintSpecRepository @@ -356,6 +364,45 @@ func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { args: testArgs, wantErr: assert.NoError, }, + { + name: "should not apply on being completed and no new statediff", + fields: fields{ + repo: func(t *testing.T) blueprintSpecRepository { + m := newMockBlueprintSpecRepository(t) + m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testCompletedBlueprintSpec, nil) + return m + }, + initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { + m := newMockInitialBlueprintStatusUseCase(t) + m.EXPECT().InitateConditions(mock.Anything, testCompletedBlueprintSpec).Return(nil) + + return m + }, + validation: func(t *testing.T) blueprintSpecValidationUseCase { + m := newMockBlueprintSpecValidationUseCase(t) + m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testCompletedBlueprintSpec).Return(nil) + m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testCompletedBlueprintSpec).Return(nil) + return m + }, + effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { + m := newMockEffectiveBlueprintUseCase(t) + m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testCompletedBlueprintSpec).Return(nil) + return m + }, + healthUseCase: func(t *testing.T) ecosystemHealthUseCase { + m := newMockEcosystemHealthUseCase(t) + m.EXPECT().CheckEcosystemHealth(mock.Anything, testCompletedBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + return m + }, + stateDiff: func(t *testing.T) stateDiffUseCase { + m := newMockStateDiffUseCase(t) + m.EXPECT().DetermineStateDiff(mock.Anything, testCompletedBlueprintSpec).Return(nil) + return m + }, + }, + args: testArgs, + wantErr: assert.NoError, + }, { name: "should return error on error handle self upgrade", fields: fields{ From fed9ba602e54d5dc7e06c134ba0c7d14521e8f12 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 1 Oct 2025 10:55:28 +0200 Subject: [PATCH 106/119] #121 add debug log line for no ops reconciles --- pkg/application/blueprintSpecChangeUseCase.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index e1806fef..f3fa722a 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -136,6 +136,8 @@ func (useCase *BlueprintSpecChangeUseCase) handleShouldNotBeApplied(ctx context. if err != nil { return fmt.Errorf("cannot update status to set stopped event: %w", err) } + } else { + logger.V(1).Info("no diff detected, no changes required") } return nil } From 1fdf8df24607318940e4577e2eb676817985efff Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 2 Oct 2025 15:15:29 +0200 Subject: [PATCH 107/119] #121 retry notFoundErrors with backoff to handle not blueprints too --- pkg/adapter/reconciler/blueprint_controller.go | 4 ++-- pkg/adapter/reconciler/blueprint_controller_test.go | 6 +++--- pkg/domainservice/adapterInterfaces.go | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index ac0ecf54..ee2dabf5 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -87,8 +87,8 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { errLogger.Error(err, "Referenced config not found. Retry later") return ctrl.Result{RequeueAfter: 10 * time.Second}, nil } - errLogger.Error(err, "Blueprint was not found, so maybe it was deleted in the meantime. No further evaluation will happen") - return ctrl.Result{}, nil + errLogger.Error(err, "Resource was not found, so maybe it was deleted in the meantime. Retry with backoff to see") + return ctrl.Result{}, err // automatic requeue because of non-nil err case errors.As(err, &invalidBlueprintError): errLogger.Info("Blueprint is invalid, therefore there will be no further evaluation.") return ctrl.Result{}, nil diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index 0be1dfd5..e7771b2a 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -163,7 +163,7 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{RequeueAfter: 1 * time.Second}, actual) assert.Contains(t, logSinkMock.output, "0: A concurrent update happened in conflict to the processing of the blueprint spec. A retry could fix this issue") }) - t.Run("should catch wrapped NotFoundError, issue a log line and do not requeue timely", func(t *testing.T) { + t.Run("should catch wrapped NotFoundError, issue a log line and requeue with backoff", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() testLogger := logr.New(logSinkMock) @@ -178,9 +178,9 @@ func Test_decideRequeueForError(t *testing.T) { actual, err := decideRequeueForError(testLogger, errorChain) // then - require.NoError(t, err) + require.Error(t, err) assert.Equal(t, ctrl.Result{}, actual) - assert.Contains(t, logSinkMock.output, "0: Blueprint was not found, so maybe it was deleted in the meantime. No further evaluation will happen") + assert.Contains(t, logSinkMock.output, "0: Resource was not found, so maybe it was deleted in the meantime. Retry with backoff to see") }) t.Run("should catch wrapped MultipleBlueprintsError, issue a error log line and requeue", func(t *testing.T) { // given diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index d45f1062..59651b7b 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -184,6 +184,7 @@ func NewNotFoundError(wrappedError error, message string, msgArgs ...any) *NotFo type NotFoundError struct { WrappedError error Message string + Retryable bool } // Error marks the struct as an error. From 7d84bce33dedf835c1e5980f13b9be3a5679fe24 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Thu, 2 Oct 2025 16:25:56 +0200 Subject: [PATCH 108/119] #121 Handle all NotFoundErrors with retry except blueprints --- .../v2/blueprintSpecCRRepository.go | 1 + .../reconciler/blueprint_controller.go | 16 +++++++-------- .../reconciler/blueprint_controller_test.go | 20 +++++++++---------- pkg/domainservice/adapterInterfaces.go | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go index 41ba1233..73ca79db 100644 --- a/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go +++ b/pkg/adapter/kubernetes/blueprintcr/v2/blueprintSpecCRRepository.go @@ -48,6 +48,7 @@ func (repo *blueprintSpecRepo) GetById(ctx context.Context, blueprintId string) return nil, &domainservice.NotFoundError{ WrappedError: err, Message: fmt.Sprintf("cannot load blueprint CR %q as it does not exist", blueprintId), + DoNotRetry: true, } } return nil, &domainservice.InternalError{ diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index ee2dabf5..469e8135 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -4,10 +4,8 @@ import ( "context" "errors" "fmt" - "strings" "time" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/application" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -81,14 +79,14 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { errLogger.Info("A concurrent update happened in conflict to the processing of the blueprint spec. A retry could fix this issue") return ctrl.Result{RequeueAfter: 1 * time.Second}, nil // no error as this would lead to the ignorance of our own retry params case errors.As(err, ¬FoundError): - if strings.Contains(err.Error(), application.REFERENCED_CONFIG_NOT_FOUND) { - // retry in this case because maybe the user will create the secret in a few seconds. - // we want to be declarative, so our API should not care about the order - errLogger.Error(err, "Referenced config not found. Retry later") - return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + if notFoundError.DoNotRetry { + // do not retry in this case, because if f.e. the blueprint is not found, nothing will bring it back, except the + // user, and this would trigger the reconciler by itself. + logger.Error(err, "Did not find resource and a retry is not expected to fix this issue. There will be no further automatic evaluation.") + return ctrl.Result{}, nil } - errLogger.Error(err, "Resource was not found, so maybe it was deleted in the meantime. Retry with backoff to see") - return ctrl.Result{}, err // automatic requeue because of non-nil err + logger.Error(err, "Resource was not found, so maybe it was deleted in the meantime. Retry later") + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil case errors.As(err, &invalidBlueprintError): errLogger.Info("Blueprint is invalid, therefore there will be no further evaluation.") return ctrl.Result{}, nil diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index e7771b2a..1412335a 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/application" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domainservice" "github.com/go-logr/logr" @@ -163,7 +162,7 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{RequeueAfter: 1 * time.Second}, actual) assert.Contains(t, logSinkMock.output, "0: A concurrent update happened in conflict to the processing of the blueprint spec. A retry could fix this issue") }) - t.Run("should catch wrapped NotFoundError, issue a log line and requeue with backoff", func(t *testing.T) { + t.Run("should catch wrapped NotFoundError, issue a log line and requeue", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() testLogger := logr.New(logSinkMock) @@ -178,9 +177,9 @@ func Test_decideRequeueForError(t *testing.T) { actual, err := decideRequeueForError(testLogger, errorChain) // then - require.Error(t, err) - assert.Equal(t, ctrl.Result{}, actual) - assert.Contains(t, logSinkMock.output, "0: Resource was not found, so maybe it was deleted in the meantime. Retry with backoff to see") + require.NoError(t, err) + assert.Equal(t, ctrl.Result{RequeueAfter: 10 * time.Second}, actual) + assert.Contains(t, logSinkMock.output, "0: Resource was not found, so maybe it was deleted in the meantime. Retry later") }) t.Run("should catch wrapped MultipleBlueprintsError, issue a error log line and requeue", func(t *testing.T) { // given @@ -200,24 +199,25 @@ func Test_decideRequeueForError(t *testing.T) { assert.Equal(t, ctrl.Result{RequeueAfter: 10 * time.Second}, actual) assert.Contains(t, logSinkMock.output, "0: Ecosystem contains multiple blueprints - delete all but one. Retry later") }) - t.Run("NotFoundError, should retry if referenced config is missing", func(t *testing.T) { + t.Run("NotFoundError, should not retry if DoNotRetry-Flag is set", func(t *testing.T) { // given logSinkMock := newTrivialTestLogSink() testLogger := logr.New(logSinkMock) intermediateErr := &domainservice.NotFoundError{ WrappedError: assert.AnError, - Message: "secret xyz does not exist", + Message: "Blueprint does not exist", + DoNotRetry: true, } - errorChain := fmt.Errorf("%s: %w", application.REFERENCED_CONFIG_NOT_FOUND, intermediateErr) + errorChain := fmt.Errorf("could not do the thing: %w", intermediateErr) // when actual, err := decideRequeueForError(testLogger, errorChain) // then require.NoError(t, err) - assert.Equal(t, ctrl.Result{RequeueAfter: 10 * time.Second}, actual) - assert.Contains(t, logSinkMock.output, "0: Referenced config not found. Retry later") + assert.Equal(t, ctrl.Result{}, actual) + assert.Contains(t, logSinkMock.output, "0: Did not find resource and a retry is not expected to fix this issue. There will be no further automatic evaluation.") }) t.Run("should catch wrapped InvalidBlueprintError, issue a log line and do not requeue", func(t *testing.T) { // given diff --git a/pkg/domainservice/adapterInterfaces.go b/pkg/domainservice/adapterInterfaces.go index 59651b7b..acb5bef5 100644 --- a/pkg/domainservice/adapterInterfaces.go +++ b/pkg/domainservice/adapterInterfaces.go @@ -184,7 +184,7 @@ func NewNotFoundError(wrappedError error, message string, msgArgs ...any) *NotFo type NotFoundError struct { WrappedError error Message string - Retryable bool + DoNotRetry bool } // Error marks the struct as an error. From 7a2054575981dfb782e50912576da8fbe8e3791d Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 7 Oct 2025 09:46:26 +0200 Subject: [PATCH 109/119] #121 remove the remaining dead code of SelfUpgrade --- .../reconciler/blueprint_controller.go | 4 -- pkg/application/blueprintSpecChangeUseCase.go | 2 - .../initiaiteBlueprintStatusUseCase_test.go | 5 +-- pkg/domain/blueprintSpec.go | 35 +++------------- pkg/domain/blueprintSpec_test.go | 42 ------------------- pkg/domain/errors.go | 8 ---- pkg/domain/events.go | 20 --------- pkg/domain/events_test.go | 12 ------ 8 files changed, 7 insertions(+), 121 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 469e8135..1b5b80ee 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -67,7 +67,6 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { var notFoundError *domainservice.NotFoundError var invalidBlueprintError *domain.InvalidBlueprintError var healthError *domain.UnhealthyEcosystemError - var awaitSelfUpgradeError *domain.AwaitSelfUpgradeError var stateDiffNotEmptyError *domain.StateDiffNotEmptyError var multipleBlueprintsError *domain.MultipleBlueprintsError var dogusNotUpToDateError *domain.DogusNotUpToDateError @@ -94,9 +93,6 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { // really normal case errLogger.Info("Ecosystem is unhealthy. Retry later") return ctrl.Result{RequeueAfter: 10 * time.Second}, nil - case errors.As(err, &awaitSelfUpgradeError): - errLogger.Info("wait for self upgrade") - return ctrl.Result{RequeueAfter: 5 * time.Second}, nil case errors.As(err, &stateDiffNotEmptyError): errLogger.Info("requeue until state diff is empty") // fast requeue here since state diff has to be determined again diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 149694f6..7f086fdc 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -81,7 +81,6 @@ func NewBlueprintSpecChangeUseCase( // Returns a domainservice.NotFoundError if the blueprintId does not correspond to a blueprintSpec or // a domainservice.InternalError if there is any error while loading or persisting the blueprintSpec or // a domainservice.ConflictError if there was a concurrent write or -// a domain.AwaitSelfUpgradeError if we need to wait for a self-upgrade or // a domain.InvalidBlueprintError if the blueprint is invalid. func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.Context, blueprintId string) error { logger := log.FromContext(givenCtx). @@ -188,7 +187,6 @@ func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, bluep err = useCase.dogusUpToDateUseCase.CheckDogus(ctx, blueprint) if err != nil { - // could be a domain.AwaitSelfUpgradeError to trigger another reconcile return err } diff --git a/pkg/application/initiaiteBlueprintStatusUseCase_test.go b/pkg/application/initiaiteBlueprintStatusUseCase_test.go index 3b2cb27c..1071612a 100644 --- a/pkg/application/initiaiteBlueprintStatusUseCase_test.go +++ b/pkg/application/initiaiteBlueprintStatusUseCase_test.go @@ -56,7 +56,7 @@ func TestInitiateBlueprintStatusUseCase_InitateConditions(t *testing.T) { }, }, }, - wantUnknownConditions: []string{domain.ConditionExecutable, domain.ConditionEcosystemHealthy, domain.ConditionCompleted, domain.ConditionSelfUpgradeCompleted}, + wantUnknownConditions: []string{domain.ConditionExecutable, domain.ConditionEcosystemHealthy, domain.ConditionCompleted}, wantErr: nil, }, { @@ -73,9 +73,6 @@ func TestInitiateBlueprintStatusUseCase_InitateConditions(t *testing.T) { { Type: domain.ConditionEcosystemHealthy, }, - { - Type: domain.ConditionSelfUpgradeCompleted, - }, { Type: domain.ConditionCompleted, }, diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index b993e10a..17676f88 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -34,19 +34,18 @@ type BlueprintSpec struct { type Condition = metav1.Condition const ( - ConditionValid = "Valid" - ConditionExecutable = "Executable" - ConditionEcosystemHealthy = "EcosystemHealthy" - ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" - ConditionCompleted = "Completed" - ConditionLastApplySucceeded = "LastApplySucceeded" + ConditionValid = "Valid" + ConditionExecutable = "Executable" + ConditionEcosystemHealthy = "EcosystemHealthy" + ConditionCompleted = "Completed" + ConditionLastApplySucceeded = "LastApplySucceeded" ReasonLastApplyErrorAtDogus = "DoguApplyFailure" ReasonLastApplyErrorAtConfig = "ConfigApplyFailure" ) var ( - BlueprintConditions = []string{ConditionValid, ConditionExecutable, ConditionEcosystemHealthy, ConditionSelfUpgradeCompleted, ConditionCompleted, ConditionLastApplySucceeded} + BlueprintConditions = []string{ConditionValid, ConditionExecutable, ConditionEcosystemHealthy, ConditionCompleted, ConditionLastApplySucceeded} // ActionSwitchDoguNamespace is an exception and should be handled with the blueprint config. notAllowedDoguActions = []Action{ActionDowngrade, ActionSwitchDoguNamespace} @@ -346,28 +345,6 @@ func (spec *BlueprintSpec) ShouldBeApplied() bool { return !meta.IsStatusConditionTrue(spec.Conditions, ConditionCompleted) || spec.StateDiff.HasChanges() } -func (spec *BlueprintSpec) MarkWaitingForSelfUpgrade() { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionSelfUpgradeCompleted, - Status: metav1.ConditionFalse, - Reason: "AwaitSelfUpgrade", - }) - if conditionChanged { - spec.Events = append(spec.Events, AwaitSelfUpgradeEvent{}) - } -} - -func (spec *BlueprintSpec) MarkSelfUpgradeCompleted() { - conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ - Type: ConditionSelfUpgradeCompleted, - Status: metav1.ConditionTrue, - Reason: "Completed", - }) - if conditionChanged { - spec.Events = append(spec.Events, SelfUpgradeCompletedEvent{}) - } -} - func (spec *BlueprintSpec) resetCompletedConditionAfterStateDiff() bool { if spec.StateDiff.HasChanges() { conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 31640422..2e7d719a 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -595,48 +595,6 @@ func TestBlueprintSpec_ShouldBeApplied(t *testing.T) { } -func TestBlueprintSpec_MarkWaitingForSelfUpgrade(t *testing.T) { - t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{} - blueprint.MarkWaitingForSelfUpgrade() - - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionSelfUpgradeCompleted)) - assert.Equal(t, []Event{AwaitSelfUpgradeEvent{}}, blueprint.Events) - }) - - t.Run("repeated call -> no event", func(t *testing.T) { - blueprint := BlueprintSpec{} - - blueprint.MarkWaitingForSelfUpgrade() - blueprint.Events = []Event(nil) - blueprint.MarkWaitingForSelfUpgrade() - - assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionSelfUpgradeCompleted)) - assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if condition did not change") - }) -} - -func TestBlueprintSpec_MarkSelfUpgradeCompleted(t *testing.T) { - t.Run("first call -> new event", func(t *testing.T) { - blueprint := BlueprintSpec{} - blueprint.MarkSelfUpgradeCompleted() - - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, ConditionSelfUpgradeCompleted)) - assert.Equal(t, []Event{SelfUpgradeCompletedEvent{}}, blueprint.Events) - }) - - t.Run("repeated call -> no event", func(t *testing.T) { - blueprint := BlueprintSpec{} - - blueprint.MarkSelfUpgradeCompleted() - blueprint.Events = []Event(nil) - blueprint.MarkSelfUpgradeCompleted() - - assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, ConditionSelfUpgradeCompleted)) - assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") - }) -} - func TestBlueprintSpec_HandleHealthResult(t *testing.T) { t.Run("healthy", func(t *testing.T) { blueprint := BlueprintSpec{} diff --git a/pkg/domain/errors.go b/pkg/domain/errors.go index a7d09d44..6cc97364 100644 --- a/pkg/domain/errors.go +++ b/pkg/domain/errors.go @@ -72,14 +72,6 @@ func (e *MultipleBlueprintsError) Error() string { return e.Message } -type AwaitSelfUpgradeError struct { - Message string -} - -func (e *AwaitSelfUpgradeError) Error() string { - return e.Message -} - type StateDiffNotEmptyError struct { Message string } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 5a6a698e..86c0dcbf 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -250,23 +250,3 @@ func (e EcosystemConfigAppliedEvent) Name() string { func (e EcosystemConfigAppliedEvent) Message() string { return "ecosystem config applied" } - -type AwaitSelfUpgradeEvent struct{} - -func (e AwaitSelfUpgradeEvent) Name() string { - return "AwaitSelfUpgrade" -} - -func (e AwaitSelfUpgradeEvent) Message() string { - return "the operator awaits an upgrade for itself before other changes will be applied" -} - -type SelfUpgradeCompletedEvent struct{} - -func (e SelfUpgradeCompletedEvent) Name() string { - return "SelfUpgradeCompleted" -} - -func (e SelfUpgradeCompletedEvent) Message() string { - return "if a self upgrade was necessary, it was successful" -} diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 4c0e5f52..185866fc 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -166,18 +166,6 @@ func TestEvents(t *testing.T) { expectedName: "EcosystemConfigApplied", expectedMessage: "ecosystem config applied", }, - { - name: "await self upgrade", - event: AwaitSelfUpgradeEvent{}, - expectedName: "AwaitSelfUpgrade", - expectedMessage: "the operator awaits an upgrade for itself before other changes will be applied", - }, - { - name: "self upgrade completed", - event: SelfUpgradeCompletedEvent{}, - expectedName: "SelfUpgradeCompleted", - expectedMessage: "if a self upgrade was necessary, it was successful", - }, } for _, tt := range tests { From 3dd94be9b54f76c4dde05c1fb5fc7f02e5523f6d Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 7 Oct 2025 11:00:32 +0200 Subject: [PATCH 110/119] #121 set executable to false on missing config reference --- .../reconciler/blueprint_controller.go | 2 +- .../reconciler/blueprint_controller_test.go | 2 +- pkg/domain/blueprintSpec.go | 10 ++++++- pkg/domain/blueprintSpec_test.go | 26 +++++++++++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index 469e8135..ba0fb284 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -102,7 +102,7 @@ func decideRequeueForError(logger logr.Logger, err error) (ctrl.Result, error) { // fast requeue here since state diff has to be determined again return ctrl.Result{RequeueAfter: 1 * time.Second}, nil case errors.As(err, &multipleBlueprintsError): - errLogger.Error(err, "Ecosystem contains multiple blueprints - delete all but one. Retry later") + errLogger.Error(err, "Ecosystem contains multiple blueprints - delete all except one. Retry later") // fast requeue here since state diff has to be determined again return ctrl.Result{RequeueAfter: 10 * time.Second}, nil case errors.As(err, &dogusNotUpToDateError): diff --git a/pkg/adapter/reconciler/blueprint_controller_test.go b/pkg/adapter/reconciler/blueprint_controller_test.go index 1412335a..86fd44c0 100644 --- a/pkg/adapter/reconciler/blueprint_controller_test.go +++ b/pkg/adapter/reconciler/blueprint_controller_test.go @@ -197,7 +197,7 @@ func Test_decideRequeueForError(t *testing.T) { // then require.NoError(t, err) assert.Equal(t, ctrl.Result{RequeueAfter: 10 * time.Second}, actual) - assert.Contains(t, logSinkMock.output, "0: Ecosystem contains multiple blueprints - delete all but one. Retry later") + assert.Contains(t, logSinkMock.output, "0: Ecosystem contains multiple blueprints - delete all except one. Retry later") }) t.Run("NotFoundError, should not retry if DoNotRetry-Flag is set", func(t *testing.T) { // given diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index a4c10dfa..1797c6f8 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -237,7 +237,15 @@ func (spec *BlueprintSpec) removeConfigForMaskedDogus() Config { // MissingConfigReferences adds a given error, which was caused during preparations for determining the state diff func (spec *BlueprintSpec) MissingConfigReferences(error error) { - spec.Events = append(spec.Events, NewMissingConfigReferencesEvent(error)) + conditionChanged := meta.SetStatusCondition(&spec.Conditions, metav1.Condition{ + Type: ConditionExecutable, + Status: metav1.ConditionFalse, + Reason: "MissingConfigReferences", + Message: error.Error(), + }) + if conditionChanged { + spec.Events = append(spec.Events, NewMissingConfigReferencesEvent(error)) + } } // DetermineStateDiff creates the StateDiff between the blueprint and the actual state of the ecosystem. diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 9290d57d..7ff2caea 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -275,11 +275,27 @@ func Test_BlueprintSpec_CalculateEffectiveBlueprint(t *testing.T) { } func TestBlueprintSpec_MissingConfigReferences(t *testing.T) { - blueprint := BlueprintSpec{} - blueprint.MissingConfigReferences(assert.AnError) - require.Equal(t, 1, len(blueprint.Events)) - assert.Equal(t, "MissingConfigReferences", blueprint.Events[0].Name()) - assert.Equal(t, assert.AnError.Error(), blueprint.Events[0].Message()) + t.Run("first call -> new event", func(t *testing.T) { + blueprint := BlueprintSpec{} + blueprint.MissingConfigReferences(assert.AnError) + + require.Equal(t, 1, len(blueprint.Events)) + assert.Equal(t, "MissingConfigReferences", blueprint.Events[0].Name()) + assert.Equal(t, assert.AnError.Error(), blueprint.Events[0].Message()) + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionExecutable)) + }) + + t.Run("repeated call -> no event", func(t *testing.T) { + blueprint := BlueprintSpec{} + + blueprint.MissingConfigReferences(assert.AnError) + blueprint.Events = []Event(nil) + blueprint.MissingConfigReferences(assert.AnError) + + assert.True(t, meta.IsStatusConditionFalse(blueprint.Conditions, ConditionExecutable)) + assert.Equal(t, []Event(nil), blueprint.Events, "no additional event if status already was AwaitSelfUpgrade") + }) + } func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { From 0d0d76e9fa77d12ae6c1d7173f90ae71227cf2e9 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 7 Oct 2025 21:45:17 +0200 Subject: [PATCH 111/119] #121 extract apply and preparation use case and clean up tests --- pkg/application/blueprintApplyUseCase.go | 88 ++ .../blueprintPreparationUseCase.go | 63 + pkg/application/blueprintSpecChangeUseCase.go | 152 +- .../blueprintSpecChangeUseCase_test.go | 1279 +++++------------ pkg/application/completeBlueprintUseCase.go | 4 - .../completeBlueprintUseCase_test.go | 24 - pkg/bootstrap.go | 4 +- 7 files changed, 528 insertions(+), 1086 deletions(-) create mode 100644 pkg/application/blueprintApplyUseCase.go create mode 100644 pkg/application/blueprintPreparationUseCase.go diff --git a/pkg/application/blueprintApplyUseCase.go b/pkg/application/blueprintApplyUseCase.go new file mode 100644 index 00000000..506e1d2f --- /dev/null +++ b/pkg/application/blueprintApplyUseCase.go @@ -0,0 +1,88 @@ +package application + +import ( + "context" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" +) + +type BlueprintApplyUseCase struct { + completeUseCase completeBlueprintUseCase + ecosystemConfigUseCase ecosystemConfigUseCase + selfUpgradeUseCase selfUpgradeUseCase + applyComponentUseCase applyComponentsUseCase + applyDogusUseCase applyDogusUseCase + healthUseCase ecosystemHealthUseCase + dogusUpToDateUseCase dogusUpToDateUseCase +} + +func NewBlueprintApplyUseCase( + completeUseCase completeBlueprintUseCase, + ecosystemConfigUseCase ecosystemConfigUseCase, + selfUpgradeUseCase selfUpgradeUseCase, + applyComponentUseCase applyComponentsUseCase, + applyDogusUseCase applyDogusUseCase, + healthUseCase ecosystemHealthUseCase, + dogusUpToDateUseCase dogusUpToDateUseCase, +) BlueprintApplyUseCase { + return BlueprintApplyUseCase{ + completeUseCase: completeUseCase, + ecosystemConfigUseCase: ecosystemConfigUseCase, + selfUpgradeUseCase: selfUpgradeUseCase, + applyComponentUseCase: applyComponentUseCase, + applyDogusUseCase: applyDogusUseCase, + healthUseCase: healthUseCase, + dogusUpToDateUseCase: dogusUpToDateUseCase, + } +} + +func (useCase *BlueprintApplyUseCase) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) + if err != nil { + // could be a domain.AwaitSelfUpgradeError to trigger another reconcile + return err + } + err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) + if err != nil { + return err + } + changedComponents, err := useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) + if err != nil { + return err + } + // check after applying components + if changedComponents { + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } + } + changedDogus, err := useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) + if err != nil { + return err + } + // check after installing or updating dogus + if changedDogus { + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } + } + + err = useCase.dogusUpToDateUseCase.CheckDogus(ctx, blueprint) + if err != nil { + // could be a domain.AwaitSelfUpgradeError to trigger another reconcile + return err + } + + // Only complete if there are no changes left + if blueprint.StateDiff.HasChanges() { + return &domain.StateDiffNotEmptyError{Message: "cannot complete blueprint because the StateDiff has still changes"} + } else { + err = useCase.completeUseCase.CompleteBlueprint(ctx, blueprint) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/application/blueprintPreparationUseCase.go b/pkg/application/blueprintPreparationUseCase.go new file mode 100644 index 00000000..365953d2 --- /dev/null +++ b/pkg/application/blueprintPreparationUseCase.go @@ -0,0 +1,63 @@ +package application + +import ( + "context" + + "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" +) + +type BlueprintPreparationUseCase struct { + initialStatus initialBlueprintStatusUseCase + validation blueprintSpecValidationUseCase + effectiveBlueprint effectiveBlueprintUseCase + stateDiff stateDiffUseCase + healthUseCase ecosystemHealthUseCase +} + +func NewBlueprintPreparationUseCase( + initialStatus initialBlueprintStatusUseCase, + validation blueprintSpecValidationUseCase, + effectiveBlueprint effectiveBlueprintUseCase, + stateDiff stateDiffUseCase, + ecosystemHealthUseCase ecosystemHealthUseCase, +) BlueprintPreparationUseCase { + return BlueprintPreparationUseCase{ + initialStatus: initialStatus, + validation: validation, + effectiveBlueprint: effectiveBlueprint, + stateDiff: stateDiff, + healthUseCase: ecosystemHealthUseCase, + } +} + +func (useCase *BlueprintPreparationUseCase) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { + err := useCase.initialStatus.InitateConditions(ctx, blueprint) + if err != nil { + return err + } + err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) + if err != nil { + return err + } + err = useCase.effectiveBlueprint.CalculateEffectiveBlueprint(ctx, blueprint) + if err != nil { + return err + } + err = useCase.validation.ValidateBlueprintSpecDynamically(ctx, blueprint) + if err != nil { + return err + } + // always check health here, even if we already know here, that we don't need to apply anything + // because we need to update the health condition + _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) + if err != nil { + return err + } + err = useCase.stateDiff.DetermineStateDiff(ctx, blueprint) + if err != nil { + // error could be either a technical error from a repository or an InvalidBlueprintError from the domain + // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. + return err + } + return nil +} diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index f3fa722a..37815eea 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -9,75 +9,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -type BlueprintPreparationUseCases struct { - initialStatus initialBlueprintStatusUseCase - validation blueprintSpecValidationUseCase - effectiveBlueprint effectiveBlueprintUseCase - stateDiff stateDiffUseCase - healthUseCase ecosystemHealthUseCase -} - -func NewBlueprintPreparationUseCases( - initialStatus initialBlueprintStatusUseCase, - validation blueprintSpecValidationUseCase, - effectiveBlueprint effectiveBlueprintUseCase, - stateDiff stateDiffUseCase, - ecosystemHealthUseCase ecosystemHealthUseCase, -) BlueprintPreparationUseCases { - return BlueprintPreparationUseCases{ - initialStatus: initialStatus, - validation: validation, - effectiveBlueprint: effectiveBlueprint, - stateDiff: stateDiff, - healthUseCase: ecosystemHealthUseCase, - } -} - -type BlueprintApplyUseCases struct { - completeUseCase completeBlueprintUseCase - ecosystemConfigUseCase ecosystemConfigUseCase - selfUpgradeUseCase selfUpgradeUseCase - applyComponentUseCase applyComponentsUseCase - applyDogusUseCase applyDogusUseCase - healthUseCase ecosystemHealthUseCase - dogusUpToDateUseCase dogusUpToDateUseCase -} - -func NewBlueprintApplyUseCases( - completeUseCase completeBlueprintUseCase, - ecosystemConfigUseCase ecosystemConfigUseCase, - selfUpgradeUseCase selfUpgradeUseCase, - applyComponentUseCase applyComponentsUseCase, - applyDogusUseCase applyDogusUseCase, - healthUseCase ecosystemHealthUseCase, - dogusUpToDateUseCase dogusUpToDateUseCase, -) BlueprintApplyUseCases { - return BlueprintApplyUseCases{ - completeUseCase: completeUseCase, - ecosystemConfigUseCase: ecosystemConfigUseCase, - selfUpgradeUseCase: selfUpgradeUseCase, - applyComponentUseCase: applyComponentUseCase, - applyDogusUseCase: applyDogusUseCase, - healthUseCase: healthUseCase, - dogusUpToDateUseCase: dogusUpToDateUseCase, - } -} - type BlueprintSpecChangeUseCase struct { - repo blueprintSpecRepository - preparationUseCases BlueprintPreparationUseCases - applyUseCases BlueprintApplyUseCases + repo blueprintSpecRepository + preparationUseCase BlueprintPreparationUseCase + applyUseCase BlueprintApplyUseCase } func NewBlueprintSpecChangeUseCase( repo blueprintSpecRepository, - preparationUseCases BlueprintPreparationUseCases, - applyUseCases BlueprintApplyUseCases, + preparationUseCase BlueprintPreparationUseCase, + applyUseCase BlueprintApplyUseCase, ) *BlueprintSpecChangeUseCase { return &BlueprintSpecChangeUseCase{ - repo: repo, - preparationUseCases: preparationUseCases, - applyUseCases: applyUseCases, + repo: repo, + preparationUseCase: preparationUseCase, + applyUseCase: applyUseCase, } } @@ -107,7 +53,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C logger.V(1).Info("handle blueprint") - err = useCase.preparationUseCases.prepareBlueprint(ctx, blueprint) + err = useCase.preparationUseCase.prepareBlueprint(ctx, blueprint) if err != nil { return err } @@ -118,7 +64,7 @@ func (useCase *BlueprintSpecChangeUseCase) HandleUntilApplied(givenCtx context.C } // === Apply from here on === - err = useCase.applyUseCases.applyBlueprint(ctx, blueprint) + err = useCase.applyUseCase.applyBlueprint(ctx, blueprint) if err != nil { return err } @@ -142,84 +88,6 @@ func (useCase *BlueprintSpecChangeUseCase) handleShouldNotBeApplied(ctx context. return nil } -func (useCase *BlueprintPreparationUseCases) prepareBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - err := useCase.initialStatus.InitateConditions(ctx, blueprint) - if err != nil { - return err - } - err = useCase.validation.ValidateBlueprintSpecStatically(ctx, blueprint) - if err != nil { - return err - } - err = useCase.effectiveBlueprint.CalculateEffectiveBlueprint(ctx, blueprint) - if err != nil { - return err - } - err = useCase.validation.ValidateBlueprintSpecDynamically(ctx, blueprint) - if err != nil { - return err - } - // always check health here, even if we already know here, that we don't need to apply anything - // because we need to update the health condition - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err - } - err = useCase.stateDiff.DetermineStateDiff(ctx, blueprint) - if err != nil { - // error could be either a technical error from a repository or an InvalidBlueprintError from the domain - // both cases can be handled the same way as the calling method (reconciler) can handle the error type itself. - return err - } - return nil -} - -func (useCase *BlueprintApplyUseCases) applyBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - err := useCase.selfUpgradeUseCase.HandleSelfUpgrade(ctx, blueprint) - if err != nil { - // could be a domain.AwaitSelfUpgradeError to trigger another reconcile - return err - } - err = useCase.ecosystemConfigUseCase.ApplyConfig(ctx, blueprint) - if err != nil { - return err - } - changedComponents, err := useCase.applyComponentUseCase.ApplyComponents(ctx, blueprint) - if err != nil { - return err - } - // check after applying components - if changedComponents { - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err - } - } - changedDogus, err := useCase.applyDogusUseCase.ApplyDogus(ctx, blueprint) - if err != nil { - return err - } - // check after installing or updating dogus - if changedDogus { - _, err = useCase.healthUseCase.CheckEcosystemHealth(ctx, blueprint) - if err != nil { - return err - } - } - - err = useCase.dogusUpToDateUseCase.CheckDogus(ctx, blueprint) - if err != nil { - // could be a domain.AwaitSelfUpgradeError to trigger another reconcile - return err - } - - err = useCase.completeUseCase.CompleteBlueprint(ctx, blueprint) - if err != nil { - return err - } - return nil -} - func (useCase *BlueprintSpecChangeUseCase) CheckForMultipleBlueprintResources(ctx context.Context) error { logger := log.FromContext(ctx).WithName("BlueprintSpecChangeUseCase.CheckForMultipleBlueprintResources") diff --git a/pkg/application/blueprintSpecChangeUseCase_test.go b/pkg/application/blueprintSpecChangeUseCase_test.go index 40f35a0f..7fa97d52 100644 --- a/pkg/application/blueprintSpecChangeUseCase_test.go +++ b/pkg/application/blueprintSpecChangeUseCase_test.go @@ -2,7 +2,6 @@ package application import ( "context" - "fmt" "testing" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain" @@ -14,1019 +13,471 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -var testCtx = context.Background() -var testBlueprintId = "testBlueprint1" - -func TestNewBlueprintSpecChangeUseCase(t *testing.T) { - // given - blueprintSpecRepositoryMock := newMockBlueprintSpecRepository(t) - initialStatusUseCaseMock := newMockInitialBlueprintStatusUseCase(t) - validationUseCaseMock := newMockBlueprintSpecValidationUseCase(t) - effectiveUseCaseMock := newMockEffectiveBlueprintUseCase(t) - stateDiffUseCaseMock := newMockStateDiffUseCase(t) - completeUseCaseMock := newMockCompleteBlueprintUseCase(t) - ecosystemConfigUseCaseMock := newMockEcosystemConfigUseCase(t) - selfUpgradeUseCaseMock := newMockSelfUpgradeUseCase(t) - applyComponentUseCaseMock := newMockApplyComponentsUseCase(t) - applyDoguUseCaseMock := newMockApplyDogusUseCase(t) - ecosystemHealthUseCaseMock := newMockEcosystemHealthUseCase(t) - dogusUpToDateUseCaseMock := newMockDogusUpToDateUseCase(t) - - preparationUseCases := NewBlueprintPreparationUseCases( - initialStatusUseCaseMock, - validationUseCaseMock, - effectiveUseCaseMock, - stateDiffUseCaseMock, - ecosystemHealthUseCaseMock, - ) - - applyUseCases := NewBlueprintApplyUseCases( - completeUseCaseMock, - ecosystemConfigUseCaseMock, - selfUpgradeUseCaseMock, - applyComponentUseCaseMock, - applyDoguUseCaseMock, - ecosystemHealthUseCaseMock, - dogusUpToDateUseCaseMock, - ) - - // when - result := NewBlueprintSpecChangeUseCase(blueprintSpecRepositoryMock, preparationUseCases, applyUseCases) - - // then - require.NotNil(t, result) - assert.Equal(t, blueprintSpecRepositoryMock, result.repo) - // preparation use cases - assert.Equal(t, validationUseCaseMock, result.preparationUseCases.validation) - assert.Equal(t, effectiveUseCaseMock, result.preparationUseCases.effectiveBlueprint) - assert.Equal(t, stateDiffUseCaseMock, result.preparationUseCases.stateDiff) - assert.Equal(t, ecosystemHealthUseCaseMock, result.preparationUseCases.healthUseCase) - assert.Equal(t, ecosystemConfigUseCaseMock, result.applyUseCases.ecosystemConfigUseCase) - // apply use cases - assert.Equal(t, selfUpgradeUseCaseMock, result.applyUseCases.selfUpgradeUseCase) - assert.Equal(t, applyComponentUseCaseMock, result.applyUseCases.applyComponentUseCase) - assert.Equal(t, applyDoguUseCaseMock, result.applyUseCases.applyDogusUseCase) - assert.Equal(t, completeUseCaseMock, result.applyUseCases.completeUseCase) - assert.Equal(t, ecosystemHealthUseCaseMock, result.applyUseCases.healthUseCase) - assert.Equal(t, dogusUpToDateUseCaseMock, result.applyUseCases.dogusUpToDateUseCase) -} - -func TestBlueprintSpecChangeUseCase_HandleUntilApplied(t *testing.T) { - testBlueprintSpec := &domain.BlueprintSpec{ +var ( + testCtx = context.Background() + testBlueprintId = "testBlueprint1" + testBlueprintSpec = &domain.BlueprintSpec{ Id: testBlueprintId, StateDiff: domain.StateDiff{DoguDiffs: domain.DoguDiffs{{NeededActions: []domain.Action{domain.ActionInstall}}}}, } - testStoppedBlueprintSpec := &domain.BlueprintSpec{ + testBlueprintSpecEmptyDiff = &domain.BlueprintSpec{ + Id: testBlueprintId, + } + testStoppedBlueprintSpec = &domain.BlueprintSpec{ Id: testBlueprintId, Config: domain.BlueprintConfiguration{Stopped: true}, } - testCompletedBlueprintSpec := &domain.BlueprintSpec{ + testCompletedBlueprintSpec = &domain.BlueprintSpec{ Id: testBlueprintId, Conditions: []domain.Condition{{ Type: domain.ConditionCompleted, Status: metav1.ConditionTrue, }}, } +) - type fields struct { - repo func(t *testing.T) blueprintSpecRepository - initialStatus func(t *testing.T) initialBlueprintStatusUseCase - validation func(t *testing.T) blueprintSpecValidationUseCase - effectiveBlueprint func(t *testing.T) effectiveBlueprintUseCase - stateDiff func(t *testing.T) stateDiffUseCase - applyUseCase func(t *testing.T) completeBlueprintUseCase - ecosystemConfigUseCase func(t *testing.T) ecosystemConfigUseCase - selfUpgradeUseCase func(t *testing.T) selfUpgradeUseCase - applyComponentUseCase func(t *testing.T) applyComponentsUseCase - applyDogusUseCase func(t *testing.T) applyDogusUseCase - healthUseCase func(t *testing.T) ecosystemHealthUseCase - upToDateUseCase func(t *testing.T) dogusUpToDateUseCase - } - type args struct { - givenCtx context.Context - blueprintId string - } +func TestNewBlueprintSpecChangeUseCase(t *testing.T) { + // given + mocks := createAllMocks(t) + preparationUseCases := NewBlueprintPreparationUseCase( + mocks.initialStatus, + mocks.validation, + mocks.effectiveBlueprint, + mocks.stateDiff, + mocks.ecosystemHealth, + ) + applyUseCases := NewBlueprintApplyUseCase( + mocks.completeBlueprint, + mocks.ecosystemConfig, + mocks.selfUpgrade, + mocks.applyComponents, + mocks.applyDogus, + mocks.ecosystemHealth, + mocks.dogusUpToDate, + ) + + // when + result := NewBlueprintSpecChangeUseCase(mocks.repo, preparationUseCases, applyUseCases) - testArgs := args{ - givenCtx: testCtx, - blueprintId: testBlueprintId, + // then + require.NotNil(t, result) + assert.Equal(t, mocks.repo, result.repo) + assertPreparationUseCases(t, result.preparationUseCase, mocks) + assertApplyUseCases(t, result.applyUseCase, mocks) +} + +type allMocks struct { + repo *mockBlueprintSpecRepository + initialStatus *mockInitialBlueprintStatusUseCase + validation *mockBlueprintSpecValidationUseCase + effectiveBlueprint *mockEffectiveBlueprintUseCase + stateDiff *mockStateDiffUseCase + completeBlueprint *mockCompleteBlueprintUseCase + ecosystemConfig *mockEcosystemConfigUseCase + selfUpgrade *mockSelfUpgradeUseCase + applyComponents *mockApplyComponentsUseCase + applyDogus *mockApplyDogusUseCase + ecosystemHealth *mockEcosystemHealthUseCase + dogusUpToDate *mockDogusUpToDateUseCase +} + +func createAllMocks(t *testing.T) *allMocks { + return &allMocks{ + repo: newMockBlueprintSpecRepository(t), + initialStatus: newMockInitialBlueprintStatusUseCase(t), + validation: newMockBlueprintSpecValidationUseCase(t), + effectiveBlueprint: newMockEffectiveBlueprintUseCase(t), + stateDiff: newMockStateDiffUseCase(t), + completeBlueprint: newMockCompleteBlueprintUseCase(t), + ecosystemConfig: newMockEcosystemConfigUseCase(t), + selfUpgrade: newMockSelfUpgradeUseCase(t), + applyComponents: newMockApplyComponentsUseCase(t), + applyDogus: newMockApplyDogusUseCase(t), + ecosystemHealth: newMockEcosystemHealthUseCase(t), + dogusUpToDate: newMockDogusUpToDateUseCase(t), } +} +func assertPreparationUseCases(t *testing.T, useCases BlueprintPreparationUseCase, mocks *allMocks) { + assert.Equal(t, mocks.validation, useCases.validation) + assert.Equal(t, mocks.effectiveBlueprint, useCases.effectiveBlueprint) + assert.Equal(t, mocks.stateDiff, useCases.stateDiff) + assert.Equal(t, mocks.ecosystemHealth, useCases.healthUseCase) +} + +func assertApplyUseCases(t *testing.T, useCases BlueprintApplyUseCase, mocks *allMocks) { + assert.Equal(t, mocks.ecosystemConfig, useCases.ecosystemConfigUseCase) + assert.Equal(t, mocks.selfUpgrade, useCases.selfUpgradeUseCase) + assert.Equal(t, mocks.applyComponents, useCases.applyComponentUseCase) + assert.Equal(t, mocks.applyDogus, useCases.applyDogusUseCase) + assert.Equal(t, mocks.completeBlueprint, useCases.completeUseCase) + assert.Equal(t, mocks.ecosystemHealth, useCases.healthUseCase) + assert.Equal(t, mocks.dogusUpToDate, useCases.dogusUpToDateUseCase) +} + +func TestBlueprintSpecChangeUseCase_HandleUntilApplied_RepositoryErrors(t *testing.T) { + t.Run("should return error on error getting blueprint by id", func(t *testing.T) { + // given + mocks := createAllMocks(t) + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(nil, assert.AnError).Run(func(ctx context.Context, blueprintId string) { + logger, err := logr.FromContext(ctx) + require.NoError(t, err) + assert.NotNil(t, logger) + }) + + useCase := createUseCase(mocks) + + // when + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + + // then + assert.ErrorContains(t, err, "cannot load blueprint spec") + }) +} + +func TestBlueprintSpecChangeUseCase_HandleUntilApplied_PreparationPhaseErrors(t *testing.T) { tests := []struct { - name string - fields fields - args args - wantErr assert.ErrorAssertionFunc + name string + setupMocks func(*allMocks) + wantErrTest func(*testing.T, error) }{ - { - name: "should return error on error getting blueprint by id", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(nil, assert.AnError).Run(func(ctx context.Context, blueprintId string) { - logger, err := logr.FromContext(ctx) - require.NoError(t, err) - assert.NotNil(t, logger) - }) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.ErrorContains(t, err, "cannot load blueprint spec") - }, - }, { name: "should return error on error initially setting the blueprint status", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(assert.AnError) - - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error validating blueprint statically", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(assert.AnError) - - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error calculate effective blueprint", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, mock.Anything).Return(nil) + mocks.effectiveBlueprint.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error validating blueprint dynamically", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, mock.Anything).Return(nil) + mocks.effectiveBlueprint.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error checking ecosystem health", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, mock.Anything).Return(nil) + mocks.effectiveBlueprint.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + mocks.ecosystemHealth.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error determining state diff", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, mock.Anything).Return(nil) + mocks.effectiveBlueprint.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) + mocks.ecosystemHealth.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) + mocks.stateDiff.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(assert.AnError) + }, + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, - { - name: "should return nil and throw stopped event on being stopped", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testStoppedBlueprintSpec, nil) - m.EXPECT().Update(mock.Anything, mock.Anything).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { - assert.Len(t, blueprint.Events, 1) - assert.Equal(t, domain.BlueprintStoppedEvent{}.Name(), blueprint.Events[0].Name()) - }).Return(nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testStoppedBlueprintSpec).Return(nil) + } - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testStoppedBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testStoppedBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testStoppedBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testStoppedBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testStoppedBlueprintSpec).Return(nil) - return m - }, - }, - args: testArgs, - wantErr: assert.NoError, - }, - { - name: "should not apply on being completed and no new statediff", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testCompletedBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testCompletedBlueprintSpec).Return(nil) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mocks := createAllMocks(t) + tt.setupMocks(mocks) + useCase := createUseCase(mocks) - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testCompletedBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testCompletedBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testCompletedBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testCompletedBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testCompletedBlueprintSpec).Return(nil) - return m - }, - }, - args: testArgs, - wantErr: assert.NoError, - }, + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + tt.wantErrTest(t, err) + }) + } +} + +func TestBlueprintSpecChangeUseCase_HandleUntilApplied_SpecialBlueprintStates(t *testing.T) { + t.Run("should handle stopped blueprint", func(t *testing.T) { + // given + mocks := createAllMocks(t) + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testStoppedBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testStoppedBlueprintSpec) + mocks.repo.EXPECT().Update(mock.Anything, mock.Anything).Run(func(ctx context.Context, blueprint *domain.BlueprintSpec) { + assert.Len(t, blueprint.Events, 1) + assert.Equal(t, domain.BlueprintStoppedEvent{}.Name(), blueprint.Events[0].Name()) + }).Return(nil) + + useCase := createUseCase(mocks) + + // when + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + + // then + assert.NoError(t, err) + }) + + t.Run("should not apply completed blueprint with no diff", func(t *testing.T) { + // given + mocks := createAllMocks(t) + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testCompletedBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testCompletedBlueprintSpec) + + useCase := createUseCase(mocks) + + // when + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + + // then + assert.NoError(t, err) + }) +} + +func TestBlueprintSpecChangeUseCase_HandleUntilApplied_ApplyPhaseErrors(t *testing.T) { + tests := []struct { + name string + setupMocks func(*allMocks) + wantErrTest func(*testing.T, error) + }{ { name: "should return error on error handle self upgrade", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error apply config", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error apply components", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error check health after component apply", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError).Times(1) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(true, nil) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(true, nil) + mocks.ecosystemHealth.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error apply dogus", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - applyDogusUseCase: func(t *testing.T) applyDogusUseCase { - m := newMockApplyDogusUseCase(t) - m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + mocks.applyDogus.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error check health after dogu apply", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil).Times(1) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - applyDogusUseCase: func(t *testing.T) applyDogusUseCase { - m := newMockApplyDogusUseCase(t) - m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(true, nil) - return m - }, - }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + mocks.applyDogus.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(true, nil) + mocks.ecosystemHealth.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, assert.AnError) + }, + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { name: "should return error on error checking if dogus are up to date", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) + mocks.applyDogus.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) + mocks.dogusUpToDate.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(assert.AnError) + }, + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mocks := createAllMocks(t) + tt.setupMocks(mocks) + useCase := createUseCase(mocks) + + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + tt.wantErrTest(t, err) + }) + } +} - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - applyDogusUseCase: func(t *testing.T) applyDogusUseCase { - m := newMockApplyDogusUseCase(t) - m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { - m := newMockDogusUpToDateUseCase(t) - m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, +func TestBlueprintSpecChangeUseCase_HandleUntilApplied_CompletionScenarios(t *testing.T) { + tests := []struct { + name string + setupMocks func(*allMocks) + wantErrTest func(*testing.T, error) + }{ + { + name: "should return nil on complete success", + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpecEmptyDiff, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpecEmptyDiff) + setupSuccessfulApplyPhaseExceptComplete(mocks, testBlueprintSpecEmptyDiff) + mocks.completeBlueprint.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpecEmptyDiff).Return(nil) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.NoError(t, err) }, }, { name: "should return error on error complete blueprint", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - applyDogusUseCase: func(t *testing.T) applyDogusUseCase { - m := newMockApplyDogusUseCase(t) - m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { - m := newMockDogusUpToDateUseCase(t) - m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyUseCase: func(t *testing.T) completeBlueprintUseCase { - m := newMockCompleteBlueprintUseCase(t) - m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(assert.AnError) - return m - }, + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpecEmptyDiff, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpecEmptyDiff) + setupSuccessfulApplyPhaseExceptComplete(mocks, testBlueprintSpecEmptyDiff) + mocks.completeBlueprint.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpecEmptyDiff).Return(assert.AnError) }, - args: testArgs, - wantErr: func(t assert.TestingT, err error, i ...interface{}) bool { - return assert.Error(t, err) + wantErrTest: func(t *testing.T, err error) { + assert.Error(t, err) }, }, { - name: "should return nil on success", - fields: fields{ - repo: func(t *testing.T) blueprintSpecRepository { - m := newMockBlueprintSpecRepository(t) - m.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) - return m - }, - initialStatus: func(t *testing.T) initialBlueprintStatusUseCase { - m := newMockInitialBlueprintStatusUseCase(t) - m.EXPECT().InitateConditions(mock.Anything, testBlueprintSpec).Return(nil) - - return m - }, - validation: func(t *testing.T) blueprintSpecValidationUseCase { - m := newMockBlueprintSpecValidationUseCase(t) - m.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, testBlueprintSpec).Return(nil) - m.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - effectiveBlueprint: func(t *testing.T) effectiveBlueprintUseCase { - m := newMockEffectiveBlueprintUseCase(t) - m.EXPECT().CalculateEffectiveBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - healthUseCase: func(t *testing.T) ecosystemHealthUseCase { - m := newMockEcosystemHealthUseCase(t) - m.EXPECT().CheckEcosystemHealth(mock.Anything, testBlueprintSpec).Return(ecosystem.HealthResult{}, nil) - return m - }, - stateDiff: func(t *testing.T) stateDiffUseCase { - m := newMockStateDiffUseCase(t) - m.EXPECT().DetermineStateDiff(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - selfUpgradeUseCase: func(t *testing.T) selfUpgradeUseCase { - m := newMockSelfUpgradeUseCase(t) - m.EXPECT().HandleSelfUpgrade(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - ecosystemConfigUseCase: func(t *testing.T) ecosystemConfigUseCase { - m := newMockEcosystemConfigUseCase(t) - m.EXPECT().ApplyConfig(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyComponentUseCase: func(t *testing.T) applyComponentsUseCase { - m := newMockApplyComponentsUseCase(t) - m.EXPECT().ApplyComponents(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - applyDogusUseCase: func(t *testing.T) applyDogusUseCase { - m := newMockApplyDogusUseCase(t) - m.EXPECT().ApplyDogus(mock.Anything, testBlueprintSpec).Return(false, nil) - return m - }, - upToDateUseCase: func(t *testing.T) dogusUpToDateUseCase { - m := newMockDogusUpToDateUseCase(t) - m.EXPECT().CheckDogus(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, - applyUseCase: func(t *testing.T) completeBlueprintUseCase { - m := newMockCompleteBlueprintUseCase(t) - m.EXPECT().CompleteBlueprint(mock.Anything, testBlueprintSpec).Return(nil) - return m - }, + name: "should return StateDiffNotEmptyError when diff not empty", + setupMocks: func(mocks *allMocks) { + mocks.repo.EXPECT().GetById(mock.Anything, testBlueprintId).Return(testBlueprintSpec, nil) + setupSuccessfulPreparationPhase(mocks, testBlueprintSpec) + setupSuccessfulApplyPhaseExceptComplete(mocks, testBlueprintSpec) + }, + wantErrTest: func(t *testing.T, err error) { + var targetError *domain.StateDiffNotEmptyError + assert.Error(t, err) + assert.ErrorAs(t, err, &targetError) }, - args: testArgs, - wantErr: assert.NoError, }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var repo blueprintSpecRepository - if tt.fields.repo != nil { - repo = tt.fields.repo(t) - } - - var initialStatus initialBlueprintStatusUseCase - if tt.fields.initialStatus != nil { - initialStatus = tt.fields.initialStatus(t) - } + mocks := createAllMocks(t) + tt.setupMocks(mocks) + useCase := createUseCase(mocks) - var validation blueprintSpecValidationUseCase - if tt.fields.validation != nil { - validation = tt.fields.validation(t) - } - - var effectiveBlueprint effectiveBlueprintUseCase - if tt.fields.effectiveBlueprint != nil { - effectiveBlueprint = tt.fields.effectiveBlueprint(t) - } - - var stateDiff stateDiffUseCase - if tt.fields.stateDiff != nil { - stateDiff = tt.fields.stateDiff(t) - } - - var completeUseCase completeBlueprintUseCase - if tt.fields.applyUseCase != nil { - completeUseCase = tt.fields.applyUseCase(t) - } - - var ecoConfigUseCase ecosystemConfigUseCase - if tt.fields.ecosystemConfigUseCase != nil { - ecoConfigUseCase = tt.fields.ecosystemConfigUseCase(t) - } - - var selfUpgrade selfUpgradeUseCase - if tt.fields.selfUpgradeUseCase != nil { - selfUpgrade = tt.fields.selfUpgradeUseCase(t) - } - - var applyComponentUseCase applyComponentsUseCase - if tt.fields.applyComponentUseCase != nil { - applyComponentUseCase = tt.fields.applyComponentUseCase(t) - } + err := useCase.HandleUntilApplied(testCtx, testBlueprintId) + tt.wantErrTest(t, err) + }) + } +} - var applyDoguUseCase applyDogusUseCase - if tt.fields.applyDogusUseCase != nil { - applyDoguUseCase = tt.fields.applyDogusUseCase(t) - } +func createUseCase(mocks *allMocks) *BlueprintSpecChangeUseCase { + preparationUseCases := BlueprintPreparationUseCase{ + initialStatus: mocks.initialStatus, + validation: mocks.validation, + effectiveBlueprint: mocks.effectiveBlueprint, + stateDiff: mocks.stateDiff, + healthUseCase: mocks.ecosystemHealth, + } + applyUseCases := BlueprintApplyUseCase{ + completeUseCase: mocks.completeBlueprint, + ecosystemConfigUseCase: mocks.ecosystemConfig, + selfUpgradeUseCase: mocks.selfUpgrade, + applyComponentUseCase: mocks.applyComponents, + applyDogusUseCase: mocks.applyDogus, + healthUseCase: mocks.ecosystemHealth, + dogusUpToDateUseCase: mocks.dogusUpToDate, + } - var ecoHealthUseCase ecosystemHealthUseCase - if tt.fields.healthUseCase != nil { - ecoHealthUseCase = tt.fields.healthUseCase(t) - } + return &BlueprintSpecChangeUseCase{ + repo: mocks.repo, + preparationUseCase: preparationUseCases, + applyUseCase: applyUseCases, + } +} - var upToDateUseCase dogusUpToDateUseCase - if tt.fields.upToDateUseCase != nil { - upToDateUseCase = tt.fields.upToDateUseCase(t) - } +func setupSuccessfulPreparationPhase(mocks *allMocks, spec *domain.BlueprintSpec) { + mocks.initialStatus.EXPECT().InitateConditions(mock.Anything, mock.Anything).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecStatically(mock.Anything, mock.Anything).Return(nil) + mocks.effectiveBlueprint.EXPECT().CalculateEffectiveBlueprint(mock.Anything, spec).Return(nil) + mocks.validation.EXPECT().ValidateBlueprintSpecDynamically(mock.Anything, spec).Return(nil) + mocks.ecosystemHealth.EXPECT().CheckEcosystemHealth(mock.Anything, spec).Return(ecosystem.HealthResult{}, nil).Times(1) + mocks.stateDiff.EXPECT().DetermineStateDiff(mock.Anything, spec).Return(nil) +} - useCase := &BlueprintSpecChangeUseCase{ - repo: repo, - preparationUseCases: BlueprintPreparationUseCases{ - initialStatus: initialStatus, - validation: validation, - effectiveBlueprint: effectiveBlueprint, - stateDiff: stateDiff, - healthUseCase: ecoHealthUseCase, - }, - applyUseCases: BlueprintApplyUseCases{ - completeUseCase: completeUseCase, - ecosystemConfigUseCase: ecoConfigUseCase, - selfUpgradeUseCase: selfUpgrade, - applyComponentUseCase: applyComponentUseCase, - applyDogusUseCase: applyDoguUseCase, - healthUseCase: ecoHealthUseCase, - dogusUpToDateUseCase: upToDateUseCase, - }, - } - tt.wantErr(t, useCase.HandleUntilApplied(tt.args.givenCtx, tt.args.blueprintId), fmt.Sprintf("HandleUntilApplied(%v, %v)", tt.args.givenCtx, tt.args.blueprintId)) - }) - } +func setupSuccessfulApplyPhaseExceptComplete(mocks *allMocks, spec *domain.BlueprintSpec) { + mocks.selfUpgrade.EXPECT().HandleSelfUpgrade(mock.Anything, mock.Anything).Return(nil) + mocks.ecosystemConfig.EXPECT().ApplyConfig(mock.Anything, spec).Return(nil) + mocks.applyComponents.EXPECT().ApplyComponents(mock.Anything, spec).Return(false, nil) + mocks.applyDogus.EXPECT().ApplyDogus(mock.Anything, spec).Return(false, nil) + mocks.dogusUpToDate.EXPECT().CheckDogus(mock.Anything, spec).Return(nil) + // Note: no completeBlueprint expectation - this allows the completion steps to be tested } func TestBlueprintSpecChangeUseCase_CheckForMultipleBlueprintResources(t *testing.T) { diff --git a/pkg/application/completeBlueprintUseCase.go b/pkg/application/completeBlueprintUseCase.go index 45844d61..c8a0c746 100644 --- a/pkg/application/completeBlueprintUseCase.go +++ b/pkg/application/completeBlueprintUseCase.go @@ -24,10 +24,6 @@ func NewCompleteBlueprintUseCase( // CompleteBlueprint handles the completion of the blueprint after all other steps were successful. // returns a domainservice.InternalError on any error. func (useCase *CompleteBlueprintUseCase) CompleteBlueprint(ctx context.Context, blueprint *domain.BlueprintSpec) error { - // Only complete if there are no changes left - if blueprint.StateDiff.HasChanges() { - return &domain.StateDiffNotEmptyError{Message: "cannot complete blueprint because the StateDiff has still changes"} - } changed := blueprint.Complete() if changed { err := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/completeBlueprintUseCase_test.go b/pkg/application/completeBlueprintUseCase_test.go index 8969248d..162c57a2 100644 --- a/pkg/application/completeBlueprintUseCase_test.go +++ b/pkg/application/completeBlueprintUseCase_test.go @@ -33,30 +33,6 @@ func TestApplyBlueprintSpecUseCase_CompleteBlueprint(t *testing.T) { assert.True(t, meta.IsStatusConditionTrue(blueprint.Conditions, domain.ConditionCompleted)) }) - t.Run("stateDiffNotEnmptyError, if StateDiff not empty", func(t *testing.T) { - blueprint := &domain.BlueprintSpec{ - Conditions: []domain.Condition{}, - StateDiff: domain.StateDiff{ - ComponentDiffs: []domain.ComponentDiff{ - { - Name: "k8s-dogu-operator", - NeededActions: []domain.Action{domain.ActionUpgrade}, - }, - }, - }, - } - - repoMock := newMockBlueprintSpecRepository(t) - useCase := NewCompleteBlueprintUseCase(repoMock) - - err := useCase.CompleteBlueprint(testCtx, blueprint) - - require.Error(t, err) - var targetErr *domain.StateDiffNotEmptyError - assert.ErrorAs(t, err, &targetErr) - assert.ErrorContains(t, err, "cannot complete blueprint because the StateDiff has still changes") - assert.Empty(t, blueprint.Conditions) - }) t.Run("no change if already completed", func(t *testing.T) { blueprint := &domain.BlueprintSpec{ Conditions: []domain.Condition{}, diff --git a/pkg/bootstrap.go b/pkg/bootstrap.go index a3effe87..b47f7dcc 100644 --- a/pkg/bootstrap.go +++ b/pkg/bootstrap.go @@ -92,14 +92,14 @@ func Bootstrap(restConfig *rest.Config, eventRecorder record.EventRecorder, name selfUpgradeUseCase := application.NewSelfUpgradeUseCase(blueprintRepo, componentRepo, componentInstallationUseCase, blueprintOperatorName.SimpleName) dogusUpToDateUseCase := application.NewDogusUpToDateUseCase(blueprintRepo, doguInstallationUseCase) - preparationUseCases := application.NewBlueprintPreparationUseCases( + preparationUseCases := application.NewBlueprintPreparationUseCase( initialBlueprintStateUseCase, blueprintValidationUseCase, effectiveBlueprintUseCase, stateDiffUseCase, ecosystemHealthUseCase, ) - applyUseCases := application.NewBlueprintApplyUseCases( + applyUseCases := application.NewBlueprintApplyUseCase( completeBlueprintSpecUseCase, ConfigUseCase, selfUpgradeUseCase, From 6522e7310b1269d6d30b50b35a77ba270223fcde Mon Sep 17 00:00:00 2001 From: meiserloh Date: Tue, 7 Oct 2025 21:45:54 +0200 Subject: [PATCH 112/119] #121 move domain object changes into aggregate --- pkg/application/doguInstallationUseCase.go | 2 +- pkg/application/dogusUpToDateUseCase.go | 1 + pkg/application/ecosystemConfigUseCase.go | 4 ++-- pkg/domain/blueprintSpec.go | 4 ++++ pkg/domain/ecosystem/doguInstallation.go | 4 ++++ 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index f06bdea1..f984802c 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -177,7 +177,7 @@ func (useCase *DoguInstallationUseCase) applyDoguState( if len(doguDiff.NeededActions) > 0 { logger.Info("upgrade dogu") // remove potential pause reconciliation flags here so that the dogu gets updates again - doguInstallation.PauseReconciliation = false + doguInstallation.SetReconciliationPaused(false) return useCase.doguRepo.Update(ctx, doguInstallation) } diff --git a/pkg/application/dogusUpToDateUseCase.go b/pkg/application/dogusUpToDateUseCase.go index 0132b8e1..2d41a6bf 100644 --- a/pkg/application/dogusUpToDateUseCase.go +++ b/pkg/application/dogusUpToDateUseCase.go @@ -26,6 +26,7 @@ func NewDogusUpToDateUseCase( } // CheckDogus checks that all dogs are up to date. +// returns domain.DogusNotUpToDateError if the dogu config or installed version are not up to date yet or // returns domainservice.ConflictError if there was a concurrent update to the blueprint or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. func (useCase *DogusUpToDateUseCase) CheckDogus(ctx context.Context, blueprint *domain.BlueprintSpec) error { diff --git a/pkg/application/ecosystemConfigUseCase.go b/pkg/application/ecosystemConfigUseCase.go index 075d5b4e..d1eb9ee8 100644 --- a/pkg/application/ecosystemConfigUseCase.go +++ b/pkg/application/ecosystemConfigUseCase.go @@ -56,7 +56,7 @@ func (useCase *EcosystemConfigUseCase) ApplyConfig(ctx context.Context, blueprin } if blueprint.StateDiff.HasConfigChanges() { - blueprint.Events = append(blueprint.Events, domain.EcosystemConfigAppliedEvent{}) + blueprint.MarkEcosystemConfigApplied() repoErr := useCase.blueprintRepository.Update(ctx, blueprint) if repoErr != nil { @@ -80,7 +80,7 @@ func (useCase *EcosystemConfigUseCase) pauseReconciliationForDogus(ctx context.C (globalConfigChanges || diff.DoguConfigDiffs[dogu.Name.SimpleName].HasChanges() || diff.SensitiveDoguConfigDiffs[dogu.Name.SimpleName].HasChanges()) { - dogu.PauseReconciliation = true + dogu.SetReconciliationPaused(true) err = useCase.doguInstallationRepository.Update(ctx, dogu) if err != nil { return fmt.Errorf("could not pause reconciliation for dogu: %w", err) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 1797c6f8..28cc03ca 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -482,3 +482,7 @@ func (spec *BlueprintSpec) SetLastApplySucceededConditionOnError(reason string, return false } + +func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { + spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) +} diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 5c6f4695..551fd2d0 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -161,3 +161,7 @@ func (dogu *DoguInstallation) UpdateProxyAdditionalConfig(value AdditionalConfig func (dogu *DoguInstallation) UpdateAdditionalMounts(mounts []AdditionalMount) { dogu.AdditionalMounts = mounts } + +func (dogu *DoguInstallation) SetReconciliationPaused(isPaused bool) { + dogu.PauseReconciliation = isPaused +} From f2f85483f08fcbe092e6c776973001fe1c5062f3 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 8 Oct 2025 10:50:18 +0200 Subject: [PATCH 113/119] #121 Move blueprint changes into the aggregate --- pkg/application/applyDogusUseCase.go | 6 +- pkg/application/blueprintSpecChangeUseCase.go | 2 +- pkg/domain/blueprintSpec.go | 12 +++ pkg/domain/blueprintSpec_test.go | 99 +++++++++++++++++++ 4 files changed, 114 insertions(+), 5 deletions(-) diff --git a/pkg/application/applyDogusUseCase.go b/pkg/application/applyDogusUseCase.go index c9004b62..bb427eb4 100644 --- a/pkg/application/applyDogusUseCase.go +++ b/pkg/application/applyDogusUseCase.go @@ -26,15 +26,13 @@ func NewApplyDogusUseCase( // ApplyDogus applies dogus if necessary. // The conditions in the blueprint will be set accordingly. +// returns true if the dogus were applied, false if not. // returns domainservice.ConflictError if there was a concurrent update to the blueprint or // returns a domainservice.InternalError if there was an unspecified error while collecting or modifying the ecosystem state. func (useCase *ApplyDogusUseCase) ApplyDogus(ctx context.Context, blueprint *domain.BlueprintSpec) (bool, error) { err := useCase.doguInstallUseCase.ApplyDoguStates(ctx, blueprint) isDogusApplied := blueprint.StateDiff.DoguDiffs.HasChanges() && err == nil - if isDogusApplied { - blueprint.Events = append(blueprint.Events, domain.DogusAppliedEvent{Diffs: blueprint.StateDiff.DoguDiffs}) - } - conditionChanged := blueprint.SetLastApplySucceededConditionOnError(domain.ReasonLastApplyErrorAtDogus, err) + conditionChanged := blueprint.MarkDogusApplied(isDogusApplied, err) if isDogusApplied || conditionChanged { updateErr := useCase.repo.Update(ctx, blueprint) diff --git a/pkg/application/blueprintSpecChangeUseCase.go b/pkg/application/blueprintSpecChangeUseCase.go index 37815eea..b58b9e91 100644 --- a/pkg/application/blueprintSpecChangeUseCase.go +++ b/pkg/application/blueprintSpecChangeUseCase.go @@ -77,7 +77,7 @@ func (useCase *BlueprintSpecChangeUseCase) handleShouldNotBeApplied(ctx context. // post event and log only if blueprint is stopped, all other cases are just NoOps if blueprint.Config.Stopped { logger.Info("blueprint is currently set as stopped and will not be applied") - blueprint.Events = append(blueprint.Events, domain.BlueprintStoppedEvent{}) + blueprint.MarkBlueprintStopped() err := useCase.repo.Update(ctx, blueprint) if err != nil { return fmt.Errorf("cannot update status to set stopped event: %w", err) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 28cc03ca..01801264 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -486,3 +486,15 @@ func (spec *BlueprintSpec) SetLastApplySucceededConditionOnError(reason string, func (spec *BlueprintSpec) MarkEcosystemConfigApplied() { spec.Events = append(spec.Events, EcosystemConfigAppliedEvent{}) } + +func (spec *BlueprintSpec) MarkBlueprintStopped() { + spec.Events = append(spec.Events, BlueprintStoppedEvent{}) +} + +func (spec *BlueprintSpec) MarkDogusApplied(isDogusApplied bool, err error) bool { + if isDogusApplied { + spec.Events = append(spec.Events, DogusAppliedEvent{Diffs: spec.StateDiff.DoguDiffs}) + } + conditionChanged := spec.SetLastApplySucceededConditionOnError(ReasonLastApplyErrorAtDogus, err) + return conditionChanged +} diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 7ff2caea..1ac673c7 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -802,3 +802,102 @@ func TestBlueprintSpec_HandleHealthResult(t *testing.T) { assert.False(t, changed, "condition should not change here") }) } + +func TestBlueprintSpec_MarkBlueprintStopped(t *testing.T) { + t.Run("should add a blueprint stopped event", func(t *testing.T) { + // given + spec := &BlueprintSpec{} + // when + spec.MarkBlueprintStopped() + // then + require.Len(t, spec.Events, 1) + assert.Equal(t, BlueprintStoppedEvent{}, spec.Events[0]) + }) +} + +func TestBlueprintSpec_MarkDogusApplied(t *testing.T) { + t.Run("should add event if dogus are applied and no error occurred", func(t *testing.T) { + // given + spec := &BlueprintSpec{ + StateDiff: StateDiff{ + DoguDiffs: DoguDiffs{{DoguName: "test"}}, + }, + } + // when + changed := spec.MarkDogusApplied(true, nil) + // then + assert.False(t, changed) + require.Len(t, spec.Events, 1) + assert.Equal(t, DogusAppliedEvent{Diffs: DoguDiffs{{DoguName: "test"}}}, spec.Events[0]) + assert.Nil(t, meta.FindStatusCondition(spec.Conditions, ConditionLastApplySucceeded)) + }) + + t.Run("should not add event if dogus are not applied and no error occurred", func(t *testing.T) { + // given + spec := &BlueprintSpec{} + // when + changed := spec.MarkDogusApplied(false, nil) + // then + assert.False(t, changed) + require.Empty(t, spec.Events) + assert.Nil(t, meta.FindStatusCondition(spec.Conditions, ConditionLastApplySucceeded)) + }) + + t.Run("should add event and set condition on error", func(t *testing.T) { + // given + spec := &BlueprintSpec{ + StateDiff: StateDiff{ + DoguDiffs: DoguDiffs{{DoguName: "test"}}, + }, + } + // when + changed := spec.MarkDogusApplied(true, assert.AnError) + // then + assert.True(t, changed) + require.Len(t, spec.Events, 2) + assert.Equal(t, DogusAppliedEvent{Diffs: DoguDiffs{{DoguName: "test"}}}, spec.Events[0]) + assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, spec.Events[1]) + + condition := meta.FindStatusCondition(spec.Conditions, ConditionLastApplySucceeded) + require.NotNil(t, condition) + assert.Equal(t, metav1.ConditionFalse, condition.Status) + assert.Equal(t, ReasonLastApplyErrorAtDogus, condition.Reason) + assert.Equal(t, assert.AnError.Error(), condition.Message) + }) + + t.Run("should not add applied event but set condition on error", func(t *testing.T) { + // given + spec := &BlueprintSpec{} + // when + changed := spec.MarkDogusApplied(false, assert.AnError) + // then + assert.True(t, changed) + require.Len(t, spec.Events, 1) + assert.Equal(t, ExecutionFailedEvent{err: assert.AnError}, spec.Events[0]) + + condition := meta.FindStatusCondition(spec.Conditions, ConditionLastApplySucceeded) + require.NotNil(t, condition) + assert.Equal(t, metav1.ConditionFalse, condition.Status) + assert.Equal(t, ReasonLastApplyErrorAtDogus, condition.Reason) + assert.Equal(t, assert.AnError.Error(), condition.Message) + }) + + t.Run("should not change condition if already set", func(t *testing.T) { + // given + spec := &BlueprintSpec{ + Conditions: []Condition{ + { + Type: ConditionLastApplySucceeded, + Status: metav1.ConditionFalse, + Reason: ReasonLastApplyErrorAtDogus, + Message: assert.AnError.Error(), + }, + }, + } + // when + changed := spec.MarkDogusApplied(false, assert.AnError) + // then + assert.False(t, changed) + require.Empty(t, spec.Events) + }) +} From f93e267cab4d2202f079ce8be3acb4dad5078eb8 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 8 Oct 2025 11:48:52 +0200 Subject: [PATCH 114/119] #121 remove unnecessary rights to update finalizers --- k8s/helm/templates/blueprint-editor-role.yaml | 10 +--------- pkg/adapter/reconciler/blueprint_controller.go | 1 - 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/k8s/helm/templates/blueprint-editor-role.yaml b/k8s/helm/templates/blueprint-editor-role.yaml index f17b4b3c..0ce46fe9 100644 --- a/k8s/helm/templates/blueprint-editor-role.yaml +++ b/k8s/helm/templates/blueprint-editor-role.yaml @@ -9,8 +9,7 @@ metadata: {{- include "k8s-blueprint-operator.labels" . | nindent 4 }} name: {{ include "k8s-blueprint-operator.name" . }}-blueprint-editor-role rules: - - # issue permissions to read/update fields beyond the status or finalizer fields + # issue permissions to read/update fields beyond the status - apiGroups: - k8s.cloudogu.com resources: @@ -21,13 +20,6 @@ rules: - patch - update - watch - # issue permissions to update the finalizer field that may control CR deletion - - apiGroups: - - k8s.cloudogu.com - resources: - - blueprints/finalizers - verbs: - - update # issue permissions to update the status which contains blueprint processing data - apiGroups: - k8s.cloudogu.com diff --git a/pkg/adapter/reconciler/blueprint_controller.go b/pkg/adapter/reconciler/blueprint_controller.go index ba0fb284..8e72d600 100644 --- a/pkg/adapter/reconciler/blueprint_controller.go +++ b/pkg/adapter/reconciler/blueprint_controller.go @@ -32,7 +32,6 @@ func NewBlueprintReconciler( // +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints,verbs=get;watch;update;patch // +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=k8s.cloudogu.com,resources=blueprints/finalizers,verbs=update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. From 7476cd79cae35bcb7907413380949b7af92229a2 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Wed, 8 Oct 2025 13:45:01 +0200 Subject: [PATCH 115/119] #121 merge proxy config dogu action into one to simplify the status --- CHANGELOG.md | 1 + pkg/application/doguInstallationUseCase.go | 14 ++------ .../doguInstallationUseCase_test.go | 10 +++--- pkg/domain/ecosystem/doguInstallation.go | 12 ++----- pkg/domain/ecosystem/doguInstallation_test.go | 33 ++++--------------- pkg/domain/events.go | 8 +---- pkg/domain/events_test.go | 4 +-- pkg/domain/stateDiff.go | 8 +---- pkg/domain/stateDiffDogu.go | 17 ++++------ pkg/domain/stateDiffDogu_test.go | 6 ++-- 10 files changed, 30 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2b5476..78b1be37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Note, that events are for humans. You should not compute them for automation as they have no consistency guarantees. - [#121] Upgrade to Golang v1.25.1 - [#121] Upgrade Makefiles to v10.4.0 +- [#121] *breaking* merge proxy config dogu action into one to simplify the status ### Removed - [#119] *breaking* no support for v1 blueprint CRs anymore diff --git a/pkg/application/doguInstallationUseCase.go b/pkg/application/doguInstallationUseCase.go index f984802c..a00fd6b5 100644 --- a/pkg/application/doguInstallationUseCase.go +++ b/pkg/application/doguInstallationUseCase.go @@ -152,17 +152,9 @@ func (useCase *DoguInstallationUseCase) applyDoguState( logger.Info("update minimum volume size for dogu") doguInstallation.UpdateMinVolumeSize(doguDiff.Expected.MinVolumeSize) continue - case domain.ActionUpdateDoguProxyBodySize: - logger.Info("update proxy body size for dogu") - doguInstallation.UpdateProxyBodySize(doguDiff.Expected.ReverseProxyConfig.MaxBodySize) - continue - case domain.ActionUpdateDoguProxyRewriteTarget: - logger.Info("update proxy rewrite target for dogu") - doguInstallation.UpdateProxyRewriteTarget(doguDiff.Expected.ReverseProxyConfig.RewriteTarget) - continue - case domain.ActionUpdateDoguProxyAdditionalConfig: - logger.Info("update proxy additional config for dogu") - doguInstallation.UpdateProxyAdditionalConfig(doguDiff.Expected.ReverseProxyConfig.AdditionalConfig) + case domain.ActionUpdateDoguReverseProxyConfig: + logger.Info("update proxy config for dogu") + doguInstallation.UpdateProxyConfig(doguDiff.Expected.ReverseProxyConfig) continue case domain.ActionUpdateAdditionalMounts: logger.Info("update additional mounts") diff --git a/pkg/application/doguInstallationUseCase_test.go b/pkg/application/doguInstallationUseCase_test.go index 4be02427..8ae3f225 100644 --- a/pkg/application/doguInstallationUseCase_test.go +++ b/pkg/application/doguInstallationUseCase_test.go @@ -307,7 +307,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { MaxBodySize: &expectedProxyBodySize, }, }, - NeededActions: []domain.Action{domain.ActionUpdateDoguProxyBodySize}, + NeededActions: []domain.Action{domain.ActionUpdateDoguReverseProxyConfig}, }, dogu, domain.BlueprintConfiguration{}, @@ -346,7 +346,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { RewriteTarget: expectedTarget, }, }, - NeededActions: []domain.Action{domain.ActionUpdateDoguProxyRewriteTarget}, + NeededActions: []domain.Action{domain.ActionUpdateDoguReverseProxyConfig}, }, dogu, domain.BlueprintConfiguration{}, @@ -385,7 +385,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { AdditionalConfig: expectedAdditionalConfig, }, }, - NeededActions: []domain.Action{domain.ActionUpdateDoguProxyAdditionalConfig}, + NeededActions: []domain.Action{domain.ActionUpdateDoguReverseProxyConfig}, }, dogu, domain.BlueprintConfiguration{}, @@ -513,9 +513,7 @@ func TestDoguInstallationUseCase_applyDoguState(t *testing.T) { }, NeededActions: []domain.Action{ domain.ActionUpgrade, - domain.ActionUpdateDoguProxyAdditionalConfig, - domain.ActionUpdateDoguProxyBodySize, - domain.ActionUpdateDoguProxyRewriteTarget, + domain.ActionUpdateDoguReverseProxyConfig, domain.ActionUpdateDoguResourceMinVolumeSize, }, }, diff --git a/pkg/domain/ecosystem/doguInstallation.go b/pkg/domain/ecosystem/doguInstallation.go index 551fd2d0..df20554c 100644 --- a/pkg/domain/ecosystem/doguInstallation.go +++ b/pkg/domain/ecosystem/doguInstallation.go @@ -142,20 +142,12 @@ func (dogu *DoguInstallation) SwitchNamespace(newNamespace cescommons.Namespace, return nil } -func (dogu *DoguInstallation) UpdateProxyBodySize(value *BodySize) { - dogu.ReverseProxyConfig.MaxBodySize = value -} - func (dogu *DoguInstallation) UpdateMinVolumeSize(size *VolumeSize) { dogu.MinVolumeSize = size } -func (dogu *DoguInstallation) UpdateProxyRewriteTarget(value RewriteTarget) { - dogu.ReverseProxyConfig.RewriteTarget = value -} - -func (dogu *DoguInstallation) UpdateProxyAdditionalConfig(value AdditionalConfig) { - dogu.ReverseProxyConfig.AdditionalConfig = value +func (dogu *DoguInstallation) UpdateProxyConfig(config ReverseProxyConfig) { + dogu.ReverseProxyConfig = config } func (dogu *DoguInstallation) UpdateAdditionalMounts(mounts []AdditionalMount) { diff --git a/pkg/domain/ecosystem/doguInstallation_test.go b/pkg/domain/ecosystem/doguInstallation_test.go index 399bddaf..b52c17f0 100644 --- a/pkg/domain/ecosystem/doguInstallation_test.go +++ b/pkg/domain/ecosystem/doguInstallation_test.go @@ -127,42 +127,23 @@ func TestDoguInstallation_SwitchNamespace(t *testing.T) { }) } -func TestDoguInstallation_UpdateProxyBodySize(t *testing.T) { +func TestDoguInstallation_UpdateProxyConfig(t *testing.T) { t.Run("should set property", func(t *testing.T) { // given bodySize := resource.MustParse("1G") dogu := DoguInstallation{} // when - dogu.UpdateProxyBodySize(&bodySize) + reverseProxyConfig := ReverseProxyConfig{ + MaxBodySize: &bodySize, + RewriteTarget: RewriteTarget(rewriteTarget), + AdditionalConfig: AdditionalConfig(additionalConfig), + } + dogu.UpdateProxyConfig(reverseProxyConfig) // then assert.Equal(t, &bodySize, dogu.ReverseProxyConfig.MaxBodySize) - }) -} - -func TestDoguInstallation_UpdateProxyRewriteTarget(t *testing.T) { - t.Run("should set property", func(t *testing.T) { - // given - dogu := DoguInstallation{} - - // when - dogu.UpdateProxyRewriteTarget(RewriteTarget(rewriteTarget)) - - // then assert.Equal(t, RewriteTarget(rewriteTarget), dogu.ReverseProxyConfig.RewriteTarget) - }) -} - -func TestDoguInstallation_UpdateProxyAdditionalConfig(t *testing.T) { - t.Run("should set property", func(t *testing.T) { - // given - dogu := DoguInstallation{} - - // when - dogu.UpdateProxyAdditionalConfig(AdditionalConfig(additionalConfig)) - - // then assert.Equal(t, AdditionalConfig(additionalConfig), dogu.ReverseProxyConfig.AdditionalConfig) }) } diff --git a/pkg/domain/events.go b/pkg/domain/events.go index 26c7e2ea..b20c04a1 100644 --- a/pkg/domain/events.go +++ b/pkg/domain/events.go @@ -107,18 +107,12 @@ func (s StateDiffDeterminedEvent) Name() string { return "StateDiffDetermined" } -const groupedDoguProxyAction = "update reverse proxy" - // Message contains the StateDiffDoguDeterminedEvent's statistics message. func (s StateDiffDeterminedEvent) Message() string { var amountActions = map[Action]int{} for _, diff := range s.doguDiffs { for _, action := range diff.NeededActions { - if action.IsDoguProxyAction() { - amountActions[groupedDoguProxyAction]++ - } else { - amountActions[action]++ - } + amountActions[action]++ } } diff --git a/pkg/domain/events_test.go b/pkg/domain/events_test.go index 8961bbb6..93f36987 100644 --- a/pkg/domain/events_test.go +++ b/pkg/domain/events_test.go @@ -71,11 +71,11 @@ func TestEvents(t *testing.T) { {NeededActions: []Action{ActionInstall}}, {NeededActions: []Action{ActionUninstall}}, {NeededActions: []Action{ActionUninstall}}, - {NeededActions: []Action{ActionUpgrade, ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguProxyBodySize, ActionUpdateDoguProxyRewriteTarget, ActionUpdateDoguProxyAdditionalConfig}}, + {NeededActions: []Action{ActionUpgrade, ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguReverseProxyConfig}}, {NeededActions: []Action{ActionDowngrade}}, }}), expectedName: "StateDiffDetermined", - expectedMessage: "state diff determined:\n 0 config changes ()\n 11 dogu actions (\"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update resource minimum volume size\": 1, \"update reverse proxy\": 3, \"upgrade\": 1)", + expectedMessage: "state diff determined:\n 0 config changes ()\n 9 dogu actions (\"downgrade\": 1, \"install\": 2, \"uninstall\": 3, \"update resource minimum volume size\": 1, \"update reverse proxy\": 1, \"upgrade\": 1)", }, { name: "component state diff determined", diff --git a/pkg/domain/stateDiff.go b/pkg/domain/stateDiff.go index fc9b777e..dc922ee6 100644 --- a/pkg/domain/stateDiff.go +++ b/pkg/domain/stateDiff.go @@ -23,19 +23,13 @@ const ( ActionUpgrade = "upgrade" ActionDowngrade = "downgrade" ActionSwitchDoguNamespace = "dogu namespace switch" - ActionUpdateDoguProxyBodySize = "update proxy body size" - ActionUpdateDoguProxyRewriteTarget = "update proxy rewrite target" - ActionUpdateDoguProxyAdditionalConfig = "update proxy additional config" + ActionUpdateDoguReverseProxyConfig = "update reverse proxy" ActionUpdateDoguResourceMinVolumeSize = "update resource minimum volume size" ActionSwitchComponentNamespace = "component namespace switch" ActionUpdateComponentDeployConfig = "update component package config" ActionUpdateAdditionalMounts = "update additional mounts" ) -func (a Action) IsDoguProxyAction() bool { - return a == ActionUpdateDoguProxyBodySize || a == ActionUpdateDoguProxyAdditionalConfig || a == ActionUpdateDoguProxyRewriteTarget -} - func (diff StateDiff) HasChanges() bool { return diff.DoguDiffs.HasChanges() || diff.ComponentDiffs.HasChanges() || diff --git a/pkg/domain/stateDiffDogu.go b/pkg/domain/stateDiffDogu.go index 47da9356..328608c4 100644 --- a/pkg/domain/stateDiffDogu.go +++ b/pkg/domain/stateDiffDogu.go @@ -197,17 +197,12 @@ func appendActionForReverseProxyConfig(neededActions []Action, expected DoguDiff return neededActions } - neededActions = appendActionForProxyBodySizes(neededActions, exp, act) - - if exp.RewriteTarget != act.RewriteTarget { - neededActions = append(neededActions, ActionUpdateDoguProxyRewriteTarget) - } - - if exp.AdditionalConfig != act.AdditionalConfig { - neededActions = append(neededActions, ActionUpdateDoguProxyAdditionalConfig) + if exp.RewriteTarget != act.RewriteTarget || exp.AdditionalConfig != act.AdditionalConfig { + neededActions = append(neededActions, ActionUpdateDoguReverseProxyConfig) + return neededActions // early return to avoid duplicate actions } - return neededActions + return appendActionForProxyBodySizes(neededActions, exp, act) } func appendActionForMinVolumeSize(actions []Action, expectedSize *ecosystem.VolumeSize, actualSize *ecosystem.VolumeSize) []Action { @@ -231,10 +226,10 @@ func appendActionForProxyBodySizes( if expectedProxyBodySize == nil && actualProxyBodySize == nil { return actions } else if proxyBodySizeIdentityChanged(expectedProxyBodySize, actualProxyBodySize) { - return append(actions, ActionUpdateDoguProxyBodySize) + return append(actions, ActionUpdateDoguReverseProxyConfig) } else { if expectedProxyBodySize != nil && actualProxyBodySize != nil && expectedProxyBodySize.Cmp(*actualProxyBodySize) != 0 { - return append(actions, ActionUpdateDoguProxyBodySize) + return append(actions, ActionUpdateDoguReverseProxyConfig) } } return actions diff --git a/pkg/domain/stateDiffDogu_test.go b/pkg/domain/stateDiffDogu_test.go index 9c81a617..f9bbec38 100644 --- a/pkg/domain/stateDiffDogu_test.go +++ b/pkg/domain/stateDiffDogu_test.go @@ -251,7 +251,7 @@ func Test_determineDoguDiff(t *testing.T) { }, MinVolumeSize: &volumeSize2, }, - NeededActions: []Action{ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguProxyBodySize, ActionUpdateDoguProxyRewriteTarget, ActionUpdateDoguProxyAdditionalConfig, ActionUpgrade}, + NeededActions: []Action{ActionUpdateDoguResourceMinVolumeSize, ActionUpdateDoguReverseProxyConfig, ActionUpgrade}, }, }, { @@ -332,7 +332,7 @@ func Test_determineDoguDiff(t *testing.T) { MaxBodySize: quantity100MPtr, }, }, - NeededActions: []Action{ActionUpdateDoguProxyBodySize}, + NeededActions: []Action{ActionUpdateDoguReverseProxyConfig}, }, }, { @@ -369,7 +369,7 @@ func Test_determineDoguDiff(t *testing.T) { MaxBodySize: quantity100MPtr, }, }, - NeededActions: []Action{ActionUpdateDoguProxyBodySize}, + NeededActions: []Action{ActionUpdateDoguReverseProxyConfig}, }, }, { From 941962abc38c754124fda8c98d5f56be08b23406 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 10 Oct 2025 13:56:45 +0200 Subject: [PATCH 116/119] #121 remove unnecessary patch and update rights to whole blueprint Status is enough --- k8s/helm/templates/blueprint-editor-role.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/k8s/helm/templates/blueprint-editor-role.yaml b/k8s/helm/templates/blueprint-editor-role.yaml index 0ce46fe9..ae09794e 100644 --- a/k8s/helm/templates/blueprint-editor-role.yaml +++ b/k8s/helm/templates/blueprint-editor-role.yaml @@ -17,8 +17,6 @@ rules: verbs: - get - list - - patch - - update - watch # issue permissions to update the status which contains blueprint processing data - apiGroups: From bd8309aafd283f616c456b3447e2c04cac5fecff Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 10 Oct 2025 13:57:04 +0200 Subject: [PATCH 117/119] #121 use crd-conditions --- pkg/domain/blueprintSpec.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 01801264..9c9f9a0b 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -8,6 +8,7 @@ import ( cescommons "github.com/cloudogu/ces-commons-lib/dogu" "github.com/cloudogu/cesapp-lib/core" + v2 "github.com/cloudogu/k8s-blueprint-lib/v2/api/v2" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/common" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/domain/ecosystem" "github.com/cloudogu/k8s-blueprint-operator/v2/pkg/util" @@ -34,12 +35,12 @@ type BlueprintSpec struct { type Condition = metav1.Condition const ( - ConditionValid = "Valid" - ConditionExecutable = "Executable" - ConditionEcosystemHealthy = "EcosystemHealthy" - ConditionSelfUpgradeCompleted = "SelfUpgradeCompleted" - ConditionCompleted = "Completed" - ConditionLastApplySucceeded = "LastApplySucceeded" + ConditionValid = v2.ConditionValid + ConditionExecutable = v2.ConditionExecutable + ConditionEcosystemHealthy = v2.ConditionEcosystemHealthy + ConditionSelfUpgradeCompleted = v2.ConditionSelfUpgradeCompleted + ConditionCompleted = v2.ConditionCompleted + ConditionLastApplySucceeded = v2.ConditionLastApplySucceeded ReasonLastApplyErrorAtComponents = "ComponentApplyFailure" ReasonLastApplyErrorAtDogus = "DoguApplyFailure" From b340d29270493aad4c38519ed7c10279a03dfb0e Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 10 Oct 2025 13:57:15 +0200 Subject: [PATCH 118/119] #121 fix typo --- ...eprintStatusUseCase.go => initiateBlueprintStatusUseCase.go} | 2 +- ...usUseCase_test.go => initiateBlueprintStatusUseCase_test.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/application/{initiaiteBlueprintStatusUseCase.go => initiateBlueprintStatusUseCase.go} (96%) rename pkg/application/{initiaiteBlueprintStatusUseCase_test.go => initiateBlueprintStatusUseCase_test.go} (100%) diff --git a/pkg/application/initiaiteBlueprintStatusUseCase.go b/pkg/application/initiateBlueprintStatusUseCase.go similarity index 96% rename from pkg/application/initiaiteBlueprintStatusUseCase.go rename to pkg/application/initiateBlueprintStatusUseCase.go index b1989824..685a6026 100644 --- a/pkg/application/initiaiteBlueprintStatusUseCase.go +++ b/pkg/application/initiateBlueprintStatusUseCase.go @@ -10,7 +10,7 @@ import ( ) // InitiateBlueprintStatusUseCase contains all use cases which are needed to initiate the -// blueprint status after the determining the state diff. +// blueprint status when it has not yet been fully set. // // Use cases: // - InitateConditions diff --git a/pkg/application/initiaiteBlueprintStatusUseCase_test.go b/pkg/application/initiateBlueprintStatusUseCase_test.go similarity index 100% rename from pkg/application/initiaiteBlueprintStatusUseCase_test.go rename to pkg/application/initiateBlueprintStatusUseCase_test.go From ef37916171b1f46175fd4a5917072277ef1d59d2 Mon Sep 17 00:00:00 2001 From: meiserloh Date: Fri, 10 Oct 2025 14:32:53 +0200 Subject: [PATCH 119/119] #121 clarify messages of not allowed actions --- pkg/domain/blueprintSpec.go | 8 ++++---- pkg/domain/blueprintSpec_test.go | 13 +++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/domain/blueprintSpec.go b/pkg/domain/blueprintSpec.go index 9c9f9a0b..681f08da 100644 --- a/pkg/domain/blueprintSpec.go +++ b/pkg/domain/blueprintSpec.go @@ -423,7 +423,7 @@ func (spec *BlueprintSpec) validateDoguDiffActions(diff DoguDiff) []error { return nil } - return getActionNotAllowedError(action) + return getActionNotAllowedError(action, string(diff.DoguName)) } return nil @@ -433,16 +433,16 @@ func (spec *BlueprintSpec) validateDoguDiffActions(diff DoguDiff) []error { func (spec *BlueprintSpec) validateComponentDiffActions(diff ComponentDiff) []error { return util.Map(diff.NeededActions, func(action Action) error { if slices.Contains(notAllowedComponentActions, action) { - return getActionNotAllowedError(action) + return getActionNotAllowedError(action, string(diff.Name)) } return nil }) } -func getActionNotAllowedError(action Action) *InvalidBlueprintError { +func getActionNotAllowedError(action Action, name string) *InvalidBlueprintError { return &InvalidBlueprintError{ - Message: fmt.Sprintf("action %q is not allowed", action), + Message: fmt.Sprintf("%s: action %q is not allowed", name, action), } } diff --git a/pkg/domain/blueprintSpec_test.go b/pkg/domain/blueprintSpec_test.go index 1ac673c7..37138465 100644 --- a/pkg/domain/blueprintSpec_test.go +++ b/pkg/domain/blueprintSpec_test.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" "testing" cescommons "github.com/cloudogu/ces-commons-lib/dogu" @@ -441,7 +442,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { { Name: cescommons.QualifiedName{ Namespace: "namespace-change", - SimpleName: "name", + SimpleName: "ldap", }, }, }, @@ -453,9 +454,9 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { clusterState := ecosystem.EcosystemState{ InstalledDogus: map[cescommons.SimpleName]*ecosystem.DoguInstallation{ - "name": {Name: cescommons.QualifiedName{ + "ldap": {Name: cescommons.QualifiedName{ Namespace: "namespace", - SimpleName: "name", + SimpleName: "ldap", }}, }, InstalledComponents: map[common.SimpleComponentName]*ecosystem.ComponentInstallation{}, @@ -467,7 +468,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) - assert.ErrorContains(t, err, "action \"dogu namespace switch\" is not allowed") + assert.ErrorContains(t, err, "ldap: action \"dogu namespace switch\" is not allowed") }) t.Run("should return error with not allowed component namespace switch action", func(t *testing.T) { @@ -501,7 +502,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) - assert.ErrorContains(t, err, "action \"component namespace switch\" is not allowed") + assert.ErrorContains(t, err, fmt.Sprintf("%s: action \"component namespace switch\" is not allowed", testComponentName.SimpleName)) }) t.Run("should return error with not allowed component downgrade action", func(t *testing.T) { @@ -535,7 +536,7 @@ func TestBlueprintSpec_DetermineStateDiff(t *testing.T) { // then assert.True(t, meta.IsStatusConditionFalse(spec.Conditions, ConditionExecutable)) require.Error(t, err) - assert.ErrorContains(t, err, "action \"downgrade\" is not allowed") + assert.ErrorContains(t, err, fmt.Sprintf("%s: action \"downgrade\" is not allowed", testComponentName.SimpleName)) }) }