From d0517037e09154d03e173250ce3318c6fc4dc413 Mon Sep 17 00:00:00 2001 From: Eduardo Espadeiro Date: Fri, 24 Jan 2025 13:21:22 +0000 Subject: [PATCH] feat(manifest-service): add DeleteEnvironment transformer (#2217) Add transformer for deleting the environment folder from the manifest repo. Ref: SRX-R3DKS5 --- .../pkg/cmd/server.go | 3 + .../pkg/repository/transformer.go | 41 ++++ .../pkg/repository/transformer_test.go | 214 +++++++++++++++++- 3 files changed, 256 insertions(+), 2 deletions(-) diff --git a/services/manifest-repo-export-service/pkg/cmd/server.go b/services/manifest-repo-export-service/pkg/cmd/server.go index b34b8cc71..d76431d64 100755 --- a/services/manifest-repo-export-service/pkg/cmd/server.go +++ b/services/manifest-repo-export-service/pkg/cmd/server.go @@ -638,6 +638,9 @@ func getTransformer(ctx context.Context, eslEventType db.EventType) (repository. case db.EvtUndeployApplication: //exhaustruct:ignore return &repository.UndeployApplication{}, nil + case db.EvtDeleteEnvironment: + //exhaustruct:ignore + return &repository.DeleteEnvironment{}, nil } return nil, fmt.Errorf("could not find transformer for event type %v", eslEventType) } diff --git a/services/manifest-repo-export-service/pkg/repository/transformer.go b/services/manifest-repo-export-service/pkg/repository/transformer.go index 99bd15343..449738111 100644 --- a/services/manifest-repo-export-service/pkg/repository/transformer.go +++ b/services/manifest-repo-export-service/pkg/repository/transformer.go @@ -26,6 +26,7 @@ import ( "slices" api "github.com/freiheit-com/kuberpult/pkg/api/v1" + "github.com/freiheit-com/kuberpult/pkg/argocd" "github.com/freiheit-com/kuberpult/pkg/auth" "github.com/freiheit-com/kuberpult/pkg/config" "github.com/freiheit-com/kuberpult/pkg/conversion" @@ -1955,3 +1956,43 @@ func (c *DeleteEnvironmentGroupLock) Transform( // group locks are handled on the cd-service, and split into environment locks return "empty commit for group lock deletion", nil } + +type DeleteEnvironment struct { + Environment string `json:"env"` + TransformerMetadata `json:"metadata"` + TransformerEslVersion db.TransformerID `json:"-"` // Tags the transformer with EventSourcingLight eslVersion +} + +func (d *DeleteEnvironment) GetEslVersion() db.TransformerID { + return d.TransformerEslVersion +} + +func (d *DeleteEnvironment) SetEslVersion(id db.TransformerID) { + d.TransformerEslVersion = id +} + +func (d *DeleteEnvironment) GetDBEventType() db.EventType { + return db.EvtDeleteEnvironment +} + +func (d *DeleteEnvironment) Transform(ctx context.Context, state *State, t TransformerContext, transaction *sql.Tx) (string, error) { + fs := state.Filesystem + envDir := fs.Join("environments", d.Environment) + argoCdAppFile := fs.Join("argocd", string(argocd.V1Alpha1), fmt.Sprintf("%s.yaml", d.Environment)) + + err := fs.Remove(envDir) + if errors.Is(err, os.ErrNotExist) { + logger.FromContext(ctx).Sugar().Warnf("DeleteEnvironment: environment directory %q does not exist.", envDir) + } else if err != nil { + return "", fmt.Errorf("error deleting the environment directory %q: %w", envDir, err) + } + + err = fs.Remove(argoCdAppFile) + if errors.Is(err, os.ErrNotExist) { + logger.FromContext(ctx).Sugar().Warnf("DeleteEnvironment: environment's argocd app file %q does not exist.", envDir) + } else if err != nil { + return "", fmt.Errorf("error deleting the environment's argocd app file %q: %w", argoCdAppFile, err) + } + + return fmt.Sprintf("delete environment %q", d.Environment), nil +} diff --git a/services/manifest-repo-export-service/pkg/repository/transformer_test.go b/services/manifest-repo-export-service/pkg/repository/transformer_test.go index ae04f5683..2d576c0f0 100644 --- a/services/manifest-repo-export-service/pkg/repository/transformer_test.go +++ b/services/manifest-repo-export-service/pkg/repository/transformer_test.go @@ -449,8 +449,8 @@ func verifyContent(fs billy.Filesystem, required []*FilenameAndData) error { for _, contentRequirement := range required { if data, err := util.ReadFile(fs, contentRequirement.path); err != nil { return fmt.Errorf("error while opening file %s, error: %w", contentRequirement.path, err) - } else if string(data) != string(contentRequirement.fileData) { - return fmt.Errorf("actual file content of file '%s' is not equal to required content.\nExpected: '%s', actual: '%s'", contentRequirement.path, contentRequirement.fileData, string(data)) + } else if diff := cmp.Diff(string(data), string(contentRequirement.fileData)); diff != "" { + return fmt.Errorf("actual file content of file '%s' is not equal to required content.\nDiff: %s", contentRequirement.path, diff) } } return nil @@ -3021,3 +3021,213 @@ func TestUndeployLogic(t *testing.T) { }) } } + +func TestDeleteEnvironment(t *testing.T) { + const authorName = "testAuthorName" + const authorEmail = "testAuthorEmail@example.com" + envAcceptanceConfig := config.EnvironmentConfig{ + Upstream: &config.EnvironmentConfigUpstream{Environment: envAcceptance, Latest: true}, + ArgoCd: &config.EnvironmentConfigArgoCd{}, + } + envAcceptance2Config := config.EnvironmentConfig{ + Upstream: &config.EnvironmentConfigUpstream{Environment: envAcceptance2, Latest: true}, + ArgoCd: &config.EnvironmentConfigArgoCd{}, + } + + tcs := []struct { + Name string + Transformers []Transformer + expectedData []*FilenameAndData + expectedMissing []*FilenameAndData + expectedMessage string + expectedError error + }{ + { + Name: "create an environment and delete it", + Transformers: []Transformer{ + &CreateEnvironment{ + Environment: envAcceptance, + Config: envAcceptanceConfig, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 1, + }, + &DeleteEnvironment{ + Environment: envAcceptance, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 2, + }, + }, + expectedMissing: []*FilenameAndData{ + { + path: "/environments/acceptance", + fileData: []byte(authorEmail), + }, + { + path: "/argocd/v1alpha1/acceptance.yaml", + fileData: []byte(authorEmail), + }, + }, + expectedMessage: "delete environment \"acceptance\"", + }, + { + Name: "create two environments and delete one of them", + Transformers: []Transformer{ + &CreateEnvironment{ + Environment: envAcceptance, + Config: envAcceptanceConfig, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 1, + }, + &CreateEnvironment{ + Environment: envAcceptance2, + Config: envAcceptance2Config, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 2, + }, + &DeleteEnvironment{ + Environment: envAcceptance, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 3, + }, + }, + expectedMissing: []*FilenameAndData{ + { + path: "/environments/acceptance", + fileData: []byte(authorEmail), + }, + { + path: "/argocd/v1alpha1/acceptance.yaml", + fileData: []byte(authorEmail), + }, + }, + expectedData: []*FilenameAndData{ + { + path: "/environments/acceptance2/config.json", + fileData: []byte(`{ + "upstream": { + "environment": "acceptance2", + "latest": true + }, + "argocd": { + "destination": { + "name": "", + "server": "" + } + } +} +`), + }, + { + path: "/argocd/v1alpha1/acceptance2.yaml", + fileData: []byte(`apiVersion: argoproj.io/v1alpha1 +kind: AppProject +metadata: + name: acceptance2 +spec: + description: acceptance2 + destinations: + - {} + sourceRepos: + - '*' +`), + }, + }, + expectedMessage: "delete environment \"acceptance\"", + }, + { + Name: "delete an environment that does not exist", + Transformers: []Transformer{ + &DeleteEnvironment{ + Environment: envAcceptance, + TransformerMetadata: TransformerMetadata{ + AuthorName: authorName, + AuthorEmail: authorEmail, + }, + TransformerEslVersion: 1, + }, + }, + expectedMessage: "delete environment \"acceptance\"", + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + repo, _ := setupRepositoryTestWithPath(t) + ctx := AddGeneratorToContext(testutil.MakeTestContext(), testutil.NewIncrementalUUIDGenerator()) + + dbHandler := repo.State().DBHandler + err := dbHandler.WithTransactionR(ctx, 0, false, func(ctx context.Context, transaction *sql.Tx) error { + // setup: + // this 'INSERT INTO' would be done one the cd-server side, so we emulate it here: + err2 := dbHandler.DBWriteMigrationsTransformer(ctx, transaction) + if err2 != nil { + t.Fatal(err2) + } + err2 = dbHandler.DBWriteEnvironment(ctx, transaction, envAcceptance, envAcceptanceConfig, []string{}) + if err2 != nil { + return err2 + } + err2 = dbHandler.DBWriteEnvironment(ctx, transaction, envAcceptance2, envAcceptance2Config, []string{}) + if err2 != nil { + return err2 + } + //populate the database + for _, tr := range tc.Transformers { + err2 := dbHandler.DBWriteEslEventInternal(ctx, tr.GetDBEventType(), transaction, t, db.ESLMetadata{AuthorName: tr.GetMetadata().AuthorName, AuthorEmail: tr.GetMetadata().AuthorEmail}) + if err2 != nil { + t.Fatal(err2) + } + } + + for _, t := range tc.Transformers { + err := repo.Apply(ctx, transaction, t) + if err != nil { + return err + } + // just for testing, we push each transformer change separately. + // if you need to debug this test, you can git clone the repo + // and we will only see anything if we push. + err = repo.PushRepo(ctx) + if err != nil { + return err + } + } + + actualMsg := repo.State().Commit.Message() + if diff := cmp.Diff(tc.expectedMessage, actualMsg); diff != "" { + t.Errorf("commit message mismatch (-want, +got):\n%s", diff) + } + + return nil + }) + + if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { + t.Errorf("error mismatch (-want, +got):\n%s", diff) + } + updatedState := repo.State() + + if err := verifyContent(updatedState.Filesystem, tc.expectedData); err != nil { + t.Fatalf("Error while verifying content: %v.\nFilesystem content:\n%s", err, strings.Join(listFiles(updatedState.Filesystem), "\n")) + } + if err := verifyMissing(updatedState.Filesystem, tc.expectedMissing); err != nil { + t.Fatalf("Error while verifying missing content: %v.\nFilesystem content:\n%s", err, strings.Join(listFiles(updatedState.Filesystem), "\n")) + } + }) + } +}