Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add suppress_stderr argument to hooks #3771

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (provider *Provider) GetCredentials(ctx context.Context) (*providers.Creden
args = parts[1:]
}

output, err := shell.RunShellCommandWithOutput(ctx, provider.terragruntOptions, "", true, false, command, args...)
output, err := shell.RunShellCommandWithOutput(ctx, provider.terragruntOptions, "", true, true, false, command, args...)
if err != nil {
return nil, err
}
Expand Down
12 changes: 12 additions & 0 deletions cli/commands/terraform/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func processErrorHooks(ctx context.Context, hooks []config.ErrorHook, terragrunt
suppressStdout = true
}

var suppressStderr bool
if curHook.SuppressStderr != nil && *curHook.SuppressStderr {
suppressStderr = true
}

actionToExecute := curHook.Execute[0]
actionParams := curHook.Execute[1:]
terragruntOptions = terragruntOptionsWithHookEnvs(terragruntOptions, curHook.Name)
Expand All @@ -76,6 +81,7 @@ func processErrorHooks(ctx context.Context, hooks []config.ErrorHook, terragrunt
terragruntOptions,
workingDir,
suppressStdout,
suppressStderr,
false,
actionToExecute, actionParams...,
)
Expand Down Expand Up @@ -148,6 +154,11 @@ func runHook(ctx context.Context, terragruntOptions *options.TerragruntOptions,
suppressStdout = true
}

var suppressStderr bool
if curHook.SuppressStderr != nil && *curHook.SuppressStderr {
suppressStderr = true
}

actionToExecute := curHook.Execute[0]
actionParams := curHook.Execute[1:]
terragruntOptions = terragruntOptionsWithHookEnvs(terragruntOptions, curHook.Name)
Expand All @@ -162,6 +173,7 @@ func runHook(ctx context.Context, terragruntOptions *options.TerragruntOptions,
terragruntOptions,
workingDir,
suppressStdout,
suppressStderr,
false,
actionToExecute, actionParams...,
)
Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ type Hook struct {
Execute []string `hcl:"execute,attr" cty:"execute"`
RunOnError *bool `hcl:"run_on_error,attr" cty:"run_on_error"`
SuppressStdout *bool `hcl:"suppress_stdout,attr" cty:"suppress_stdout"`
SuppressStderr *bool `hcl:"suppress_stderr,attr" cty:"suppress_stderr"`
WorkingDir *string `hcl:"working_dir,attr" cty:"working_dir"`
}

Expand All @@ -446,6 +447,7 @@ type ErrorHook struct {
Execute []string `hcl:"execute,attr" cty:"execute"`
OnErrors []string `hcl:"on_errors,attr" cty:"on_errors"`
SuppressStdout *bool `hcl:"suppress_stdout,attr" cty:"suppress_stdout"`
SuppressStderr *bool `hcl:"suppress_stderr,attr" cty:"suppress_stderr"`
WorkingDir *string `hcl:"working_dir,attr" cty:"working_dir"`
}

Expand Down
4 changes: 3 additions & 1 deletion config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ func RunCommand(ctx *ParsingContext, args []string) (string, error) {
}

suppressOutput := false
suppressErrorOutput := false
currentPath := filepath.Dir(ctx.TerragruntOptions.TerragruntConfigPath)
cachePath := currentPath

