Repository: gruntwork-io/terragrunt (language: go)

Issue:
Track code quality
**Describe the solution you'd like**
Integrate tools to track code quality and flag any deviations  

**Describe alternatives you've considered**
Considered linters:

https://golangci-lint.run/

https://github.com/alecthomas/gometalinter

https://www.jetbrains.com/qodana/

**Additional context**
N/A


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e6d454cf90..355699238d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -103,6 +103,11 @@ jobs:
             export GOPATH=~/go/bin && export PATH=$PATH:$GOPATH
             pre-commit install
             pre-commit run --all-files
+      - run:
+          name: run lint
+          command: |
+            make install-lint
+            make run-lint
       - run:
           command: |
             mkdir -p logs
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000000..ddd5e95e1b
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,103 @@
+run:
+  timeout: 2m
+  issues-exit-code: 1
+  tests: true
+  skip-dirs:
+    - docs
+    - _ci
+    - .github
+    - .circleci
+output:
+  format: colored-line-number
+  print-issued-lines: true
+  print-linter-name: true
+
+linters-settings:
+  staticcheck:
+    checks:
+      - all
+      - '-SA9005'
+  errcheck:
+    check-type-assertions: false
+    check-blank: false
+    exclude-functions:
+      - (*os.File).Close
+  errorlint:
+    errorf: true
+    asserts: true
+    comparison: true
+  gofmt:
+    simplify: true
+  gofumpt:
+    lang-version: "1.21"
+    extra-rules: false
+  gosimple:
+    go: "1.21"
+    checks: [ "all" ]
+  dupl:
+    threshold: 120
+  goconst:
+    min-len: 3
+    min-occurrences: 5
+  gomnd:
+    settings:
+      mnd:
+        ignored-functions: strconv.Format*,os.*,strconv.Parse*,strings.SplitN,bytes.SplitN
+  revive:
+    min-confidence: 0.8
+  unused:
+    check-exported: false
+  unparam:
+    check-exported: false
+  nakedret:
+    max-func-lines: 20
+  gocritic:
+    disabled-checks:
+      - regexpMust
+      - rangeValCopy
+      - appendAssign
+      - hugeParam
+    enabled-tags:
+      - performance
+    disabled-tags:
+      - experimental
+
+linters:
+  enable:
+    - dupl
+    - errcheck
+    - goconst
+    - gocritic
+    - goimports
+    - gomnd
+    - gosimple
+    - govet
+    - ineffassign
+    - megacheck
+    - misspell
+    - unconvert
+    - unused
+    - unparam
+  enable-all: false
+  disable:
+    - depguard
+    - gosec
+    - interfacer
+    - gocyclo
+  fast: false
+
+issues:
+  exclude-rules:
+    - path: _test\.go
+      linters:
+        - dupl
+        - gocyclo
+        - lll
+        - errcheck
+        - wsl
+        - gomnd
+        - unparam
+
+  exclude-use-default: false
+  max-issues-per-linter: 0
+  max-same-issues: 0
diff --git a/Makefile b/Makefile
index 3458ab428c..ae8bdd63e4 100644
--- a/Makefile
+++ b/Makefile
@@ -38,4 +38,10 @@ terragrunt: $(shell find . \( -type d -name 'vendor' -prune \) \
 clean:
 	rm -f terragrunt
 
-.PHONY: help fmtcheck fmt install-fmt-hook clean
+install-lint:
+	go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2
+
+run-lint:
+	golangci-lint run -v ./...
+
+.PHONY: help fmtcheck fmt install-fmt-hook clean install-lint run-lint
diff --git a/cli/commands/aws-provider-patch/action.go b/cli/commands/aws-provider-patch/action.go
index 5ac1a307d5..c86db49b6d 100644
--- a/cli/commands/aws-provider-patch/action.go
+++ b/cli/commands/aws-provider-patch/action.go
@@ -47,6 +47,8 @@ import (
 	"github.com/gruntwork-io/terragrunt/util"
 )
 
+const defaultKeyParts = 2
+
 func Run(opts *options.TerragruntOptions) error {
 	target := terraform.NewTarget(terraform.TargetPointInitCommand, runAwsProviderPatch)
 
@@ -310,7 +312,7 @@ func traverseBlock(block *hclwrite.Block, keyParts []string) (*hclwrite.Body, st
 		return nil, ""
 	}
 
-	if len(keyParts) < 2 {
+	if len(keyParts) < defaultKeyParts {
 		return block.Body(), strings.Join(keyParts, "")
 	}
 
diff --git a/cli/commands/hclfmt/action.go b/cli/commands/hclfmt/action.go
index 9f3955c389..9bcd8f94c3 100644
--- a/cli/commands/hclfmt/action.go
+++ b/cli/commands/hclfmt/action.go
@@ -1,4 +1,4 @@
-// `hclFmt` commmand recursively looks for hcl files in the directory tree starting at workingDir, and formats them
+// `hclFmt` command recursively looks for hcl files in the directory tree starting at workingDir, and formats them
 // based on the language style guides provided by Hashicorp. This is done using the official hcl2 library.
 
 package hclfmt
@@ -11,6 +11,8 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/gruntwork-io/go-commons/errors"
+
 	"github.com/sirupsen/logrus"
 
 	"github.com/hashicorp/go-multierror"
@@ -35,8 +37,6 @@ func Run(opts *options.TerragruntOptions) error {
 		return formatTgHCL(opts, targetFile)
 	}
 
-	targetFile = filepath.ToSlash(targetFile)
-
 	opts.Logger.Debugf("Formatting hcl files from the directory tree %s.", opts.WorkingDir)
 	// zglob normalizes paths to "/"
 	tgHclFiles, err := zglob.Glob(util.JoinPath(workingDir, "**", "*.hcl"))
@@ -96,7 +96,7 @@ func formatTgHCL(opts *options.TerragruntOptions, tgHclFile string) error {
 	fileUpdated := !bytes.Equal(newContents, contents)
 
 	if opts.Diff && fileUpdated {
-		diff, err := bytesDiff(contents, newContents, tgHclFile)
+		diff, err := bytesDiff(opts, contents, newContents, tgHclFile)
 		if err != nil {
 			opts.Logger.Errorf("Failed to generate diff for %s", tgHclFile)
 			return err
@@ -125,7 +125,10 @@ func checkErrors(logger *logrus.Entry, contents []byte, tgHclFile string) error
 	parser := hclparse.NewParser()
 	_, diags := parser.ParseHCL(contents, tgHclFile)
 	diagWriter := util.GetDiagnosticsWriter(logger, parser)
-	diagWriter.WriteDiagnostics(diags)
+	err := diagWriter.WriteDiagnostics(diags)
+	if err != nil {
+		return errors.WithStackTrace(err)
+	}
 	if diags.HasErrors() {
 		return diags
 	}
@@ -133,14 +136,18 @@ func checkErrors(logger *logrus.Entry, contents []byte, tgHclFile string) error
 }
 
 // bytesDiff uses GNU diff to display the differences between the contents of HCL file before and after formatting
-func bytesDiff(b1, b2 []byte, path string) ([]byte, error) {
+func bytesDiff(opts *options.TerragruntOptions, b1, b2 []byte, path string) ([]byte, error) {
 	f1, err := os.CreateTemp("", "")
 	if err != nil {
 		return nil, err
 	}
 	defer func() {
-		f1.Close()
-		os.Remove(f1.Name())
+		if err := f1.Close(); err != nil {
+			opts.Logger.Warnf("Failed to close file %s %v", f1.Name(), err)
+		}
+		if err := os.Remove(f1.Name()); err != nil {
+			opts.Logger.Warnf("Failed to remove file %s %v", f1.Name(), err)
+		}
 	}()
 
 	f2, err := os.CreateTemp("", "")
@@ -148,8 +155,12 @@ func bytesDiff(b1, b2 []byte, path string) ([]byte, error) {
 		return nil, err
 	}
 	defer func() {
-		f2.Close()
-		os.Remove(f2.Name())
+		if err := f2.Close(); err != nil {
+			opts.Logger.Warnf("Failed to close file %s %v", f2.Name(), err)
+		}
+		if err := os.Remove(f2.Name()); err != nil {
+			opts.Logger.Warnf("Failed to remove file %s %v", f2.Name(), err)
+		}
 	}()
 	if _, err := f1.Write(b1); err != nil {
 		return nil, err
diff --git a/cli/commands/run-all/action.go b/cli/commands/run-all/action.go
index 209f17e735..b4194f263a 100644
--- a/cli/commands/run-all/action.go
+++ b/cli/commands/run-all/action.go
@@ -62,7 +62,7 @@ func Run(opts *options.TerragruntOptions) error {
 		if err != nil {
 			return err
 		}
-		if shouldRunAll == false {
+		if !shouldRunAll {
 			return nil
 		}
 	}
diff --git a/cli/commands/terraform/action.go b/cli/commands/terraform/action.go
index 124d8bde09..9f0f83a5c9 100644
--- a/cli/commands/terraform/action.go
+++ b/cli/commands/terraform/action.go
@@ -209,7 +209,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
 			return nil
 		}
 	}
-	return runTerragruntWithConfig(terragruntOptions, updatedTerragruntOptions, terragruntConfig, false, target)
+	return runTerragruntWithConfig(terragruntOptions, updatedTerragruntOptions, terragruntConfig, target)
 }
 
 func generateConfig(terragruntConfig *config.TerragruntConfig, updatedTerragruntOptions *options.TerragruntOptions) error {
@@ -242,7 +242,7 @@ func generateConfig(terragruntConfig *config.TerragruntConfig, updatedTerragrunt
 
 // This function takes in the "original" terragrunt options which has the unmodified 'WorkingDir' from before downloading the code from the source URL,
 // and the "updated" terragrunt options that will contain the updated 'WorkingDir' into which the code has been downloaded
-func runTerragruntWithConfig(originalTerragruntOptions *options.TerragruntOptions, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig, allowSourceDownload bool, target *Target) error {
+func runTerragruntWithConfig(originalTerragruntOptions *options.TerragruntOptions, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig, target *Target) error {
 	// Add extra_arguments to the command
 	if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.ExtraArgs != nil && len(terragruntConfig.Terraform.ExtraArgs) > 0 {
 		args := filterTerraformExtraArgs(terragruntOptions, terragruntConfig)
@@ -257,7 +257,7 @@ func runTerragruntWithConfig(originalTerragruntOptions *options.TerragruntOption
 	}
 
 	if util.FirstArg(terragruntOptions.TerraformCliArgs) == CommandNameInit {
-		if err := prepareInitCommand(terragruntOptions, terragruntConfig, allowSourceDownload); err != nil {
+		if err := prepareInitCommand(terragruntOptions, terragruntConfig); err != nil {
 			return err
 		}
 	} else {
@@ -420,7 +420,7 @@ func runTerraformWithRetry(terragruntOptions *options.TerragruntOptions) error {
 
 // Prepare for running 'terraform init' by initializing remote state storage and adding backend configuration arguments
 // to the TerraformCliArgs
-func prepareInitCommand(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig, allowSourceDownload bool) error {
+func prepareInitCommand(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
 	if terragruntConfig.RemoteState != nil {
 		// Initialize the remote state if necessary  (e.g. create S3 bucket and DynamoDB table)
 		remoteStateNeedsInit, err := remoteStateNeedsInit(terragruntConfig.RemoteState, terragruntOptions)
@@ -501,7 +501,7 @@ func prepareNonInitCommand(originalTerragruntOptions *options.TerragruntOptions,
 	}
 
 	if needsInit {
-		if err := runTerraformInit(originalTerragruntOptions, terragruntOptions, terragruntConfig, nil); err != nil {
+		if err := runTerraformInit(originalTerragruntOptions, terragruntOptions, terragruntConfig); err != nil {
 			return err
 		}
 	}
@@ -549,7 +549,7 @@ func providersNeedInit(terragruntOptions *options.TerragruntOptions) bool {
 //
 // This method takes in the "original" terragrunt options which has the unmodified 'WorkingDir' from before downloading the code from the source URL,
 // and the "updated" terragrunt options that will contain the updated 'WorkingDir' into which the code has been downloaded
-func runTerraformInit(originalTerragruntOptions *options.TerragruntOptions, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig, terraformSource *terraform.Source) error {
+func runTerraformInit(originalTerragruntOptions *options.TerragruntOptions, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
 
 	// Prevent Auto-Init if the user has disabled it
 	if util.FirstArg(terragruntOptions.TerraformCliArgs) != CommandNameInit && !terragruntOptions.AutoInit {
@@ -557,12 +557,9 @@ func runTerraformInit(originalTerragruntOptions *options.TerragruntOptions, terr
 		return nil
 	}
 
-	initOptions, err := prepareInitOptions(terragruntOptions, terraformSource)
-	if err != nil {
-		return err
-	}
+	initOptions := prepareInitOptions(terragruntOptions)
 
-	err = runTerragruntWithConfig(originalTerragruntOptions, initOptions, terragruntConfig, terraformSource != nil, new(Target))
+	err := runTerragruntWithConfig(originalTerragruntOptions, initOptions, terragruntConfig, new(Target))
 	if err != nil {
 		return err
 	}
@@ -574,7 +571,7 @@ func runTerraformInit(originalTerragruntOptions *options.TerragruntOptions, terr
 	return nil
 }
 
-func prepareInitOptions(terragruntOptions *options.TerragruntOptions, terraformSource *terraform.Source) (*options.TerragruntOptions, error) {
+func prepareInitOptions(terragruntOptions *options.TerragruntOptions) *options.TerragruntOptions {
 	// Need to clone the terragruntOptions, so the TerraformCliArgs can be configured to run the init command
 	initOptions := terragruntOptions.Clone(terragruntOptions.TerragruntConfigPath)
 	initOptions.TerraformCliArgs = []string{CommandNameInit}
@@ -592,7 +589,7 @@ func prepareInitOptions(terragruntOptions *options.TerragruntOptions, terraformS
 		initOptions.TerraformCliArgs = append(initOptions.TerraformCliArgs, TerraformFlagNoColor)
 	}
 
-	return initOptions, nil
+	return initOptions
 }
 
 // Return true if modules aren't already downloaded and the Terraform templates in this project reference modules.
@@ -731,7 +728,7 @@ func toTerraformEnvVars(vars map[string]interface{}) (map[string]string, error)
 			return nil, err
 		}
 
-		out[envVarName] = string(envVarValue)
+		out[envVarName] = envVarValue
 	}
 
 	return out, nil
diff --git a/cli/commands/terraform/debug.go b/cli/commands/terraform/debug.go
index a4481f5e9c..a5ea91a62d 100644
--- a/cli/commands/terraform/debug.go
+++ b/cli/commands/terraform/debug.go
@@ -16,6 +16,8 @@ import (
 
 const TerragruntTFVarsFile = "terragrunt-debug.tfvars.json"
 
+const defaultPermissions = int(0600)
+
 // WriteTerragruntDebugFile will create a tfvars file that can be used to invoke the terraform module in the same way
 // that terragrunt invokes the module, so that you can debug issues with the terragrunt config.
 func WriteTerragruntDebugFile(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
@@ -41,7 +43,7 @@ func WriteTerragruntDebugFile(terragruntOptions *options.TerragruntOptions, terr
 
 	configFolder := filepath.Dir(terragruntOptions.TerragruntConfigPath)
 	fileName := filepath.Join(configFolder, TerragruntTFVarsFile)
-	if err := os.WriteFile(fileName, fileContents, os.FileMode(int(0600))); err != nil {
+	if err := os.WriteFile(fileName, fileContents, os.FileMode(defaultPermissions)); err != nil {
 		return errors.WithStackTrace(err)
 	}
 
@@ -78,14 +80,15 @@ func terragruntDebugFileContents(
 		// Only add to the file if the explicit env var does NOT exist and the variable is defined in the module.
 		// We must do this in order to avoid overriding the env var when the user follows up with a direct invocation to
 		// terraform using this file (due to the order in which terraform resolves config sources).
-		if !varIsInEnv && varIsDefined {
+		switch {
+		case !varIsInEnv && varIsDefined:
 			jsonValuesByKey[varName] = varValue
-		} else if varIsInEnv {
+		case varIsInEnv:
 			terragruntOptions.Logger.Debugf(
 				"WARN: The variable %s was omitted from the debug file because the env var %s is already set.",
 				varName, nameAsEnvVar,
 			)
-		} else if !varIsDefined {
+		case !varIsDefined:
 			terragruntOptions.Logger.Debugf(
 				"WARN: The variable %s was omitted because it is not defined in the terraform module.",
 				varName,
diff --git a/cli/commands/terraform/download_source.go b/cli/commands/terraform/download_source.go
index 57dc379934..b6884fbb4e 100644
--- a/cli/commands/terraform/download_source.go
+++ b/cli/commands/terraform/download_source.go
@@ -220,7 +220,7 @@ func downloadSource(terraformSource *terraform.Source, terragruntOptions *option
 
 // Check if working terraformSource.WorkingDir exists and is directory
 func validateWorkingDir(terraformSource *terraform.Source) error {
-	workingLocalDir := strings.Replace(terraformSource.WorkingDir, terraformSource.DownloadDir+filepath.FromSlash("/"), "", -1)
+	workingLocalDir := strings.ReplaceAll(terraformSource.WorkingDir, terraformSource.DownloadDir+filepath.FromSlash("/"), "")
 	if util.IsFile(terraformSource.WorkingDir) {
 		return WorkingDirNotDir{Dir: workingLocalDir, Source: terraformSource.CanonicalSourceURL.String()}
 	}
diff --git a/cli/commands/terraform/file_copy_getter.go b/cli/commands/terraform/file_copy_getter.go
index 3b219a217a..d7e8b83ecf 100644
--- a/cli/commands/terraform/file_copy_getter.go
+++ b/cli/commands/terraform/file_copy_getter.go
@@ -41,12 +41,12 @@ func (g *FileCopyGetter) Get(dst string, u *url.URL) error {
 	return util.CopyFolderContents(path, dst, SourceManifestName, g.IncludeInCopy)
 }
 
-// The original FileGetter already knows how to do file copying so long as we set the Copy flag to true, so just
+// GetFile The original FileGetter already knows how to do file copying so long as we set the Copy flag to true, so just
 // delegate to it
 func (g *FileCopyGetter) GetFile(dst string, u *url.URL) error {
 	underlying := &getter.FileGetter{Copy: true}
 	if err := underlying.GetFile(dst, u); err != nil {
-		errors.WithStackTrace(err)
+		return errors.WithStackTrace(err)
 	}
 	return nil
 }
diff --git a/cli/commands/terraform/hook.go b/cli/commands/terraform/hook.go
index 8c6b9ae2d7..6db55b4bce 100644
--- a/cli/commands/terraform/hook.go
+++ b/cli/commands/terraform/hook.go
@@ -99,12 +99,12 @@ func processHooks(hooks []config.Hook, terragruntOptions *options.TerragruntOpti
 }
 
 func shouldRunHook(hook config.Hook, terragruntOptions *options.TerragruntOptions, previousExecErrors *multierror.Error) bool {
-	//if there's no previous error, execute command
-	//OR if a previous error DID happen AND we want to run anyways
-	//then execute.
-	//Skip execution if there was an error AND we care about errors
+	// if there's no previous error, execute command
+	// OR if a previous error DID happen AND we want to run anyways
+	// then execute.
+	// Skip execution if there was an error AND we care about errors
 
-	//resolves: https://github.com/gruntwork-io/terragrunt/issues/459
+	// resolves: https://github.com/gruntwork-io/terragrunt/issues/459
 	hasErrors := previousExecErrors.ErrorOrNil() != nil
 	isCommandInHook := util.ListContainsElement(hook.Commands, terragruntOptions.TerraformCommand)
 
diff --git a/cli/commands/terraform/version_check.go b/cli/commands/terraform/version_check.go
index 29e244cf97..40d78a6764 100644
--- a/cli/commands/terraform/version_check.go
+++ b/cli/commands/terraform/version_check.go
@@ -24,6 +24,8 @@ const DefaultTerraformVersionConstraint = ">= v0.12.0"
 // We only make sure the "v#.#.#" part is present in the output.
 var TerraformVersionRegex = regexp.MustCompile(`^(.*?)\s(v?\d+\.\d+\.\d+).*`)
 
+const versionParts = 3
+
 // Check the version constraints of both terragrunt and terraform. Note that as a side effect this will set the
 // following settings on terragruntOptions:
 // - TerraformPath
@@ -153,7 +155,7 @@ func checkTerraformVersionMeetsConstraint(currentVersion *version.Version, const
 func parseTerraformVersion(versionCommandOutput string) (*version.Version, error) {
 	matches := TerraformVersionRegex.FindStringSubmatch(versionCommandOutput)
 
-	if len(matches) != 3 {
+	if len(matches) != versionParts {
 		return nil, errors.WithStackTrace(InvalidTerraformVersionSyntax(versionCommandOutput))
 	}
 
@@ -164,7 +166,7 @@ func parseTerraformVersion(versionCommandOutput string) (*version.Version, error
 func parseTerraformImplementationType(versionCommandOutput string) (options.TerraformImplementationType, error) {
 	matches := TerraformVersionRegex.FindStringSubmatch(versionCommandOutput)
 
-	if len(matches) != 3 {
+	if len(matches) != versionParts {
 		return options.UnknownImpl, errors.WithStackTrace(InvalidTerraformVersionSyntax(versionCommandOutput))
 	}
 	rawType := strings.ToLower(matches[1])
diff --git a/cli/commands/validate-inputs/action.go b/cli/commands/validate-inputs/action.go
index 200bef0f5c..556ea9cf31 100644
--- a/cli/commands/validate-inputs/action.go
+++ b/cli/commands/validate-inputs/action.go
@@ -20,6 +20,8 @@ import (
 	"github.com/gruntwork-io/terragrunt/util"
 )
 
+const splitCount = 2
+
 func Run(opts *options.TerragruntOptions) error {
 	target := terraform.NewTarget(terraform.TargetPointGenerateConfig, runValidateInputs)
 
@@ -312,7 +314,7 @@ func getVarFlagsFromArgList(argList []string) ([]string, []string, error) {
 		if strings.HasPrefix(shlexedArg, "-var=") {
 			// -var is passed in in the format -var=VARNAME=VALUE, so we split on '=' and take the middle value.
 			splitArg := strings.Split(shlexedArg, "=")
-			if len(splitArg) < 2 {
+			if len(splitArg) < splitCount {
 				return vars, varFiles, fmt.Errorf("Unexpected -var arg format in terraform.extra_arguments.arguments. Expected '-var=VARNAME=VALUE', got %s.", arg)
 			}
 			vars = append(vars, splitArg[1])
diff --git a/cli/deprecated_commands.go b/cli/deprecated_commands.go
index dd8573b297..55b15c1037 100644
--- a/cli/deprecated_commands.go
+++ b/cli/deprecated_commands.go
@@ -1,3 +1,4 @@
+//nolint:unparam
 package cli
 
 import (
diff --git a/config/config.go b/config/config.go
index 3162bece3f..93829c2aac 100644
--- a/config/config.go
+++ b/config/config.go
@@ -447,13 +447,11 @@ func (conf *TerraformExtraArguments) String() string {
 }
 
 func (conf *TerraformExtraArguments) GetVarFiles(logger *logrus.Entry) []string {
-	varFiles := []string{}
+	var varFiles []string
 
 	// Include all specified RequiredVarFiles.
 	if conf.RequiredVarFiles != nil {
-		for _, file := range util.RemoveDuplicatesFromListKeepLast(*conf.RequiredVarFiles) {
-			varFiles = append(varFiles, file)
-		}
+		varFiles = append(varFiles, util.RemoveDuplicatesFromListKeepLast(*conf.RequiredVarFiles)...)
 	}
 
 	// If OptionalVarFiles is specified, check for each file if it exists and if so, include in the var
@@ -476,11 +474,12 @@ func (conf *TerraformExtraArguments) GetVarFiles(logger *logrus.Entry) []string
 // URL: via a command-line option or via an entry in the Terragrunt configuration. If the user used one of these, this
 // method returns the source URL or an empty string if there is no source url
 func GetTerraformSourceUrl(terragruntOptions *options.TerragruntOptions, terragruntConfig *TerragruntConfig) (string, error) {
-	if terragruntOptions.Source != "" {
+	switch {
+	case terragruntOptions.Source != "":
 		return terragruntOptions.Source, nil
-	} else if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.Source != nil {
+	case terragruntConfig.Terraform != nil && terragruntConfig.Terraform.Source != nil:
 		return adjustSourceWithMap(terragruntOptions.SourceMap, *terragruntConfig.Terraform.Source, terragruntOptions.OriginalTerragruntConfigPath)
-	} else {
+	default:
 		return "", nil
 	}
 }
@@ -531,7 +530,7 @@ func adjustSourceWithMap(sourceMap map[string]string, source string, modulePath
 	// Check if there is an entry to replace the URL portion in the map. Return the source as is if there is no entry in
 	// the map.
 	sourcePath, hasKey := sourceMap[moduleUrlQuery]
-	if hasKey == false {
+	if !hasKey {
 		return source, nil
 	}
 
@@ -1125,7 +1124,7 @@ func validateGenerateBlocks(blocks *[]terragruntGenerateBlock) error {
 // configFileHasDependencyBlock statically checks the terrragrunt config file at the given path and checks if it has any
 // dependency or dependencies blocks defined. Note that this does not do any decoding of the blocks, as it is only meant
 // to check for block presence.
-func configFileHasDependencyBlock(configPath string, terragruntOptions *options.TerragruntOptions) (bool, error) {
+func configFileHasDependencyBlock(configPath string) (bool, error) {
 	configBytes, err := os.ReadFile(configPath)
 	if err != nil {
 		return false, errors.WithStackTrace(err)
@@ -1173,7 +1172,7 @@ func (conf *TerragruntConfig) SetFieldMetadata(fieldName string, m map[string]in
 // SetFieldMetadataMap set metadata on fields from map keys.
 // Example usage - setting metadata on all variables from inputs.
 func (conf *TerragruntConfig) SetFieldMetadataMap(field string, data map[string]interface{}, metadata map[string]interface{}) {
-	for name, _ := range data {
+	for name := range data {
 		conf.SetFieldMetadataWithType(field, name, metadata)
 	}
 }
diff --git a/config/config_helpers.go b/config/config_helpers.go
index 1b3d8c2faf..6ce46a6eb4 100644
--- a/config/config_helpers.go
+++ b/config/config_helpers.go
@@ -24,6 +24,9 @@ import (
 	"github.com/gruntwork-io/terragrunt/util"
 )
 
+const noMatchedPats = 1
+const matchedPats = 2
+
 // List of terraform commands that accept -lock-timeout
 var TERRAFORM_COMMANDS_NEED_LOCKING = []string{
 	"apply",
@@ -217,7 +220,7 @@ func getPathToRepoRoot(trackInclude *TrackInclude, terragruntOptions *options.Te
 		return "", errors.WithStackTrace(err)
 	}
 
-	repoRootPathAbs, err := filepath.Rel(terragruntOptions.WorkingDir, string(repoAbsPath))
+	repoRootPathAbs, err := filepath.Rel(terragruntOptions.WorkingDir, repoAbsPath)
 	if err != nil {
 		return "", errors.WithStackTrace(err)
 	}
@@ -268,10 +271,10 @@ func parseGetEnvParameters(parameters []string) (EnvVar, error) {
 	envVariable := EnvVar{}
 
 	switch len(parameters) {
-	case 1:
+	case noMatchedPats:
 		envVariable.IsRequired = true
 		envVariable.Name = parameters[0]
-	case 2:
+	case matchedPats:
 		envVariable.Name = parameters[0]
 		envVariable.DefaultValue = parameters[1]
 	default:
@@ -382,7 +385,7 @@ func findInParentFolders(
 	if numParams > 1 {
 		fallbackParam = params[1]
 	}
-	if numParams > 2 {
+	if numParams > matchedPats {
 		return "", errors.WithStackTrace(WrongNumberOfParams{Func: "find_in_parent_folders", Expected: "0, 1, or 2", Actual: numParams})
 	}
 
@@ -403,7 +406,7 @@ func findInParentFolders(
 	for i := 0; i < terragruntOptions.MaxFoldersToCheck; i++ {
 		currentDir := filepath.ToSlash(filepath.Dir(previousDir))
 		if currentDir == previousDir {
-			if numParams == 2 {
+			if numParams == matchedPats {
 				return fallbackParam, nil
 			}
 			return "", errors.WithStackTrace(ParentFileNotFound{Path: terragruntOptions.TerragruntConfigPath, File: fileToFindStr, Cause: "Traversed all the way to the root"})
@@ -433,16 +436,17 @@ func pathRelativeToInclude(params []string, trackInclude *TrackInclude, terragru
 	}
 
 	var included IncludeConfig
-	if trackInclude.Original != nil {
+	switch {
+	case trackInclude.Original != nil:
 		included = *trackInclude.Original
-	} else if len(trackInclude.CurrentList) > 0 {
+	case len(trackInclude.CurrentList) > 0:
 		// Called in child context, so we need to select the right include file.
 		selected, err := getSelectedIncludeBlock(*trackInclude, params)
 		if err != nil {
 			return "", err
 		}
 		included = *selected
-	} else {
+	default:
 		return ".", nil
 	}
 
@@ -548,7 +552,10 @@ func readTerragruntConfig(configPath string, defaultVal *cty.Value, terragruntOp
 	// NOTE: this will not call terragrunt output, since all the values are cached from the ParseConfigFile call
 	// NOTE: we don't use range here because range will copy the slice, thereby undoing the set attribute.
 	for i := 0; i < len(config.TerragruntDependencies); i++ {
-		config.TerragruntDependencies[i].setRenderedOutputs(targetOptions)
+		err := config.TerragruntDependencies[i].setRenderedOutputs(targetOptions)
+		if err != nil {
+			return cty.NilVal, errors.WithStackTrace(err)
+		}
 	}
 
 	return TerragruntConfigAsCty(config)
@@ -575,7 +582,7 @@ func readTerragruntConfigAsFuncImpl(terragruntOptions *options.TerragruntOptions
 			}
 
 			var defaultVal *cty.Value = nil
-			if numParams == 2 {
+			if numParams == matchedPats {
 				defaultVal = &args[1]
 			}
 
@@ -657,7 +664,7 @@ func getModulePathFromSourceUrl(sourceUrl string) (string, error) {
 	matches := moduleNameRegexp.FindStringSubmatch(sourceUrl)
 
 	// if regexp returns less/more than the full match + 1 capture group, then something went wrong with regex (invalid source string)
-	if len(matches) != 2 {
+	if len(matches) != matchedPats {
 		return "", errors.WithStackTrace(ErrorParsingModulePath{ModuleSourceUrl: sourceUrl})
 	}
 
@@ -803,7 +810,7 @@ func endsWith(args []string, trackInclude *TrackInclude, terragruntOptions *opti
 
 // timeCmp implements Terraform's `timecmp` function that compares two timestamps.
 func timeCmp(args []string, trackInclude *TrackInclude, terragruntOptions *options.TerragruntOptions) (int64, error) {
-	if len(args) != 2 {
+	if len(args) != matchedPats {
 		return 0, errors.WithStackTrace(fmt.Errorf("function can take only two parameters: timestamp_a and timestamp_b"))
 	}
 
diff --git a/config/config_partial.go b/config/config_partial.go
index 419d298b9b..679cfcf648 100644
--- a/config/config_partial.go
+++ b/config/config_partial.go
@@ -290,7 +290,7 @@ func PartialParseConfigString(
 
 			// Convert dependency blocks into module depenency lists. If we already decoded some dependencies,
 			// merge them in. Otherwise, set as the new list.
-			dependencies := dependencyBlocksToModuleDependencies(terragruntOptions.WorkingDir, decoded.Dependencies)
+			dependencies := dependencyBlocksToModuleDependencies(decoded.Dependencies)
 			if output.Dependencies != nil {
 				output.Dependencies.Merge(dependencies)
 			} else {
diff --git a/config/cty_helpers.go b/config/cty_helpers.go
index 7f21ae890f..338d3369c2 100644
--- a/config/cty_helpers.go
+++ b/config/cty_helpers.go
@@ -1,3 +1,4 @@
+//nolint:dupl
 package config
 
 import (
@@ -49,7 +50,7 @@ func wrapStringSliceToNumberAsFuncImpl(
 		Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
 			params, err := ctySliceToStringSlice(args)
 			if err != nil {
-				return cty.StringVal(""), err
+				return cty.NumberIntVal(0), err
 			}
 			out, err := toWrap(params, trackInclude, terragruntOptions)
 			if err != nil {
@@ -240,7 +241,7 @@ type CtyJsonOutput struct {
 // convertValuesMapToCtyVal takes a map of name - cty.Value pairs and converts to a single cty.Value object.
 func convertValuesMapToCtyVal(valMap map[string]cty.Value) (cty.Value, error) {
 	valMapAsCty := cty.NilVal
-	if valMap != nil && len(valMap) > 0 {
+	if len(valMap) > 0 {
 		var err error
 		valMapAsCty, err = gocty.ToCtyValue(valMap, generateTypeFromValuesMap(valMap))
 		if err != nil {
diff --git a/config/dependency.go b/config/dependency.go
index 11a4aa562f..fb88638b13 100644
--- a/config/dependency.go
+++ b/config/dependency.go
@@ -137,7 +137,7 @@ func (dependencyConfig *Dependency) setRenderedOutputs(terragruntOptions *option
 		return nil
 	}
 
-	if (*dependencyConfig).shouldGetOutputs() || (*dependencyConfig).shouldReturnMockOutputs(terragruntOptions) {
+	if dependencyConfig.shouldGetOutputs() || dependencyConfig.shouldReturnMockOutputs(terragruntOptions) {
 		outputVal, err := getTerragruntOutputIfAppliedElseConfiguredDefault(*dependencyConfig, terragruntOptions)
 		if err != nil {
 			return err
@@ -200,7 +200,7 @@ func decodeAndRetrieveOutputs(
 
 // Convert the list of parsed Dependency blocks into a list of module dependencies. Each output block should
 // become a dependency of the current config, since that module has to be applied before we can read the output.
-func dependencyBlocksToModuleDependencies(workingDir string, decodedDependencyBlocks []Dependency) *ModuleDependencies {
+func dependencyBlocksToModuleDependencies(decodedDependencyBlocks []Dependency) *ModuleDependencies {
 	if len(decodedDependencyBlocks) == 0 {
 		return nil
 	}
@@ -706,7 +706,12 @@ func getTerragruntOutputJsonFromRemoteState(
 	if err != nil {
 		return nil, err
 	}
-	defer os.RemoveAll(tempWorkDir)
+	defer func(path string) {
+		err := os.RemoveAll(path)
+		if err != nil {
+			terragruntOptions.Logger.Warnf("Failed to remove %s: %v", path, err)
+		}
+	}(tempWorkDir)
 	terragruntOptions.Logger.Debugf("Setting dependency working directory to %s", tempWorkDir)
 
 	targetTGOptions, err := setupTerragruntOptionsForBareTerraform(terragruntOptions, tempWorkDir, targetConfig, iamRoleOpts)
@@ -797,7 +802,12 @@ func getTerragruntOutputJsonFromRemoteStateS3(
 		return nil, err
 	}
 
-	defer result.Body.Close()
+	defer func(Body io.ReadCloser) {
+		err := Body.Close()
+		if err != nil {
+			terragruntOptions.Logger.Warnf("Failed to close remote state response %v", err)
+		}
+	}(result.Body)
 	steateBody, err := io.ReadAll(result.Body)
 	if err != nil {
 		return nil, err
diff --git a/config/include.go b/config/include.go
index 90a168591c..d9b0624a43 100644
--- a/config/include.go
+++ b/config/include.go
@@ -81,7 +81,7 @@ func parseIncludedConfig(
 	// NOTE: To make the logic easier to implement, we implement the inverse here, where we check whether the included
 	// config has a dependency block, and if we are in the middle of a partial parse, we perform a partial parse of the
 	// included config.
-	hasDependency, err := configFileHasDependencyBlock(includePath, terragruntOptions)
+	hasDependency, err := configFileHasDependencyBlock(includePath)
 	if err != nil {
 		return nil, err
 	}
@@ -691,7 +691,9 @@ func getTrackInclude(
 	}
 
 	hasInclude := len(terragruntIncludeList) > 0
-	if hasInclude && includeFromChild != nil {
+	var trackInc TrackInclude
+	switch {
+	case hasInclude && includeFromChild != nil:
 		// tgInc appears in a parent that is already included, which means a nested include block. This is not
 		// something we currently support.
 		err := errors.WithStackTrace(TooManyLevelsOfInheritance{
@@ -700,23 +702,22 @@ func getTrackInclude(
 			SecondLevelIncludePath: strings.Join(includedPaths, ","),
 		})
 		return nil, err
-	} else if hasInclude && includeFromChild == nil {
+	case hasInclude && includeFromChild == nil:
 		// Current parsing context where there is no included config already loaded.
-		trackInc := TrackInclude{
+		trackInc = TrackInclude{
 			CurrentList: terragruntIncludeList,
 			CurrentMap:  terragruntIncludeMap,
 			Original:    nil,
 		}
-		return &trackInc, nil
-	} else {
+	case !hasInclude:
 		// Parsing context where there is an included config already loaded.
-		trackInc := TrackInclude{
+		trackInc = TrackInclude{
 			CurrentList: terragruntIncludeList,
 			CurrentMap:  terragruntIncludeMap,
 			Original:    includeFromChild,
 		}
-		return &trackInc, nil
 	}
+	return &trackInc, nil
 }
 
 // updateBareIncludeBlock searches the parsed terragrunt contents for a bare include block (include without a label),
diff --git a/config/locals.go b/config/locals.go
index c95465f5e5..25f9e7ab2a 100644
--- a/config/locals.go
+++ b/config/locals.go
@@ -54,7 +54,10 @@ func evaluateLocalsBlock(
 
 	localsBlock, diags := getLocalsBlock(hclFile)
 	if diags.HasErrors() {
-		diagsWriter.WriteDiagnostics(diags)
+		err := diagsWriter.WriteDiagnostics(diags)
+		if err != nil {
+			return nil, errors.WithStackTrace(err)
+		}
 		return nil, errors.WithStackTrace(diags)
 	}
 	if localsBlock == nil {
@@ -68,7 +71,10 @@ func evaluateLocalsBlock(
 	locals, diags := decodeLocalsBlock(localsBlock)
 	if diags.HasErrors() {
 		terragruntOptions.Logger.Errorf("Encountered error while decoding locals block into name expression pairs.")
-		diagsWriter.WriteDiagnostics(diags)
+		err := diagsWriter.WriteDiagnostics(diags)
+		if err != nil {
+			return nil, errors.WithStackTrace(err)
+		}
 		return nil, errors.WithStackTrace(diags)
 	}
 
@@ -102,7 +108,7 @@ func evaluateLocalsBlock(
 		// This is an error because we couldn't evaluate all locals
 		terragruntOptions.Logger.Errorf("Not all locals could be evaluated:")
 		for _, local := range locals {
-			_, reason := canEvaluate(terragruntOptions, local.Expr, evaluatedLocals)
+			_, reason := canEvaluate(local.Expr, evaluatedLocals)
 			terragruntOptions.Logger.Errorf("\t- %s [REASON: %s]", local.Name, reason)
 		}
 		return nil, errors.WithStackTrace(CouldNotEvaluateAllLocalsError{})
@@ -168,11 +174,14 @@ func attemptEvaluateLocals(
 		newEvaluatedLocals[key] = val
 	}
 	for _, local := range locals {
-		localEvaluated, _ := canEvaluate(terragruntOptions, local.Expr, evaluatedLocals)
+		localEvaluated, _ := canEvaluate(local.Expr, evaluatedLocals)
 		if localEvaluated {
 			evaluatedVal, diags := local.Expr.Value(evalCtx)
 			if diags.HasErrors() {
-				diagsWriter.WriteDiagnostics(diags)
+				err := diagsWriter.WriteDiagnostics(diags)
+				if err != nil {
+					return nil, nil, false, errors.WithStackTrace(err)
+				}
 				return nil, evaluatedLocals, false, errors.WithStackTrace(diags)
 			}
 			newEvaluatedLocals[local.Name] = evaluatedVal
@@ -198,9 +207,7 @@ func attemptEvaluateLocals(
 // - It has references to other locals that have already been evaluated.
 // Note that the second return value is a human friendly reason for why the expression can not be evaluated, and is
 // useful for error reporting.
-func canEvaluate(
-	terragruntOptions *options.TerragruntOptions,
-	expression hcl.Expression,
+func canEvaluate(expression hcl.Expression,
 	evaluatedLocals map[string]cty.Value,
 ) (bool, string) {
 	vars := expression.Variables()
@@ -234,7 +241,7 @@ func canEvaluate(
 		}
 
 		// If we can't get any local name, we can't evaluate it.
-		localName := getLocalName(terragruntOptions, var_)
+		localName := getLocalName(var_)
 		if localName == "" {
 			reason := fmt.Sprintf(
 				"Can't evaluate expression at %s because local var name can not be determined.",
@@ -263,7 +270,7 @@ func canEvaluate(
 // getLocalName takes a variable reference encoded as a HCL tree traversal that is rooted at the name `local` and
 // returns the underlying variable lookup on the local map. If it is not a local name lookup, this will return empty
 // string.
-func getLocalName(terragruntOptions *options.TerragruntOptions, traversal hcl.Traversal) string {
+func getLocalName(traversal hcl.Traversal) string {
 	if traversal.IsRelative() {
 		return ""
 	}
@@ -302,16 +309,17 @@ func getLocalsBlock(hclFile *hcl.File) (*hcl.Block, hcl.Diagnostics) {
 		}
 	}
 	// We currently only support parsing a single locals block
-	if len(extractedLocalsBlocks) == 1 {
+	switch {
+	case len(extractedLocalsBlocks) == 1:
 		return extractedLocalsBlocks[0], diags
-	} else if len(extractedLocalsBlocks) > 1 {
+	case len(extractedLocalsBlocks) > 1:
 		diags = append(diags, &hcl.Diagnostic{
 			Severity: hcl.DiagError,
 			Summary:  "Multiple locals block",
 			Detail:   multipleLocalsBlockDetail,
 		})
 		return nil, diags
-	} else {
+	default:
 		// No locals block parsed
 		return nil, diags
 	}
diff --git a/configstack/graphviz.go b/configstack/graphviz.go
index 57f9160cc0..8dcc381030 100644
--- a/configstack/graphviz.go
+++ b/configstack/graphviz.go
@@ -6,6 +6,8 @@ import (
 	"path/filepath"
 	"strings"
 
+	"github.com/gruntwork-io/go-commons/errors"
+
 	"github.com/gruntwork-io/terragrunt/options"
 )
 
@@ -15,8 +17,16 @@ import (
 // adding some styling to modules that are excluded from the execution in *-all commands
 func WriteDot(w io.Writer, terragruntOptions *options.TerragruntOptions, modules []*TerraformModule) error {
 
-	w.Write([]byte("digraph {\n"))
-	defer w.Write([]byte("}\n"))
+	_, err := w.Write([]byte("digraph {\n"))
+	if err != nil {
+		return errors.WithStackTrace(err)
+	}
+	defer func(w io.Writer, p []byte) {
+		_, err := w.Write(p)
+		if err != nil {
+			terragruntOptions.Logger.Warnf("Failed to close graphviz output: %v", err)
+		}
+	}(w, []byte("}\n"))
 
 	// all paths are relative to the TerragruntConfigPath
 	prefix := filepath.Dir(terragruntOptions.TerragruntConfigPath) + "/"
@@ -25,19 +35,26 @@ func WriteDot(w io.Writer, terragruntOptions *options.TerragruntOptions, modules
 		// apply a different coloring for excluded nodes
 		style := ""
 		if source.FlagExcluded {
-			style = fmt.Sprintf("[color=red]")
+			style = "[color=red]"
 		}
 
 		nodeLine := fmt.Sprintf("\t\"%s\" %s;\n",
 			strings.TrimPrefix(source.Path, prefix), style)
 
-		w.Write([]byte(nodeLine))
+		_, err := w.Write([]byte(nodeLine))
+		if err != nil {
+			return errors.WithStackTrace(err)
+		}
+
 		for _, target := range source.Dependencies {
 			line := fmt.Sprintf("\t\"%s\" -> \"%s\";\n",
 				strings.TrimPrefix(source.Path, prefix),
 				strings.TrimPrefix(target.Path, prefix),
 			)
-			w.Write([]byte(line))
+			_, err := w.Write([]byte(line))
+			if err != nil {
+				return errors.WithStackTrace(err)
+			}
 		}
 	}
 
diff --git a/configstack/module.go b/configstack/module.go
index 35536cafb8..62bc058606 100644
--- a/configstack/module.go
+++ b/configstack/module.go
@@ -71,15 +71,9 @@ func ResolveTerraformModules(terragruntConfigPaths []string, terragruntOptions *
 		return nil, err
 	}
 
-	includedModules, err := flagIncludedDirs(crossLinkedModules, terragruntOptions)
-	if err != nil {
-		return nil, err
-	}
+	includedModules := flagIncludedDirs(crossLinkedModules, terragruntOptions)
 
-	includedModulesWithExcluded, err := flagExcludedDirs(includedModules, terragruntOptions)
-	if err != nil {
-		return nil, err
-	}
+	includedModulesWithExcluded := flagExcludedDirs(includedModules, terragruntOptions)
 
 	finalModules, err := flagModulesThatDontInclude(includedModulesWithExcluded, terragruntOptions)
 	if err != nil {
@@ -90,7 +84,7 @@ func ResolveTerraformModules(terragruntConfigPaths []string, terragruntOptions *
 }
 
 // flagExcludedDirs iterates over a module slice and flags all entries as excluded, which should be ignored via the terragrunt-exclude-dir CLI flag.
-func flagExcludedDirs(modules []*TerraformModule, terragruntOptions *options.TerragruntOptions) ([]*TerraformModule, error) {
+func flagExcludedDirs(modules []*TerraformModule, terragruntOptions *options.TerragruntOptions) []*TerraformModule {
 	for _, module := range modules {
 		if findModuleInPath(module, terragruntOptions.ExcludeDirs) {
 			// Mark module itself as excluded
@@ -105,20 +99,20 @@ func flagExcludedDirs(modules []*TerraformModule, terragruntOptions *options.Ter
 		}
 	}
 
-	return modules, nil
+	return modules
 }
 
 // flagIncludedDirs iterates over a module slice and flags all entries not in the list specified via the terragrunt-include-dir CLI flag as excluded.
-func flagIncludedDirs(modules []*TerraformModule, terragruntOptions *options.TerragruntOptions) ([]*TerraformModule, error) {
+func flagIncludedDirs(modules []*TerraformModule, terragruntOptions *options.TerragruntOptions) []*TerraformModule {
 
 	// If no IncludeDirs is specified return the modules list instantly
 	if len(terragruntOptions.IncludeDirs) == 0 {
 		// If we aren't given any include directories, but are given the strict include flag,
 		// return no modules.
 		if terragruntOptions.StrictInclude {
-			return []*TerraformModule{}, nil
+			return []*TerraformModule{}
 		}
-		return modules, nil
+		return modules
 	}
 
 	for _, module := range modules {
@@ -140,7 +134,7 @@ func flagIncludedDirs(modules []*TerraformModule, terragruntOptions *options.Ter
 		}
 	}
 
-	return modules, nil
+	return modules
 }
 
 // findModuleInPath returns true if a module is located under one of the target directories
diff --git a/configstack/running_module.go b/configstack/running_module.go
index 59cf58a0f0..b7e36df5a9 100644
--- a/configstack/running_module.go
+++ b/configstack/running_module.go
@@ -16,6 +16,7 @@ const (
 	Waiting ModuleStatus = iota
 	Running
 	Finished
+	channelSize = 1000 // Use a huge buffer to ensure senders are never blocked
 )
 
 // Represents a module we are trying to "run" (i.e. apply or destroy) as part of the apply-all or destroy-all command
@@ -45,7 +46,7 @@ func newRunningModule(module *TerraformModule) *runningModule {
 	return &runningModule{
 		Module:         module,
 		Status:         Waiting,
-		DependencyDone: make(chan *runningModule, 1000), // Use a huge buffer to ensure senders are never blocked
+		DependencyDone: make(chan *runningModule, channelSize),
 		Dependencies:   map[string]*runningModule{},
 		NotifyWhenDone: []*runningModule{},
 		FlagExcluded:   module.FlagExcluded,
@@ -114,12 +115,13 @@ func crossLinkDependencies(modules map[string]*runningModule, dependencyOrder De
 			if !hasDependency {
 				return modules, errors.WithStackTrace(DependencyNotFoundWhileCrossLinking{module, dependency})
 			}
-			if dependencyOrder == NormalOrder {
+			switch dependencyOrder {
+			case NormalOrder:
 				module.Dependencies[runningDependency.Module.Path] = runningDependency
 				runningDependency.NotifyWhenDone = append(runningDependency.NotifyWhenDone, module)
-			} else if dependencyOrder == IgnoreOrder {
+			case IgnoreOrder:
 				// Nothing
-			} else {
+			default:
 				runningDependency.Dependencies[module.Module.Path] = module
 				module.NotifyWhenDone = append(module.NotifyWhenDone, runningDependency)
 			}
diff --git a/configstack/stack.go b/configstack/stack.go
index f53d060ff6..c9e4e1a818 100644
--- a/configstack/stack.go
+++ b/configstack/stack.go
@@ -78,7 +78,10 @@ func (stack *Stack) JsonModuleDeployOrder(terraformCommand string) (string, erro
 
 // Graph creates a graphviz representation of the modules
 func (stack *Stack) Graph(terragruntOptions *options.TerragruntOptions) {
-	WriteDot(terragruntOptions.Writer, terragruntOptions, stack.Modules)
+	err := WriteDot(terragruntOptions.Writer, terragruntOptions, stack.Modules)
+	if err != nil {
+		terragruntOptions.Logger.Warnf("Failed to graph dot: %v", err)
+	}
 }
 
 func (stack *Stack) Run(terragruntOptions *options.TerragruntOptions) error {
@@ -119,11 +122,12 @@ func (stack *Stack) Run(terragruntOptions *options.TerragruntOptions) error {
 		defer stack.summarizePlanAllErrors(terragruntOptions, errorStreams)
 	}
 
-	if terragruntOptions.IgnoreDependencyOrder {
+	switch {
+	case terragruntOptions.IgnoreDependencyOrder:
 		return RunModulesIgnoreOrder(stack.Modules, terragruntOptions.Parallelism)
-	} else if stackCmd == "destroy" {
+	case stackCmd == "destroy":
 		return RunModulesReverseOrder(stack.Modules, terragruntOptions.Parallelism)
-	} else {
+	default:
 		return RunModules(stack.Modules, terragruntOptions.Parallelism)
 	}
 }
@@ -212,23 +216,24 @@ func (stack *Stack) getModuleRunGraph(terraformCommand string) ([][]*TerraformMo
 		// removeDep tracks which modules are run in the current iteration so that they need to be removed in the
 		// dependency list for the next iteration. This is separately tracked from currentIterationDeploy for
 		// convenience: this tracks the map key of the Dependencies attribute.
-		removeDep := []string{}
+		var removeDep []string
 
 		// Iterate the modules, looking for those that have no dependencies and select them for "running". In the
 		// process, track those that still need to run in a separate map for further processing.
 		for path, module := range moduleRunGraph {
 			// Anything that is already applied is culled from the graph when running, so we ignore them here as well.
-			if module.Module.AssumeAlreadyApplied {
+			switch {
+			case module.Module.AssumeAlreadyApplied:
 				removeDep = append(removeDep, path)
-			} else if len(module.Dependencies) == 0 {
+			case len(module.Dependencies) == 0:
 				currentIterationDeploy = append(currentIterationDeploy, module.Module)
 				removeDep = append(removeDep, path)
-			} else {
+			default:
 				next[path] = module
 			}
 		}
 
-		// Go through the remaining module and remove the dependencies that were selected to run in this curent
+		// Go through the remaining module and remove the dependencies that were selected to run in this current
 		// iteration.
 		for _, module := range next {
 			for _, path := range removeDep {
diff --git a/dynamodb/dynamo_lock_table.go b/dynamodb/dynamo_lock_table.go
index 0d3e4a41f2..4ca393f2a5 100644
--- a/dynamodb/dynamo_lock_table.go
+++ b/dynamodb/dynamo_lock_table.go
@@ -15,7 +15,9 @@ import (
 
 // DynamoDB only allows 10 table creates/deletes simultaneously. To ensure we don't hit this error, especially when
 // running many automated tests in parallel, we use a counting semaphore
-var tableCreateDeleteSemaphore = NewCountingSemaphore(10)
+const dynamoParallelOperations = 10
+
+var tableCreateDeleteSemaphore = NewCountingSemaphore(dynamoParallelOperations)
 
 // Terraform requires the DynamoDB table to have a primary key with this name
 const ATTR_LOCK_ID = "LockID"
@@ -26,6 +28,9 @@ const SLEEP_BETWEEN_TABLE_STATUS_CHECKS = 10 * time.Second
 
 const DYNAMODB_PAY_PER_REQUEST_BILLING_MODE = "PAY_PER_REQUEST"
 
+const sleepBetweenRetries = 20 * time.Second
+const maxRetries = 15
+
 // Create an authenticated client for DynamoDB
 func CreateDynamoDbClient(config *aws_helper.AwsSessionConfig, terragruntOptions *options.TerragruntOptions) (*dynamodb.DynamoDB, error) {
 	session, err := aws_helper.CreateAwsSession(config, terragruntOptions)
@@ -127,7 +132,7 @@ func CreateLockTable(tableName string, tags map[string]string, client *dynamodb.
 
 func tagTableIfTagsGiven(tags map[string]string, tableArn *string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
 
-	if tags == nil || len(tags) == 0 {
+	if len(tags) == 0 {
 		terragruntOptions.Logger.Debugf("No tags for lock table given.")
 		return nil
 	}
@@ -237,9 +242,6 @@ func UpdateLockTableSetSSEncryptionOnIfNecessary(tableName string, client *dynam
 
 // Wait until encryption is enabled for the given table
 func waitForEncryptionToBeEnabled(tableName string, client *dynamodb.DynamoDB, terragruntOptions *options.TerragruntOptions) error {
-	maxRetries := 15
-	sleepBetweenRetries := 20 * time.Second
-
 	terragruntOptions.Logger.Debugf("Waiting for encryption to be enabled on table %s", tableName)
 
 	for i := 0; i < maxRetries; i++ {
diff --git a/options/options.go b/options/options.go
index 0955c39d63..fb0b9c061e 100644
--- a/options/options.go
+++ b/options/options.go
@@ -39,6 +39,8 @@ const (
 	DefaultTFDataDir = ".terraform"
 
 	DefaultIAMAssumeRoleDuration = 3600
+
+	minCommandLength = 2
 )
 
 const ContextKey ctxKey = iota
@@ -479,7 +481,7 @@ func (opts *TerragruntOptions) InsertTerraformCliArgs(argsToInsert ...string) {
 	if util.ListContainsElement(TERRAFORM_COMMANDS_WITH_SUBCOMMAND, opts.TerraformCliArgs[0]) {
 		// Since these terraform commands require subcommands which may not always be properly passed by the user,
 		// using util.Min to return the minimum to avoid potential out of bounds slice errors.
-		commandLength = util.Min(2, len(opts.TerraformCliArgs))
+		commandLength = util.Min(minCommandLength, len(opts.TerraformCliArgs))
 	}
 
 	// Options must be inserted after command but before the other args
diff --git a/pkg/cli/app.go b/pkg/cli/app.go
index 5bab046f07..d29432a8ac 100644
--- a/pkg/cli/app.go
+++ b/pkg/cli/app.go
@@ -14,8 +14,9 @@ import (
 //
 // For example, CLI command:
 // `terragrunt run-all apply --terragrunt-log-level debug --auto-approve --terragrunt-non-interactive`
-// The `App` will runs the registered command `run-all`, define the registered flags `--terragrunt-log-level`, `--terragrunt-non-interactive`,
-// and define args `apply --auto-approve` which can be obtained from the App context, ctx.Args().Slice()
+// The `App` will runs the registered command `run-all`, define the registered flags `--terragrunt-log-level`,
+// `--terragrunt-non-interactive`, and define args `apply --auto-approve` which can be obtained from the App context,
+// ctx.Args().Slice()
 type App struct {
 	*cli.App
 	// List of commands to execute
diff --git a/pkg/cli/args.go b/pkg/cli/args.go
index 93bb688607..2bdffc9093 100644
--- a/pkg/cli/args.go
+++ b/pkg/cli/args.go
@@ -8,6 +8,8 @@ import (
 const (
 	SingleDashFlag NormalizeActsType = iota
 	DoubleDashFlag NormalizeActsType = iota
+
+	minTailLen = 2
 )
 
 var (
@@ -36,7 +38,7 @@ func (args *Args) First() string {
 // Tail returns the rest of the arguments (not the first one)
 // or else an empty string slice
 func (args *Args) Tail() []string {
-	if args.Len() >= 2 {
+	if args.Len() >= minTailLen {
 		tail := []string((*args)[1:])
 		ret := make([]string, len(tail))
 		copy(ret, tail)
@@ -68,7 +70,7 @@ func (args *Args) Slice() []string {
 //	`SingleDashFlag` - converts all arguments containing double dashes to single dashes
 //	`DoubleDashFlag` - converts all arguments containing signle dashes to double dashes
 func (args *Args) Normalize(acts ...NormalizeActsType) *Args {
-	var strArgs Args
+	strArgs := make(Args, 0, len(args.Slice()))
 
 	for _, arg := range args.Slice() {
 		for _, act := range acts {
diff --git a/pkg/cli/errors.go b/pkg/cli/errors.go
index 36aa3bc538..a57d1cab25 100644
--- a/pkg/cli/errors.go
+++ b/pkg/cli/errors.go
@@ -42,8 +42,6 @@ func (ee *exitError) ExitCode() int {
 }
 
 // NewExitError calls Exit to create a new ExitCoder.
-//
-// Deprecated: This function is a duplicate of Exit and will eventually be removed.
 func NewExitError(message interface{}, exitCode int) cli.ExitCoder {
 	var err error
 
diff --git a/pkg/cli/map_flag.go b/pkg/cli/map_flag.go
index daae5b8524..32994995b3 100644
--- a/pkg/cli/map_flag.go
+++ b/pkg/cli/map_flag.go
@@ -12,6 +12,7 @@ import (
 var (
 	MapFlagEnvVarSep = ","
 	MapFlagKeyValSep = "="
+	flatPatsCount    = 2
 )
 
 type MapFlagKeyType interface {
@@ -157,7 +158,7 @@ func (flag *mapValue[K, V]) Set(str string) error {
 	}
 
 	parts := flag.splitter(str, flag.valSep)
-	if len(parts) != 2 {
+	if len(parts) != flatPatsCount {
 		return errors.WithStackTrace(NewInvalidKeyValueError(flag.valSep, str))
 	}
 
diff --git a/pkg/cli/slice_flag.go b/pkg/cli/slice_flag.go
index e08acf1bd3..1d3e332194 100644
--- a/pkg/cli/slice_flag.go
+++ b/pkg/cli/slice_flag.go
@@ -174,10 +174,7 @@ func (flag *sliceValue[T]) IsSet() bool {
 func (flag *sliceValue[T]) Get() any {
 	var vals []T
 
-	for _, val := range *flag.values {
-		vals = append(vals, val)
-	}
-
+	vals = append(vals, *flag.values...)
 	return vals
 }
 
diff --git a/remote/remote_state.go b/remote/remote_state.go
index a99759f5c8..9426a1efe2 100644
--- a/remote/remote_state.go
+++ b/remote/remote_state.go
@@ -1,3 +1,4 @@
+// Package remote contains code for configuring remote state storage.
 package remote
 
 import (
diff --git a/remote/remote_state_gcs.go b/remote/remote_state_gcs.go
index 3e50cfb678..34d0da2226 100644
--- a/remote/remote_state_gcs.go
+++ b/remote/remote_state_gcs.go
@@ -74,6 +74,11 @@ type accountFile struct {
 const MAX_RETRIES_WAITING_FOR_GCS_BUCKET = 12
 const SLEEP_BETWEEN_RETRIES_WAITING_FOR_GCS_BUCKET = 5 * time.Second
 
+const (
+	gcpMaxRetries          = 3
+	gcpSleepBetweenRetries = 10 * time.Second
+)
+
 type GCSInitializer struct{}
 
 // Returns true if:
@@ -163,7 +168,7 @@ func (gcsInitializer GCSInitializer) Initialize(remoteState *RemoteState, terrag
 		return err
 	}
 
-	if err := validateGCSConfig(gcsConfigExtended, terragruntOptions); err != nil {
+	if err := validateGCSConfig(gcsConfigExtended); err != nil {
 		return err
 	}
 
@@ -234,7 +239,7 @@ func parseExtendedGCSConfig(config map[string]interface{}) (*ExtendedRemoteState
 }
 
 // Validate all the parameters of the given GCS remote state configuration
-func validateGCSConfig(extendedConfig *ExtendedRemoteStateConfigGCS, terragruntOptions *options.TerragruntOptions) error {
+func validateGCSConfig(extendedConfig *ExtendedRemoteStateConfigGCS) error {
 	var config = extendedConfig.remoteStateConfigGCS
 
 	if config.Bucket == "" {
@@ -273,10 +278,8 @@ func createGCSBucketIfNecessary(gcsClient *storage.Client, config *ExtendedRemot
 		if shouldCreateBucket {
 			// To avoid any eventual consistency issues with creating a GCS bucket we use a retry loop.
 			description := fmt.Sprintf("Create GCS bucket %s", config.remoteStateConfigGCS.Bucket)
-			maxRetries := 3
-			sleepBetweenRetries := 10 * time.Second
 
-			return util.DoWithRetry(description, maxRetries, sleepBetweenRetries, terragruntOptions.Logger, logrus.DebugLevel, func() error {
+			return util.DoWithRetry(description, gcpMaxRetries, gcpSleepBetweenRetries, terragruntOptions.Logger, logrus.DebugLevel, func() error {
 				return CreateGCSBucketWithVersioning(gcsClient, config, terragruntOptions)
 			})
 		}
diff --git a/remote/remote_state_s3.go b/remote/remote_state_s3.go
index 4d698ecde6..aed80b049d 100644
--- a/remote/remote_state_s3.go
+++ b/remote/remote_state_s3.go
@@ -24,6 +24,10 @@ const (
 	DefaultS3BucketAccessLoggingTargetPrefix = "TFStateLogs/"
 	SidRootPolicy                            = "RootAccess"
 	SidEnforcedTLSPolicy                     = "EnforcedTLS"
+
+	s3TimeBetweenRetries  = 5 * time.Second
+	s3MaxRetries          = 3
+	s3SleepBetweenRetries = 10 * time.Second
 )
 
 /*
@@ -401,10 +405,8 @@ func createS3BucketIfNecessary(s3Client *s3.S3, config *ExtendedRemoteStateConfi
 		// eventual consistency issues. Each S3 operation should be idempotent, so redoing steps that have already
 		// been performed should be a no-op.
 		description := fmt.Sprintf("Create S3 bucket with retry %s", config.remoteStateConfigS3.Bucket)
-		maxRetries := 3
-		sleepBetweenRetries := 10 * time.Second
 
-		return util.DoWithRetry(description, maxRetries, sleepBetweenRetries, terragruntOptions.Logger, logrus.DebugLevel, func() error {
+		return util.DoWithRetry(description, s3MaxRetries, s3SleepBetweenRetries, terragruntOptions.Logger, logrus.DebugLevel, func() error {
 			err := CreateS3BucketWithVersioningSSEncryptionAndAccessLogging(s3Client, config, terragruntOptions)
 			if err != nil {
 				if isBucketCreationErrorRetriable(err) {
@@ -485,29 +487,8 @@ func updateS3BucketIfNecessary(s3Client *s3.S3, config *ExtendedRemoteStateConfi
 			terragruntOptions.Logger.Debugf("Access logging is disabled for the remote state AWS S3 bucket %s using 'skip_bucket_access_logging' config.", config.remoteStateConfigS3.Bucket)
 		} else {
 			if config.AccessLoggingBucketName != "" {
-				terragruntOptions.Logger.Debugf("Enabling bucket-wide Access Logging on AWS S3 bucket %s - using as TargetBucket %s", config.remoteStateConfigS3.Bucket, config.AccessLoggingBucketName)
-
-				if err := CreateLogsS3BucketIfNecessary(s3Client, aws.String(config.AccessLoggingBucketName), terragruntOptions); err != nil {
-					terragruntOptions.Logger.Errorf("Could not create logs bucket %s for AWS S3 bucket %s\n%s", config.AccessLoggingBucketName, config.remoteStateConfigS3.Bucket, err.Error())
-					return err
-				}
-
-				if err := EnablePublicAccessBlockingForS3Bucket(s3Client, config.AccessLoggingBucketName, terragruntOptions); err != nil {
-					return err
-				}
-
-				if err := EnableAccessLoggingForS3BucketWide(s3Client, &config.remoteStateConfigS3, terragruntOptions, config.AccessLoggingBucketName, config.AccessLoggingTargetPrefix); err != nil {
-					return err
-				}
-
-				if !config.SkipBucketSSEncryption {
-					if err := EnableSSEForS3BucketWide(s3Client, config.AccessLoggingBucketName, s3.ServerSideEncryptionAes256, config, terragruntOptions); err != nil {
-						return err
-					}
-				}
-
-				if err := EnableEnforcedTLSAccesstoS3Bucket(s3Client, config.AccessLoggingBucketName, config, terragruntOptions); err != nil {
-					return err
+				if err := configureAccessLogBucket(terragruntOptions, s3Client, config); err != nil {
+					return nil
 				}
 			} else {
 				terragruntOptions.Logger.Debugf("Access Logging is disabled for the remote state AWS S3 bucket %s", config.remoteStateConfigS3.Bucket)
@@ -526,6 +507,35 @@ func updateS3BucketIfNecessary(s3Client *s3.S3, config *ExtendedRemoteStateConfi
 	return nil
 }
 
+// configureAccessLogBucket - configure access log bucket.
+func configureAccessLogBucket(terragruntOptions *options.TerragruntOptions, s3Client *s3.S3, config *ExtendedRemoteStateConfigS3) error {
+	terragruntOptions.Logger.Debugf("Enabling bucket-wide Access Logging on AWS S3 bucket %s - using as TargetBucket %s", config.remoteStateConfigS3.Bucket, config.AccessLoggingBucketName)
+
+	if err := CreateLogsS3BucketIfNecessary(s3Client, aws.String(config.AccessLoggingBucketName), terragruntOptions); err != nil {
+		terragruntOptions.Logger.Errorf("Could not create logs bucket %s for AWS S3 bucket %s\n%s", config.AccessLoggingBucketName, config.remoteStateConfigS3.Bucket, err.Error())
+		return errors.WithStackTrace(err)
+	}
+
+	if err := EnablePublicAccessBlockingForS3Bucket(s3Client, config.AccessLoggingBucketName, terragruntOptions); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	if err := EnableAccessLoggingForS3BucketWide(s3Client, &config.remoteStateConfigS3, terragruntOptions, config.AccessLoggingBucketName, config.AccessLoggingTargetPrefix); err != nil {
+		return errors.WithStackTrace(err)
+	}
+
+	if !config.SkipBucketSSEncryption {
+		if err := EnableSSEForS3BucketWide(s3Client, config.AccessLoggingBucketName, s3.ServerSideEncryptionAes256, config, terragruntOptions); err != nil {
+			return errors.WithStackTrace(err)
+		}
+	}
+
+	if err := EnableEnforcedTLSAccesstoS3Bucket(s3Client, config.AccessLoggingBucketName, config, terragruntOptions); err != nil {
+		return errors.WithStackTrace(err)
+	}
+	return nil
+}
+
 type S3BucketUpdatesRequired struct {
 	Versioning    bool
 	SSEEncryption bool
@@ -704,29 +714,8 @@ func CreateS3BucketWithVersioningSSEncryptionAndAccessLogging(s3Client *s3.S3, c
 	}
 
 	if config.AccessLoggingBucketName != "" {
-		terragruntOptions.Logger.Debugf("Enabling bucket-wide Access Logging on AWS S3 bucket %s - using as TargetBucket %s", config.remoteStateConfigS3.Bucket, config.AccessLoggingBucketName)
-
-		if err := CreateLogsS3BucketIfNecessary(s3Client, aws.String(config.AccessLoggingBucketName), terragruntOptions); err != nil {
-			terragruntOptions.Logger.Errorf("Could not create logs bucket %s for AWS S3 bucket %s\n%s", config.AccessLoggingBucketName, config.remoteStateConfigS3.Bucket, err.Error())
-			return err
-		}
-
-		if err := EnablePublicAccessBlockingForS3Bucket(s3Client, config.AccessLoggingBucketName, terragruntOptions); err != nil {
-			return err
-		}
-
-		if err := EnableAccessLoggingForS3BucketWide(s3Client, &config.remoteStateConfigS3, terragruntOptions, config.AccessLoggingBucketName, config.AccessLoggingTargetPrefix); err != nil {
-			return err
-		}
-
-		if !config.SkipBucketSSEncryption {
-			if err := EnableSSEForS3BucketWide(s3Client, config.AccessLoggingBucketName, s3.ServerSideEncryptionAes256, config, terragruntOptions); err != nil {
-				return err
-			}
-		}
-
-		if err := EnableEnforcedTLSAccesstoS3Bucket(s3Client, config.AccessLoggingBucketName, config, terragruntOptions); err != nil {
-			return err
+		if err := configureAccessLogBucket(terragruntOptions, s3Client, config); err != nil {
+			return nil
 		}
 	} else {
 		terragruntOptions.Logger.Debugf("Access Logging is disabled for the remote state AWS S3 bucket %s", config.remoteStateConfigS3.Bucket)
@@ -1325,7 +1314,6 @@ func waitUntilBucketHasAccessLoggingAcl(s3Client *s3.S3, bucket *string, terragr
 	terragruntOptions.Logger.Debugf("Waiting for ACL bucket %s to have the updated ACL for access logging.", aws.StringValue(bucket))
 
 	maxRetries := 10
-	timeBetweenRetries := 5 * time.Second
 
 	for i := 0; i < maxRetries; i++ {
 		out, err := s3Client.GetBucketAcl(&s3.GetBucketAclInput{Bucket: bucket})
@@ -1352,8 +1340,8 @@ func waitUntilBucketHasAccessLoggingAcl(s3Client *s3.S3, bucket *string, terragr
 			return nil
 		}
 
-		terragruntOptions.Logger.Debugf("Bucket %s still does not have the ACL permissions for access logging. Will sleep for %v and check again.", aws.StringValue(bucket), timeBetweenRetries)
-		time.Sleep(timeBetweenRetries)
+		terragruntOptions.Logger.Debugf("Bucket %s still does not have the ACL permissions for access logging. Will sleep for %v and check again.", aws.StringValue(bucket), s3TimeBetweenRetries)
+		time.Sleep(s3TimeBetweenRetries)
 	}
 
 	return errors.WithStackTrace(MaxRetriesWaitingForS3ACLExceeded(aws.StringValue(bucket)))
diff --git a/remote/terraform_state_file.go b/remote/terraform_state_file.go
index 88d394862e..094e2271a8 100644
--- a/remote/terraform_state_file.go
+++ b/remote/terraform_state_file.go
@@ -13,13 +13,14 @@ import (
 // complicated and doesn't seem to be designed for standalone use. Fortunately, the .tfstate format is a fairly simple
 // JSON format, so hopefully this simple parsing code will not be a maintenance burden.
 
-// When storing Terraform state locally, this is the default path to the tfstate file
-const DEFAULT_PATH_TO_LOCAL_STATE_FILE = "terraform.tfstate"
+// DefaultPathToLocalStateFile is the default path to the tfstate file when storing Terraform state locally.
+const DefaultPathToLocalStateFile = "terraform.tfstate"
 
-// When using remote state storage, Terraform keeps a local copy of the state file in this folder
-const DEFAULT_PATH_TO_REMOTE_STATE_FILE = "terraform.tfstate"
+// DefaultPathToRemoteStateFile is the default folder location for the local copy of the state file when using remote
+// state storage in Terraform.
+const DefaultPathToRemoteStateFile = "terraform.tfstate"
 
-// The structure of the Terraform .tfstate file
+// TerraformState - represents the structure of the Terraform .tfstate file.
 type TerraformState struct {
 	Version int
 	Serial  int
@@ -27,55 +28,65 @@ type TerraformState struct {
 	Modules []TerraformStateModule
 }
 
-// The structure of the "backend" section of the Terraform .tfstate file
+// TerraformBackend represents the structure of the "backend" section in the Terraform .tfstate file.
 type TerraformBackend struct {
 	Type   string
 	Config map[string]interface{}
 }
 
-// The structure of a "module" section of the Terraform .tfstate file
+// TerraformStateModule represents the structure of a "module" section in the Terraform .tfstate file.
 type TerraformStateModule struct {
 	Path      []string
 	Outputs   map[string]interface{}
 	Resources map[string]interface{}
 }
 
-// Return true if this Terraform state is configured for remote state storage
+// IsRemote returns true if this Terraform state is configured for remote state storage.
 func (state *TerraformState) IsRemote() bool {
 	return state.Backend != nil && state.Backend.Type != "local"
 }
 
-// Parses the Terraform .tfstate file. If a local backend is used then search the given path, or
-// return nil if the file is missing. If the backend is not local then parse the Terraform .tfstate
+// ParseTerraformStateFileFromLocation parses the Terraform .tfstate file. If a local backend is used then search
+// the given path, or return nil if the file is missing. If the backend is not local then parse the Terraform .tfstate
 // file from the location specified by workingDir. If no location is specified, search the current
 // directory. If the file doesn't exist at any of the default locations, return nil.
-func ParseTerraformStateFileFromLocation(backend string, config map[string]interface{}, workingDir, dataDir string) (*TerraformState, error) {
+func ParseTerraformStateFileFromLocation(backend string, config map[string]interface{},
+	workingDir, dataDir string) (*TerraformState, error) {
 	stateFile, ok := config["path"].(string)
+
 	if backend == "local" && ok && util.FileExists(stateFile) {
 		return ParseTerraformStateFile(stateFile)
-	} else if util.FileExists(util.JoinPath(dataDir, DEFAULT_PATH_TO_REMOTE_STATE_FILE)) {
-		return ParseTerraformStateFile(util.JoinPath(dataDir, DEFAULT_PATH_TO_REMOTE_STATE_FILE))
-	} else if util.FileExists(util.JoinPath(workingDir, DEFAULT_PATH_TO_LOCAL_STATE_FILE)) {
-		return ParseTerraformStateFile(util.JoinPath(workingDir, DEFAULT_PATH_TO_LOCAL_STATE_FILE))
-	} else {
-		return nil, nil
 	}
+
+	if util.FileExists(util.JoinPath(dataDir, DefaultPathToRemoteStateFile)) {
+		return ParseTerraformStateFile(util.JoinPath(dataDir, DefaultPathToRemoteStateFile))
+	}
+
+	if util.FileExists(util.JoinPath(workingDir, DefaultPathToLocalStateFile)) {
+		return ParseTerraformStateFile(util.JoinPath(workingDir, DefaultPathToLocalStateFile))
+	}
+
+	return nil, nil
 }
 
-// ParseTerraformStateFile Parse the Terraform .tfstate file at the given path
+// ParseTerraformStateFile parses the Terraform .tfstate file located at the specified path.
 func ParseTerraformStateFile(path string) (*TerraformState, error) {
 	bytes, err := os.ReadFile(path)
+
 	if err != nil {
-		return nil, errors.WithStackTrace(CantParseTerraformStateFile{Path: path, UnderlyingErr: err})
+		return nil, errors.WithStackTrace(CantParseTerraformStateFileError{Path: path, UnderlyingErr: err})
 	}
+
 	state, err := parseTerraformState(bytes)
+
 	if err != nil {
-		return nil, errors.WithStackTrace(CantParseTerraformStateFile{Path: path, UnderlyingErr: err})
+		return nil, errors.WithStackTrace(CantParseTerraformStateFileError{Path: path, UnderlyingErr: err})
 	}
+
 	return state, nil
 }
 
-// Parse the Terraform state file data in the given byte slice
+// parseTerraformState parses the Terraform state file data from the provided byte slice.
 func parseTerraformState(terraformStateData []byte) (*TerraformState, error) {
 	terraformState := &TerraformState{}
 
@@ -90,11 +101,12 @@ func parseTerraformState(terraformStateData []byte) (*TerraformState, error) {
 	return terraformState, nil
 }
 
-type CantParseTerraformStateFile struct {
+// CantParseTerraformStateFileError error that occurs when we can't parse the Terraform state file.
+type CantParseTerraformStateFileError struct {
 	Path          string
 	UnderlyingErr error
 }
 
-func (err CantParseTerraformStateFile) Error() string {
+func (err CantParseTerraformStateFileError) Error() string {
 	return fmt.Sprintf("Error parsing Terraform state file %s: %s", err.Path, err.UnderlyingErr.Error())
 }
diff --git a/shell/ptty_unix.go b/shell/ptty_unix.go
index b0fe38f1ab..82da3e2df6 100644
--- a/shell/ptty_unix.go
+++ b/shell/ptty_unix.go
@@ -10,9 +10,9 @@ import (
 	"os/signal"
 	"syscall"
 
-	"github.com/creack/pty"
-	"golang.org/x/crypto/ssh/terminal"
+	"golang.org/x/term"
 
+	"github.com/creack/pty"
 	"github.com/gruntwork-io/go-commons/errors"
 	"github.com/gruntwork-io/terragrunt/options"
 )
@@ -54,12 +54,12 @@ func runCommandWithPTTY(terragruntOptions *options.TerragruntOptions, cmd *exec.
 	ch <- syscall.SIGWINCH // Make sure the pty matches current size
 
 	// Set stdin in raw mode so that we preserve readline properties
-	oldState, setRawErr := terminal.MakeRaw(int(os.Stdin.Fd()))
+	oldState, setRawErr := term.MakeRaw(int(os.Stdin.Fd()))
 	if setRawErr != nil {
 		return errors.WithStackTrace(setRawErr)
 	}
 	defer func() {
-		if restoreErr := terminal.Restore(int(os.Stdin.Fd()), oldState); restoreErr != nil {
+		if restoreErr := term.Restore(int(os.Stdin.Fd()), oldState); restoreErr != nil {
 			terragruntOptions.Logger.Errorf("Error restoring terminal state: %s", restoreErr)
 			// Only overwrite the previous error if there was no error since this error has lower priority than any
 			// errors in the main routine
diff --git a/shell/run_shell_cmd.go b/shell/run_shell_cmd.go
index 5c964717ef..9e13ca28a9 100644
--- a/shell/run_shell_cmd.go
+++ b/shell/run_shell_cmd.go
@@ -12,6 +12,9 @@ import (
 	"syscall"
 	"time"
 
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
+
 	"github.com/gruntwork-io/go-commons/collections"
 	"github.com/gruntwork-io/go-commons/errors"
 	"github.com/gruntwork-io/terragrunt/options"
@@ -137,7 +140,12 @@ func RunShellCommandWithOutput(
 	// Make sure to forward signals to the subcommand.
 	cmdChannel := make(chan error) // used for closing the signals forwarder goroutine
 	signalChannel := NewSignalsForwarder(forwardSignals, cmd, terragruntOptions.Logger, cmdChannel)
-	defer signalChannel.Close()
+	defer func(signalChannel *SignalsForwarder) {
+		err := signalChannel.Close()
+		if err != nil {
+			terragruntOptions.Logger.Warnf("Error closing signal channel: %v", err)
+		}
+	}(&signalChannel)
 
 	err := cmd.Wait()
 	cmdChannel <- err
@@ -234,7 +242,7 @@ func NewSignalsForwarder(signals []os.Signal, c *exec.Cmd, logger *logrus.Entry,
 		for {
 			select {
 			case s := <-signalChannel:
-				logger.Debugf("%s signal received. Gracefully shutting down... (it can take up to %v)", strings.Title(s.String()), signalForwardingDelay)
+				logger.Debugf("%s signal received. Gracefully shutting down... (it can take up to %v)", cases.Title(language.English).String(s.String()), signalForwardingDelay)
 
 				select {
 				case <-time.After(signalForwardingDelay):
@@ -280,7 +288,7 @@ func GitTopLevelDir(terragruntOptions *options.TerragruntOptions, path string) (
 	opts.Writer = &stdout
 	opts.ErrWriter = &stderr
 	cmd, err := RunShellCommandWithOutput(opts, path, true, false, "git", "rev-parse", "--show-toplevel")
-	terragruntOptions.Logger.Debugf("git show-toplevel result: \n%v\n%v\n", (string)(stdout.Bytes()), (string)(stderr.Bytes()))
+	terragruntOptions.Logger.Debugf("git show-toplevel result: \n%v\n%v\n", stdout.String(), stderr.String())
 	if err != nil {
 		return "", err
 	}
diff --git a/terraform/getter.go b/terraform/getter.go
index d33df12292..7be669fdfa 100644
--- a/terraform/getter.go
+++ b/terraform/getter.go
@@ -1,3 +1,4 @@
+// nolint:unparam
 package terraform
 
 import (
@@ -159,7 +160,12 @@ func (tfrGetter *RegistryGetter) getSubdir(ctx context.Context, dstPath, sourceU
 	if err != nil {
 		return err
 	}
-	defer tempdirCloser.Close()
+	defer func(tempdirCloser io.Closer) {
+		err := tempdirCloser.Close()
+		if err != nil {
+			util.GlobalFallbackLogEntry.Warnf("Error closing temporary directory %s: %v", tempdirPath, err)
+		}
+	}(tempdirCloser)
 
 	var opts []getter.ClientOption
 	if tfrGetter.client != nil {
@@ -196,7 +202,12 @@ func (tfrGetter *RegistryGetter) getSubdir(ctx context.Context, dstPath, sourceU
 	// back to it.
 	manifestFname := ".tgmanifest"
 	manifestPath := filepath.Join(dstPath, manifestFname)
-	defer os.Remove(manifestPath)
+	defer func(name string) {
+		err := os.Remove(name)
+		if err != nil {
+			util.GlobalFallbackLogEntry.Warnf("Error removing temporary directory %s: %v", name, err)
+		}
+	}(manifestPath)
 	return util.CopyFolderContentsWithFilter(sourcePath, dstPath, manifestFname, func(path string) bool { return true })
 }
 
@@ -278,7 +289,12 @@ func httpGETAndGetResponse(ctx context.Context, getURL url.URL) ([]byte, *http.H
 		return nil, nil, errors.WithStackTrace(err)
 	}
 
-	defer resp.Body.Close()
+	defer func(Body io.ReadCloser) {
+		err := Body.Close()
+		if err != nil {
+			util.GlobalFallbackLogEntry.Warnf("Error closing response body: %v", err)
+		}
+	}(resp.Body)
 	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 		return nil, nil, errors.WithStackTrace(RegistryAPIErr{url: getURL.String(), statusCode: resp.StatusCode})
 	}
diff --git a/terraform/source.go b/terraform/source.go
index c7d16a404e..1c9477cdda 100644
--- a/terraform/source.go
+++ b/terraform/source.go
@@ -19,6 +19,8 @@ import (
 
 var forcedRegexp = regexp.MustCompile(`^([A-Za-z0-9]+)::(.+)$`)
 
+const matchCount = 2
+
 // This struct represents information about Terraform source code that needs to be downloaded
 type Source struct {
 	// A canonical version of RawSource, in URL format
@@ -279,7 +281,7 @@ func encodeSourceName(sourceUrl *url.URL) (string, error) {
 // rest of the URL. This code is copied from the getForcedGetter method of go-getter/get.go, as that method is not
 // exported publicly.
 func getForcedGetter(sourceUrl string) (string, string) {
-	if matches := forcedRegexp.FindStringSubmatch(sourceUrl); matches != nil && len(matches) > 2 {
+	if matches := forcedRegexp.FindStringSubmatch(sourceUrl); len(matches) > matchCount {
 		return matches[1], matches[2]
 	}
 
diff --git a/tflint/tflint.go b/tflint/tflint.go
index b3401f7d15..7dc80d0062 100644
--- a/tflint/tflint.go
+++ b/tflint/tflint.go
@@ -1,6 +1,5 @@
-// This code embeds tflint, which is under an MPL license, and you can
+// Package tflint embeds execution of tflint, which is under an MPL license, and you can
 // find its source code at https://github.com/terraform-linters/tflint
-
 package tflint
 
 import (
@@ -20,40 +19,42 @@ import (
 )
 
 const (
-	// TFVarPrefix Prefix to use for terraform variables set with environment variables.
-	TFVarPrefix      = "TF_VAR_"
-	ArgVarPrefix     = "-var="
-	ArgVarFilePrefix = "-var-file="
-	TFExternalTFLint = "--terragrunt-external-tflint"
+	// tfVarPrefix Prefix to use for terraform variables set with environment variables.
+	tfVarPrefix      = "TF_VAR_"
+	argVarPrefix     = "-var="
+	argVarFilePrefix = "-var-file="
+	tfExternalTFLint = "--terragrunt-external-tflint"
 )
 
 // RunTflintWithOpts runs tflint with the given options and returns an error if there are any issues.
-func RunTflintWithOpts(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig, hook config.Hook) error {
+func RunTflintWithOpts(opts *options.TerragruntOptions, config *config.TerragruntConfig, hook config.Hook) error {
 	// try to fetch configuration file from hook parameters
 	configFile := tflintConfigFilePath(hook.Execute)
 	if configFile == "" {
 		// find .tflint.hcl configuration in project files if it is not provided in arguments
-		projectConfigFile, err := findTflintConfigInProject(terragruntOptions)
+		projectConfigFile, err := findTflintConfigInProject(opts)
 		if err != nil {
 			return err
 		}
+
 		configFile = projectConfigFile
 	}
-	terragruntOptions.Logger.Debugf("Using .tflint.hcl file in %s", configFile)
 
-	variables, err := inputsToTflintVar(terragruntConfig.Inputs)
+	opts.Logger.Debugf("Using .tflint.hcl file in %s", configFile)
+
+	variables, err := inputsToTflintVar(config.Inputs)
 	if err != nil {
 		return err
 	}
 
-	tfVariables, err := tfArgumentsToTflintVar(terragruntOptions, hook, terragruntConfig.Terraform)
+	tfVariables, err := tfArgumentsToTflintVar(opts, hook, config.Terraform)
 	if err != nil {
 		return err
 	}
 	variables = append(variables, tfVariables...)
 
-	terragruntOptions.Logger.Debugf("Initializing tflint in directory %s", terragruntOptions.WorkingDir)
-	cli, err := cmd.NewCLI(terragruntOptions.Writer, terragruntOptions.ErrWriter)
+	opts.Logger.Debugf("Initializing tflint in directory %s", opts.WorkingDir)
+	cli, err := cmd.NewCLI(opts.Writer, opts.ErrWriter)
 	if err != nil {
 		return errors.WithStackTrace(err)
 	}
@@ -61,15 +62,16 @@ func RunTflintWithOpts(terragruntOptions *options.TerragruntOptions, terragruntC
 	tflintArgs, externalTfLint := tflintArguments(hook.Execute[1:])
 
 	// tflint init
-	initArgs := []string{"tflint", "--init", "--config", configFile, "--chdir", terragruntOptions.WorkingDir}
+	initArgs := []string{"tflint", "--init", "--config", configFile, "--chdir", opts.WorkingDir}
 	if externalTfLint {
-		terragruntOptions.Logger.Debugf("Running external tflint init with args %v", initArgs)
-		_, err := shell.RunShellCommandWithOutput(terragruntOptions, terragruntOptions.WorkingDir, false, false, initArgs[0], initArgs[1:]...)
+		opts.Logger.Debugf("Running external tflint init with args %v", initArgs)
+		_, err := shell.RunShellCommandWithOutput(opts, opts.WorkingDir, false, false,
+			initArgs[0], initArgs[1:]...)
 		if err != nil {
 			return errors.WithStackTrace(ErrorRunningTflint{args: initArgs})
 		}
 	} else {
-		terragruntOptions.Logger.Debugf("Running internal tflint init with args %v", initArgs)
+		opts.Logger.Debugf("Running internal tflint init with args %v", initArgs)
 		statusCode := cli.Run(initArgs)
 		if statusCode != 0 {
 			return errors.WithStackTrace(ErrorRunningTflint{args: initArgs})
@@ -77,21 +79,24 @@ func RunTflintWithOpts(terragruntOptions *options.TerragruntOptions, terragruntC
 	}
 
 	// tflint execution
-	args := []string{"tflint"}
-	args = append(args, "--config", configFile)
-	args = append(args, "--chdir", terragruntOptions.WorkingDir)
+	args := []string{
+		"tflint",
+		"--config", configFile,
+		"--chdir", opts.WorkingDir,
+	}
 	args = append(args, variables...)
 	args = append(args, tflintArgs...)
 
 	if externalTfLint {
-		terragruntOptions.Logger.Debugf("Running external tflint with args %v", args)
-		_, err := shell.RunShellCommandWithOutput(terragruntOptions, terragruntOptions.WorkingDir, false, false, args[0], args[1:]...)
+		opts.Logger.Debugf("Running external tflint with args %v", args)
+		_, err := shell.RunShellCommandWithOutput(opts, opts.WorkingDir, false, false,
+			args[0], args[1:]...)
 		if err != nil {
 			return errors.WithStackTrace(ErrorRunningTflint{args: args})
 		}
-		terragruntOptions.Logger.Info("Tflint has run successfully. No issues found.")
+		opts.Logger.Info("Tflint has run successfully. No issues found.")
 	} else {
-		terragruntOptions.Logger.Debugf("Running internal tflint with args %v", args)
+		opts.Logger.Debugf("Running internal tflint with args %v", args)
 		statusCode := cli.Run(args)
 
 		switch statusCode {
@@ -100,7 +105,7 @@ func RunTflintWithOpts(terragruntOptions *options.TerragruntOptions, terragruntC
 		case cmd.ExitCodeIssuesFound:
 			return errors.WithStackTrace(IssuesFound{})
 		case cmd.ExitCodeOK:
-			terragruntOptions.Logger.Info("Tflint has run successfully. No issues found.")
+			opts.Logger.Info("Tflint has run successfully. No issues found.")
 		default:
 			return errors.WithStackTrace(UnknownError{statusCode: statusCode})
 		}
@@ -108,13 +113,14 @@ func RunTflintWithOpts(terragruntOptions *options.TerragruntOptions, terragruntC
 	return nil
 }
 
-// tflintArguments check arguments for --terragrunt-external-tflint and returns filtered arguments and flag if should use external tflint
+// tflintArguments filters args for --terragrunt-external-tflint, returning filtered args and a flag for using
+// external tflint.
 func tflintArguments(arguments []string) ([]string, bool) {
 	externalTfLint := false
-	var filteredArguments []string
+	filteredArguments := make([]string, 0, len(arguments))
 
 	for _, arg := range arguments {
-		if arg == TFExternalTFLint {
+		if arg == tfExternalTFLint {
 			externalTfLint = true
 			continue
 		}
@@ -135,7 +141,7 @@ func tflintConfigFilePath(arguments []string) string {
 
 // inputsToTflintVar converts the inputs map to a list of tflint variables.
 func inputsToTflintVar(inputs map[string]interface{}) ([]string, error) {
-	var variables []string
+	variables := make([]string, 0, len(inputs))
 	for key, value := range inputs {
 		varValue, err := util.AsTerraformEnvVarJsonValue(value)
 		if err != nil {
@@ -149,7 +155,8 @@ func inputsToTflintVar(inputs map[string]interface{}) ([]string, error) {
 }
 
 // tfArgumentsToTflintVar converts variables from the terraform config to a list of tflint variables.
-func tfArgumentsToTflintVar(terragruntOptions *options.TerragruntOptions, hook config.Hook, config *config.TerraformConfig) ([]string, error) {
+func tfArgumentsToTflintVar(terragruntOptions *options.TerragruntOptions, hook config.Hook,
+	config *config.TerraformConfig) ([]string, error) {
 	var variables []string
 
 	for _, arg := range config.ExtraArgs {
@@ -160,8 +167,8 @@ func tfArgumentsToTflintVar(terragruntOptions *options.TerragruntOptions, hook c
 		if arg.EnvVars != nil {
 			// extract env_vars
 			for name, value := range *arg.EnvVars {
-				if strings.HasPrefix(name, TFVarPrefix) {
-					varName := strings.TrimPrefix(name, TFVarPrefix)
+				if strings.HasPrefix(name, tfVarPrefix) {
+					varName := strings.TrimPrefix(name, tfVarPrefix)
 					varValue, err := util.AsTerraformEnvVarJsonValue(value)
 					if err != nil {
 						return nil, err
@@ -174,18 +181,21 @@ func tfArgumentsToTflintVar(terragruntOptions *options.TerragruntOptions, hook c
 		if arg.Arguments != nil {
 			// extract variables and var files from arguments
 			for _, value := range *arg.Arguments {
-				if strings.HasPrefix(value, ArgVarPrefix) {
-					varName := strings.TrimPrefix(value, ArgVarPrefix)
+
+				if strings.HasPrefix(value, argVarPrefix) {
+					varName := strings.TrimPrefix(value, argVarPrefix)
 					newVar := fmt.Sprintf("--var='%s'", varName)
 					variables = append(variables, newVar)
 				}
-				if strings.HasPrefix(value, ArgVarFilePrefix) {
-					varName := strings.TrimPrefix(value, ArgVarFilePrefix)
+
+				if strings.HasPrefix(value, argVarFilePrefix) {
+					varName := strings.TrimPrefix(value, argVarFilePrefix)
 					newVar := fmt.Sprintf("--var-file=%s", varName)
 					variables = append(variables, newVar)
 				}
 			}
 		}
+
 		if arg.RequiredVarFiles != nil {
 			// extract required variables
 			for _, file := range *arg.RequiredVarFiles {
@@ -220,6 +230,7 @@ func findTflintConfigInProject(terragruntOptions *options.TerragruntOptions) (st
 	for i := 0; i < terragruntOptions.MaxFoldersToCheck; i++ {
 		currentDir := filepath.ToSlash(filepath.Dir(previousDir))
 		terragruntOptions.Logger.Debugf("Finding .tflint.hcl file from %s and going to %s", previousDir, currentDir)
+
 		if currentDir == previousDir {
 			return "", errors.WithStackTrace(ConfigNotFound{cause: "Traversed all the day to the root"})
 		}
@@ -233,7 +244,9 @@ func findTflintConfigInProject(terragruntOptions *options.TerragruntOptions) (st
 		previousDir = currentDir
 	}
 
-	return "", errors.WithStackTrace(ConfigNotFound{cause: fmt.Sprintf("Exceeded maximum folders to check (%d)", terragruntOptions.MaxFoldersToCheck)})
+	return "", errors.WithStackTrace(ConfigNotFound{
+		cause: fmt.Sprintf("Exceeded maximum folders to check (%d)", terragruntOptions.MaxFoldersToCheck),
+	})
 }
 
 // Custom error types
diff --git a/util/collections.go b/util/collections.go
index c2206b5b83..7bdb42f7ad 100644
--- a/util/collections.go
+++ b/util/collections.go
@@ -117,10 +117,8 @@ func CommaSeparatedStrings(list []string) string {
 
 // Make a copy of the given list of strings
 func CloneStringList(listToClone []string) []string {
-	out := []string{}
-	for _, item := range listToClone {
-		out = append(out, item)
-	}
+	var out []string
+	out = append(out, listToClone...)
 	return out
 }
 
@@ -177,7 +175,7 @@ func SplitUrls(s, sep string) []string {
 
 	// mask
 	for src, mask := range masks {
-		s = strings.Replace(s, src, mask, -1)
+		s = strings.ReplaceAll(s, src, mask)
 	}
 
 	urls := strings.Split(s, sep)
@@ -185,7 +183,7 @@ func SplitUrls(s, sep string) []string {
 	// unmask
 	for i := range urls {
 		for src, mask := range masks {
-			urls[i] = strings.Replace(urls[i], mask, src, -1)
+			urls[i] = strings.ReplaceAll(urls[i], mask, src)
 		}
 	}
 
diff --git a/util/datetime.go b/util/datetime.go
index 4a7871c2eb..5a4226d6f9 100644
--- a/util/datetime.go
+++ b/util/datetime.go
@@ -1,3 +1,4 @@
+//nolint:gocritic
 package util
 
 import (
diff --git a/util/file.go b/util/file.go
index 2a835a0ff3..8ac9237fc3 100644
--- a/util/file.go
+++ b/util/file.go
@@ -89,7 +89,7 @@ func GlobCanonicalPath(basePath string, globPaths ...string) ([]string, error) {
 
 	var err error
 
-	// Ensure basePath is cannonical
+	// Ensure basePath is canonical
 	basePath, err = CanonicalPath("", basePath)
 	if err != nil {
 		return nil, err
@@ -289,7 +289,12 @@ func CopyFolderContentsWithFilter(source, destination, manifestFile string, filt
 	if err := manifest.Create(); err != nil {
 		return errors.WithStackTrace(err)
 	}
-	defer manifest.Close()
+	defer func(manifest *fileManifest) {
+		err := manifest.Close()
+		if err != nil {
+			GlobalFallbackLogEntry.Warnf("Error closing manifest file: %v", err)
+		}
+	}(manifest)
 
 	// Why use filepath.Glob here? The original implementation used os.ReadDir, but that method calls lstat on all
 	// the files/folders in the directory, including files/folders you may want to explicitly skip. The next attempt
@@ -513,7 +518,12 @@ func (manifest *fileManifest) clean(manifestPath string) error {
 	}
 	// remove the manifest itself
 	// it will run after the close defer
-	defer os.Remove(manifestPath)
+	defer func(name string) {
+		err := os.Remove(name)
+		if err != nil {
+			GlobalFallbackLogEntry.Warnf("Error removing manifest file %s: %v", name, err)
+		}
+	}(manifestPath)
 
 	return nil
 }
diff --git a/util/hash.go b/util/hash.go
index 72004fb2a4..e34a920174 100644
--- a/util/hash.go
+++ b/util/hash.go
@@ -1,21 +1,25 @@
 package util
 
 import (
+	"crypto/rand"
 	"crypto/sha1"
 	"crypto/sha256"
 	"encoding/base64"
 	"fmt"
-	"math/rand"
 )
 
-// Returns the base 64 encoded sha1 hash of the given string
+const (
+	sha256InputSize = 32
+)
+
+// EncodeBase64Sha1 Returns the base 64 encoded sha1 hash of the given string
 func EncodeBase64Sha1(str string) string {
 	hash := sha1.Sum([]byte(str))
 	return base64.RawURLEncoding.EncodeToString(hash[:])
 }
 
 func GenerateRandomSha256() (string, error) {
-	randomBytes := make([]byte, 32)
+	randomBytes := make([]byte, sha256InputSize)
 	_, err := rand.Read(randomBytes)
 	if err != nil {
 		return "", err
diff --git a/util/jsons.go b/util/jsons.go
index adac7c33f1..82d109fc49 100644
--- a/util/jsons.go
+++ b/util/jsons.go
@@ -15,7 +15,7 @@ func AsTerraformEnvVarJsonValue(value interface{}) (string, error) {
 	default:
 		envVarValue, err := json.Marshal(val)
 		if err != nil {
-			return "", err //errors.WithStackTrace(err)
+			return "", err
 		}
 		return string(envVarValue), nil
 	}
diff --git a/util/logger.go b/util/logger.go
index 89037d27eb..235fab4cd2 100644
--- a/util/logger.go
+++ b/util/logger.go
@@ -5,10 +5,11 @@ import (
 	"io"
 	"os"
 
+	"golang.org/x/term"
+
 	"github.com/hashicorp/hcl/v2"
 	"github.com/hashicorp/hcl/v2/hclparse"
 	"github.com/sirupsen/logrus"
-	"golang.org/x/crypto/ssh/terminal"
 )
 
 // used in integration tests
@@ -44,7 +45,7 @@ func DisableLogColors() {
 func CreateLogger(lvl logrus.Level) *logrus.Logger {
 	logger := logrus.New()
 	logger.SetLevel(lvl)
-	logger.SetOutput(os.Stderr) //Terragrunt should output all it's logs to stderr by default
+	logger.SetOutput(os.Stderr) // Terragrunt should output all it's logs to stderr by default
 	logger.SetFormatter(&logrus.TextFormatter{
 		DisableQuote:  true,
 		DisableColors: disableLogColors,
@@ -79,8 +80,8 @@ func CreateLogEntryWithWriter(writer io.Writer, prefix string, level logrus.Leve
 
 // GetDiagnosticsWriter returns a hcl2 parsing diagnostics emitter for the current terminal.
 func GetDiagnosticsWriter(logger *logrus.Entry, parser *hclparse.Parser) hcl.DiagnosticWriter {
-	termColor := terminal.IsTerminal(int(os.Stderr.Fd()))
-	termWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
+	termColor := term.IsTerminal(int(os.Stderr.Fd()))
+	termWidth, _, err := term.GetSize(int(os.Stdout.Fd()))
 	if err != nil {
 		termWidth = 80
 	}
diff --git a/util/random.go b/util/random.go
index 89428355e9..18a6e0ddfd 100644
--- a/util/random.go
+++ b/util/random.go
@@ -6,6 +6,10 @@ import (
 	"time"
 )
 
+const (
+	mSecond = 1000
+)
+
 // Get a random time duration between the lower bound and upper bound. This is useful because some of our automated tests
 // wound up flooding the AWS API all at once, leading to a "Subscriber limit exceeded" error.
 // TODO: Some of the more exotic test cases fail, but it's not worth catching them given the intended use of this function.
@@ -26,8 +30,8 @@ func GetRandomTime(lowerBound, upperBound time.Duration) time.Duration {
 		return lowerBound
 	}
 
-	lowerBoundMs := lowerBound.Seconds() * 1000
-	upperBoundMs := upperBound.Seconds() * 1000
+	lowerBoundMs := lowerBound.Seconds() * mSecond
+	upperBoundMs := upperBound.Seconds() * mSecond
 
 	lowerBoundMsInt := int(lowerBoundMs)
 	upperBoundMsInt := int(upperBoundMs)
@@ -38,14 +42,9 @@ func GetRandomTime(lowerBound, upperBound time.Duration) time.Duration {
 
 // Generate a random int between min and max, inclusive
 func random(min int, max int) int {
-	rand.Seed(time.Now().UnixNano())
 	return rand.Intn(max-min) + min
 }
 
-func init() {
-	rand.Seed(time.Now().UnixNano())
-}
-
 const BASE_62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 const UNIQUE_ID_LENGTH = 6 // Should be good for 62^6 = 56+ billion combinations
 


Test patch:
diff --git a/aws_helper/policy_test.go b/aws_helper/policy_test.go
index f2ed972213..27b4fccca4 100644
--- a/aws_helper/policy_test.go
+++ b/aws_helper/policy_test.go
@@ -62,7 +62,6 @@ func TestUnmarshalStringActionResource(t *testing.T) {
 	switch action := bucketPolicy.Statement[0].Action.(type) {
 	case string:
 		assert.Equal(t, "s3:*", action)
-		break
 	default:
 		assert.Fail(t, "Expected string type for Action")
 	}
@@ -70,7 +69,6 @@ func TestUnmarshalStringActionResource(t *testing.T) {
 	switch resource := bucketPolicy.Statement[0].Resource.(type) {
 	case string:
 		assert.Equal(t, "*", resource)
-		break
 	default:
 		assert.Fail(t, "Expected string type for Resource")
 	}
@@ -93,7 +91,6 @@ func TestUnmarshalActionResourceList(t *testing.T) {
 	case []interface{}:
 		assert.Equal(t, 11, len(actions))
 		assert.Contains(t, actions, "s3:ListJobs")
-		break
 	default:
 		assert.Fail(t, "Expected []string type for Action")
 	}
@@ -102,7 +99,6 @@ func TestUnmarshalActionResourceList(t *testing.T) {
 	case []interface{}:
 		assert.Equal(t, 2, len(resource))
 		assert.Contains(t, resource, "arn:aws:s3:*:666:job/*")
-		break
 	default:
 		assert.Fail(t, "Expected []string type for Resource")
 	}
diff --git a/cli/app_test.go b/cli/app_test.go
index c30cf1d6bc..1da440ff33 100644
--- a/cli/app_test.go
+++ b/cli/app_test.go
@@ -230,18 +230,18 @@ func mockOptionsWithIamRole(t *testing.T, terragruntConfigPath string, workingDi
 	return opts
 }
 
-func mockOptionsWithIamAssumeRoleDuration(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, nonInteractive bool, terragruntSource string, ignoreDependencyErrors bool, IamAssumeRoleDuration int64) *options.TerragruntOptions {
+func mockOptionsWithIamAssumeRoleDuration(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, nonInteractive bool, terragruntSource string, ignoreDependencyErrors bool, iamAssumeRoleDuration int64) *options.TerragruntOptions {
 	opts := mockOptions(t, terragruntConfigPath, workingDir, terraformCliArgs, nonInteractive, terragruntSource, ignoreDependencyErrors, false, defaultLogLevel, false)
-	opts.OriginalIAMRoleOptions.AssumeRoleDuration = IamAssumeRoleDuration
-	opts.IAMRoleOptions.AssumeRoleDuration = IamAssumeRoleDuration
+	opts.OriginalIAMRoleOptions.AssumeRoleDuration = iamAssumeRoleDuration
+	opts.IAMRoleOptions.AssumeRoleDuration = iamAssumeRoleDuration
 
 	return opts
 }
 
-func mockOptionsWithIamAssumeRoleSessionName(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, nonInteractive bool, terragruntSource string, ignoreDependencyErrors bool, IamAssumeRoleSessionName string) *options.TerragruntOptions {
+func mockOptionsWithIamAssumeRoleSessionName(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, nonInteractive bool, terragruntSource string, ignoreDependencyErrors bool, iamAssumeRoleSessionName string) *options.TerragruntOptions {
 	opts := mockOptions(t, terragruntConfigPath, workingDir, terraformCliArgs, nonInteractive, terragruntSource, ignoreDependencyErrors, false, defaultLogLevel, false)
-	opts.OriginalIAMRoleOptions.AssumeRoleSessionName = IamAssumeRoleSessionName
-	opts.IAMRoleOptions.AssumeRoleSessionName = IamAssumeRoleSessionName
+	opts.OriginalIAMRoleOptions.AssumeRoleSessionName = iamAssumeRoleSessionName
+	opts.IAMRoleOptions.AssumeRoleSessionName = iamAssumeRoleSessionName
 
 	return opts
 }
diff --git a/cli/commands/aws-provider-patch/action_test.go b/cli/commands/aws-provider-patch/action_test.go
index 04ed75915e..c8886f82cb 100644
--- a/cli/commands/aws-provider-patch/action_test.go
+++ b/cli/commands/aws-provider-patch/action_test.go
@@ -33,16 +33,6 @@ output "hello" {
 }
 `
 
-const terraformCodeExampleAwsProviderRegionOverridenExpected = `
-provider "aws" {
-  region = "eu-west-1"
-}
-
-output "hello" {
-  value = "Hello, World"
-}
-`
-
 const terraformCodeExampleAwsProviderRegionVersionOverridenExpected = `
 provider "aws" {
   region  = "eu-west-1"
diff --git a/cli/commands/terraform/download_source_test.go b/cli/commands/terraform/download_source_test.go
index de408e7c28..71618de7b0 100644
--- a/cli/commands/terraform/download_source_test.go
+++ b/cli/commands/terraform/download_source_test.go
@@ -249,7 +249,7 @@ func TestInvalidModulePath(t *testing.T) {
 
 	terraformSource, _, _, err := createConfig(t, canonicalUrl, downloadDir, false)
 	assert.Nil(t, err)
-	terraformSource.WorkingDir = terraformSource.WorkingDir + "/not-existing-path"
+	terraformSource.WorkingDir += "/not-existing-path"
 
 	err = validateWorkingDir(terraformSource)
 	assert.NotNil(t, err)
@@ -268,7 +268,7 @@ func TestDownloadInvalidPathToFilePath(t *testing.T) {
 
 	terraformSource, _, _, err := createConfig(t, canonicalUrl, downloadDir, false)
 	assert.Nil(t, err)
-	terraformSource.WorkingDir = terraformSource.WorkingDir + "/main.tf"
+	terraformSource.WorkingDir += "/main.tf"
 
 	err = validateWorkingDir(terraformSource)
 	assert.NotNil(t, err)
@@ -343,6 +343,8 @@ func TestDownloadTerraformSourceFromLocalFolderWithManifest(t *testing.T) {
 func testDownloadTerraformSourceIfNecessary(t *testing.T, canonicalUrl string, downloadDir string, sourceUpdate bool, expectedFileContents string, requireInitFile bool) {
 	terraformSource, terragruntOptions, terragruntConfig, err := createConfig(t, canonicalUrl, downloadDir, sourceUpdate)
 
+	assert.NoError(t, err)
+
 	err = downloadTerraformSourceIfNecessary(terraformSource, terragruntOptions, terragruntConfig)
 	require.NoError(t, err, "For terraform source %v: %v", terraformSource, err)
 
diff --git a/cli/commands/terraform/version_check_test.go b/cli/commands/terraform/version_check_test.go
index f09ad3aafc..27d90fc300 100644
--- a/cli/commands/terraform/version_check_test.go
+++ b/cli/commands/terraform/version_check_test.go
@@ -1,3 +1,4 @@
+//nolint:unparam
 package terraform
 
 import (
diff --git a/config/config_helpers_test.go b/config/config_helpers_test.go
index 9b484f977a..336e930d31 100644
--- a/config/config_helpers_test.go
+++ b/config/config_helpers_test.go
@@ -428,7 +428,7 @@ func TestResolveEnvInterpolationConfigString(t *testing.T) {
 		{
 			`iam_role = get_env("TEST_ENV_TERRAGRUNT_VAR")`,
 			nil,
-			terragruntOptionsForTestWithEnv(t, "/root/child/"+DefaultTerragruntConfigPath, map[string]string{"TEST_ENV_TERRAGRUNT_VAR": "SOMETHING"}),
+			terragruntOptionsForTestWithEnv(t, fmt.Sprintf("/root/child/%s", DefaultTerragruntConfigPath), map[string]string{"TEST_ENV_TERRAGRUNT_VAR": "SOMETHING"}),
 			"SOMETHING",
 			"",
 		},
@@ -1137,7 +1137,7 @@ func mockConfigWithSource(sourceUrl string) *TerragruntConfig {
 // Return keys as a map so it is treated like a set, and order doesn't matter when comparing equivalence
 func getKeys(valueMap map[string]cty.Value) map[string]bool {
 	keys := map[string]bool{}
-	for k, _ := range valueMap {
+	for k := range valueMap {
 		keys[k] = true
 	}
 	return keys
diff --git a/dynamodb/counting_semaphore_test.go b/dynamodb/counting_semaphore_test.go
index 68f4cfa906..0c2c401c0d 100644
--- a/dynamodb/counting_semaphore_test.go
+++ b/dynamodb/counting_semaphore_test.go
@@ -1,3 +1,4 @@
+//nolint:govet
 package dynamodb
 
 import (
diff --git a/pkg/cli/args_test.go b/pkg/cli/args_test.go
index b5c433245f..c565ce5f4f 100644
--- a/pkg/cli/args_test.go
+++ b/pkg/cli/args_test.go
@@ -68,7 +68,7 @@ func TestArgsCommandName(t *testing.T) {
 	expected := "one"
 	assert.Equal(t, expected, actual)
 
-	mockArgs := Args(mockArgs[1:])
+	mockArgs := mockArgs[1:]
 	actual = mockArgs.CommandName()
 	expected = ""
 	assert.Equal(t, expected, actual)
diff --git a/shell/run_shell_cmd_output_test.go b/shell/run_shell_cmd_output_test.go
index f53f3e5983..fe40bdac58 100644
--- a/shell/run_shell_cmd_output_test.go
+++ b/shell/run_shell_cmd_output_test.go
@@ -20,11 +20,19 @@ func TestCommandOutputOrder(t *testing.T) {
 
 	t.Run("withPtty", func(t *testing.T) {
 		t.Parallel()
-		testCommandOutputOrder(t, true)
+		testCommandOutputOrder(t, true,
+			[]string{"stdout1", "stderr1", "stdout2", "stderr2", "stderr3"},
+			[]string{"stdout1", "stdout2"},
+			[]string{"stderr1", "stderr2", "stderr3"},
+		)
 	})
 	t.Run("withoutPtty", func(t *testing.T) {
 		t.Parallel()
-		testCommandOutputOrder(t, false)
+		testCommandOutputOrder(t, false,
+			[]string{"stderr1", "stderr2", "stderr3"},
+			[]string{"stdout1", "stdout2"},
+			[]string{"stderr1", "stderr2", "stderr3"},
+		)
 	})
 }
 
@@ -34,8 +42,8 @@ var FULL_OUTPUT = []string{"stdout1", "stderr1", "stdout2", "stderr2", "stderr3"
 var STDOUT = []string{"stdout1", "stdout2"}
 var STDERR = []string{"stderr1", "stderr2", "stderr3"}
 
-func testCommandOutputOrder(t *testing.T, withPtty bool) {
-	testCommandOutput(t, noop[*options.TerragruntOptions], assertOutputs(t, FULL_OUTPUT, STDOUT, STDERR))
+func testCommandOutputOrder(t *testing.T, withPtty bool, fullOutput []string, stdout []string, stderr []string) {
+	testCommandOutput(t, noop[*options.TerragruntOptions], assertOutputs(t, fullOutput, stdout, stderr), withPtty)
 }
 
 func TestCommandOutputPrefix(t *testing.T) {
@@ -51,10 +59,10 @@ func TestCommandOutputPrefix(t *testing.T) {
 		prefixedOutput,
 		STDOUT,
 		STDERR,
-	))
+	), true)
 }
 
-func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions), assertResults func(string, *CmdOutput)) {
+func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions), assertResults func(string, *CmdOutput), allocateStdout bool) {
 	terragruntOptions, err := options.NewTerragruntOptionsForTest("")
 	require.NoError(t, err, "Unexpected error creating NewTerragruntOptionsForTest: %v", err)
 
@@ -68,7 +76,7 @@ func testCommandOutput(t *testing.T, withOptions func(*options.TerragruntOptions
 
 	withOptions(terragruntOptions)
 
-	out, err := RunShellCommandWithOutput(terragruntOptions, "", false, false, "../testdata/test_outputs.sh", "same")
+	out, err := RunShellCommandWithOutput(terragruntOptions, "", !allocateStdout, false, "../testdata/test_outputs.sh", "same")
 
 	require.NotNil(t, out, "Should get output")
 	assert.Nil(t, err, "Should have no error")
diff --git a/test/fixture-s3-encryption/custom-key/backend.tf b/test/fixture-s3-encryption/custom-key/backend.tf
index 62e5f4af36..90546c6c7e 100644
--- a/test/fixture-s3-encryption/custom-key/backend.tf
+++ b/test/fixture-s3-encryption/custom-key/backend.tf
@@ -1,8 +1,8 @@
 # Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
 terraform {
   backend "s3" {
-    bucket         = "terragrunt-test-bucket-3zu6lh"
-    dynamodb_table = "terragrunt-test-locks-9nzzrp"
+    bucket         = "terragrunt-test-bucket-qh7pvm"
+    dynamodb_table = "terragrunt-test-locks-jmktrj"
     encrypt        = true
     key            = "terraform.tfstate"
     region         = "us-west-2"
diff --git a/test/integration_debug_test.go b/test/integration_debug_test.go
index 2b64d1dc27..447d00cc30 100644
--- a/test/integration_debug_test.go
+++ b/test/integration_debug_test.go
@@ -67,7 +67,7 @@ func TestDebugGeneratedInputs(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	validateInputs(t, outputs)
 
 	// Also make sure the undefined variable is not included in the json file
diff --git a/test/integration_download_test.go b/test/integration_download_test.go
index 37237bb144..7e780fdad2 100644
--- a/test/integration_download_test.go
+++ b/test/integration_download_test.go
@@ -416,9 +416,9 @@ func TestTerragruntExternalDependencies(t *testing.T) {
 		"module-b",
 	}
 
-	cleanupTerraformFolder(t, TEST_FIXTURE_EXTERNAL_DEPENDENCIE)
+	cleanupTerraformFolder(t, TEST_FIXTURE_EXTERNAL_DEPENDENCE)
 	for _, module := range modules {
-		cleanupTerraformFolder(t, util.JoinPath(TEST_FIXTURE_EXTERNAL_DEPENDENCIE, module))
+		cleanupTerraformFolder(t, util.JoinPath(TEST_FIXTURE_EXTERNAL_DEPENDENCE, module))
 	}
 
 	var (
@@ -426,8 +426,8 @@ func TestTerragruntExternalDependencies(t *testing.T) {
 		applyAllStderr bytes.Buffer
 	)
 
-	rootPath := copyEnvironment(t, TEST_FIXTURE_EXTERNAL_DEPENDENCIE)
-	modulePath := util.JoinPath(rootPath, TEST_FIXTURE_EXTERNAL_DEPENDENCIE, "module-b")
+	rootPath := copyEnvironment(t, TEST_FIXTURE_EXTERNAL_DEPENDENCE)
+	modulePath := util.JoinPath(rootPath, TEST_FIXTURE_EXTERNAL_DEPENDENCE, "module-b")
 
 	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt apply-all --terragrunt-non-interactive --terragrunt-include-external-dependencies --terragrunt-working-dir %s", modulePath), &applyAllStdout, &applyAllStderr)
 	logBufferContentsLineByLine(t, applyAllStdout, "apply-all stdout")
diff --git a/test/integration_include_test.go b/test/integration_include_test.go
index 623e95c126..23cacc3a95 100644
--- a/test/integration_include_test.go
+++ b/test/integration_include_test.go
@@ -55,7 +55,7 @@ func TestTerragruntWorksWithIncludeLocals(t *testing.T) {
 			require.NoError(t, err)
 
 			outputs := map[string]TerraformOutput{}
-			require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+			require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 			assert.Equal(t, "us-west-1-test", outputs["region"].Value.(string))
 		})
 	}
@@ -166,7 +166,7 @@ func TestTerragruntWorksWithIncludeDeepMerge(t *testing.T) {
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, "mock", outputs["attribute"].Value.(string))
 	assert.Equal(t, "new val", outputs["new_attribute"].Value.(string))
@@ -220,7 +220,7 @@ func TestTerragruntWorksWithMultipleInclude(t *testing.T) {
 			require.NoError(t, err)
 
 			outputs := map[string]TerraformOutput{}
-			require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+			require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 			validateMultipleIncludeTestOutput(t, outputs)
 		})
 	}
@@ -256,7 +256,7 @@ func validateIncludeRemoteStateReflection(t *testing.T, s3BucketName string, key
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	remoteStateOut := map[string]interface{}{}
 	require.NoError(t, json.Unmarshal([]byte(outputs["reflect"].Value.(string)), &remoteStateOut))
 	assert.Equal(
diff --git a/test/integration_local_dev_test.go b/test/integration_local_dev_test.go
index 66a8909dd7..48f1813bc9 100644
--- a/test/integration_local_dev_test.go
+++ b/test/integration_local_dev_test.go
@@ -90,7 +90,7 @@ func TestGetTerragruntSourceHCL(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, fmt.Sprintf("HCL: %s", terraformSource), outputs["terragrunt_source"].Value)
 }
 
@@ -115,6 +115,6 @@ func TestGetTerragruntSourceCLI(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, fmt.Sprintf("CLI: %s", terraformSource), outputs["terragrunt_source"].Value)
 }
diff --git a/test/integration_registry_test.go b/test/integration_registry_test.go
index b0883046d7..b11598de6a 100644
--- a/test/integration_registry_test.go
+++ b/test/integration_registry_test.go
@@ -50,7 +50,7 @@ func testTerraformRegistryFetching(t *testing.T, modPath, expectedOutputKey stri
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	_, hasOutput := outputs[expectedOutputKey]
 	assert.True(t, hasOutput)
 
diff --git a/test/integration_test.go b/test/integration_test.go
index fc3e2fe87e..f4c0a06e5c 100644
--- a/test/integration_test.go
+++ b/test/integration_test.go
@@ -75,8 +75,8 @@ const (
 	TEST_FIXTURE_LOCAL_PREVENT_DESTROY_DEPENDENCIES                          = "fixture-download/local-with-prevent-destroy-dependencies"
 	TEST_FIXTURE_LOCAL_INCLUDE_PREVENT_DESTROY_DEPENDENCIES                  = "fixture-download/local-include-with-prevent-destroy-dependencies"
 	TEST_FIXTURE_NOT_EXISTING_SOURCE                                         = "fixture-download/invalid-path"
-	TEST_FIXTURE_EXTERNAL_DEPENDENCIE                                        = "fixture-external-dependencies"
-	TEST_FIXTURE_MISSING_DEPENDENCIE                                         = "fixture-missing-dependencies/main"
+	TEST_FIXTURE_EXTERNAL_DEPENDENCE                                         = "fixture-external-dependencies"
+	TEST_FIXTURE_MISSING_DEPENDENCE                                          = "fixture-missing-dependencies/main"
 	TEST_FIXTURE_GET_OUTPUT                                                  = "fixture-get-output"
 	TEST_FIXTURE_HOOKS_BEFORE_ONLY_PATH                                      = "fixture-hooks/before-only"
 	TEST_FIXTURE_HOOKS_ALL_PATH                                              = "fixture-hooks/all"
@@ -185,10 +185,6 @@ const (
 	fixtureDownload = "fixture-download"
 )
 
-func init() {
-	rand.Seed(time.Now().UnixNano())
-}
-
 func TestTerragruntInitHookNoSourceNoBackend(t *testing.T) {
 	t.Parallel()
 
@@ -1000,10 +996,6 @@ func TestTerragruntOutputAllCommandSpecificVariableIgnoreDependencyErrors(t *tes
 func testRemoteFixtureParallelism(t *testing.T, parallelism int, numberOfModules int, timeToDeployEachModule time.Duration) (string, int, error) {
 	s3BucketName := fmt.Sprintf("terragrunt-test-bucket-%s", strings.ToLower(uniqueId()))
 
-	// folders inside the fixture
-	//fixtureTemplate := path.Join(TEST_FIXTURE_PARALLELISM, "template")
-	//fixtureApp := path.Join(TEST_FIXTURE_PARALLELISM, "app")
-
 	// copy the template `numberOfModules` times into the app
 	tmpEnvPath, err := os.MkdirTemp("", "terragrunt-test")
 	if err != nil {
@@ -1025,7 +1017,7 @@ func testRemoteFixtureParallelism(t *testing.T, parallelism int, numberOfModules
 	rootTerragruntConfigPath := util.JoinPath(tmpEnvPath, config.DefaultTerragruntConfigPath)
 	copyTerragruntConfigAndFillPlaceholders(t, rootTerragruntConfigPath, rootTerragruntConfigPath, s3BucketName, "not-used", "not-used")
 
-	environmentPath := fmt.Sprintf("%s", tmpEnvPath)
+	environmentPath := tmpEnvPath
 
 	// forces plugin download & initialization (no parallelism control)
 	runTerragrunt(t, fmt.Sprintf("terragrunt plan-all --terragrunt-non-interactive --terragrunt-working-dir %s -var sleep_seconds=%d", environmentPath, timeToDeployEachModule/time.Second))
@@ -1192,7 +1184,7 @@ func TestAutoRetryGetDefaultErrors(t *testing.T) {
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	list, hasVal := outputs["retryable_errors"]
 	require.True(t, hasVal)
@@ -1310,7 +1302,7 @@ func TestAwsProviderPatch(t *testing.T) {
 	// https://www.terraform.io/docs/language/modules/sources.html#modules-in-package-sub-directories
 	// https://github.com/gruntwork-io/terragrunt/issues/1778
 	branchName = url.QueryEscape(branchName)
-	mainContents = strings.Replace(mainContents, "__BRANCH_NAME__", branchName, -1)
+	mainContents = strings.ReplaceAll(mainContents, "__BRANCH_NAME__", branchName)
 	require.NoError(t, os.WriteFile(mainTFFile, []byte(mainContents), 0444))
 
 	assert.NoError(
@@ -1594,7 +1586,7 @@ func TestInputsPassedThroughCorrectly(t *testing.T) {
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	validateInputs(t, outputs)
 }
 
@@ -1628,7 +1620,7 @@ func TestLocalsParsing(t *testing.T) {
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["data"].Value, "Hello world\n")
 	assert.Equal(t, outputs["answer"].Value, float64(42))
@@ -1652,7 +1644,7 @@ func TestLocalsInInclude(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(
 		t,
@@ -1773,7 +1765,7 @@ func TestPreventDestroyDependenciesIncludedConfig(t *testing.T) {
 func TestTerragruntMissingDependenciesFail(t *testing.T) {
 	t.Parallel()
 
-	generateTestCase := TEST_FIXTURE_MISSING_DEPENDENCIE
+	generateTestCase := TEST_FIXTURE_MISSING_DEPENDENCE
 	cleanupTerraformFolder(t, generateTestCase)
 	cleanupTerragruntFolder(t, generateTestCase)
 
@@ -1798,9 +1790,9 @@ func TestTerragruntExcludeExternalDependencies(t *testing.T) {
 		includedModule,
 	}
 
-	cleanupTerraformFolder(t, TEST_FIXTURE_EXTERNAL_DEPENDENCIE)
+	cleanupTerraformFolder(t, TEST_FIXTURE_EXTERNAL_DEPENDENCE)
 	for _, module := range modules {
-		cleanupTerraformFolder(t, util.JoinPath(TEST_FIXTURE_EXTERNAL_DEPENDENCIE, module))
+		cleanupTerraformFolder(t, util.JoinPath(TEST_FIXTURE_EXTERNAL_DEPENDENCE, module))
 	}
 
 	var (
@@ -1808,8 +1800,8 @@ func TestTerragruntExcludeExternalDependencies(t *testing.T) {
 		applyAllStderr bytes.Buffer
 	)
 
-	rootPath := copyEnvironment(t, TEST_FIXTURE_EXTERNAL_DEPENDENCIE)
-	modulePath := util.JoinPath(rootPath, TEST_FIXTURE_EXTERNAL_DEPENDENCIE, includedModule)
+	rootPath := copyEnvironment(t, TEST_FIXTURE_EXTERNAL_DEPENDENCE)
+	modulePath := util.JoinPath(rootPath, TEST_FIXTURE_EXTERNAL_DEPENDENCE, includedModule)
 
 	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt apply-all --terragrunt-non-interactive --terragrunt-ignore-external-dependencies --terragrunt-working-dir %s", modulePath), &applyAllStdout, &applyAllStderr)
 	logBufferContentsLineByLine(t, applyAllStdout, "apply-all stdout")
@@ -1955,7 +1947,7 @@ func TestYamlDecodeRegressions(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["test1"].Value, "003")
 	assert.Equal(t, outputs["test2"].Value, "1.00")
 	assert.Equal(t, outputs["test3"].Value, "0ba")
@@ -2105,7 +2097,7 @@ func TestDependencyOutput(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, int(outputs["z"].Value.(float64)), 42)
 }
 
@@ -2174,7 +2166,7 @@ func TestDependencyOutputSkipOutputsWithMockOutput(t *testing.T) {
 		runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", dependent3Path), &stdout, &stderr),
 	)
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["truth"].Value, "The answer is 0")
 
 	// Now apply-all so that the dependency is applied, and verify it still uses the mock output
@@ -2192,7 +2184,7 @@ func TestDependencyOutputSkipOutputsWithMockOutput(t *testing.T) {
 		runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", dependent3Path), &stdout, &stderr),
 	)
 	outputs = map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["truth"].Value, "The answer is 0")
 }
 
@@ -2223,7 +2215,7 @@ func TestDependencyMockOutput(t *testing.T) {
 		runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", dependent1Path), &stdout, &stderr),
 	)
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["truth"].Value, "The answer is 0")
 
 	// We need to bust the output cache that stores the dependency outputs so that the second run pulls the outputs.
@@ -2245,7 +2237,7 @@ func TestDependencyMockOutput(t *testing.T) {
 		runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", dependent1Path), &stdout, &stderr),
 	)
 	outputs = map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["truth"].Value, "The answer is 42")
 }
 
@@ -2273,7 +2265,7 @@ func TestDependencyMockOutputMergeWithStateDefault(t *testing.T) {
 	stderr.Reset()
 	err = runTerragruntCommand(t, fmt.Sprintf("terragrunt plan --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr)
 	assert.Error(t, err)
-	// Verify that we fail because the dependency is not applied yet, and the new attribue is not available and in
+	// Verify that we fail because the dependency is not applied yet, and the new attribute is not available and in
 	// this case, mocked outputs are not used.
 	assert.Contains(t, err.Error(), "This object does not have an attribute named \"test_output2\"")
 
@@ -2306,7 +2298,7 @@ func TestDependencyMockOutputMergeWithStateFalse(t *testing.T) {
 	stderr.Reset()
 	err = runTerragruntCommand(t, fmt.Sprintf("terragrunt plan --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr)
 	assert.Error(t, err)
-	// Verify that we fail because the dependency is not applied yet, and the new attribue is not available and in
+	// Verify that we fail because the dependency is not applied yet, and the new attribute is not available and in
 	// this case, mocked outputs are not used.
 	assert.Contains(t, err.Error(), "This object does not have an attribute named \"test_output2\"")
 
@@ -2350,7 +2342,7 @@ func TestDependencyMockOutputMergeWithStateTrue(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["test_output1_from_parent"].Value, "value1")
 	assert.Equal(t, outputs["test_output2_from_parent"].Value, "fake-data2")
@@ -2427,7 +2419,7 @@ func TestDependencyMockOutputMergeWithStateNoOverride(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["test_output1_from_parent"].Value, "value1")
 	assert.Equal(t, outputs["test_output2_from_parent"].Value, "value2")
@@ -2496,7 +2488,7 @@ func TestDependencyMockOutputMergeStrategyWithStateCompatTrue(t *testing.T) {
 
 	require.NoError(t, runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr))
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	logBufferContentsLineByLine(t, stdout, "output stdout")
 	logBufferContentsLineByLine(t, stderr, "output stderr")
 
@@ -2529,7 +2521,7 @@ func TestDependencyMockOutputMergeStrategyWithStateCompatConflict(t *testing.T)
 
 	require.NoError(t, runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr))
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	logBufferContentsLineByLine(t, stdout, "output stdout")
 	logBufferContentsLineByLine(t, stderr, "output stderr")
 
@@ -2582,7 +2574,7 @@ func TestDependencyMockOutputMergeStrategyWithStateShallow(t *testing.T) {
 
 	require.NoError(t, runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr))
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	logBufferContentsLineByLine(t, stdout, "output stdout")
 	logBufferContentsLineByLine(t, stderr, "output stderr")
 
@@ -2617,7 +2609,7 @@ func TestDependencyMockOutputMergeStrategyWithStateDeepMapOnly(t *testing.T) {
 
 	require.NoError(t, runTerragruntCommand(t, fmt.Sprintf("terragrunt output -no-color -json --terragrunt-non-interactive --terragrunt-working-dir %s", childPath), &stdout, &stderr))
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	logBufferContentsLineByLine(t, stdout, "output stdout")
 	logBufferContentsLineByLine(t, stderr, "output stderr")
 
@@ -2711,7 +2703,7 @@ func TestDependencyOutputTypeConversion(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["bool"].Value, true)
 	assert.Equal(t, outputs["list_bool"].Value, []interface{}{true, false})
@@ -2977,7 +2969,7 @@ func TestAWSGetCallerIdentityFunctions(t *testing.T) {
 	}
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, outputs["account"].Value, *identity.Account)
 	assert.Equal(t, outputs["arn"].Value, *identity.Arn)
 	assert.Equal(t, outputs["user_id"].Value, *identity.UserId)
@@ -3007,7 +2999,7 @@ func TestGetRepoRoot(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	repoRoot, ok := outputs["repo_root"]
 
@@ -3039,7 +3031,7 @@ func TestPathRelativeFromInclude(t *testing.T) {
 	assert.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	val, hasVal := outputs["some_output"]
 	require.True(t, hasVal)
@@ -3080,7 +3072,7 @@ func TestGetPathFromRepoRoot(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	pathFromRoot, hasPathFromRoot := outputs["path_from_root"]
 
@@ -3112,7 +3104,7 @@ func TestGetPathToRepoRoot(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	expectedToRoot, err := filepath.Rel(rootPath, tmpEnvPath)
 	require.NoError(t, err)
@@ -3148,7 +3140,7 @@ func TestGetPlatform(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	platform, hasPlatform := outputs["platform"]
 	require.True(t, hasPlatform)
 	require.Equal(t, platform.Value, runtime.GOOS)
@@ -3215,7 +3207,7 @@ func TestReadTerragruntConfigWithDependency(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["bool"].Value, true)
 	assert.Equal(t, outputs["list_bool"].Value, []interface{}{true, false})
@@ -3257,7 +3249,7 @@ func TestReadTerragruntConfigFromDependency(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["bar"].Value, "hello world")
 }
@@ -3280,7 +3272,7 @@ func TestReadTerragruntConfigWithDefault(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["data"].Value, "default value")
 }
@@ -3308,7 +3300,7 @@ func TestReadTerragruntConfigWithOriginalTerragruntDir(t *testing.T) {
 	)
 
 	depOutputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(depStdout.String()), &depOutputs))
+	require.NoError(t, json.Unmarshal(depStdout.Bytes(), &depOutputs))
 
 	assert.Equal(t, depPathAbs, depOutputs["terragrunt_dir"].Value)
 	assert.Equal(t, depPathAbs, depOutputs["original_terragrunt_dir"].Value)
@@ -3327,7 +3319,7 @@ func TestReadTerragruntConfigWithOriginalTerragruntDir(t *testing.T) {
 	)
 
 	rootOutputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(rootStdout.String()), &rootOutputs))
+	require.NoError(t, json.Unmarshal(rootStdout.Bytes(), &rootOutputs))
 
 	assert.Equal(t, fooPathAbs, rootOutputs["terragrunt_dir"].Value)
 	assert.Equal(t, rootPathAbs, rootOutputs["original_terragrunt_dir"].Value)
@@ -3348,7 +3340,7 @@ func TestReadTerragruntConfigWithOriginalTerragruntDir(t *testing.T) {
 	)
 
 	runAllRootOutputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(runAllRootStdout.String()), &runAllRootOutputs))
+	require.NoError(t, json.Unmarshal(runAllRootStdout.Bytes(), &runAllRootOutputs))
 
 	runAllDepStdout := bytes.Buffer{}
 	runAllDepStderr := bytes.Buffer{}
@@ -3359,7 +3351,7 @@ func TestReadTerragruntConfigWithOriginalTerragruntDir(t *testing.T) {
 	)
 
 	runAllDepOutputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(runAllDepStdout.String()), &runAllDepOutputs))
+	require.NoError(t, json.Unmarshal(runAllDepStdout.Bytes(), &runAllDepOutputs))
 
 	assert.Equal(t, fooPathAbs, runAllRootOutputs["terragrunt_dir"].Value)
 	assert.Equal(t, rootPathAbs, runAllRootOutputs["original_terragrunt_dir"].Value)
@@ -3391,7 +3383,7 @@ func TestReadTerragruntConfigFull(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	// Primitive config attributes
 	assert.Equal(t, outputs["terraform_binary"].Value, "terragrunt")
@@ -3620,7 +3612,7 @@ func TestTerragruntGenerateBlockDisableSignature(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["text"].Value, "Hello, World!")
 }
@@ -3891,7 +3883,7 @@ func TestReadTerragruntConfigIamRole(t *testing.T) {
 	err = runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_READ_IAM_ROLE), &stdout, &stderr)
 
 	// Since are used not existing AWS accounts, for validation are used success and error outputs
-	output := fmt.Sprintf("%v %v %v", string(stderr.Bytes()), string(stdout.Bytes()), err.Error())
+	output := fmt.Sprintf("%v %v %v", stderr.String(), stdout.String(), err.Error())
 
 	// Check that output contains value defined in IAM role
 	assert.Equal(t, 1, strings.Count(output, "666666666666"))
@@ -3912,7 +3904,7 @@ func TestIamRolesLoadingFromDifferentModules(t *testing.T) {
 	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-log-level debug --terragrunt-debugreset --terragrunt-working-dir %s", TEST_FIXTURE_IAM_ROLES_MULTIPLE_MODULES), &stdout, &stderr)
 
 	// Taking all outputs in one string
-	output := fmt.Sprintf("%v %v %v", string(stderr.Bytes()), string(stdout.Bytes()), err.Error())
+	output := fmt.Sprintf("%v %v %v", stderr.String(), stdout.String(), err.Error())
 
 	component1 := ""
 	component2 := ""
@@ -4062,28 +4054,6 @@ func copyEnvironment(t *testing.T, environmentPath string) string {
 	return tmpDir
 }
 
-func copyEnvironmentWithTflint(t *testing.T, environmentPath string) string {
-	tmpDir, err := os.MkdirTemp("", "terragrunt-test")
-	if err != nil {
-		t.Fatalf("Failed to create temp dir due to error: %v", err)
-	}
-
-	t.Logf("Copying %s to %s", environmentPath, tmpDir)
-
-	require.NoError(t, util.CopyFolderContents(environmentPath, util.JoinPath(tmpDir, environmentPath), ".terragrunt-test", []string{".tflint.hcl"}))
-
-	return tmpDir
-}
-
-func copyEnvironmentToPath(t *testing.T, environmentPath, targetPath string) {
-	if err := os.MkdirAll(targetPath, 0777); err != nil {
-		t.Fatalf("Failed to create temp dir %s due to error %v", targetPath, err)
-	}
-
-	copyErr := util.CopyFolderContents(environmentPath, util.JoinPath(targetPath, environmentPath), ".terragrunt-test", nil)
-	require.NoError(t, copyErr)
-}
-
 func createTmpTerragruntConfigWithParentAndChild(t *testing.T, parentPath string, childRelPath string, s3BucketName string, parentConfigFileName string, childConfigFileName string) string {
 	tmpDir, err := os.MkdirTemp("", "terragrunt-parent-child-test")
 	if err != nil {
@@ -4154,10 +4124,10 @@ func copyTerragruntConfigAndFillPlaceholders(t *testing.T, configSrcPath string,
 		t.Fatalf("Error reading Terragrunt config at %s: %v", configSrcPath, err)
 	}
 
-	contents = strings.Replace(contents, "__FILL_IN_BUCKET_NAME__", s3BucketName, -1)
-	contents = strings.Replace(contents, "__FILL_IN_LOCK_TABLE_NAME__", lockTableName, -1)
-	contents = strings.Replace(contents, "__FILL_IN_REGION__", region, -1)
-	contents = strings.Replace(contents, "__FILL_IN_LOGS_BUCKET_NAME__", s3BucketName+"-tf-state-logs", -1)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_BUCKET_NAME__", s3BucketName)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_LOCK_TABLE_NAME__", lockTableName)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_REGION__", region)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_LOGS_BUCKET_NAME__", s3BucketName+"-tf-state-logs")
 
 	if err := os.WriteFile(configDestPath, []byte(contents), 0444); err != nil {
 		t.Fatalf("Error writing temp Terragrunt config to %s: %v", configDestPath, err)
@@ -4170,12 +4140,12 @@ func copyTerragruntGCSConfigAndFillPlaceholders(t *testing.T, configSrcPath stri
 		t.Fatalf("Error reading Terragrunt config at %s: %v", configSrcPath, err)
 	}
 
-	contents = strings.Replace(contents, "__FILL_IN_PROJECT__", project, -1)
-	contents = strings.Replace(contents, "__FILL_IN_LOCATION__", location, -1)
-	contents = strings.Replace(contents, "__FILL_IN_BUCKET_NAME__", gcsBucketName, -1)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_PROJECT__", project)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_LOCATION__", location)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_BUCKET_NAME__", gcsBucketName)
 
 	email := os.Getenv("GOOGLE_IDENTITY_EMAIL")
-	contents = strings.Replace(contents, "__FILL_IN_GCP_EMAIL__", email, -1)
+	contents = strings.ReplaceAll(contents, "__FILL_IN_GCP_EMAIL__", email)
 
 	if err := os.WriteFile(configDestPath, []byte(contents), 0444); err != nil {
 		t.Fatalf("Error writing temp Terragrunt config to %s: %v", configDestPath, err)
@@ -4648,7 +4618,7 @@ func runValidateAllWithIncludeAndGetIncludedModules(t *testing.T, rootModulePath
 		),
 	)
 	require.NoError(t, err)
-	matches := includedModulesRegexp.FindAllStringSubmatch(string(validateAllStderr.Bytes()), -1)
+	matches := includedModulesRegexp.FindAllStringSubmatch(validateAllStderr.String(), -1)
 	includedModules := []string{}
 	for _, match := range matches {
 		if match[2] == "false" {
@@ -4676,7 +4646,7 @@ func TestSopsDecryptedCorrectly(t *testing.T) {
 	require.NoError(t, err)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	assert.Equal(t, outputs["json_bool_array"].Value, []interface{}{true, false})
 	assert.Equal(t, outputs["json_string_array"].Value, []interface{}{"example_value1", "example_value2"})
@@ -4721,9 +4691,10 @@ func TestTerragruntInitOnce(t *testing.T) {
 	stdout := bytes.Buffer{}
 	stderr := bytes.Buffer{}
 
-	runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_LOCAL_RUN_ONCE), &stdout, &stderr)
+	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_LOCAL_RUN_ONCE), &stdout, &stderr)
+	assert.Error(t, err)
 
-	errout := string(stdout.Bytes())
+	errout := stdout.String()
 
 	assert.Equal(t, 1, strings.Count(errout, "foo"))
 }
@@ -4735,9 +4706,10 @@ func TestTerragruntInitRunCmd(t *testing.T) {
 	stdout := bytes.Buffer{}
 	stderr := bytes.Buffer{}
 
-	runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_LOCAL_RUN_MULTIPLE), &stdout, &stderr)
+	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_LOCAL_RUN_MULTIPLE), &stdout, &stderr)
+	assert.Error(t, err)
 
-	errout := string(stdout.Bytes())
+	errout := stdout.String()
 
 	// Check for cached values between locals and inputs sections
 	assert.Equal(t, 1, strings.Count(errout, "potato"))
@@ -4780,7 +4752,7 @@ func TestShowWarningWithDependentModulesBeforeDestroy(t *testing.T) {
 	err = runTerragruntCommand(t, fmt.Sprintf("terragrunt destroy --terragrunt-non-interactive --terragrunt-working-dir %s", vpcPath), &stdout, &stderr)
 	assert.NoError(t, err)
 
-	output := string(stderr.Bytes())
+	output := stderr.String()
 	assert.Equal(t, 1, strings.Count(output, appV1Path))
 	assert.Equal(t, 1, strings.Count(output, appV2Path))
 }
@@ -4848,7 +4820,7 @@ func TestPathRelativeToIncludeInvokedInCorrectPathFromChild(t *testing.T) {
 	stderr := bytes.Buffer{}
 	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt version --terragrunt-log-level trace --terragrunt-non-interactive --terragrunt-working-dir %s", appPath), &stdout, &stderr)
 	require.NoError(t, err)
-	output := string(stdout.Bytes())
+	output := stdout.String()
 	assert.Equal(t, 1, strings.Count(output, "path_relative_to_inclue: app\n"))
 	assert.Equal(t, 0, strings.Count(output, "path_relative_to_inclue: .\n"))
 }
@@ -4867,7 +4839,7 @@ func TestTerragruntInitConfirmation(t *testing.T) {
 	stderr := bytes.Buffer{}
 	err := runTerragruntCommand(t, fmt.Sprintf("terragrunt run-all init --terragrunt-working-dir %s", tmpEnvPath), &stdout, &stderr)
 	require.Error(t, err)
-	errout := string(stderr.Bytes())
+	errout := stderr.String()
 	assert.Equal(t, 1, strings.Count(errout, "does not exist or you don't have permissions to access it. Would you like Terragrunt to create it? (y/n)"))
 }
 
@@ -4908,7 +4880,7 @@ func TestAutoInitWhenSourceIsChanged(t *testing.T) {
 	if err != nil {
 		require.NoError(t, err)
 	}
-	updatedHcl := strings.Replace(contents, "__TAG_VALUE__", "v0.35.1", -1)
+	updatedHcl := strings.ReplaceAll(contents, "__TAG_VALUE__", "v0.35.1")
 	require.NoError(t, os.WriteFile(terragruntHcl, []byte(updatedHcl), 0444))
 
 	stdout := bytes.Buffer{}
@@ -4919,7 +4891,7 @@ func TestAutoInitWhenSourceIsChanged(t *testing.T) {
 	// providers initialization during first plan
 	assert.Equal(t, 1, strings.Count(stdout.String(), "has been successfully initialized!"))
 
-	updatedHcl = strings.Replace(contents, "__TAG_VALUE__", "v0.35.2", -1)
+	updatedHcl = strings.ReplaceAll(contents, "__TAG_VALUE__", "v0.35.2")
 	require.NoError(t, os.WriteFile(terragruntHcl, []byte(updatedHcl), 0444))
 
 	stdout = bytes.Buffer{}
@@ -5479,7 +5451,7 @@ func TestStartsWith(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	validateOutput(t, outputs, "startswith1", true)
 	validateOutput(t, outputs, "startswith2", false)
@@ -5512,7 +5484,7 @@ func TestTimeCmp(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	validateOutput(t, outputs, "timecmp1", float64(0))
 	validateOutput(t, outputs, "timecmp2", float64(0))
@@ -5559,7 +5531,7 @@ func TestEndsWith(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	validateOutput(t, outputs, "endswith1", true)
 	validateOutput(t, outputs, "endswith2", false)
@@ -5659,7 +5631,7 @@ func TestDependencyOutputModulePrefix(t *testing.T) {
 	)
 	// validate that output is valid json
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 	assert.Equal(t, int(outputs["z"].Value.(float64)), 42)
 }
 
@@ -5810,7 +5782,7 @@ func TestStrContains(t *testing.T) {
 
 	outputs := map[string]TerraformOutput{}
 
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	validateOutput(t, outputs, "o1", true)
 	validateOutput(t, outputs, "o2", false)
@@ -5953,7 +5925,7 @@ func TestTerragruntDisableBucketUpdate(t *testing.T) {
 func TestTerragruntPassNullValues(t *testing.T) {
 	t.Parallel()
 
-	generateTestCase := filepath.Join(TEST_FIXTURE_NULL_VALUE)
+	generateTestCase := TEST_FIXTURE_NULL_VALUE
 	cleanupTerraformFolder(t, generateTestCase)
 	cleanupTerragruntFolder(t, generateTestCase)
 
@@ -5969,7 +5941,7 @@ func TestTerragruntPassNullValues(t *testing.T) {
 	)
 
 	outputs := map[string]TerraformOutput{}
-	require.NoError(t, json.Unmarshal([]byte(stdout.String()), &outputs))
+	require.NoError(t, json.Unmarshal(stdout.Bytes(), &outputs))
 
 	// check that the null values are passed correctly
 	assert.Equal(t, outputs["output1"].Value, nil)
diff --git a/test/integration_tflint_test.go b/test/integration_tflint_test.go
index a42d1c0c92..704b3154bb 100644
--- a/test/integration_tflint_test.go
+++ b/test/integration_tflint_test.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/gruntwork-io/terragrunt/util"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestTflintFindsNoIssuesWithValidCode(t *testing.T) {
@@ -172,3 +173,16 @@ func TestTflintCustomConfig(t *testing.T) {
 	assert.Contains(t, errOut.String(), "--config custom.tflint.hcl")
 	assert.Contains(t, errOut.String(), "Tflint has run successfully. No issues found")
 }
+
+func copyEnvironmentWithTflint(t *testing.T, environmentPath string) string {
+	tmpDir, err := os.MkdirTemp("", "terragrunt-test")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir due to error: %v", err)
+	}
+
+	t.Logf("Copying %s to %s", environmentPath, tmpDir)
+
+	require.NoError(t, util.CopyFolderContents(environmentPath, util.JoinPath(tmpDir, environmentPath), ".terragrunt-test", []string{".tflint.hcl"}))
+
+	return tmpDir
+}
diff --git a/test/integration_windows_test.go b/test/integration_windows_test.go
index 2a88542eb0..42927a7a34 100644
--- a/test/integration_windows_test.go
+++ b/test/integration_windows_test.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/gruntwork-io/terragrunt/util"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 const (
@@ -85,3 +86,25 @@ func TestWindowsTflintIsInvoked(t *testing.T) {
 	assert.NoError(t, err)
 	assert.True(t, found)
 }
+
+func copyEnvironmentToPath(t *testing.T, environmentPath, targetPath string) {
+	if err := os.MkdirAll(targetPath, 0777); err != nil {
+		t.Fatalf("Failed to create temp dir %s due to error %v", targetPath, err)
+	}
+
+	copyErr := util.CopyFolderContents(environmentPath, util.JoinPath(targetPath, environmentPath), ".terragrunt-test", nil)
+	require.NoError(t, copyErr)
+}
+
+func copyEnvironmentWithTflint(t *testing.T, environmentPath string) string {
+	tmpDir, err := os.MkdirTemp("", "terragrunt-test")
+	if err != nil {
+		t.Fatalf("Failed to create temp dir due to error: %v", err)
+	}
+
+	t.Logf("Copying %s to %s", environmentPath, tmpDir)
+
+	require.NoError(t, util.CopyFolderContents(environmentPath, util.JoinPath(tmpDir, environmentPath), ".terragrunt-test", []string{".tflint.hcl"}))
+
+	return tmpDir
+}
diff --git a/util/random_test.go b/util/random_test.go
index d7b5892f4e..71fb4c8444 100644
--- a/util/random_test.go
+++ b/util/random_test.go
@@ -17,7 +17,7 @@ func TestGetRandomTime(t *testing.T) {
 		{-1 * time.Second, -3 * time.Second},
 		{1 * time.Second, 2000000001 * time.Nanosecond},
 		{1 * time.Millisecond, 10 * time.Millisecond},
-		//{1 * time.Second, 1000000001 * time.Nanosecond}, // This case fails
+		// {1 * time.Second, 1000000001 * time.Nanosecond}, // This case fails
 	}
 
 	// Loop through each test case
diff --git a/util/reflect_test.go b/util/reflect_test.go
index 3d4f96f6df..b5caea8b55 100644
--- a/util/reflect_test.go
+++ b/util/reflect_test.go
@@ -23,7 +23,7 @@ func TestKindOf(t *testing.T) {
 		{nil, reflect.Invalid},
 		{"Hello World!", reflect.String},
 		{new(string), reflect.Ptr},
-		{*new(string), reflect.String},
+		{"", reflect.String},
 		{interface{}(false), reflect.Bool},
 	}
 	for _, testCase := range testCases {