From e8264ebdfbc9a6579a8a9cb93e4091b69db4ea58 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:18:04 +0100 Subject: [PATCH 1/7] rename overwriteOutput param to replaceOutput --- README.md | 2 +- internal/cli/clu2adv/clu2adv.go | 2 +- internal/cli/clu2adv/opts.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6daa86d..1475cde 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ 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. ### Limitations diff --git a/internal/cli/clu2adv/clu2adv.go b/internal/cli/clu2adv/clu2adv.go index a5b4080..e797d0f 100644 --- a/internal/cli/clu2adv/clu2adv.go +++ b/internal/cli/clu2adv/clu2adv.go @@ -23,6 +23,6 @@ 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") return cmd } diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index 2939d70..5fa68be 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -12,7 +12,7 @@ type opts struct { fs afero.Fs file string output string - overwriteOutput bool + replaceOutput bool } func (o *opts) PreRun() error { From e15fcaf3ce5ea6cc67e83e933001e5232adba2fd Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:18:50 +0100 Subject: [PATCH 2/7] watch --- go.mod | 2 ++ go.sum | 4 +++ internal/cli/clu2adv/clu2adv.go | 1 + internal/cli/clu2adv/opts.go | 60 ++++++++++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 5 deletions(-) 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 e797d0f..825f80a 100644 --- a/internal/cli/clu2adv/clu2adv.go +++ b/internal/cli/clu2adv/clu2adv.go @@ -24,5 +24,6 @@ func Builder() *cobra.Command { 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 command 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 5fa68be..90316c1 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -1,41 +1,91 @@ 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 + 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 { + prefix := []byte("# CONVERT ERROR: " + err.Error() + "\n\n") + outConfig = append([]byte(nil), prefix...) + 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() + err = watcher.Add(o.file) + if err != nil { + return err + } + watcherError := errors.New("watcher has been closed") + for { + 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 + } + } +} From b6dd60c727b7ef92c99c5a8137e8f19c9169d4b9 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:27:09 +0100 Subject: [PATCH 3/7] watch readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1475cde..4a068d5 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ 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 keep the plugin running and update the output file as you change the input file, use the `--watch true` or the `-w` flag. 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 - The plugin doesn't support `regions_config` without `electable_nodes` as there can be some issues with `priority` when they only have `analytics_nodes` and/or `electable_nodes`. From ff296ec037befaf778b30c5a57e08a987601e997 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:33:13 +0100 Subject: [PATCH 4/7] adjust doc --- README.md | 2 +- internal/cli/clu2adv/clu2adv.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4a068d5..83c301a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ 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 keep the plugin running and update the output file as you change the input file, use the `--watch true` or the `-w` flag. You can have input and output files open in an editor and see easily how changes to the input file affect the output file. +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/internal/cli/clu2adv/clu2adv.go b/internal/cli/clu2adv/clu2adv.go index 825f80a..6d7015e 100644 --- a/internal/cli/clu2adv/clu2adv.go +++ b/internal/cli/clu2adv/clu2adv.go @@ -24,6 +24,6 @@ func Builder() *cobra.Command { 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 command running and watches the input file for changes") + cmd.Flags().BoolVarP(&o.watch, "watch", "w", false, "keeps the plugin running and watches the input file for changes") return cmd } From 8d5aaa4d79746315a6c820d9f342fe665433cbb4 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:36:36 +0100 Subject: [PATCH 5/7] simplify outConfig with convert errors --- internal/cli/clu2adv/opts.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index 90316c1..e34f1b4 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -46,8 +46,7 @@ func (o *opts) generateFile(allowParseErrors bool) error { outConfig, err := convert.ClusterToAdvancedCluster(inConfig) if err != nil { if allowParseErrors { - prefix := []byte("# CONVERT ERROR: " + err.Error() + "\n\n") - outConfig = append([]byte(nil), prefix...) + outConfig = []byte("# CONVERT ERROR: " + err.Error() + "\n\n") outConfig = append(outConfig, inConfig...) } else { return err From 8716477579c7e9ec205fd78a091426a27310775f Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:56:05 +0100 Subject: [PATCH 6/7] join err check --- internal/cli/clu2adv/opts.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index e34f1b4..7d39ca9 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -64,8 +64,7 @@ func (o *opts) watchFile() error { return nil } defer watcher.Close() - err = watcher.Add(o.file) - if err != nil { + if err = watcher.Add(o.file); err != nil { return err } watcherError := errors.New("watcher has been closed") From b125a7257c3eb8278803e9ae12981df3c7e1f2b3 Mon Sep 17 00:00:00 2001 From: Leo Antoli <430982+lantoli@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:08:50 +0100 Subject: [PATCH 7/7] waitForFileEvent --- internal/cli/clu2adv/opts.go | 39 +++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/internal/cli/clu2adv/opts.go b/internal/cli/clu2adv/opts.go index 7d39ca9..8ba67f5 100644 --- a/internal/cli/clu2adv/opts.go +++ b/internal/cli/clu2adv/opts.go @@ -64,26 +64,33 @@ func (o *opts) watchFile() error { return nil } defer watcher.Close() - if err = watcher.Add(o.file); err != nil { + if err := watcher.Add(o.file); err != nil { return err } - watcherError := errors.New("watcher has been closed") for { - 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 - } + 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 +}