From 1e9936a8c1434d9d6e475d38ed249a6d1fb045ed Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 29 Jan 2025 10:08:16 +0900 Subject: [PATCH] feat: run pipeform when running terraform plan --- internal/exec/shell_utils.go | 72 +++++++++++++++++++++++++++ internal/exec/terraform.go | 94 +++++++++++++++++++++++++++++++----- 2 files changed, 155 insertions(+), 11 deletions(-) diff --git a/internal/exec/shell_utils.go b/internal/exec/shell_utils.go index 119058172..224f239f1 100644 --- a/internal/exec/shell_utils.go +++ b/internal/exec/shell_utils.go @@ -45,6 +45,78 @@ func getNextShellLevel() (int, error) { return shellVal, nil } +// ExecuteShellCommandWithPipe prints and executes the provided command with args and flags +func ExecuteShellCommandWithPipe( + atmosConfig schema.AtmosConfiguration, + command string, + args []string, + dir string, + env []string, + dryRun bool, + redirectStdError string, + stdin io.Reader, + stdout io.Writer, +) (func() error, error) { + newShellLevel, err := getNextShellLevel() + if err != nil { + return nil, err + } + updatedEnv := append(env, fmt.Sprintf("ATMOS_SHLVL=%d", newShellLevel)) + + cmd := exec.Command(command, args...) + cmd.Env = append(os.Environ(), updatedEnv...) + cmd.Dir = dir + if stdin != nil { + cmd.Stdin = stdin + } else { + cmd.Stdin = os.Stdin + } + if stdout != nil { + cmd.Stdout = stdout + } else { + cmd.Stdout = os.Stdout + } + + if runtime.GOOS == "windows" && redirectStdError == "/dev/null" { + redirectStdError = "NUL" + } + + if redirectStdError == "/dev/stderr" { + cmd.Stderr = os.Stderr + } else if redirectStdError == "/dev/stdout" { + cmd.Stderr = os.Stdout + } else if redirectStdError == "" { + cmd.Stderr = os.Stderr + } else { + f, err := os.OpenFile(redirectStdError, os.O_WRONLY|os.O_CREATE, 0o644) + if err != nil { + u.LogWarning(atmosConfig, err.Error()) + return nil, err + } + + defer func(f *os.File) { + err = f.Close() + if err != nil { + u.LogWarning(atmosConfig, err.Error()) + } + }(f) + + cmd.Stderr = f + } + + u.LogDebug(atmosConfig, "\nExecuting command:") + u.LogDebug(atmosConfig, cmd.String()) + + if dryRun { + return nil, nil + } + + if err := cmd.Start(); err != nil { + return nil, err + } + return cmd.Wait, nil +} + // ExecuteShellCommand prints and executes the provided command with args and flags func ExecuteShellCommand( atmosConfig schema.AtmosConfiguration, diff --git a/internal/exec/terraform.go b/internal/exec/terraform.go index d76980d1d..cb26f5bf6 100644 --- a/internal/exec/terraform.go +++ b/internal/exec/terraform.go @@ -2,6 +2,7 @@ package exec import ( "fmt" + "io" "os" osexec "os/exec" "path/filepath" @@ -9,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" cfg "github.com/cloudposse/atmos/pkg/config" "github.com/cloudposse/atmos/pkg/schema" @@ -524,17 +526,87 @@ func ExecuteTerraform(info schema.ConfigAndStacksInfo) error { // Execute the provided command (except for `terraform workspace` which was executed above) if !(info.SubCommand == "workspace" && info.SubCommand2 == "") { - err = ExecuteShellCommand( - atmosConfig, - info.Command, - allArgsAndFlags, - componentPath, - info.ComponentEnvList, - info.DryRun, - info.RedirectStdErr, - ) - if err != nil { - return err + if info.SubCommand == "plan" { + allArgsAndFlags = append(allArgsAndFlags, "-json") + pipeformStdin, planStdout := io.Pipe() + // Get a plan file + p := "" + for i, a := range allArgsAndFlags { + if a == "-out" || a == "--out" { + p = allArgsAndFlags[i+1] + break + } + } + if p == "" { + p = planFile + allArgsAndFlags = append(allArgsAndFlags, "-out", p) + } + + // terraform plan + waitPlan, err := ExecuteShellCommandWithPipe( + atmosConfig, + info.Command, + allArgsAndFlags, + componentPath, + info.ComponentEnvList, + info.DryRun, + info.RedirectStdErr, + nil, + planStdout, + ) + if err != nil { + return err + } + // pipeform + waitPipeform, err := ExecuteShellCommandWithPipe( + atmosConfig, + "pipeform", + nil, + componentPath, + info.ComponentEnvList, + info.DryRun, + info.RedirectStdErr, + pipeformStdin, + nil, + ) + if err != nil { + return err + } + g := &errgroup.Group{} + g.Go(func() error { + err := waitPlan() + planStdout.Close() + return err + }) + g.Go(waitPipeform) + if err := g.Wait(); err != nil { + return err + } + // terraform show + if err := ExecuteShellCommand( + atmosConfig, + info.Command, + []string{"show", p}, + componentPath, + info.ComponentEnvList, + info.DryRun, + info.RedirectStdErr, + ); err != nil { + return err + } + } else { + err = ExecuteShellCommand( + atmosConfig, + info.Command, + allArgsAndFlags, + componentPath, + info.ComponentEnvList, + info.DryRun, + info.RedirectStdErr, + ) + if err != nil { + return err + } } }