Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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 cmd/plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/adv2new"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli/clu2adv"
"github.com/spf13/cobra"
)
Expand All @@ -15,6 +16,7 @@ func main() {
Aliases: []string{"tf"},
}
terraformCmd.AddCommand(clu2adv.Builder())
terraformCmd.AddCommand(adv2new.Builder())

completionOption := &cobra.CompletionOptions{
DisableDefaultCmd: true,
Expand Down
25 changes: 25 additions & 0 deletions internal/cli/adv2new/adv2new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package adv2new

import (
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

func Builder() *cobra.Command {
o := &cli.BaseOpts{
Fs: afero.NewOsFs(),
Convert: convert.AdvancedClusterToNew,
}
cmd := &cobra.Command{
Use: "advancedClusterToNew",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Use: "advancedClusterToNew",
Use: "advancedClusterV2",

just for sanity, how would the full command look like?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

some equivalent examples to use it with names or aliases are:

atlas tf adv2new -f example.in.tf -o example.out.tf
atlas terraform advancedClusterToNew --file example.in.tf --output example.out.tf

# current cluster to adv_cluster command
atlas tf clu2adv -f example.in.tf -o example.out.tf
atlas terraform clusterToAdvancedCluster --file example.in.tf --output example.out.tf

you can also mix names and aliases in the same command

I like the convert command pattern name of xxxToyyy and alias x2y, maybe advancedClusterToV2 and adv2v2 ? (not convinced with so many 2, that's why i tried to avoid 2 in the command name).

Having this context, what command and alias do you think it's better?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@marcosuma let me know if it's ok that I keep the current name and alias at the moment and I merge the PR as it is, or you prefer to use a different alias and name

Copy link
Contributor

Choose a reason for hiding this comment

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

Not blocking this PR, but I think New and Latest quickly become obsolete or ambiguous, that's why ...ToV2 I like it more. 2v2 is not ideal, but that's also an alias so I am less concerned

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

that's fair, changed here: 32398ac

Short: "Convert advanced_cluster from provider version 1 to 2",
Long: "Convert a Terraform configuration from mongodbatlas_advanced_cluster in provider version 1.X.X (SDKv2)" +
" to version 2.X.X (TPF - Terraform Plugin Framework)",
Aliases: []string{"adv2new"},
RunE: o.RunE,
}
cli.SetupCommonFlags(cmd, o)
return cmd
}
30 changes: 15 additions & 15 deletions internal/cli/clu2adv/clu2adv.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
package clu2adv

import (
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/cli"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/flag"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

func Builder() *cobra.Command {
o := &opts{fs: afero.NewOsFs()}
o := &struct {
*cli.BaseOpts
includeMoved bool
}{
BaseOpts: &cli.BaseOpts{
Fs: afero.NewOsFs(),
},
}
o.Convert = func(config []byte) ([]byte, error) {
return convert.ClusterToAdvancedCluster(config, o.includeMoved)
}
cmd := &cobra.Command{
Use: "clusterToAdvancedCluster",
Short: "Convert cluster to advanced_cluster preview provider 2.0.0",
Long: "Convert a Terraform configuration from mongodbatlas_cluster to " +
"mongodbatlas_advanced_cluster preview provider 2.0.0",
Aliases: []string{"clu2adv"},
RunE: func(_ *cobra.Command, _ []string) error {
if err := o.PreRun(); err != nil {
return err
}
return o.Run()
},
RunE: o.RunE,
}
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")
cli.SetupCommonFlags(cmd, o.BaseOpts)
cmd.Flags().BoolVarP(&o.includeMoved, flag.IncludeMoved, flag.IncludeMovedShort, false,
"include moved blocks in the output file")
return cmd
Expand Down
97 changes: 0 additions & 97 deletions internal/cli/clu2adv/opts.go

This file was deleted.

130 changes: 130 additions & 0 deletions internal/cli/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package cli
Copy link
Contributor

Choose a reason for hiding this comment

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

nice re-use!


import (
"errors"
"fmt"

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

type ConvertFn func(config []byte) ([]byte, error)

// BaseOpts contains common functionality for CLI commands that convert files.
type BaseOpts struct {
Fs afero.Fs
Convert ConvertFn
File string
Output string
ReplaceOutput bool
Watch bool
}

// RunE is the entry point for the command.
func (o *BaseOpts) RunE(cmd *cobra.Command, args []string) error {
if err := o.preRun(); err != nil {
return err
}
return o.run()
}

// preRun validates the input and output files before running the command.
func (o *BaseOpts) preRun() error {
if err := file.MustExist(o.Fs, o.File); err != nil {
return err
}
if !o.ReplaceOutput {
return file.MustNotExist(o.Fs, o.Output)
}
return nil
}

// run executes the conversion and optionally watches for file changes.
func (o *BaseOpts) run() error {
if err := o.generateFile(false); err != nil {
return err
}
if o.Watch {
return o.watchFile()
}
return nil
}

// generateFile reads the input file, converts it, and writes the output.
func (o *BaseOpts) generateFile(allowParseErrors bool) 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 := o.Convert(inConfig)
if err != nil {
if allowParseErrors {
outConfig = []byte("# CONVERT ERROR: " + err.Error() + "\n\n")
outConfig = append(outConfig, inConfig...)
} else {
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
}

// watchFile watches the input file for changes and regenerates the output.
func (o *BaseOpts) watchFile() error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()

if err := watcher.Add(o.File); err != nil {
return err
}

for {
if err := o.waitForFileEvent(watcher); err != nil {
return err
}
}
}

// waitForFileEvent waits for file system events and regenerates the output file.
func (o *BaseOpts) waitForFileEvent(watcher *fsnotify.Watcher) error {
watcherError := errors.New("watcher has been closed")
select {
case event, ok := <-watcher.Events:
if !ok {
return watcherError
}
if event.Has(fsnotify.Write) {
if err := o.generateFile(true); err != nil {
return err
}
}
case err, ok := <-watcher.Errors:
if !ok {
return watcherError
}
return err
}
return nil
}

// SetupCommonFlags sets up the common flags used by all commands.
func SetupCommonFlags(cmd *cobra.Command, opts *BaseOpts) {
cmd.Flags().StringVarP(&opts.File, flag.File, flag.FileShort, "", "input file")
_ = cmd.MarkFlagRequired(flag.File)
cmd.Flags().StringVarP(&opts.Output, flag.Output, flag.OutputShort, "", "output file")
_ = cmd.MarkFlagRequired(flag.Output)
cmd.Flags().BoolVarP(&opts.ReplaceOutput, flag.ReplaceOutput, flag.ReplaceOutputShort, false,
"replace output file if exists")
cmd.Flags().BoolVarP(&opts.Watch, flag.Watch, flag.WatchShort, false,
"keeps the plugin running and watches the input file for changes")
}
8 changes: 8 additions & 0 deletions internal/convert/adv2new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package convert

// AdvancedClusterToNew transforms all mongodbatlas_advanced_cluster resource definitions in a
// Terraform configuration file from SDKv2 schema to TPF (Terraform Plugin Framework) schema.
// All other resources and data sources are left untouched.
func AdvancedClusterToNew(config []byte) ([]byte, error) {
return config, nil
}
13 changes: 13 additions & 0 deletions internal/convert/adv2new_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package convert_test

import (
"testing"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
)

func TestAdvancedClusterToNew(t *testing.T) {
runConvertTests(t, "adv2new", func(testName string, inConfig []byte) ([]byte, error) {
return convert.AdvancedClusterToNew(inConfig)
})
}
File renamed without changes.
15 changes: 15 additions & 0 deletions internal/convert/clu2adv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package convert_test

import (
"strings"
"testing"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
)

func TestClusterToAdvancedCluster(t *testing.T) {
runConvertTests(t, "clu2adv", func(testName string, inConfig []byte) ([]byte, error) {
includeMoved := strings.Contains(testName, "includeMoved")
return convert.ClusterToAdvancedCluster(inConfig, includeMoved)
})
}
10 changes: 5 additions & 5 deletions internal/convert/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import (
"strings"
"testing"

"github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert"
"github.com/sebdah/goldie/v2"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestClusterToAdvancedCluster(t *testing.T) {
// runConvertTests runs common conversion tests with the given test directory and convert function
func runConvertTests(t *testing.T, cmdName string, convert func(testName string, inConfig []byte) ([]byte, error)) {
t.Helper()
const (
root = "testdata/clu2adv"
inSuffix = ".in.tf"
outSuffix = ".out.tf"
errFilename = "errors.json"
)
root := filepath.Join("testdata", cmdName)
fs := afero.NewOsFs()
errMap := make(map[string]string)
errContent, err := afero.ReadFile(fs, filepath.Join(root, errFilename))
Expand All @@ -38,8 +39,7 @@ func TestClusterToAdvancedCluster(t *testing.T) {
t.Run(testName, func(t *testing.T) {
inConfig, err := afero.ReadFile(fs, inputFile)
require.NoError(t, err)
includeMoved := strings.Contains(testName, "includeMoved")
outConfig, err := convert.ClusterToAdvancedCluster(inConfig, includeMoved)
outConfig, err := convert(testName, inConfig)
if err == nil {
g.Assert(t, testName, outConfig)
} else {
Expand Down
4 changes: 4 additions & 0 deletions internal/convert/testdata/adv2new/basic.in.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "mongodbatlas_advanced_cluster" "basic" {
name = "basic"
// TODO: missing fields as transformation is not implemented yet
}
Loading