Skip to content

Commit

Permalink
terragrunt-info parsing improvement (#2901)
Browse files Browse the repository at this point in the history
* Add test for parsing TerragruntInfoGroup

* PR cleanup

* Update cli/commands/terragrunt-info/action.go

Co-authored-by: Levko Burburas <62853952+levkohimins@users.noreply.github.com>

* Update cli/commands/terragrunt-info/action.go

Co-authored-by: Levko Burburas <62853952+levkohimins@users.noreply.github.com>

* Updated imports

* imports update

---------

Co-authored-by: Levko Burburas <62853952+levkohimins@users.noreply.github.com>
  • Loading branch information
denis256 and levkohimins committed Apr 4, 2024
1 parent 6e218c1 commit 8d8bd8e
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 19 deletions.
26 changes: 14 additions & 12 deletions cli/commands/terraform/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ func RunWithTarget(opts *options.TerragruntOptions, target *Target) error {

func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target) error {
if err := checkVersionConstraints(terragruntOptions); err != nil {
return err
return target.runErrorCallback(terragruntOptions, nil, err)
}

terragruntConfig, err := config.ReadTerragruntConfig(terragruntOptions)
if err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

if target.isPoint(TargetPointParseConfig) {
Expand All @@ -112,7 +112,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
terragruntOptionsClone.TerraformCommand = CommandNameTerragruntReadConfig

if err := processHooks(terragruntConfig.Terraform.GetAfterHooks(), terragruntOptionsClone, terragruntConfig, nil); err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

if terragruntConfig.Skip {
Expand All @@ -137,7 +137,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
// get the default download dir
_, defaultDownloadDir, err := options.DefaultWorkingAndDownloadDirs(terragruntOptions.TerragruntConfigPath)
if err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

// if the download dir hasn't been changed from default, and is set in the config,
Expand Down Expand Up @@ -168,7 +168,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
updatedTerragruntOptions := terragruntOptions
sourceUrl, err := config.GetTerraformSourceUrl(terragruntOptions, terragruntConfig)
if err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

if sourceUrl != "" {
Expand All @@ -180,7 +180,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
})

if err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}
}

Expand All @@ -193,7 +193,7 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
// Handle code generation configs, both generate blocks and generate attribute of remote_state.
// Note that relative paths are relative to the terragrunt working dir (where terraform is called).
if err = generateConfig(terragruntConfig, updatedTerragruntOptions); err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

if target.isPoint(TargetPointGenerateConfig) {
Expand All @@ -203,14 +203,13 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
// We do the debug file generation here, after all the terragrunt generated terraform files are created so that we
// can ensure the tfvars json file only includes the vars that are defined in the module.
if updatedTerragruntOptions.Debug {
err := WriteTerragruntDebugFile(updatedTerragruntOptions, terragruntConfig)
if err != nil {
return err
if err := WriteTerragruntDebugFile(updatedTerragruntOptions, terragruntConfig); err != nil {
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}
}

if err := checkFolderContainsTerraformCode(updatedTerragruntOptions); err != nil {
return err
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}

if terragruntOptions.CheckDependentModules {
Expand All @@ -219,7 +218,10 @@ func runTerraform(terragruntOptions *options.TerragruntOptions, target *Target)
return nil
}
}
return runTerragruntWithConfig(terragruntOptions, updatedTerragruntOptions, terragruntConfig, target)
if err := runTerragruntWithConfig(terragruntOptions, updatedTerragruntOptions, terragruntConfig, target); err != nil {
return target.runErrorCallback(terragruntOptions, terragruntConfig, err)
}
return nil
}

func generateConfig(terragruntConfig *config.TerragruntConfig, updatedTerragruntOptions *options.TerragruntOptions) error {
Expand Down
22 changes: 20 additions & 2 deletions cli/commands/terraform/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type TargetPointType byte

type TargetCallbackType func(opts *options.TerragruntOptions, config *config.TerragruntConfig) error

type TargetErrorCallbackType func(opts *options.TerragruntOptions, config *config.TerragruntConfig, e error) error

// Since most terragrunt CLI commands like `render-json`, `aws-provider-patch` ... require preparatory steps, such as `generate configuration` which is already coded in `terraform.runTerraform` and com;licated to extracted into a separate function due to some steps that can be called recursively in case of nested configuration or dependencies.
// Target struct helps to run `terraform.runTerraform` func up to the certain logic point, and the runs target's callback func and returns the flow.
// For example, `terragrunt-info` CLI command requires source to be downloaded before running its specific action. To do this it:
Expand Down Expand Up @@ -51,8 +53,9 @@ type TargetCallbackType func(opts *options.TerragruntOptions, config *config.Ter
*/

type Target struct {
point TargetPointType
callbackFunc TargetCallbackType
point TargetPointType
callbackFunc TargetCallbackType
errorCallbackFunc TargetErrorCallbackType
}

func NewTarget(point TargetPointType, callbackFunc TargetCallbackType) *Target {
Expand All @@ -62,10 +65,25 @@ func NewTarget(point TargetPointType, callbackFunc TargetCallbackType) *Target {
}
}

func NewTargetWithErrorHandler(point TargetPointType, callbackFunc TargetCallbackType, errorCallbackFunc TargetErrorCallbackType) *Target {
return &Target{
point: point,
callbackFunc: callbackFunc,
errorCallbackFunc: errorCallbackFunc,
}
}

func (target *Target) isPoint(point TargetPointType) bool {
return target.point == point
}

func (target *Target) runCallback(opts *options.TerragruntOptions, config *config.TerragruntConfig) error {
return target.callbackFunc(opts, config)
}

func (target *Target) runErrorCallback(opts *options.TerragruntOptions, config *config.TerragruntConfig, e error) error {
if target.errorCallbackFunc == nil {
return e
}
return target.errorCallbackFunc(opts, config, e)
}
24 changes: 19 additions & 5 deletions cli/commands/terragrunt-info/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"encoding/json"
"fmt"

"github.com/gruntwork-io/go-commons/errors"

"github.com/gruntwork-io/terragrunt/cli/commands/terraform"
"github.com/gruntwork-io/terragrunt/config"
"github.com/gruntwork-io/terragrunt/options"
)

func Run(opts *options.TerragruntOptions) error {
target := terraform.NewTarget(terraform.TargetPointDownloadSource, runTerragruntInfo)
target := terraform.NewTargetWithErrorHandler(terraform.TargetPointDownloadSource, runTerragruntInfo, runErrorTerragruntInfo)

return terraform.RunWithTarget(opts, target)
}
Expand All @@ -25,7 +27,7 @@ type TerragruntInfoGroup struct {
WorkingDir string
}

func runTerragruntInfo(opts *options.TerragruntOptions, cfg *config.TerragruntConfig) error {
func printTerragruntInfo(opts *options.TerragruntOptions) error {
group := TerragruntInfoGroup{
ConfigPath: opts.TerragruntConfigPath,
DownloadDir: opts.DownloadDir,
Expand All @@ -38,10 +40,22 @@ func runTerragruntInfo(opts *options.TerragruntOptions, cfg *config.TerragruntCo
b, err := json.MarshalIndent(group, "", " ")
if err != nil {
opts.Logger.Errorf("JSON error marshalling terragrunt-info")
return err
return errors.WithStackTrace(err)
}
if _, err := fmt.Fprintf(opts.Writer, "%s\n", b); err != nil {
return errors.WithStackTrace(err)
}
fmt.Fprintf(opts.Writer, "%s\n", b)

return nil
}

func runTerragruntInfo(opts *options.TerragruntOptions, cfg *config.TerragruntConfig) error {
return printTerragruntInfo(opts)
}

func runErrorTerragruntInfo(opts *options.TerragruntOptions, cfg *config.TerragruntConfig, err error) error {
opts.Logger.Debugf("Fetching terragrunt-info: %v", err)
if err := printTerragruntInfo(opts); err != nil {
opts.Logger.Errorf("Error printing terragrunt-info: %v", err)
}
return err
}
3 changes: 3 additions & 0 deletions test/fixture-terragrunt-info-error/module-a/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "test_var" {
value = "hello"
}
3 changes: 3 additions & 0 deletions test/fixture-terragrunt-info-error/module-a/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
source = ".//"
}
7 changes: 7 additions & 0 deletions test/fixture-terragrunt-info-error/module-b/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variable "test_var" {
type = string
}

output "result" {
value = var.test_var
}
16 changes: 16 additions & 0 deletions test/fixture-terragrunt-info-error/module-b/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
terraform {
source = ".//"
}

dependency "module_a" {
config_path = "../module-a"
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
mock_outputs_merge_strategy_with_state = "shallow"
mock_outputs = {
test_mock = "abc"
}
}

inputs = {
test_var = dependency.module_a.outputs.test_mock
}
20 changes: 20 additions & 0 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ const (
TEST_FIXTURE_ASSUME_ROLE_DURATION = "fixture-assume-role/duration"
TEST_FIXTURE_GRAPH = "fixture-graph"
TEST_FIXTURE_SKIP_DEPENDENCIES = "fixture-skip-dependencies"
TEST_FIXTURE_INFO_ERROR = "fixture-terragrunt-info-error"
TERRAFORM_BINARY = "terraform"
TOFU_BINARY = "tofu"
TERRAFORM_FOLDER = ".terraform"
Expand Down Expand Up @@ -6736,6 +6737,25 @@ func prepareGraphFixture(t *testing.T) string {
return tmpEnvPath
}

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

tmpEnvPath := copyEnvironment(t, TEST_FIXTURE_INFO_ERROR)
cleanupTerraformFolder(t, tmpEnvPath)
testPath := util.JoinPath(tmpEnvPath, TEST_FIXTURE_INFO_ERROR, "module-b")

stdout := bytes.Buffer{}
stderr := bytes.Buffer{}

err := runTerragruntCommand(t, fmt.Sprintf("terragrunt terragrunt-info --terragrunt-non-interactive --terragrunt-working-dir %s", testPath), &stdout, &stderr)
assert.Error(t, err)

// parse stdout json as TerragruntInfoGroup
var output terragruntinfo.TerragruntInfoGroup
err = json.Unmarshal(stdout.Bytes(), &output)
assert.NoError(t, err)
}

func validateOutput(t *testing.T, outputs map[string]TerraformOutput, key string, value interface{}) {
t.Helper()
output, hasPlatform := outputs[key]
Expand Down

0 comments on commit 8d8bd8e

Please sign in to comment.