Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .github/workflows/code-health.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
go-version-file: 'go.mod'
- name: Build
run: make build
- name: Unit Test
run: make test
lint:
runs-on: ubuntu-latest
permissions: {}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
bin
bin-plugin
.vscode
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ tools: ## Install the dev tools (dependencies)
clean: ## Clean binary folders
rm -rf ./bin ./bin-plugin

.PHONY: test
test: ## Run unit tests
go test ./... -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

.PHONY: local
local: clean build ## Allow to run the plugin locally
@echo "==> Configuring plugin locally"
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ atlas plugin install github.com/mongodb-labs/atlas-cli-plugin-terraform

## Usage

### Convert cluster to advanced_cluster v2
If you want to convert a Terraform configuration from `mongodbatlas_cluster` to `mongodbatlas_advanced_cluster` schema v2, use the following command:
```bash
atlas terraform clusterToAdvancedCluster --file in.tf --output out.tf
```

you can also use shorter aliases, e.g.:
```bash
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 `--overwriteOutput true` or the `-w` flag.


## Contributing

Expand Down
8 changes: 3 additions & 5 deletions cmd/plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import (
"fmt"
"os"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/hello"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/adv2"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/clu2adv"
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit unconventional package name, but I like it

Copy link
Collaborator Author

@lantoli lantoli Jan 30, 2025

Choose a reason for hiding this comment

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

it's the (short) alias for the command, but let me know if you think of a better name

"github.com/spf13/cobra"
)

Expand All @@ -14,10 +15,7 @@ func main() {
Short: "Utilities for Terraform's MongoDB Atlas Provider",
Aliases: []string{"tf"},
}

terraformCmd.AddCommand(
hello.Builder(),
)
terraformCmd.AddCommand(clu2adv.Builder(), adv2.Builder())

completionOption := &cobra.CompletionOptions{
DisableDefaultCmd: true,
Expand Down
21 changes: 20 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,28 @@ module github.com/mongodb-labs/atlas-cli-plugin-terraform

go 1.23.4

require github.com/spf13/cobra v1.8.1
require (
github.com/hashicorp/hcl/v2 v2.23.0
github.com/sebdah/goldie/v2 v2.5.5
github.com/spf13/afero v1.12.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like a solid choice for fs handling

Copy link
Collaborator Author

@lantoli lantoli Jan 30, 2025

Choose a reason for hiding this comment

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

yes, same as in CLI

github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.4.0
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
51 changes: 51 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,61 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/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.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 changes: 20 additions & 0 deletions internal/cli/adv2/adv2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
}
28 changes: 28 additions & 0 deletions internal/cli/clu2adv/clu2adv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package clu2adv

import (
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

func Builder() *cobra.Command {
o := &opts{fs: afero.NewOsFs()}
cmd := &cobra.Command{
Use: "clusterToAdvancedCluster",
Short: "Convert cluster to advanced_cluster v2",
Long: "Convert a Terraform configuration from mongodbatlas_cluster to mongodbatlas_advanced_cluster schema v2",
Aliases: []string{"clu2adv"},
RunE: func(_ *cobra.Command, _ []string) error {
if err := o.PreRun(); err != nil {
return err
}
return o.Run()
},
}
cmd.Flags().StringVarP(&o.file, "file", "f", "", "input file")
Copy link
Collaborator Author

@lantoli lantoli Jan 30, 2025

Choose a reason for hiding this comment

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

I think that at the moment it's not necessary to have const files for Flag, Usage, etc.

_ = cmd.MarkFlagRequired("file")
cmd.Flags().StringVarP(&o.output, "output", "o", "", "output file")
_ = cmd.MarkFlagRequired("output")
cmd.Flags().BoolVarP(&o.overwriteOutput, "overwriteOutput", "w", false, "overwrite output file if exists")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

file and output are the standard params for input and output files

return cmd
}
41 changes: 41 additions & 0 deletions internal/cli/clu2adv/opts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package clu2adv

import (
"fmt"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/file"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/hcl"
"github.com/spf13/afero"
)

type opts struct {
fs afero.Fs
file string
output string
overwriteOutput bool
}

func (o *opts) PreRun() error {
if err := file.MustExist(o.fs, o.file); err != nil {
return err
}
if !o.overwriteOutput {
return file.MustNotExist(o.fs, o.output)
}
return nil
}

func (o *opts) Run() error {
inConfig, err := afero.ReadFile(o.fs, o.file)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", o.file, err)
}
outConfig, err := hcl.ClusterToAdvancedCluster(inConfig)
if err != nil {
return err
}
if err := afero.WriteFile(o.fs, o.output, outConfig, 0o600); err != nil {
return fmt.Errorf("failed to write file %s: %w", o.output, err)
}
return nil
}
17 changes: 0 additions & 17 deletions internal/cli/hello/hello.go

This file was deleted.

41 changes: 41 additions & 0 deletions internal/file/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package file

import (
"fmt"

"github.com/spf13/afero"
)

func Exists(fs afero.Fs, filename string) (exists bool, err error) {
exists, err = afero.Exists(fs, filename)
if err != nil {
return false, newError(err, filename)
}
return
}

func MustExist(fs afero.Fs, filename string) error {
exists, err := Exists(fs, filename)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("file must exist: %s", filename)
}
return nil
}

func MustNotExist(fs afero.Fs, filename string) error {
exists, err := Exists(fs, filename)
if err != nil {
return err
}
if exists {
return fmt.Errorf("file must not exist: %s", filename)
}
return nil
}

func newError(err error, filename string) error {
return fmt.Errorf("error in file %s: %w", filename, err)
}
60 changes: 60 additions & 0 deletions internal/hcl/hcl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package hcl

import (
"fmt"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
)

const (
resourceType = "resource"
cluster = "mongodbatlas_cluster"
advCluster = "mongodbatlas_advanced_cluster"
)

// ClusterToAdvancedCluster transforms all mongodbatlas_cluster definitions in a
// Terraform configuration file into mongodbatlas_advanced_cluster schema v2 definitions.
// All other resources and data sources are left untouched.
// TODO: at the moment it just changes the resource type.
func ClusterToAdvancedCluster(config []byte) ([]byte, error) {
parser, err := getParser(config)
if err != nil {
return nil, err
}
for _, resource := range parser.Body().Blocks() {
labels := resource.Labels()
resourceName := labels[0]
if resource.Type() != resourceType || resourceName != cluster {
continue
}
resourceBody := resource.Body()

// TODO: Do the full transformation
labels[0] = advCluster
resource.SetLabels(labels)
resourceBody.AppendNewline()
appendComment(resourceBody, "Generated by atlas-cli-plugin-terraform.")
appendComment(resourceBody, "Please confirm that all references to this resource are updated.")
}
return parser.Bytes(), nil
}

func getParser(config []byte) (*hclwrite.File, error) {
parser, diags := hclwrite.ParseConfig(config, "", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
return nil, fmt.Errorf("failed to parse Terraform config file: %s", diags.Error())
}
return parser, nil
}

func appendComment(body *hclwrite.Body, comment string) {
tokens := hclwrite.Tokens{
&hclwrite.Token{
Type: hclsyntax.TokenComment,
Bytes: []byte("# " + comment + "\n"),
},
}
body.AppendUnstructuredTokens(tokens)
}
Loading