Expand All @@ -348,6 +349,7 @@ func RunCommand(ctx *ParsingContext, args []string) (string, error) {
switch args[0] {
case "--terragrunt-quiet":
suppressOutput = true
suppressErrorOutput = true

args = append(args[:0], args[1:]...)
case "--terragrunt-global-cache":
Expand All @@ -374,7 +376,7 @@ func RunCommand(ctx *ParsingContext, args []string) (string, error) {
return cachedValue, nil
}

cmdOutput, err := shell.RunShellCommandWithOutput(ctx, ctx.TerragruntOptions, currentPath, suppressOutput, false, args[0], args[1:]...)
cmdOutput, err := shell.RunShellCommandWithOutput(ctx, ctx.TerragruntOptions, currentPath, suppressOutput, suppressErrorOutput, false, args[0], args[1:]...)
if err != nil {
return "", errors.New(err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ The `terraform` block supports the following arguments:
- `run_on_error` (optional) : If set to true, this hook will run even if a previous hook hit an error, or in the
case of "after" hooks, if the OpenTofu/Terraform command hit an error. Default is false.
- `suppress_stdout` (optional) : If set to true, the stdout output of the executed commands will be suppressed. This can be useful when there are scripts relying on OpenTofu/Terraform's output and any other output would break their parsing.
- `suppress_stderr` (optional) : If set to true, the stderr output of the executed commands will be suppressed. This can be useful when there are scripts relying on OpenTofu/Terraform's output and any other output would break their parsing.

- `after_hook` (block): Nested blocks used to specify command hooks that should be run after `tofu`/`terraform` is called.
Hooks run from the terragrunt configuration directory (the directory where `terragrunt.hcl` lives). Supports the same
Expand Down
1 change: 1 addition & 0 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ExecutionOptions struct {
CmdStderr io.Writer
WorkingDir string
SuppressStdout bool
SuppressStderr bool
AllocatePseudoTty bool
Command string
Args []string
Expand Down
4 changes: 2 additions & 2 deletions shell/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func GitTopLevelDir(ctx context.Context, terragruntOptions *options.TerragruntOp
opts.Writer = &stdout
opts.ErrWriter = &stderr

cmd, err := RunShellCommandWithOutput(ctx, opts, path, true, false, "git", "rev-parse", "--show-toplevel")
cmd, err := RunShellCommandWithOutput(ctx, opts, path, true, true, false, "git", "rev-parse", "--show-toplevel")
if err != nil {
return "", err
}
Expand Down Expand Up @@ -72,7 +72,7 @@ func GitRepoTags(ctx context.Context, opts *options.TerragruntOptions, gitRepo *
gitOpts.Writer = &stdout
gitOpts.ErrWriter = &stderr

output, err := RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, true, false, "git", "ls-remote", "--tags", repoPath)
output, err := RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, true, true, false, "git", "ls-remote", "--tags", repoPath)
if err != nil {
return nil, errors.New(err)
}
Expand Down
12 changes: 10 additions & 2 deletions shell/run_shell_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func RunTerraformCommandWithOutput(ctx context.Context, opts *options.Terragrunt
return nil, err
}

output, err := RunShellCommandWithOutput(ctx, opts, "", false, needsPTY, opts.TerraformPath, args...)
output, err := RunShellCommandWithOutput(ctx, opts, "", false, false, needsPTY, opts.TerraformPath, args...)

if err != nil && util.ListContainsElement(args, terraform.FlagNameDetailedExitCode) {
code, _ := util.GetExitCode(err)
Expand All @@ -87,7 +87,7 @@ func RunTerraformCommandWithOutput(ctx context.Context, opts *options.Terragrunt

// RunShellCommand runs the given shell command.
func RunShellCommand(ctx context.Context, opts *options.TerragruntOptions, command string, args ...string) error {
_, err := RunShellCommandWithOutput(ctx, opts, "", false, false, command, args...)
_, err := RunShellCommandWithOutput(ctx, opts, "", false, false, false, command, args...)

return err
}
Expand All @@ -102,6 +102,7 @@ func RunShellCommandWithOutput(
opts *options.TerragruntOptions,
workingDir string,
suppressStdout bool,
suppressStderr bool,
needsPTY bool,
command string,
args ...string,
Expand Down Expand Up @@ -182,6 +183,12 @@ func RunShellCommandWithOutput(
cmdStdout = io.MultiWriter(&output.Stdout)
}

if suppressStderr {
opts.Logger.Debugf("Command error output will be suppressed.")

cmdStderr = io.MultiWriter(&output.Stderr)
}

if command == opts.TerraformPath {
// If the engine is enabled and the command is IaC executable, use the engine to run the command.
if opts.Engine != nil && opts.EngineEnabled {
Expand All @@ -193,6 +200,7 @@ func RunShellCommandWithOutput(
CmdStderr: cmdStderr,
WorkingDir: commandDir,
SuppressStdout: suppressStdout,
SuppressStderr: suppressStderr,
AllocatePseudoTty: needsPTY,
Command: command,
Args: args,
Expand Down
8 changes: 4 additions & 4 deletions shell/run_shell_cmd_output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ var (
func testCommandOutputOrder(t *testing.T, withPtty bool, fullOutput []string, stdout []string, stderr []string) {
t.Helper()

testCommandOutput(t, noop[*options.TerragruntOptions], assertOutputs(t, fullOutput, stdout, stderr), withPtty)
testCommandOutput(t, noop[*options.TerragruntOptions], assertOutputs(t, fullOutput, stdout, stderr), withPtty, withPtty)
}

func TestCommandOutputPrefix(t *testing.T) {
Expand All @@ -78,10 +78,10 @@ func TestCommandOutputPrefix(t *testing.T) {
prefixedOutput,
Stdout,
Stderr,
), true)
), true, true)
}

func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions), assertResults func(string, *util.CmdOutput), allocateStdout bool) {
func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions), assertResults func(string, *util.CmdOutput), allocateStdout bool, allocateStderr bool) {
t.Helper()

terragruntOptions, err := options.NewTerragruntOptionsForTest("")
Expand All @@ -97,7 +97,7 @@ func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions

withOptions(terragruntOptions)

out, err := shell.RunShellCommandWithOutput(context.Background(), terragruntOptions, "", !allocateStdout, false, "testdata/test_outputs.sh", "same")
out, err := shell.RunShellCommandWithOutput(context.Background(), terragruntOptions, "", !allocateStdout, !allocateStderr, false, "testdata/test_outputs.sh", "same")

assert.NotNil(t, out, "Should get output")
require.NoError(t, err, "Should have no error")
Expand Down
2 changes: 1 addition & 1 deletion shell/run_shell_cmd_unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestRunShellCommandWithOutputInterrupt(t *testing.T) {
cmdPath := "testdata/test_sigint_wait.sh"

go func() {
_, err := shell.RunShellCommandWithOutput(ctx, terragruntOptions, "", false, false, cmdPath, strconv.Itoa(expectedWait))
_, err := shell.RunShellCommandWithOutput(ctx, terragruntOptions, "", false, false, false, cmdPath, strconv.Itoa(expectedWait))
errCh <- err
}()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
terraform {
source = "../base-module"

# SHOULD execute.
after_hook "after_init_from_module" {
commands = ["init-from-module"]
execute = ["${get_parent_terragrunt_dir()}/util.sh","ERROR MESSAGE"]
suppress_stderr = false
}

# SHOULD execute.
after_hook "after_init" {
commands = ["init"]
execute = ["${get_parent_terragrunt_dir()}/util.sh","ERROR MESSAGE"]
suppress_stderr = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
>&2 echo "$1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
terraform {
source = "../base-module"

# SHOULD execute.
after_hook "after_init_from_module" {
commands = ["init-from-module"]
execute = ["${get_parent_terragrunt_dir()}/util.sh","ERROR MESSAGE"]
suppress_stderr = true
}

# SHOULD execute.
after_hook "after_init" {
commands = ["init"]
execute = ["${get_parent_terragrunt_dir()}/util.sh","ERROR MESSAGE"]
suppress_stderr = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
>&2 echo "$1"
70 changes: 54 additions & 16 deletions test/integration_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,24 @@ import (
)

const (
testFixtureHooksBeforeOnlyPath = "fixtures/hooks/before-only"
testFixtureHooksAllPath = "fixtures/hooks/all"
testFixtureHooksAfterOnlyPath = "fixtures/hooks/after-only"
testFixtureHooksBeforeAndAfterPath = "fixtures/hooks/before-and-after"
testFixtureHooksBeforeAfterAndErrorMergePath = "fixtures/hooks/before-after-and-error-merge"
testFixtureHooksSkipOnErrorPath = "fixtures/hooks/skip-on-error"
testFixtureErrorHooksPath = "fixtures/hooks/error-hooks"
testFixtureHooksOneArgActionPath = "fixtures/hooks/one-arg-action"
testFixtureHooksEmptyStringCommandPath = "fixtures/hooks/bad-arg-action/empty-string-command"
testFixtureHooksEmptyCommandListPath = "fixtures/hooks/bad-arg-action/empty-command-list"
testFixtureHooksInterpolationsPath = "fixtures/hooks/interpolations"
testFixtureHooksInitOnceNoSourceNoBackend = "fixtures/hooks/init-once/no-source-no-backend"
testFixtureHooksInitOnceNoSourceWithBackend = "fixtures/hooks/init-once/no-source-with-backend"
testFixtureHooksInitOnceWithSourceNoBackend = "fixtures/hooks/init-once/with-source-no-backend"
testFixtureHooksInitOnceWithSourceNoBackendSuppressHookStdout = "fixtures/hooks/init-once/with-source-no-backend-suppress-hook-stdout"
testFixtureHooksInitOnceWithSourceWithBackend = "fixtures/hooks/init-once/with-source-with-backend"
testFixtureHooksBeforeOnlyPath = "fixtures/hooks/before-only"
testFixtureHooksAllPath = "fixtures/hooks/all"
testFixtureHooksAfterOnlyPath = "fixtures/hooks/after-only"
testFixtureHooksBeforeAndAfterPath = "fixtures/hooks/before-and-after"
testFixtureHooksBeforeAfterAndErrorMergePath = "fixtures/hooks/before-after-and-error-merge"
testFixtureHooksSkipOnErrorPath = "fixtures/hooks/skip-on-error"
testFixtureErrorHooksPath = "fixtures/hooks/error-hooks"
testFixtureHooksOneArgActionPath = "fixtures/hooks/one-arg-action"
testFixtureHooksEmptyStringCommandPath = "fixtures/hooks/bad-arg-action/empty-string-command"
testFixtureHooksEmptyCommandListPath = "fixtures/hooks/bad-arg-action/empty-command-list"
testFixtureHooksInterpolationsPath = "fixtures/hooks/interpolations"
testFixtureHooksInitOnceNoSourceNoBackend = "fixtures/hooks/init-once/no-source-no-backend"
testFixtureHooksInitOnceNoSourceWithBackend = "fixtures/hooks/init-once/no-source-with-backend"
testFixtureHooksInitOnceWithSourceNoBackend = "fixtures/hooks/init-once/with-source-no-backend"
testFixtureHooksInitOnceWithSourceNoBackendSuppressHookStdout = "fixtures/hooks/init-once/with-source-no-backend-suppress-hook-stdout"
testFixtureHooksInitOnceWithSourceNoBackendNoSuppressHookStderr = "fixtures/hooks/init-once/with-source-no-backend-no-suppress-hook-stderr"
testFixtureHooksInitOnceWithSourceNoBackendSuppressHookStderr = "fixtures/hooks/init-once/with-source-no-backend-suppress-hook-stderr"
testFixtureHooksInitOnceWithSourceWithBackend = "fixtures/hooks/init-once/with-source-with-backend"
)

func TestTerragruntBeforeHook(t *testing.T) {
Expand Down Expand Up @@ -385,3 +387,39 @@ func TestTerragruntInfo(t *testing.T) {
assert.Equal(t, wrappedBinary(), dat.TerraformBinary)
assert.Empty(t, dat.IamRole)
}

func TestTerragruntStderr(t *testing.T) {
t.Parallel()

helpers.CleanupTerraformFolder(t, testFixtureHooksInitOnceWithSourceNoBackendNoSuppressHookStderr)
tmpEnvPath := helpers.CopyEnvironment(t, "fixtures/hooks/init-once")
rootPath := util.JoinPath(tmpEnvPath, testFixtureHooksInitOnceWithSourceNoBackendNoSuppressHookStderr)

showStdout := bytes.Buffer{}
showStderr := bytes.Buffer{}

err := helpers.RunTerragruntCommand(t, "terragrunt terragrunt-info --terragrunt-non-interactive --terragrunt-working-dir "+rootPath, &showStdout, &showStderr)
require.NoError(t, err)

helpers.LogBufferContentsLineByLine(t, showStderr, "show stderr")

assert.Contains(t, showStderr.String(), "ERROR MESSAGE")
}

func TestTerragruntStderrSuppress(t *testing.T) {
t.Parallel()

helpers.CleanupTerraformFolder(t, testFixtureHooksInitOnceWithSourceNoBackendSuppressHookStderr)
tmpEnvPath := helpers.CopyEnvironment(t, "fixtures/hooks/init-once")
rootPath := util.JoinPath(tmpEnvPath, testFixtureHooksInitOnceWithSourceNoBackendSuppressHookStderr)

showStdout := bytes.Buffer{}
showStderr := bytes.Buffer{}

err := helpers.RunTerragruntCommand(t, "terragrunt terragrunt-info --terragrunt-non-interactive --terragrunt-working-dir "+rootPath, &showStdout, &showStderr)
require.NoError(t, err)

helpers.LogBufferContentsLineByLine(t, showStderr, "show stderr")

assert.NotContains(t, showStderr.String(), "ERROR MESSAGE")
}
4 changes: 2 additions & 2 deletions tflint/tflint.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func RunTflintWithOpts(ctx context.Context, opts *options.TerragruntOptions, con
if externalTfLint {
opts.Logger.Debugf("Running external tflint init with args %v", initArgs)

_, err := shell.RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, false, false,
_, err := shell.RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, false, false, false,
initArgs[0], initArgs[1:]...)
if err != nil {
return errors.New(ErrorRunningTflint{args: initArgs})
Expand All @@ -95,7 +95,7 @@ func RunTflintWithOpts(ctx context.Context, opts *options.TerragruntOptions, con
if externalTfLint {
opts.Logger.Debugf("Running external tflint with args %v", args)

_, err := shell.RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, false, false,
_, err := shell.RunShellCommandWithOutput(ctx, opts, opts.WorkingDir, false, false, false,
args[0], args[1:]...)
if err != nil {
return errors.New(ErrorRunningTflint{args: args})
Expand Down