Skip to content

Commit

Permalink
feat: Adding retry for clone errors (#3933)
Browse files Browse the repository at this point in the history
* feat: Adding `clone` block to `terraform` block

* fix: Adding nieve implementation that hopefully works

* fix: Simplifying implementation

* feat: Adding test for retried clone error

* docs: Documenting ability to handle errors on source fetching

* fix: Fixing markdownlint errors

* fix: Fixing `hcl_fmt` bug

* fix: Fixing broken tofu test
  • Loading branch information
yhakbar authored Feb 25, 2025
1 parent 060b9dc commit 27dd83b
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 22 deletions.
18 changes: 8 additions & 10 deletions cli/commands/run/download_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func DownloadTerraformSourceIfNecessary(ctx context.Context, terraformSource *tf

terragruntOptionsForDownload.TerraformCommand = tf.CommandNameInitFromModule
downloadErr := RunActionWithHooks(ctx, "download source", terragruntOptionsForDownload, terragruntConfig, func(_ context.Context) error {
return downloadSource(terraformSource, terragruntOptions, terragruntConfig)
return downloadSource(ctx, terraformSource, terragruntOptions, terragruntConfig)
})

if downloadErr != nil {
Expand Down Expand Up @@ -256,23 +256,21 @@ func UpdateGetters(terragruntOptions *options.TerragruntOptions, terragruntConfi
}

// Download the code from the Canonical Source URL into the Download Folder using the go-getter library
func downloadSource(terraformSource *tf.Source, terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
canonicalSourceURL := terraformSource.CanonicalSourceURL.String()
func downloadSource(ctx context.Context, src *tf.Source, opts *options.TerragruntOptions, cfg *config.TerragruntConfig) error {
canonicalSourceURL := src.CanonicalSourceURL.String()

// Since we convert abs paths to rel in logs, `file://../../path/to/dir` doesn't look good,
// so it's better to get rid of it.
canonicalSourceURL = strings.TrimPrefix(canonicalSourceURL, fileURIScheme)

terragruntOptions.Logger.Infof(
opts.Logger.Infof(
"Downloading Terraform configurations from %s into %s",
canonicalSourceURL,
terraformSource.DownloadDir)
src.DownloadDir)

if err := getter.GetAny(terraformSource.DownloadDir, terraformSource.CanonicalSourceURL.String(), UpdateGetters(terragruntOptions, terragruntConfig)); err != nil {
return errors.New(err)
}

return nil
return opts.RunWithErrorHandling(ctx, func() error {
return getter.GetAny(src.DownloadDir, src.CanonicalSourceURL.String(), UpdateGetters(opts, cfg))
})
}

// ValidateWorkingDir checks if working terraformSource.WorkingDir exists and is directory
Expand Down
24 changes: 17 additions & 7 deletions docs-starlight/src/content/docs/03-community/01-contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,26 @@ Occasionally, Terragrunt maintainers will cut a pre-release to get feedback on t

These releases are generally cut off a feature branch, in order to keep the `main` branch stable and releasable at all times.

Pre-releases are tagged with a pre-release suffix that looks like the following: `-alpha2024101701`, `-beta2024101701`, etc. with the following information:
Pre-releases are tagged with a pre-release name that looks like the following: `alpha2025022501`, etc. with the following information:

- Channel: e.g. `alpha`, `beta` (indicating the stability of the release)
- Channel: e.g. `alpha` (indicating the stability of the release)

The `alpha` and `beta` channels have the following meaning in Terragrunt:
The `alpha` channel has the following meaning in Terragrunt:

- `alpha`: This release is not recommended for external use. It is intended for early adoption by Gruntwork developers testing new features.
- `beta`: This release is recommended for testing in non-production environments only. It is intended for testing out new features with stakeholders external to Gruntwork before a general release.
- `alpha`: This release is recommended for testing in non-production environments only. It is intended for testing out new features with stakeholders external to Gruntwork before a general release.

- Date: e.g. `20241017` (indicating the date the release was cut without dashes or slashes)
At the moment, this is really the only channel we need. In the future, we might adjust this to include more channels, such as `beta`, etc.

- Date: e.g. `20250225` (indicating the date the release was cut without dashes or slashes)
- Incremental number: e.g. `01` (indicating the number of pre-releases cut on that day)

This suffix is appended to the end of the next appropriate version number, e.g. if the current release is `v0.19.1`, and the next appropriate version number is `v0.20.0` based on semver, the pre-release tag would be `v0.20.0-alpha2024101701`.
This pre-release system is subject to change, and maintainers will update this documentation to reflect any changes.

The current plan for how maintainers are going to handle pre-releases after 1.0 is that:

1. Pre-releases for the `alpha` channel will continue to be cut from feature branches, and use the same naming convention as before.
2. Pre-releases for the `rc` channel will be cut from the `main` branch, and use a naming convention that looks like `v1.0.0-rc2025022501`, etc.

Release candidates in the `rc` channel will undergo more thorough testing, both automated and manual.

Maintainers will have a workflow to promote release candidates to general availability, and this documentation will be updated to reflect that workflow at the time of the 1.0 release.
24 changes: 24 additions & 0 deletions docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,30 @@ Evaluation Order:
> **Note:**
> Only the **first matching rule** is applied. If there are multiple conflicting rules, any matches after the first one are ignored.
#### Errors during source fetching

In addition to handling errors during OpenTofu/Terraform runs, the `errors` block will also handle errors that occur during source fetching.

This can be particularly useful when fetching from artifact repositories that may be temporarily unavailable.

Example:

```hcl
# terragrunt.hcl
terraform {
source = "https://unreliable-source.com/module.zip"
}
errors {
retry "source_fetch" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
```

## unit

<Aside type="tip" title="Stacks">
Expand Down
8 changes: 3 additions & 5 deletions docs/_docs/03_community/01-contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ Occasionally, Terragrunt maintainers will cut a pre-release to get feedback on t

These releases are generally cut off a feature branch, in order to keep the `main` branch stable and releasable at all times.

Pre-releases are tagged with a pre-release name that looks like the following: `alpha2024101701`, etc. with the following information:
Pre-releases are tagged with a pre-release name that looks like the following: `alpha20250225`, etc. with the following information:

- Channel: e.g. `alpha` (indicating the stability of the release)

Expand All @@ -377,14 +377,12 @@ Pre-releases are tagged with a pre-release name that looks like the following: `
- Date: e.g. `20241017` (indicating the date the release was cut without dashes or slashes)
- Incremental number: e.g. `01` (indicating the number of pre-releases cut on that day)

This pre-release system is subject to change, and maintainers will update this documentation to reflect any changes. It will be updated at the time of the 1.0 release to reflect a new policy for release candidates, as a different policy will be in place at that time.

**Current plan for post-1.0 pre-releases**
This pre-release system is subject to change, and maintainers will update this documentation to reflect any changes.

The current plan for how maintainers are going to handle pre-releases after 1.0 is that:

1. Pre-releases for the `alpha` channel will continue to be cut from feature branches, and use the same naming convention as before.
2. Pre-releases for the `rc` channel will be cut from the `main` branch, and use a naming convention that looks like `v1.0.0-rc1`, etc.
2. Pre-releases for the `rc` channel will be cut from the `main` branch, and use a naming convention that looks like `v1.0.0-rc20250225`, etc.

Release candidates in the `rc` channel will undergo more thorough testing, both automated and manual.

Expand Down
22 changes: 22 additions & 0 deletions docs/_docs/04_reference/04-config-blocks-and-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1602,6 +1602,28 @@ Evaluation Order:
> **Note:**
> Only the **first matching rule** is applied. If there are multiple conflicting rules, any matches after the first one are ignored.
#### Errors during source fetching

In addition to handling errors during OpenTofu/Terraform runs, the `errors` block will also handle errors that occur during source fetching.

This can be particularly useful when fetching from artifact repositories that may be temporarily unavailable.

Example:

```hcl
terraform {
source = "https://unreliable-source.com/module.zip"
}
errors {
retry "source_fetch" {
retryable_errors = [".*Error: transient network issue.*"]
max_attempts = 3
sleep_interval_sec = 5
}
}
```

### unit

> **Note:**
Expand Down
13 changes: 13 additions & 0 deletions test/fixtures/download/remote-invalid-with-retries/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
source = "https://github.com/totallyfakedoesnotexist/notreal.git//foo?ref=v1.2.3"

}

errors {
retry "any" {
retryable_errors = [".*"]
max_attempts = 2
sleep_interval_sec = 1
}
}

2 changes: 2 additions & 0 deletions test/integration_debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ func TestRenderJSONConfig(t *testing.T) {
"disable": false,
"if_exists": "overwrite_terragrunt",
"if_disabled": "skip",
"hcl_fmt": nil,
"contents": `provider "aws" {
region = "us-east-1"
}
Expand Down Expand Up @@ -362,6 +363,7 @@ func TestRenderJSONConfigWithIncludesDependenciesAndLocals(t *testing.T) {
"disable": false,
"if_exists": "overwrite",
"if_disabled": "skip",
"hcl_fmt": nil,
"contents": "# This is just a test",
},
},
Expand Down
19 changes: 19 additions & 0 deletions test/integration_download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
testFixtureCustomLockFile = "fixtures/download/custom-lock-file"
testFixtureRemoteDownloadPath = "fixtures/download/remote"
testFixtureInvalidRemoteDownloadPath = "fixtures/download/remote-invalid"
testFixtureInvalidRemoteDownloadPathWithRetries = "fixtures/download/remote-invalid-with-retries"
testFixtureOverrideDonwloadPath = "fixtures/download/override"
testFixtureLocalRelativeDownloadPath = "fixtures/download/local-relative"
testFixtureRemoteRelativeDownloadPath = "fixtures/download/remote-relative"
Expand Down Expand Up @@ -178,6 +179,24 @@ func TestInvalidRemoteDownload(t *testing.T) {

}

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

helpers.CleanupTerraformFolder(t, testFixtureInvalidRemoteDownloadPathWithRetries)
applyStdout := bytes.Buffer{}
applyStderr := bytes.Buffer{}

err := helpers.RunTerragruntCommand(t, "terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+testFixtureInvalidRemoteDownloadPathWithRetries, &applyStdout, &applyStderr)

helpers.LogBufferContentsLineByLine(t, applyStdout, "apply stdout")
helpers.LogBufferContentsLineByLine(t, applyStderr, "apply stderr")

require.Error(t, err)
errMessage := "max retry attempts (2) reached for error"
assert.Containsf(t, err.Error(), errMessage, "expected error containing %q, got %s", errMessage, err)

}

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

Expand Down
1 change: 1 addition & 0 deletions test/integration_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ func TestRenderJsonMetadataIncludes(t *testing.T) {
"disable": false,
"if_exists": "overwrite",
"if_disabled": "skip",
"hcl_fmt": nil,
"path": "provider.tf",
},
},
Expand Down
1 change: 1 addition & 0 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,7 @@ func TestReadTerragruntConfigFull(t *testing.T) {
"provider": map[string]interface{}{
"path": "provider.tf",
"if_exists": "overwrite_terragrunt",
"hcl_fmt": nil,
"if_disabled": "skip",
"comment_prefix": "# ",
"disable_signature": false,
Expand Down
1 change: 1 addition & 0 deletions test/integration_tofu_state_encryption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ func TestTofuRenderJSONConfigWithEncryption(t *testing.T) {
"disable": false,
"if_exists": "overwrite_terragrunt",
"if_disabled": "skip",
"hcl_fmt": nil,
"contents": `provider "aws" {
region = "us-east-1"
}
Expand Down

0 comments on commit 27dd83b

Please sign in to comment.