From edec959c4dae86e87a91de140e064fc2539715c8 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:28:53 +0100 Subject: [PATCH 01/12] remove adv2 command --- cmd/plugin/main.go | 3 +-- internal/cli/adv2/adv2.go | 20 -------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 internal/cli/adv2/adv2.go diff --git a/cmd/plugin/main.go b/cmd/plugin/main.go index a4510dc..f4b22c9 100644 --- a/cmd/plugin/main.go +++ b/cmd/plugin/main.go @@ -4,7 +4,6 @@ import ( "fmt" "os" - "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/adv2" "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/clu2adv" "github.com/spf13/cobra" ) @@ -15,7 +14,7 @@ func main() { Short: "Utilities for Terraform's MongoDB Atlas Provider", Aliases: []string{"tf"}, } - terraformCmd.AddCommand(clu2adv.Builder(), adv2.Builder()) + terraformCmd.AddCommand(clu2adv.Builder()) completionOption := &cobra.CompletionOptions{ DisableDefaultCmd: true, diff --git a/internal/cli/adv2/adv2.go b/internal/cli/adv2/adv2.go deleted file mode 100644 index a4bd704..0000000 --- a/internal/cli/adv2/adv2.go +++ /dev/null @@ -1,20 +0,0 @@ -package adv2 - -import ( - "errors" - - "github.com/spf13/cobra" -) - -func Builder() *cobra.Command { - cmd := &cobra.Command{ - Use: "advancedClusterV1ToV2", - Short: "Convert advanced_cluster v1 to v2", - Long: "Convert a Terraform configuration from mongodbatlas_advanced_cluster schema v1 to v2", - Aliases: []string{"adv2"}, - RunE: func(_ *cobra.Command, _ []string) error { - return errors.New("TODO: not implemented yet, will be implemented in the future") - }, - } - return cmd -} From dc4e4f2bd86e2b13f031bd2a2560d04c5f14a99c Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:33:16 +0100 Subject: [PATCH 02/12] e2e tests --- .github/workflows/code-health.yml | 12 ++++++++++++ Makefile | 8 ++++++-- test/e2e/cli.go | 9 +++++++++ test/e2e/cli_test.go | 15 +++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 test/e2e/cli.go create mode 100644 test/e2e/cli_test.go diff --git a/.github/workflows/code-health.yml b/.github/workflows/code-health.yml index 6f6833c..7e14224 100644 --- a/.github/workflows/code-health.yml +++ b/.github/workflows/code-health.yml @@ -20,6 +20,18 @@ jobs: run: make build - name: Unit Test run: make test + + e2e: + runs-on: ubuntu-latest + permissions: {} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 + with: + go-version-file: 'go.mod' + - name: E2E Test + run: make test-e2e + lint: runs-on: ubuntu-latest permissions: {} diff --git a/Makefile b/Makefile index 203e514..4ea7bce 100644 --- a/Makefile +++ b/Makefile @@ -25,11 +25,15 @@ clean: ## Clean binary folders .PHONY: test test: ## Run unit tests - go test ./... -timeout=30s -parallel=4 -race + go test ./internal/... -timeout=30s -parallel=4 -race .PHONY: test-update test-update: ## Run unit tests and update the golden files - go test ./... -timeout=30s -parallel=4 -race -update + go test ./internal/... -timeout=30s -parallel=4 -race -update + +.PHONY: test-e2e +test-e2e: local ## Run E2E tests (running the plugin binary) + ATLAS_CLI_EXTRA_PLUGIN_DIRECTORY="${PWD}/bin-plugin" go test ./test/... -timeout=30s -parallel=4 -race .PHONY: local local: clean build ## Allow to run the plugin locally diff --git a/test/e2e/cli.go b/test/e2e/cli.go new file mode 100644 index 0000000..f76ff3e --- /dev/null +++ b/test/e2e/cli.go @@ -0,0 +1,9 @@ +package e2e + +import "os/exec" + +func RunPlugin(args ...string) (string, error) { + cmd := exec.Command("atlas", args...) + resp, err := cmd.CombinedOutput() + return string(resp), err +} diff --git a/test/e2e/cli_test.go b/test/e2e/cli_test.go new file mode 100644 index 0000000..8655dac --- /dev/null +++ b/test/e2e/cli_test.go @@ -0,0 +1,15 @@ +package e2e_test + +import ( + "testing" + + "github.com/mongodb-labs/atlas-cli-plugin-terraform/test/e2e" + "github.com/stretchr/testify/require" +) + +func TestPlugin(t *testing.T) { + t.Run("Execute TF command", func(t *testing.T) { + resp, err := e2e.RunPlugin("tf") + require.NoError(t, err, resp) + }) +} From 109ad0b8337c29eeffa829e404404e7d8be1c7d3 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:33:43 +0100 Subject: [PATCH 03/12] flags file --- internal/cli/clu2adv/clu2adv.go | 14 ++++++++------ internal/flag/flags.go | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 internal/flag/flags.go diff --git a/internal/cli/clu2adv/clu2adv.go b/internal/cli/clu2adv/clu2adv.go index 6d7015e..92bd509 100644 --- a/internal/cli/clu2adv/clu2adv.go +++ b/internal/cli/clu2adv/clu2adv.go @@ -1,6 +1,7 @@ package clu2adv import ( + "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/flag" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -19,11 +20,12 @@ func Builder() *cobra.Command { return o.Run() }, } - cmd.Flags().StringVarP(&o.file, "file", "f", "", "input file") - _ = cmd.MarkFlagRequired("file") - cmd.Flags().StringVarP(&o.output, "output", "o", "", "output file") - _ = cmd.MarkFlagRequired("output") - cmd.Flags().BoolVarP(&o.replaceOutput, "replaceOutput", "r", false, "replace output file if exists") - cmd.Flags().BoolVarP(&o.watch, "watch", "w", false, "keeps the plugin running and watches the input file for changes") + cmd.Flags().StringVarP(&o.file, flag.File, flag.FileShort, "", "input file") + _ = cmd.MarkFlagRequired(flag.File) + cmd.Flags().StringVarP(&o.output, flag.Output, flag.OutputShort, "", "output file") + _ = cmd.MarkFlagRequired(flag.Output) + cmd.Flags().BoolVarP(&o.replaceOutput, flag.ReplaceOutput, flag.ReplaceOutputShort, false, "replace output file if exists") + cmd.Flags().BoolVarP(&o.watch, flag.Watch, flag.WatchShort, false, "keeps the plugin running and watches the input file for changes") + cmd.Flags().BoolVarP(&o.includeMoved, flag.IncludeMoved, flag.IncludeMovedShort, false, "include moved blocks in the output file") return cmd } diff --git a/internal/flag/flags.go b/internal/flag/flags.go new file mode 100644 index 0000000..d3273fd --- /dev/null +++ b/internal/flag/flags.go @@ -0,0 +1,14 @@ +package flag + +const ( + File = "file" + FileShort = "f" + Output = "output" + OutputShort = "o" + ReplaceOutput = "replaceOutput" + ReplaceOutputShort = "r" + Watch = "watch" + WatchShort = "w" + IncludeMoved = "includeMoved" + IncludeMovedShort = "m" +) From 1312a94f577e75825b6fd9990068a1cb2481fb5c Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:33:57 +0100 Subject: [PATCH 04/12] includeMoved --- README.md | 6 +- internal/cli/clu2adv/opts.go | 3 +- internal/convert/const_names.go | 3 + internal/convert/convert.go | 47 ++++++- internal/convert/convert_test.go | 3 +- .../clu2adv/includeMoved_multiple.in.tf | 71 ++++++++++ .../clu2adv/includeMoved_multiple.out.tf | 125 ++++++++++++++++++ .../clu2adv/includeMoved_single.in.tf | 15 +++ .../clu2adv/includeMoved_single.out.tf | 30 +++++ internal/hcl/hcl.go | 6 + 10 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 internal/convert/testdata/clu2adv/includeMoved_multiple.in.tf create mode 100644 internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf create mode 100644 internal/convert/testdata/clu2adv/includeMoved_single.in.tf create mode 100644 internal/convert/testdata/clu2adv/includeMoved_single.out.tf diff --git a/README.md b/README.md index 83c301a..a504bd2 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,11 @@ you can also use shorter aliases, e.g.: atlas tf clu2adv -f in.tf -o out.tf ``` -If you want to overwrite the output file if it exists, or even use the same output file as the input file, use the `--replaceOutput true` or the `-r` flag. +If you want to include the `moved blocks` in the output file, use the `--includeMoved` or the `-m` flag. -You can use the `--watch true` or the `-w` flag to keep the plugin running and watching for changes in the input file. You can have input and output files open in an editor and see easily how changes to the input file affect the output file. +If you want to overwrite the output file if it exists, or even use the same output file as the input file, use the `--replaceOutput` or the `-r` flag. + +You can use the `--watch` or the `-w` flag to keep the plugin running and watching for changes in the input file. You can have input and output files open in an editor and see easily how changes to the input file affect the output file. ### Limitations diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index 8ba67f5..214b055 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -16,6 +16,7 @@ type opts struct { output string replaceOutput bool watch bool + includeMoved bool } func (o *opts) PreRun() error { @@ -43,7 +44,7 @@ func (o *opts) generateFile(allowParseErrors bool) error { if err != nil { return fmt.Errorf("failed to read file %s: %w", o.file, err) } - outConfig, err := convert.ClusterToAdvancedCluster(inConfig) + outConfig, err := convert.ClusterToAdvancedCluster(inConfig, o.includeMoved) if err != nil { if allowParseErrors { outConfig = []byte("# CONVERT ERROR: " + err.Error() + "\n\n") diff --git a/internal/convert/const_names.go b/internal/convert/const_names.go index a31564e..65fccf1 100644 --- a/internal/convert/const_names.go +++ b/internal/convert/const_names.go @@ -48,4 +48,7 @@ const ( nKey = "key" nValue = "value" nUseRepSpecsPerShard = "use_replication_spec_per_shard" + nMoved = "moved" + nFrom = "from" + nTo = "to" ) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index bed092e..21e55ca 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -40,24 +40,33 @@ type attrVals struct { // All other resources and data sources are left untouched. // Note: hclwrite.Tokens are used instead of cty.Value so expressions with interpolations like var.region can be preserved. // cty.Value only supports literal expressions. -func ClusterToAdvancedCluster(config []byte) ([]byte, error) { +func ClusterToAdvancedCluster(config []byte, includeMoved bool) ([]byte, error) { + var moveLabels []string parser, err := hcl.GetParser(config) if err != nil { return nil, err } - for _, block := range parser.Body().Blocks() { - converted, err := convertResource(block) + parserb := parser.Body() + for _, block := range parserb.Blocks() { + convertedResource, err := convertResource(block) if err != nil { - return nil, err + return nil, + err } - converted = converted || convertDataSource(block) - if converted { + if includeMoved && convertedResource { + if moveLabel := getResourceLabel(block); moveLabel != "" { + moveLabels = append(moveLabels, moveLabel) + } + } + convertedDataSource := convertDataSource(block) + if convertedResource || convertedDataSource { blockb := block.Body() blockb.AppendNewline() hcl.AppendComment(blockb, "Generated by atlas-cli-plugin-terraform.") hcl.AppendComment(blockb, "Please confirm that all references to this resource are updated.") } } + fillMovedBlocks(parserb, moveLabels) return parser.Bytes(), nil } @@ -99,6 +108,24 @@ func convertDataSource(block *hclwrite.Block) bool { return false } +func fillMovedBlocks(body *hclwrite.Body, moveLabels []string) { + if len(moveLabels) == 0 { + return + } + body.AppendNewline() + hcl.AppendComment(body, "Moved blocks") + body.AppendNewline() + for i, moveLabel := range moveLabels { + block := body.AppendNewBlock(nMoved, nil) + blockb := block.Body() + hcl.SetAttrExpr(blockb, nFrom, fmt.Sprintf("%s.%s", cluster, moveLabel)) + hcl.SetAttrExpr(blockb, nTo, fmt.Sprintf("%s.%s", advCluster, moveLabel)) + if i < len(moveLabels)-1 { + body.AppendNewline() + } + } +} + // fillFreeTierCluster is the entry point to convert clusters in free tier func fillFreeTierCluster(resourceb *hclwrite.Body) error { resourceb.SetAttributeValue(nClusterType, cty.StringVal(valClusterType)) @@ -346,6 +373,14 @@ func getResourceName(resource *hclwrite.Block) string { return labels[0] } +func getResourceLabel(resource *hclwrite.Block) string { + labels := resource.Labels() + if len(labels) <= 1 { + return "" + } + return labels[1] +} + func checkDynamicBlock(body *hclwrite.Body) error { for _, block := range body.Blocks() { if block.Type() == "dynamic" { diff --git a/internal/convert/convert_test.go b/internal/convert/convert_test.go index 65056fc..79487ce 100644 --- a/internal/convert/convert_test.go +++ b/internal/convert/convert_test.go @@ -38,7 +38,8 @@ func TestClusterToAdvancedCluster(t *testing.T) { t.Run(testName, func(t *testing.T) { inConfig, err := afero.ReadFile(fs, inputFile) require.NoError(t, err) - outConfig, err := convert.ClusterToAdvancedCluster(inConfig) + includeMoved := strings.Contains(testName, "includeMoved") + outConfig, err := convert.ClusterToAdvancedCluster(inConfig, includeMoved) if err == nil { g.Assert(t, testName, outConfig) } else { diff --git a/internal/convert/testdata/clu2adv/includeMoved_multiple.in.tf b/internal/convert/testdata/clu2adv/includeMoved_multiple.in.tf new file mode 100644 index 0000000..42a3375 --- /dev/null +++ b/internal/convert/testdata/clu2adv/includeMoved_multiple.in.tf @@ -0,0 +1,71 @@ +resource "mongodbatlas_cluster" "cluster1" { + project_id = var.project_id + name = "clu1" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M10" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + } + } +} + +resource "mongodbatlas_cluster" "cluster2" { + project_id = var.project_id + name = "clu2" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M30" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_WEST_2" + electable_nodes = 3 + priority = 7 + } + } +} + +resource "mongodbatlas_cluster" "count" { + # count doesn't affect moved blocks, it works in the same way + count = local.create_cluster ? 1 : 0 + project_id = var.project_id + name = "count" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M30" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_WEST_2" + electable_nodes = 3 + priority = 7 + } + } +} + +resource "mongodbatlas_cluster" "forEach" { + # for_each doesn't affect moved blocks, it works in the same way + for_each = toset(["clu1", "clu2", "clu3"]) + project_id = var.project_id + name = each.key + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M30" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_WEST_2" + electable_nodes = 3 + priority = 7 + } + } +} + +resource "another_resource" "another" { + hello = "there" +} diff --git a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf new file mode 100644 index 0000000..1570ccc --- /dev/null +++ b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf @@ -0,0 +1,125 @@ +resource "mongodbatlas_advanced_cluster" "cluster1" { + project_id = var.project_id + name = "clu1" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M10" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +resource "mongodbatlas_advanced_cluster" "cluster2" { + project_id = var.project_id + name = "clu2" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_WEST_2" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M30" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +resource "mongodbatlas_advanced_cluster" "count" { + # count doesn't affect moved blocks, it works in the same way + count = local.create_cluster ? 1 : 0 + project_id = var.project_id + name = "count" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_WEST_2" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M30" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +resource "mongodbatlas_advanced_cluster" "forEach" { + # for_each doesn't affect moved blocks, it works in the same way + for_each = toset(["clu1", "clu2", "clu3"]) + project_id = var.project_id + name = each.key + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_WEST_2" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M30" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +resource "another_resource" "another" { + hello = "there" +} + +# Moved blocks + +moved { + from = mongodbatlas_cluster.cluster1 + to = mongodbatlas_advanced_cluster.cluster1 +} + +moved { + from = mongodbatlas_cluster.cluster2 + to = mongodbatlas_advanced_cluster.cluster2 +} + +moved { + from = mongodbatlas_cluster.count + to = mongodbatlas_advanced_cluster.count +} + +moved { + from = mongodbatlas_cluster.forEach + to = mongodbatlas_advanced_cluster.forEach +} diff --git a/internal/convert/testdata/clu2adv/includeMoved_single.in.tf b/internal/convert/testdata/clu2adv/includeMoved_single.in.tf new file mode 100644 index 0000000..1d848aa --- /dev/null +++ b/internal/convert/testdata/clu2adv/includeMoved_single.in.tf @@ -0,0 +1,15 @@ +resource "mongodbatlas_cluster" "cluster" { + project_id = var.project_id + name = "clu" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M10" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + } + } +} diff --git a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf new file mode 100644 index 0000000..0f389ab --- /dev/null +++ b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf @@ -0,0 +1,30 @@ +resource "mongodbatlas_advanced_cluster" "cluster" { + project_id = var.project_id + name = "clu" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M10" + } + } + ] + } + ] + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +# Moved blocks + +moved { + from = mongodbatlas_cluster.cluster + to = mongodbatlas_advanced_cluster.cluster +} diff --git a/internal/hcl/hcl.go b/internal/hcl/hcl.go index 6e998a0..66ab072 100644 --- a/internal/hcl/hcl.go +++ b/internal/hcl/hcl.go @@ -30,6 +30,12 @@ func PopAttr(body *hclwrite.Body, attrName, errPrefix string) (hclwrite.Tokens, return tokens, nil } +// SetAttrExpr sets an attribute to an expression (possibly with interpolations) without quotes. +func SetAttrExpr(body *hclwrite.Body, attrName, expresion string) { + tokens := hclwrite.Tokens{{Type: hclsyntax.TokenIdent, Bytes: []byte(expresion)}} + body.SetAttributeRaw(attrName, tokens) +} + // SetAttrInt sets an attribute to a number. func SetAttrInt(body *hclwrite.Body, attrName string, number int) { tokens := hclwrite.Tokens{ From cb80821bfd9d6d8e28d70db206bb9a4742978c18 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:40:12 +0100 Subject: [PATCH 05/12] atlas cli setup --- .github/workflows/code-health.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/code-health.yml b/.github/workflows/code-health.yml index 7e14224..ff048d1 100644 --- a/.github/workflows/code-health.yml +++ b/.github/workflows/code-health.yml @@ -29,6 +29,8 @@ jobs: - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 with: go-version-file: 'go.mod' + - name: setup Atlas CLI + uses: mongodb/atlas-github-action@15663d068c40a8582d881560961fce9d45e0df9a - name: E2E Test run: make test-e2e From 4416495dfcc3fcc618d0690bd35da7769736deef Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:42:41 +0100 Subject: [PATCH 06/12] add e2e tests --- test/e2e/cli.go | 9 ---- test/e2e/cli_test.go | 15 ------ test/e2e/clu2adv_test.go | 68 +++++++++++++++++++++++++++ test/e2e/e2e_helper.go | 31 ++++++++++++ test/e2e/run_test.go | 13 +++++ test/e2e/testdata/clu2adv.expected.tf | 26 ++++++++++ test/e2e/testdata/clu2adv.in.tf | 19 ++++++++ 7 files changed, 157 insertions(+), 24 deletions(-) delete mode 100644 test/e2e/cli.go delete mode 100644 test/e2e/cli_test.go create mode 100644 test/e2e/clu2adv_test.go create mode 100644 test/e2e/e2e_helper.go create mode 100644 test/e2e/run_test.go create mode 100644 test/e2e/testdata/clu2adv.expected.tf create mode 100644 test/e2e/testdata/clu2adv.in.tf diff --git a/test/e2e/cli.go b/test/e2e/cli.go deleted file mode 100644 index f76ff3e..0000000 --- a/test/e2e/cli.go +++ /dev/null @@ -1,9 +0,0 @@ -package e2e - -import "os/exec" - -func RunPlugin(args ...string) (string, error) { - cmd := exec.Command("atlas", args...) - resp, err := cmd.CombinedOutput() - return string(resp), err -} diff --git a/test/e2e/cli_test.go b/test/e2e/cli_test.go deleted file mode 100644 index 8655dac..0000000 --- a/test/e2e/cli_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package e2e_test - -import ( - "testing" - - "github.com/mongodb-labs/atlas-cli-plugin-terraform/test/e2e" - "github.com/stretchr/testify/require" -) - -func TestPlugin(t *testing.T) { - t.Run("Execute TF command", func(t *testing.T) { - resp, err := e2e.RunPlugin("tf") - require.NoError(t, err, resp) - }) -} diff --git a/test/e2e/clu2adv_test.go b/test/e2e/clu2adv_test.go new file mode 100644 index 0000000..95ea2bf --- /dev/null +++ b/test/e2e/clu2adv_test.go @@ -0,0 +1,68 @@ +package e2e_test + +import ( + "os" + "testing" + + "github.com/mongodb-labs/atlas-cli-plugin-terraform/test/e2e" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClu2AdvParams(t *testing.T) { + cwd, err := os.Getwd() + require.NoError(t, err) + var ( + prefix = cwd + "/testdata/" + fileIn = prefix + "clu2adv.in.tf" + fileOut = prefix + "clu2adv.out.tf" + fileExpected = prefix + "clu2adv.expected.tf" + fileUnexisting = prefix + "clu2adv.unexisting.tf" + fs = afero.NewOsFs() + ) + tests := map[string]struct { + expectedErrContains string + assertFunc func(t *testing.T) + args []string + }{ + "no params": { + expectedErrContains: "required flag(s) \"file\", \"output\" not set", + }, + "no input file": { + args: []string{"--output", fileOut}, + expectedErrContains: "required flag(s) \"file\" not set", + }, + "no output file": { + args: []string{"--file", fileIn}, + expectedErrContains: "required flag(s) \"output\" not set", + }, + "unexisting input file": { + args: []string{"--file", fileUnexisting, "--output", fileOut}, + expectedErrContains: "file must exist: " + fileUnexisting, + }, + "existing output file without replaceOutput flag": { + args: []string{"--file", fileIn, "--output", fileExpected}, + expectedErrContains: "file must not exist: " + fileExpected, + }, + "basic use": { + args: []string{"--file", fileIn, "--output", fileOut}, + assertFunc: func(t *testing.T) { e2e.CompareFiles(t, fs, fileOut, fileExpected) }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + resp, err := e2e.RunClu2Adv(tc.args...) + assert.Equal(t, tc.expectedErrContains == "", err == nil) + if err == nil { + assert.Empty(t, resp) + if tc.assertFunc != nil { + tc.assertFunc(t) + } + } else { + assert.Contains(t, resp, tc.expectedErrContains) + } + _ = fs.Remove(fileOut) // Ensure the output file does not exist in case it was generated in some test case + }) + } +} diff --git a/test/e2e/e2e_helper.go b/test/e2e/e2e_helper.go new file mode 100644 index 0000000..9053b51 --- /dev/null +++ b/test/e2e/e2e_helper.go @@ -0,0 +1,31 @@ +package e2e + +import ( + "os/exec" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func RunTF(args ...string) (string, error) { + args = append([]string{"tf"}, args...) + cmd := exec.Command("atlas", args...) + resp, err := cmd.CombinedOutput() + return string(resp), err +} + +func RunClu2Adv(args ...string) (string, error) { + args = append([]string{"clu2adv"}, args...) + return RunTF(args...) +} + +func CompareFiles(t *testing.T, fs afero.Fs, file1, file2 string) { + t.Helper() + data1, err1 := afero.ReadFile(fs, file1) + require.NoError(t, err1) + data2, err2 := afero.ReadFile(fs, file2) + require.NoError(t, err2) + assert.Equal(t, string(data1), string(data2)) +} diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go new file mode 100644 index 0000000..3c58393 --- /dev/null +++ b/test/e2e/run_test.go @@ -0,0 +1,13 @@ +package e2e_test + +import ( + "testing" + + "github.com/mongodb-labs/atlas-cli-plugin-terraform/test/e2e" + "github.com/stretchr/testify/require" +) + +func TestRunTF(t *testing.T) { + resp, err := e2e.RunTF() + require.NoError(t, err, resp) +} diff --git a/test/e2e/testdata/clu2adv.expected.tf b/test/e2e/testdata/clu2adv.expected.tf new file mode 100644 index 0000000..684dafe --- /dev/null +++ b/test/e2e/testdata/clu2adv.expected.tf @@ -0,0 +1,26 @@ +resource "mongodbatlas_advanced_cluster" "cluster" { + project_id = var.project_id + name = "cluster" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M10" + } + } + ] + } + ] + tags = { + environment = "dev" + } + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} diff --git a/test/e2e/testdata/clu2adv.in.tf b/test/e2e/testdata/clu2adv.in.tf new file mode 100644 index 0000000..bfce0ec --- /dev/null +++ b/test/e2e/testdata/clu2adv.in.tf @@ -0,0 +1,19 @@ +resource "mongodbatlas_cluster" "cluster" { + project_id = var.project_id + name = "cluster" + cluster_type = "REPLICASET" + provider_name = "AWS" + provider_instance_size_name = "M10" + replication_specs { + num_shards = 1 + regions_config { + region_name = "US_EAST_1" + electable_nodes = 3 + priority = 7 + } + } + tags { + key = "environment" + value = "dev" + } +} From ea3491e0d1914bea6fdd32816a415e7fcf452020 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:45:57 +0100 Subject: [PATCH 07/12] fix linter --- test/e2e/clu2adv_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/clu2adv_test.go b/test/e2e/clu2adv_test.go index 95ea2bf..daa6898 100644 --- a/test/e2e/clu2adv_test.go +++ b/test/e2e/clu2adv_test.go @@ -47,7 +47,7 @@ func TestClu2AdvParams(t *testing.T) { }, "basic use": { args: []string{"--file", fileIn, "--output", fileOut}, - assertFunc: func(t *testing.T) { e2e.CompareFiles(t, fs, fileOut, fileExpected) }, + assertFunc: func(t *testing.T) { t.Helper(); e2e.CompareFiles(t, fs, fileOut, fileExpected) }, }, } for name, tc := range tests { From d0617be75f5c7635bac6a00d3dfdc9815ba6701d Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:53:00 +0100 Subject: [PATCH 08/12] add moved e2e test --- test/e2e/clu2adv_test.go | 17 +++++++---- test/e2e/testdata/clu2adv.expected_moved.tf | 33 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 test/e2e/testdata/clu2adv.expected_moved.tf diff --git a/test/e2e/clu2adv_test.go b/test/e2e/clu2adv_test.go index daa6898..edf2f1b 100644 --- a/test/e2e/clu2adv_test.go +++ b/test/e2e/clu2adv_test.go @@ -14,12 +14,13 @@ func TestClu2AdvParams(t *testing.T) { cwd, err := os.Getwd() require.NoError(t, err) var ( - prefix = cwd + "/testdata/" - fileIn = prefix + "clu2adv.in.tf" - fileOut = prefix + "clu2adv.out.tf" - fileExpected = prefix + "clu2adv.expected.tf" - fileUnexisting = prefix + "clu2adv.unexisting.tf" - fs = afero.NewOsFs() + prefix = cwd + "/testdata/" + fileIn = prefix + "clu2adv.in.tf" + fileOut = prefix + "clu2adv.out.tf" + fileExpected = prefix + "clu2adv.expected.tf" + fileExpectedMoved = prefix + "clu2adv.expected_moved.tf" + fileUnexisting = prefix + "clu2adv.unexisting.tf" + fs = afero.NewOsFs() ) tests := map[string]struct { expectedErrContains string @@ -49,6 +50,10 @@ func TestClu2AdvParams(t *testing.T) { args: []string{"--file", fileIn, "--output", fileOut}, assertFunc: func(t *testing.T) { t.Helper(); e2e.CompareFiles(t, fs, fileOut, fileExpected) }, }, + "include moved": { + args: []string{"--file", fileIn, "--output", fileOut, "--includeMoved"}, + assertFunc: func(t *testing.T) { t.Helper(); e2e.CompareFiles(t, fs, fileOut, fileExpectedMoved) }, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/test/e2e/testdata/clu2adv.expected_moved.tf b/test/e2e/testdata/clu2adv.expected_moved.tf new file mode 100644 index 0000000..5815b4b --- /dev/null +++ b/test/e2e/testdata/clu2adv.expected_moved.tf @@ -0,0 +1,33 @@ +resource "mongodbatlas_advanced_cluster" "cluster" { + project_id = var.project_id + name = "cluster" + cluster_type = "REPLICASET" + replication_specs = [ + { + region_configs = [ + { + provider_name = "AWS" + region_name = "US_EAST_1" + priority = 7 + electable_specs = { + node_count = 3 + instance_size = "M10" + } + } + ] + } + ] + tags = { + environment = "dev" + } + + # Generated by atlas-cli-plugin-terraform. + # Please confirm that all references to this resource are updated. +} + +# Moved blocks + +moved { + from = mongodbatlas_cluster.cluster + to = mongodbatlas_advanced_cluster.cluster +} From c0552ec78711ee038267e0662df21f90d266ca4c Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 17:57:52 +0100 Subject: [PATCH 09/12] extract comments to const --- internal/convert/convert.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index 21e55ca..417047d 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -23,11 +23,16 @@ const ( valClusterType = "REPLICASET" valMaxPriority = 7 valMinPriority = 1 - errFreeCluster = "free cluster (because no " + nRepSpecs + ")" - errRepSpecs = "setting " + nRepSpecs - errConfigs = "setting " + nConfig - errPriority = "setting " + nPriority - errNumShards = "setting " + nNumShards + + errFreeCluster = "free cluster (because no " + nRepSpecs + ")" + errRepSpecs = "setting " + nRepSpecs + errConfigs = "setting " + nConfig + errPriority = "setting " + nPriority + errNumShards = "setting " + nNumShards + + commentGeneratedBy = "Generated by atlas-cli-plugin-terraform." + commentConfirmReferences = "Please confirm that all references to this resource are updated." + commentMovedBlock = "Moved blocks" ) type attrVals struct { @@ -62,8 +67,8 @@ func ClusterToAdvancedCluster(config []byte, includeMoved bool) ([]byte, error) if convertedResource || convertedDataSource { blockb := block.Body() blockb.AppendNewline() - hcl.AppendComment(blockb, "Generated by atlas-cli-plugin-terraform.") - hcl.AppendComment(blockb, "Please confirm that all references to this resource are updated.") + hcl.AppendComment(blockb, commentGeneratedBy) + hcl.AppendComment(blockb, commentConfirmReferences) } } fillMovedBlocks(parserb, moveLabels) @@ -113,7 +118,7 @@ func fillMovedBlocks(body *hclwrite.Body, moveLabels []string) { return } body.AppendNewline() - hcl.AppendComment(body, "Moved blocks") + hcl.AppendComment(body, commentMovedBlock) body.AppendNewline() for i, moveLabel := range moveLabels { block := body.AppendNewBlock(nMoved, nil) From a3b1635558f8d4b0d7374cedd941eb011596dc12 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:13:33 +0100 Subject: [PATCH 10/12] note to remind delete old clusters --- internal/convert/convert.go | 2 ++ internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf | 1 + internal/convert/testdata/clu2adv/includeMoved_single.out.tf | 1 + test/e2e/testdata/clu2adv.expected_moved.tf | 1 + 4 files changed, 5 insertions(+) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index 417047d..cd848ef 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -33,6 +33,7 @@ const ( commentGeneratedBy = "Generated by atlas-cli-plugin-terraform." commentConfirmReferences = "Please confirm that all references to this resource are updated." commentMovedBlock = "Moved blocks" + commentRemovedOld = "Note: Remember to remove or coment out the old cluster definitions." ) type attrVals struct { @@ -119,6 +120,7 @@ func fillMovedBlocks(body *hclwrite.Body, moveLabels []string) { } body.AppendNewline() hcl.AppendComment(body, commentMovedBlock) + hcl.AppendComment(body, commentRemovedOld) body.AppendNewline() for i, moveLabel := range moveLabels { block := body.AppendNewBlock(nMoved, nil) diff --git a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf index 1570ccc..edbbd51 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf @@ -103,6 +103,7 @@ resource "another_resource" "another" { } # Moved blocks +# Note: Remember to remove or coment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster1 diff --git a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf index 0f389ab..01a9b74 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf @@ -23,6 +23,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Moved blocks +# Note: Remember to remove or coment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster diff --git a/test/e2e/testdata/clu2adv.expected_moved.tf b/test/e2e/testdata/clu2adv.expected_moved.tf index 5815b4b..9e6936f 100644 --- a/test/e2e/testdata/clu2adv.expected_moved.tf +++ b/test/e2e/testdata/clu2adv.expected_moved.tf @@ -26,6 +26,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Moved blocks +# Note: Remember to remove or coment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster From f873ad570c5dc5c03c8c5cdeae9242a2a02d07b3 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:15:30 +0100 Subject: [PATCH 11/12] typo --- internal/convert/convert.go | 2 +- internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf | 2 +- internal/convert/testdata/clu2adv/includeMoved_single.out.tf | 2 +- test/e2e/testdata/clu2adv.expected_moved.tf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index cd848ef..a8908eb 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -33,7 +33,7 @@ const ( commentGeneratedBy = "Generated by atlas-cli-plugin-terraform." commentConfirmReferences = "Please confirm that all references to this resource are updated." commentMovedBlock = "Moved blocks" - commentRemovedOld = "Note: Remember to remove or coment out the old cluster definitions." + commentRemovedOld = "Note: Remember to remove or comment out the old cluster definitions." ) type attrVals struct { diff --git a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf index edbbd51..8a11ff4 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_multiple.out.tf @@ -103,7 +103,7 @@ resource "another_resource" "another" { } # Moved blocks -# Note: Remember to remove or coment out the old cluster definitions. +# Note: Remember to remove or comment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster1 diff --git a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf index 01a9b74..5a49d34 100644 --- a/internal/convert/testdata/clu2adv/includeMoved_single.out.tf +++ b/internal/convert/testdata/clu2adv/includeMoved_single.out.tf @@ -23,7 +23,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Moved blocks -# Note: Remember to remove or coment out the old cluster definitions. +# Note: Remember to remove or comment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster diff --git a/test/e2e/testdata/clu2adv.expected_moved.tf b/test/e2e/testdata/clu2adv.expected_moved.tf index 9e6936f..6ef84c3 100644 --- a/test/e2e/testdata/clu2adv.expected_moved.tf +++ b/test/e2e/testdata/clu2adv.expected_moved.tf @@ -26,7 +26,7 @@ resource "mongodbatlas_advanced_cluster" "cluster" { } # Moved blocks -# Note: Remember to remove or coment out the old cluster definitions. +# Note: Remember to remove or comment out the old cluster definitions. moved { from = mongodbatlas_cluster.cluster From 2e83a2d560d8008178b32295a45ca36c9c1bc3dd Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Mon, 24 Feb 2025 18:21:24 +0100 Subject: [PATCH 12/12] func comments for getResourceName and getResourceLabel --- internal/convert/convert.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/convert/convert.go b/internal/convert/convert.go index a8908eb..65a7f1f 100644 --- a/internal/convert/convert.go +++ b/internal/convert/convert.go @@ -372,6 +372,8 @@ func setResourceName(resource *hclwrite.Block, name string) { resource.SetLabels(labels) } +// getResourceName returns the first label of a block, if it exists. +// e.g. in resource "mongodbatlas_cluster" "mycluster", the first label is "mongodbatlas_cluster". func getResourceName(resource *hclwrite.Block) string { labels := resource.Labels() if len(labels) == 0 { @@ -380,6 +382,8 @@ func getResourceName(resource *hclwrite.Block) string { return labels[0] } +// getResourceLabel returns the second label of a block, if it exists. +// e.g. in resource "mongodbatlas_cluster" "mycluster", the second label is "mycluster". func getResourceLabel(resource *hclwrite.Block) string { labels := resource.Labels() if len(labels) <= 1 {