Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/code-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: {}
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 1 addition & 2 deletions cmd/plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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())
Copy link
Collaborator Author

@lantoli lantoli Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove skeleton for adv_cluster v1 to v2 conversion to avoid customer confusion as it won't be implemented yet


completionOption := &cobra.CompletionOptions{
DisableDefaultCmd: true,
Expand Down
20 changes: 0 additions & 20 deletions internal/cli/adv2/adv2.go

This file was deleted.

14 changes: 8 additions & 6 deletions internal/cli/clu2adv/clu2adv.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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
}
3 changes: 2 additions & 1 deletion internal/cli/clu2adv/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type opts struct {
output string
replaceOutput bool
watch bool
includeMoved bool
}

func (o *opts) PreRun() error {
Expand Down Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions internal/convert/const_names.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ const (
nKey = "key"
nValue = "value"
nUseRepSpecsPerShard = "use_replication_spec_per_shard"
nMoved = "moved"
nFrom = "from"
nTo = "to"
)
47 changes: 41 additions & 6 deletions internal/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -99,6 +108,24 @@ func convertDataSource(block *hclwrite.Block) bool {
return false
}

func fillMovedBlocks(body *hclwrite.Body, moveLabels []string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

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))
Expand Down Expand Up @@ -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" {
Expand Down
3 changes: 2 additions & 1 deletion internal/convert/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
71 changes: 71 additions & 0 deletions internal/convert/testdata/clu2adv/includeMoved_multiple.in.tf
Original file line number Diff line number Diff line change
@@ -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"
}
Loading
Loading