diff --git a/README.md b/README.md index 6daa86d..83c301a 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ 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 `--overwriteOutput true` or the `-w` flag. +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. + +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. ### Limitations diff --git a/go.mod b/go.mod index 6213548..5f6765b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/mongodb-labs/atlas-cli-plugin-terraform go 1.23.6 require ( + github.com/fsnotify/fsnotify v1.8.0 github.com/hashicorp/hcl/v2 v2.23.0 github.com/sebdah/goldie/v2 v2.5.5 github.com/spf13/afero v1.12.0 @@ -23,6 +24,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f1f146c..5267519 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t 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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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= @@ -49,6 +51,8 @@ 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/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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= diff --git a/internal/cli/clu2adv/clu2adv.go b/internal/cli/clu2adv/clu2adv.go index a5b4080..6d7015e 100644 --- a/internal/cli/clu2adv/clu2adv.go +++ b/internal/cli/clu2adv/clu2adv.go @@ -23,6 +23,7 @@ func Builder() *cobra.Command { _ = 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") + 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") return cmd } diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index 2939d70..8ba67f5 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -1,41 +1,96 @@ package clu2adv import ( + "errors" "fmt" + "github.com/fsnotify/fsnotify" "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/convert" "github.com/mongodb-labs/atlas-cli-plugin-terraform/internal/file" "github.com/spf13/afero" ) type opts struct { - fs afero.Fs - file string - output string - overwriteOutput bool + fs afero.Fs + file string + output string + replaceOutput bool + watch bool } func (o *opts) PreRun() error { if err := file.MustExist(o.fs, o.file); err != nil { return err } - if !o.overwriteOutput { + if !o.replaceOutput { return file.MustNotExist(o.fs, o.output) } return nil } func (o *opts) Run() error { + if err := o.generateFile(false); err != nil { + return err + } + if o.watch { + return o.watchFile() + } + return nil +} + +func (o *opts) 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 := convert.ClusterToAdvancedCluster(inConfig) if err != nil { - return err + 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 } + +func (o *opts) watchFile() error { + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil + } + defer watcher.Close() + if err := watcher.Add(o.file); err != nil { + return err + } + for { + if err := o.waitForFileEvent(watcher); err != nil { + return err + } + } +} + +func (o *opts) 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 +}