From 814fb36ab22d3473d3f9daab8e694a18a77743e7 Mon Sep 17 00:00:00 2001 From: Levko Burburas <62853952+levkohimins@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:26:06 +0300 Subject: [PATCH 1/6] User-friendly error for invalid envs/flags (#3414) * fix: user-friendly error for invalid envs * chore: code improvements * chore: code comment --- pkg/cli/bool_flag.go | 16 ++++++++++++++-- pkg/cli/bool_flag_test.go | 24 +++++++++++++++++++++--- pkg/cli/errors.go | 14 ++++++++++++++ pkg/cli/generic_flag.go | 23 +++++++++++++++++------ pkg/cli/generic_flag_test.go | 21 ++++++++++++++++++--- pkg/cli/map_flag.go | 15 +++++++++++++-- pkg/cli/slice_flag.go | 16 ++++++++++++++-- 7 files changed, 111 insertions(+), 18 deletions(-) diff --git a/pkg/cli/bool_flag.go b/pkg/cli/bool_flag.go index 0730c6bf2..1b3ffc69b 100644 --- a/pkg/cli/bool_flag.go +++ b/pkg/cli/bool_flag.go @@ -3,6 +3,7 @@ package cli import ( libflag "flag" + "github.com/gruntwork-io/go-commons/errors" "github.com/urfave/cli/v2" ) @@ -42,11 +43,22 @@ func (flag *BoolFlag) Apply(set *libflag.FlagSet) error { flag.Destination = new(bool) } - var err error + var ( + err error + envValue *string + ) valType := FlagType[bool](&boolFlagType{negative: flag.Negative}) - if flag.FlagValue, err = newGenericValue(valType, flag.LookupEnv(flag.EnvVar), flag.Destination); err != nil { + if val := flag.LookupEnv(flag.EnvVar); val != nil && *val != "" { + envValue = val + } + + if flag.FlagValue, err = newGenericValue(valType, envValue, flag.Destination); err != nil { + if envValue != nil { + return errors.Errorf("invalid boolean value %q for %s: %w", *envValue, flag.EnvVar, err) + } + return err } diff --git a/pkg/cli/bool_flag_test.go b/pkg/cli/bool_flag_test.go index 9de3ed49d..582682f26 100644 --- a/pkg/cli/bool_flag_test.go +++ b/pkg/cli/bool_flag_test.go @@ -11,6 +11,7 @@ import ( "github.com/gruntwork-io/terragrunt/pkg/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" ) func TestBoolFlagApply(t *testing.T) { @@ -86,6 +87,20 @@ func TestBoolFlagApply(t *testing.T) { false, errors.New(`invalid boolean flag foo: setting the flag multiple times`), }, + { + cli.BoolFlag{Name: "foo", EnvVar: "FOO"}, + nil, + map[string]string{"FOO": ""}, + false, + nil, + }, + { + cli.BoolFlag{Name: "foo", EnvVar: "FOO"}, + nil, + map[string]string{"FOO": "monkey"}, + false, + errors.New(`invalid boolean value "monkey" for FOO: must be one of: "0", "1", "f", "t", "false", "true"`), + }, } for i, testCase := range testCases { @@ -130,11 +145,12 @@ func testBoolFlagApply(t *testing.T, flag *cli.BoolFlag, args []string, envs map flagSet.SetOutput(io.Discard) err := flag.Apply(flagSet) - require.NoError(t, err) + if err == nil { + err = flagSet.Parse(args) + } - err = flagSet.Parse(args) if expectedErr != nil { - require.Equal(t, expectedErr, err) + require.ErrorContains(t, expectedErr, err.Error()) return } require.NoError(t, err) @@ -148,6 +164,8 @@ func testBoolFlagApply(t *testing.T, flag *cli.BoolFlag, args []string, envs map assert.Equal(t, strconv.FormatBool(expectedValue), flag.GetValue(), "GetValue()") } + maps.DeleteFunc(envs, func(k, v string) bool { return v == "" }) + assert.Equal(t, len(args) > 0 || len(envs) > 0, flag.Value().IsSet(), "IsSet()") assert.Equal(t, expectedDefaultValue, flag.Value().GetDefaultText(), "GetDefaultText()") diff --git a/pkg/cli/errors.go b/pkg/cli/errors.go index 1a922a929..aad91324a 100644 --- a/pkg/cli/errors.go +++ b/pkg/cli/errors.go @@ -88,3 +88,17 @@ func handleExitCoder(err error, osExiter func(code int)) error { return err } + +// InvalidValueError is used to wrap errors from `strconv` to make the error message more user friendly. +type InvalidValueError struct { + underlyingError error + msg string +} + +func (err InvalidValueError) Error() string { + return err.msg +} + +func (err InvalidValueError) Unwrap() error { + return err.underlyingError +} diff --git a/pkg/cli/generic_flag.go b/pkg/cli/generic_flag.go index b86dd0cb8..6628c9c82 100644 --- a/pkg/cli/generic_flag.go +++ b/pkg/cli/generic_flag.go @@ -47,11 +47,22 @@ func (flag *GenericFlag[T]) Apply(set *libflag.FlagSet) error { flag.Destination = new(T) } - var err error + var ( + err error + envValue *string + ) valType := FlagType[T](new(genericType[T])) - if flag.FlagValue, err = newGenericValue(valType, flag.LookupEnv(flag.EnvVar), flag.Destination); err != nil { + if val := flag.LookupEnv(flag.EnvVar); val != nil { + envValue = val + } + + if flag.FlagValue, err = newGenericValue(valType, envValue, flag.Destination); err != nil { + if envValue != nil { + return errors.Errorf("invalid value %q for %s: %w", *envValue, flag.EnvVar, err) + } + return err } @@ -199,7 +210,7 @@ func (val *genericType[T]) Set(str string) error { case *bool: v, err := strconv.ParseBool(str) if err != nil { - return errors.Errorf("error parse: %w", err) + return errors.WithStackTrace(InvalidValueError{underlyingError: err, msg: `must be one of: "0", "1", "f", "t", "false", "true"`}) } *dest = v @@ -207,7 +218,7 @@ func (val *genericType[T]) Set(str string) error { case *int: v, err := strconv.ParseInt(str, 0, strconv.IntSize) if err != nil { - return errors.Errorf("error parse: %w", err) + return errors.WithStackTrace(InvalidValueError{underlyingError: err, msg: "must be 32-bit integer"}) } *dest = int(v) @@ -215,7 +226,7 @@ func (val *genericType[T]) Set(str string) error { case *uint: v, err := strconv.ParseUint(str, 10, 64) if err != nil { - return errors.Errorf("error parse: %w", err) + return errors.WithStackTrace(InvalidValueError{underlyingError: err, msg: "must be 32-bit unsigned integer"}) } *dest = uint(v) @@ -223,7 +234,7 @@ func (val *genericType[T]) Set(str string) error { case *int64: v, err := strconv.ParseInt(str, 0, 64) if err != nil { - return errors.Errorf("error parse: %w", err) + return errors.WithStackTrace(InvalidValueError{underlyingError: err, msg: "must be 64-bit integer"}) } *dest = v diff --git a/pkg/cli/generic_flag_test.go b/pkg/cli/generic_flag_test.go index 2138274be..ac864e72e 100644 --- a/pkg/cli/generic_flag_test.go +++ b/pkg/cli/generic_flag_test.go @@ -101,6 +101,13 @@ func TestGenericFlagIntApply(t *testing.T) { 20, nil, }, + { + cli.GenericFlag[int]{Name: "foo", EnvVar: "FOO"}, + []string{}, + map[string]string{"FOO": "monkey"}, + 0, + errors.New(`invalid value "monkey" for FOO: must be 32-bit integer`), + }, { cli.GenericFlag[int]{Name: "foo", Destination: mockDestValue(55)}, nil, @@ -145,6 +152,13 @@ func TestGenericFlagInt64Apply(t *testing.T) { 20, nil, }, + { + cli.GenericFlag[int64]{Name: "foo", EnvVar: "FOO"}, + []string{}, + map[string]string{"FOO": "monkey"}, + 0, + errors.New(`invalid value "monkey" for FOO: must be 64-bit integer`), + }, { cli.GenericFlag[int64]{Name: "foo", Destination: mockDestValue(int64(55))}, nil, @@ -196,11 +210,12 @@ func testGenericFlagApply[T cli.GenericType](t *testing.T, flag *cli.GenericFlag flagSet.SetOutput(io.Discard) err := flag.Apply(flagSet) - require.NoError(t, err) + if err == nil { + err = flagSet.Parse(args) + } - err = flagSet.Parse(args) if expectedErr != nil { - require.Equal(t, expectedErr, err) + require.ErrorContains(t, expectedErr, err.Error()) return } require.NoError(t, err) diff --git a/pkg/cli/map_flag.go b/pkg/cli/map_flag.go index 4a7ac691e..dd2850492 100644 --- a/pkg/cli/map_flag.go +++ b/pkg/cli/map_flag.go @@ -76,12 +76,23 @@ func (flag *MapFlag[K, V]) Apply(set *libflag.FlagSet) error { flag.KeyValSep = MapFlagKeyValSep } - var err error + var ( + err error + envValue *string + ) keyType := FlagType[K](new(genericType[K])) valType := FlagType[V](new(genericType[V])) - if flag.FlagValue, err = newMapValue(keyType, valType, flag.LookupEnv(flag.EnvVar), flag.EnvVarSep, flag.KeyValSep, flag.Splitter, flag.Destination); err != nil { + if val := flag.LookupEnv(flag.EnvVar); val != nil { + envValue = val + } + + if flag.FlagValue, err = newMapValue(keyType, valType, envValue, flag.EnvVarSep, flag.KeyValSep, flag.Splitter, flag.Destination); err != nil { + if envValue != nil { + return errors.Errorf("invalid value %q for %s: %w", *envValue, flag.EnvVar, err) + } + return err } diff --git a/pkg/cli/slice_flag.go b/pkg/cli/slice_flag.go index 3c355fe06..5f25ea1ec 100644 --- a/pkg/cli/slice_flag.go +++ b/pkg/cli/slice_flag.go @@ -4,6 +4,7 @@ import ( libflag "flag" "strings" + "github.com/gruntwork-io/go-commons/errors" "github.com/urfave/cli/v2" ) @@ -62,11 +63,22 @@ func (flag *SliceFlag[T]) Apply(set *libflag.FlagSet) error { flag.EnvVarSep = SliceFlagEnvVarSep } - var err error + var ( + err error + envValue *string + ) valType := FlagType[T](new(genericType[T])) - if flag.FlagValue, err = newSliceValue(valType, flag.LookupEnv(flag.EnvVar), flag.EnvVarSep, flag.Splitter, flag.Destination); err != nil { + if val := flag.LookupEnv(flag.EnvVar); val != nil { + envValue = val + } + + if flag.FlagValue, err = newSliceValue(valType, envValue, flag.EnvVarSep, flag.Splitter, flag.Destination); err != nil { + if envValue != nil { + return errors.Errorf("invalid value %q for %s: %w", *envValue, flag.EnvVar, err) + } + return err } From 1b5231a7831fd85d09a32fc6883db213a2c9e37a Mon Sep 17 00:00:00 2001 From: Yousif Akbar <11247449+yhakbar@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:45:08 -0400 Subject: [PATCH 2/6] feat: Adding `codespell` to Automatically Check Spelling (#3413) * feat: Adding `.codespellrc` to add configuration for spellchecker * fix: Fixing a bunch of misspelled words * feat: Adding `codespell` to CI --- .circleci/config.yml | 21 +++++++++++++++++++ .codespellrc | 3 +++ cli/app.go | 2 +- .../README.md | 2 +- .../eks-alb-ingress-controller/README.md | 2 +- .../eks-aws-auth-merger/core-concepts.md | 2 +- cli/commands/flags.go | 4 ++-- cli/commands/terraform/target.go | 4 +++- config/config.go | 2 +- config/config_helpers.go | 2 +- config/config_partial.go | 2 +- config/locals.go | 2 +- config/locals_test.go | 6 +++--- docs/_docs/02_features/debugging.md | 2 +- docs/_docs/02_features/provider-cache.md | 2 +- docs/_docs/04_reference/cli-options.md | 4 ++-- docs/_docs/05_rfc/imports.md | 2 +- docs/assets/js/collection-browser_search.js | 2 +- docs/assets/js/collection-browser_toc.js | 4 ++-- pkg/cli/args.go | 2 +- pkg/log/format/prefix_style.go | 2 +- pkg/log/writer/writer.go | 2 +- remote/remote_state_gcs.go | 2 +- terraform/log.go | 2 +- terraform/source_test.go | 2 +- test/fixtures/hclvalidate/second/d/main.tf | 2 +- test/integration_engine_test.go | 4 ++-- test/integration_serial_test.go | 5 +++-- test/integration_test.go | 12 +++++------ 29 files changed, 66 insertions(+), 39 deletions(-) create mode 100644 .codespellrc diff --git a/.circleci/config.yml b/.circleci/config.yml index 64ed568a7..cecdeb3cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -123,6 +123,12 @@ run_markdownlint: &run_markdownlint -- \ docs +codespell: &codespell + name: Run codespell + command: | + pip install codespell + codespell . + version: 2.1 jobs: test_windows: @@ -520,6 +526,13 @@ jobs: - run: <<: *run_markdownlint + codespell: + <<: *defaults + steps: + - checkout + - run: + <<: *codespell + build: resource_class: xlarge <<: *defaults @@ -758,6 +771,14 @@ workflows: - AWS__PHXDEVOPS__circle-ci-test - GCP__automated-tests - GITHUB__PAT__gruntwork-ci + - codespell: + filters: + tags: + only: /^v.*/ + context: + - AWS__PHXDEVOPS__circle-ci-test + - GCP__automated-tests + - GITHUB__PAT__gruntwork-ci - build: filters: tags: diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 000000000..7f613268e --- /dev/null +++ b/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +skip = go.mod,go.sum,*.svg,Gemfile.lock +ignore-words-list = dRan diff --git a/cli/app.go b/cli/app.go index b920d033c..169643aaa 100644 --- a/cli/app.go +++ b/cli/app.go @@ -247,7 +247,7 @@ func runAction(cliCtx *cli.Context, opts *options.TerragruntOptions, action cli. // mostly preparing terragrunt options func initialSetup(cliCtx *cli.Context, opts *options.TerragruntOptions) error { - // The env vars are renamed to "..._NO_AUTO_..." in the gobal flags`. These ones are left for backwards compatibility. + // The env vars are renamed to "..._NO_AUTO_..." in the global flags`. These ones are left for backwards compatibility. opts.AutoInit = env.GetBool(os.Getenv("TERRAGRUNT_AUTO_INIT"), opts.AutoInit) opts.AutoRetry = env.GetBool(os.Getenv("TERRAGRUNT_AUTO_RETRY"), opts.AutoRetry) opts.RunAllAutoApprove = env.GetBool(os.Getenv("TERRAGRUNT_AUTO_APPROVE"), opts.RunAllAutoApprove) diff --git a/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller-iam-policy/README.md b/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller-iam-policy/README.md index c87be2ee0..56e3ece6d 100644 --- a/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller-iam-policy/README.md +++ b/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller-iam-policy/README.md @@ -16,7 +16,7 @@ Controller on to your EKS cluster. * See the [eks-cluster-with-supporting-services example](/examples/eks-cluster-with-supporting-services) for example usage. * See [variables.tf](./variables.tf) for all the variables you can set on this module. -* See [outputs.tf](./outputs.tf) for all the variables that are outputed by this module. +* See [outputs.tf](./outputs.tf) for all the variables that are outputted by this module. ## Attaching IAM policy to workers diff --git a/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller/README.md b/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller/README.md index 674460e96..322d57e14 100644 --- a/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller/README.md +++ b/cli/commands/catalog/module/testdata/find_modules/modules/eks-alb-ingress-controller/README.md @@ -54,7 +54,7 @@ spec: ``` In the above configuration, we create a Cluster IP based `Service` (so that it is only available internally to the -Kubernetes cluster) that routes requests to port 80 to any `Pod` that maches the label `app=backend` on port 80. Then, +Kubernetes cluster) that routes requests to port 80 to any `Pod` that matches the label `app=backend` on port 80. Then, we configure an `Ingress` rule that routes any requests prefixed with `/service` to that `Service` endpoint on port 80. The actual load balancer that is configured by the `Ingress` resource is defined by the particular [Ingress diff --git a/cli/commands/catalog/module/testdata/find_modules/modules/eks-aws-auth-merger/core-concepts.md b/cli/commands/catalog/module/testdata/find_modules/modules/eks-aws-auth-merger/core-concepts.md index 2da0061c3..ea62a3dfa 100644 --- a/cli/commands/catalog/module/testdata/find_modules/modules/eks-aws-auth-merger/core-concepts.md +++ b/cli/commands/catalog/module/testdata/find_modules/modules/eks-aws-auth-merger/core-concepts.md @@ -32,7 +32,7 @@ The `aws-auth-merger` works as follows: - The `aws-auth-merger` then does an initial merger of all the `ConfigMaps` in the configured namespace to create the initial version of the main `aws-auth` `ConfigMap`. - The `aws-auth-merger` then enters an infinite event loop that watches for changes to the `ConfigMaps` in the - configured namespace. The syncing routine will run everytime the merger detects changes in the namespace. + configured namespace. The syncing routine will run every time the merger detects changes in the namespace. ## How do I use the aws-auth-merger? diff --git a/cli/commands/flags.go b/cli/commands/flags.go index ea2186e15..28fc3a22d 100644 --- a/cli/commands/flags.go +++ b/cli/commands/flags.go @@ -257,7 +257,7 @@ func NewGlobalFlags(opts *options.TerragruntOptions) cli.Flags { Name: TerragruntIAMAssumeRoleSessionNameFlagName, EnvVar: TerragruntIAMAssumeRoleSessionNameEnvName, Destination: &opts.IAMRoleOptions.AssumeRoleSessionName, - Usage: "Name for the IAM Assummed Role session. Can also be set via TERRAGRUNT_IAM_ASSUME_ROLE_SESSION_NAME environment variable.", + Usage: "Name for the IAM Assumed Role session. Can also be set via TERRAGRUNT_IAM_ASSUME_ROLE_SESSION_NAME environment variable.", }, &cli.GenericFlag[string]{ Name: TerragruntIAMWebIdentityTokenFlagName, @@ -411,7 +411,7 @@ func NewGlobalFlags(opts *options.TerragruntOptions) cli.Flags { Name: TerragruntFetchDependencyOutputFromStateFlagName, EnvVar: TerragruntFetchDependencyOutputFromStateEnvName, Destination: &opts.FetchDependencyOutputFromState, - Usage: "The option fetchs dependency output directly from the state file instead of init dependencies and running terraform on them.", + Usage: "The option fetches dependency output directly from the state file instead of init dependencies and running terraform on them.", }, &cli.BoolFlag{ Name: TerragruntForwardTFStdoutFlagName, diff --git a/cli/commands/terraform/target.go b/cli/commands/terraform/target.go index c6e143f37..3079ce3a1 100644 --- a/cli/commands/terraform/target.go +++ b/cli/commands/terraform/target.go @@ -20,7 +20,9 @@ type TargetCallbackType func(ctx context.Context, opts *options.TerragruntOption 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. +// 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 complicated to extract +// 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: /* diff --git a/config/config.go b/config/config.go index 7a4c25071..783495d77 100644 --- a/config/config.go +++ b/config/config.go @@ -823,7 +823,7 @@ func ParseConfigString(ctx *ParsingContext, configPath string, configString stri // Allowed References: // - locals // - dependency -// 5. Merge the included config with the parsed config. Note that all the config data is mergable except for `locals` +// 5. Merge the included config with the parsed config. Note that all the config data is mergeable except for `locals` // blocks, which are only scoped to be available within the defining config. func ParseConfig(ctx *ParsingContext, file *hclparse.File, includeFromChild *IncludeConfig) (*TerragruntConfig, error) { ctx = ctx.WithTrackInclude(nil) diff --git a/config/config_helpers.go b/config/config_helpers.go index 830a037a2..bce034756 100644 --- a/config/config_helpers.go +++ b/config/config_helpers.go @@ -319,7 +319,7 @@ func parseGetEnvParameters(parameters []string) (EnvVar, error) { return envVariable, nil } -// RunCommand is a helper function that runs a command and returns the stdout as the interporation +// RunCommand is a helper function that runs a command and returns the stdout as the interpolation // for each `run_cmd` in locals section, function is called twice // result func RunCommand(ctx *ParsingContext, args []string) (string, error) { diff --git a/config/config_partial.go b/config/config_partial.go index c73acc781..16aa67e3a 100644 --- a/config/config_partial.go +++ b/config/config_partial.go @@ -301,7 +301,7 @@ func PartialParseConfig(ctx *ParsingContext, file *hclparse.File, includeFromChi decoded.Dependencies = decoded.Dependencies.FilteredWithoutConfigPath() output.TerragruntDependencies = decoded.Dependencies - // Convert dependency blocks into module depenency lists. If we already decoded some dependencies, + // Convert dependency blocks into module dependency lists. If we already decoded some dependencies, // merge them in. Otherwise, set as the new list. dependencies := dependencyBlocksToModuleDependencies(decoded.Dependencies) if output.Dependencies != nil { diff --git a/config/locals.go b/config/locals.go index 9e2de8901..4f96e1d6d 100644 --- a/config/locals.go +++ b/config/locals.go @@ -18,7 +18,7 @@ const MaxIter = 1000 // EvaluateLocalsBlock is a routine to evaluate the locals block in a way to allow references to other locals. This // will: // - Extract a reference to the locals block from the parsed file -// - Continuously evaluate the block until all references are evaluated, defering evaluation of anything that references +// - Continuously evaluate the block until all references are evaluated, deferring evaluation of anything that references // other locals until those references are evaluated. // // This returns a map of the local names to the evaluated expressions (represented as `cty.Value` objects). This will diff --git a/config/locals_test.go b/config/locals_test.go index 74d26d419..28aab788f 100644 --- a/config/locals_test.go +++ b/config/locals_test.go @@ -39,9 +39,9 @@ func TestEvaluateLocalsBlock(t *testing.T) { require.NoError(t, gocty.FromCtyValue(evaluatedLocals["x"], &actualX)) assert.InEpsilon(t, float64(1), actualX, 0.0000001) - var actualY float64 - require.NoError(t, gocty.FromCtyValue(evaluatedLocals["y"], &actualY)) - assert.InEpsilon(t, float64(2), actualY, 0.0000001) + var actualY float64 //codespell:ignore + require.NoError(t, gocty.FromCtyValue(evaluatedLocals["y"], &actualY)) //codespell:ignore + assert.InEpsilon(t, float64(2), actualY, 0.0000001) //codespell:ignore var actualZ float64 require.NoError(t, gocty.FromCtyValue(evaluatedLocals["z"], &actualZ)) diff --git a/docs/_docs/02_features/debugging.md b/docs/_docs/02_features/debugging.md index ff556c3af..86bd0a5c5 100644 --- a/docs/_docs/02_features/debugging.md +++ b/docs/_docs/02_features/debugging.md @@ -13,7 +13,7 @@ nav_title_link: /docs/ ## Debugging Terragrunt and OpenTofu/Terraform usually play well together in helping you -write DRY, re-usable infrastructure. But how do we figure out what +write DRY, reusable infrastructure. But how do we figure out what went wrong in the rare case that they _don't_ play well? Terragrunt provides a way to configure logging level through the `--terragrunt-log-level` diff --git a/docs/_docs/02_features/provider-cache.md b/docs/_docs/02_features/provider-cache.md index 88f5188df..19723213a 100644 --- a/docs/_docs/02_features/provider-cache.md +++ b/docs/_docs/02_features/provider-cache.md @@ -93,7 +93,7 @@ terragrunt apply - The Terragrunt Provider Cache server will download the provider from the remote registry, unpack and store it into the cache directory or [create a symlink](#reusing-providers-from-the-user-plugins-directory) if the required provider exists in the user plugins directory. Note that the Terragrunt Provider Cache server will ensure that each unique provider is only ever downloaded and stored on disk once, handling concurrency (from multiple OpenTofu/Terraform and Terragrunt instances) correctly. Along with the provider, the cache server downloads hashes and signatures of the providers to check that the files are not corrupted. - The Terragrunt Provider Cache server returns the HTTP status _429 Locked_ to OpenTofu/Terraform. This is because we do _not_ want OpenTofu/Terraform to actually download any providers as a result of calling `terraform init`; we only use that command to request the Terragrunt Provider Cache Server to start caching providers. - At this point, all providers are downloaded and cached, so finally, we run `terragrunt init` a second time, which will find all the providers it needs in the cache, and it'll create symlinks to them nearly instantly, with no additional downloading. - - Note that if a OpenTofu/Terraform module doesn't have a lock file, OpenTofu/Terraform does _not_ use the cache, so it would end up downloading all the providers from scratch. To work around this, we generate `.terraform.lock.hcl` based on the request made by `terrafrom init` to the Terragrunt Provider Cache server. Since `terraform init` only requestes the providers that need to be added/updated, we can keep track of them using the Terragrunt Provider Cache server and update the OpenTofu/Terraform lock file with the appropriate hashes without having to parse `tf` configs. + - Note that if a OpenTofu/Terraform module doesn't have a lock file, OpenTofu/Terraform does _not_ use the cache, so it would end up downloading all the providers from scratch. To work around this, we generate `.terraform.lock.hcl` based on the request made by `terrafrom init` to the Terragrunt Provider Cache server. Since `terraform init` only requests the providers that need to be added/updated, we can keep track of them using the Terragrunt Provider Cache server and update the OpenTofu/Terraform lock file with the appropriate hashes without having to parse `tf` configs. #### Reusing providers from the user plugins directory diff --git a/docs/_docs/04_reference/cli-options.md b/docs/_docs/04_reference/cli-options.md index 462b71563..00fbfd25d 100644 --- a/docs/_docs/04_reference/cli-options.md +++ b/docs/_docs/04_reference/cli-options.md @@ -617,7 +617,7 @@ Run the provided OpenTofu/Terraform command against the graph of dependencies fo The Command will be executed following the order of dependencies: so it'll run on the module in the current working directory first, then on modules that depend on it directly, then on the modules that depend on those modules, and so on. Note that if the command is `destroy`, it will execute in the opposite order of the dependencies. Example: -Having bellow dependencies: +Having below dependencies: [![dependency-graph](/assets/img/collections/documentation/dependency-graph.png){: width="80%" }]({{site.baseurl}}/assets/img/collections/documentation/dependency-graph.png) Running `terragrunt graph apply` in `eks` module will lead to the following execution order: @@ -1041,7 +1041,7 @@ When passed in, and running `terragrunt validate-inputs`, enables strict mode fo **CLI Arg**: `--terragrunt-ignore-dependency-order`
**Environment Variable**: `TERRAGRUNT_IGNORE_DEPENDENCY_ORDER`
-When passed in, ignore the depedencies between modules when running `*-all` commands. +When passed in, ignore the dependencies between modules when running `*-all` commands. ### terragrunt-ignore-external-dependencies diff --git a/docs/_docs/05_rfc/imports.md b/docs/_docs/05_rfc/imports.md index dae9b42ad..ab9518c02 100644 --- a/docs/_docs/05_rfc/imports.md +++ b/docs/_docs/05_rfc/imports.md @@ -984,7 +984,7 @@ look for an alternative implementation. This challenge has come up numerous times in the lifetime of Terragrunt. The following are relevant issues that raise similar concerns: -- [Shared and overridable variabls](https://github.com/gruntwork-io/terragrunt/issues/814) +- [Shared and overridable variables](https://github.com/gruntwork-io/terragrunt/issues/814) - [Being able to merge maps from inputs](https://github.com/gruntwork-io/terragrunt/issues/744) - [Request to allow more than one level of include](https://github.com/gruntwork-io/terragrunt/issues/303) - [Request to reference inputs from another config](https://github.com/gruntwork-io/terragrunt/issues/967) diff --git a/docs/assets/js/collection-browser_search.js b/docs/assets/js/collection-browser_search.js index b1580045f..bf9ffa674 100644 --- a/docs/assets/js/collection-browser_search.js +++ b/docs/assets/js/collection-browser_search.js @@ -4,7 +4,7 @@ * TOC: * - FILTER FUNCTIONS - functions to extract the terms from DOM element(s) and use them in search engine to show/hide elements. * - MAIN - INITIALIZE - initializes Browser Search and registers actions (click etc.) on filter components. - * - SEARCH ENGINE - here is the logic to show & hide elements accoriding to filters terms. + * - SEARCH ENGINE - here is the logic to show & hide elements according to filters terms. * - OTHER */ (function () { diff --git a/docs/assets/js/collection-browser_toc.js b/docs/assets/js/collection-browser_toc.js index 7e40c1f4b..0aa725c77 100644 --- a/docs/assets/js/collection-browser_toc.js +++ b/docs/assets/js/collection-browser_toc.js @@ -26,7 +26,7 @@ $(document).ready(function () { } }) - // Expand / collpase on click + // Expand / collapse on click $('#toc .nav-collapse-handler').on('click', function() { toggleNav($(this)) }) @@ -34,7 +34,7 @@ $(document).ready(function () { $(docSidebarInitialExpand) }) -// Expand / collpase on click +// Expand / collapse on click function toggleNav(el) { if (el.hasClass('collapsed')) { el.removeClass('collapsed') diff --git a/pkg/cli/args.go b/pkg/cli/args.go index 94ddf85a5..27c6641f0 100644 --- a/pkg/cli/args.go +++ b/pkg/cli/args.go @@ -77,7 +77,7 @@ func (args Args) Slice() []string { // if the given act is: // // `SingleDashFlag` - converts all arguments containing double dashes to single dashes -// `DoubleDashFlag` - converts all arguments containing signle dashes to double dashes +// `DoubleDashFlag` - converts all arguments containing single dashes to double dashes func (args Args) Normalize(acts ...NormalizeActsType) Args { strArgs := make(Args, 0, len(args.Slice())) diff --git a/pkg/log/format/prefix_style.go b/pkg/log/format/prefix_style.go index 0686efc79..44b00b0b8 100644 --- a/pkg/log/format/prefix_style.go +++ b/pkg/log/format/prefix_style.go @@ -18,7 +18,7 @@ var ( type prefixStyle struct { // cache stores prefixes with their color schemes. - // We use [xsync.MapOf](https://github.com/puzpuzpuz/xsync?tab=readme-ov-file#map) instaed of standard `sync.Map` since it's faster and has generic types. + // We use [xsync.MapOf](https://github.com/puzpuzpuz/xsync?tab=readme-ov-file#map) instead of standard `sync.Map` since it's faster and has generic types. cache *xsync.MapOf[string, ColorFunc] availableStyles []ColorStyle diff --git a/pkg/log/writer/writer.go b/pkg/log/writer/writer.go index ed8a2f829..f349681f7 100644 --- a/pkg/log/writer/writer.go +++ b/pkg/log/writer/writer.go @@ -38,7 +38,7 @@ func (writer *Writer) SetOption(opts ...Option) { } } -// Write implements `io.Writer` interafce. +// Write implements `io.Writer` interface. func (writer *Writer) Write(p []byte) (n int, err error) { var ( str = string(p) diff --git a/remote/remote_state_gcs.go b/remote/remote_state_gcs.go index 1915f56be..4deda7275 100644 --- a/remote/remote_state_gcs.go +++ b/remote/remote_state_gcs.go @@ -458,7 +458,7 @@ func DoesGCSBucketExist(gcsClient *storage.Client, config *RemoteStateConfigGCS) bucket := gcsClient.Bucket(config.Bucket) // TODO - the code below attempts to determine whether the storage bucket exists by making a making a number of API - // calls, then attemping to list the contents of the bucket. It was adapted from Google's own integration tests and + // calls, then attempting to list the contents of the bucket. It was adapted from Google's own integration tests and // should be improved once the appropriate API call is added. For more info see: // https://github.com/GoogleCloudPlatform/google-cloud-go/blob/de879f7be552d57556875b8aaa383bce9396cc8c/storage/integration_test.go#L1231 if _, err := bucket.Attrs(ctx); err != nil { diff --git a/terraform/log.go b/terraform/log.go index 2a7e6cb45..ee0cbe5d8 100644 --- a/terraform/log.go +++ b/terraform/log.go @@ -25,7 +25,7 @@ var ( tfLogTimeLevelMsgReg = regexp.MustCompile(`(?i)(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\S*)\s*\[(trace|debug|warn|info|error)\]\s*(.+\S)$`) ) -// ParseLogFunc wraps `ParseLog` to add msg prefix and bypasses the the parse erorr if `returnError` is false, +// ParseLogFunc wraps `ParseLog` to add msg prefix and bypasses the parse error if `returnError` is false, // since returning the error for `log/writer` will cause TG to fall with a `broken pipe` error. func ParseLogFunc(msgPrefix string, returnError bool) writer.WriterParseFunc { return func(str string) (msg string, ptrTime *time.Time, ptrLevel *log.Level, err error) { diff --git a/terraform/source_test.go b/terraform/source_test.go index 5d66e2df4..dd44e31d1 100644 --- a/terraform/source_test.go +++ b/terraform/source_test.go @@ -80,7 +80,7 @@ func TestToSourceUrl(t *testing.T) { {"https://www.googleapis.com/storage/v1/modules/foomodule.zip", "gcs::https://www.googleapis.com/storage/v1/modules/foomodule.zip"}, {"https://www.googleapis.com/storage/v1/modules/foomodule.zip", "gcs::https://www.googleapis.com/storage/v1/modules/foomodule.zip"}, {"git::https://name@dev.azure.com/name/project-name/_git/repo-name", "git::https://name@dev.azure.com/name/project-name/_git/repo-name"}, - {"https://repositry.rnd.net/artifactory/generic-production-iac/tf-auto-azr-iam.2.6.0.zip", "https://repositry.rnd.net/artifactory/generic-production-iac/tf-auto-azr-iam.2.6.0.zip"}, + {"https://repository.rnd.net/artifactory/generic-production-iac/tf-auto-azr-iam.2.6.0.zip", "https://repository.rnd.net/artifactory/generic-production-iac/tf-auto-azr-iam.2.6.0.zip"}, } for i, testCase := range testCases { diff --git a/test/fixtures/hclvalidate/second/d/main.tf b/test/fixtures/hclvalidate/second/d/main.tf index 95ee55772..e3bdc4d16 100644 --- a/test/fixtures/hclvalidate/second/d/main.tf +++ b/test/fixtures/hclvalidate/second/d/main.tf @@ -1,4 +1,4 @@ -variabl "d" { +variabl "d" { //codespell:ignore type = string default = "d" } diff --git a/test/integration_engine_test.go b/test/integration_engine_test.go index 016d4cea9..df97e4fdf 100644 --- a/test/integration_engine_test.go +++ b/test/integration_engine_test.go @@ -85,7 +85,7 @@ func TestEngineRunAllOpentofu(t *testing.T) { assert.Contains(t, stderr, "plugin process exited:") assert.Contains(t, stdout, "resource \"local_file\" \"test\"") assert.Contains(t, stdout, "filename = \"./test.txt\"\n") - assert.Contains(t, stdout, "OpenTofu has been successfull") + assert.Contains(t, stdout, "OpenTofu has been successful") assert.Contains(t, stdout, "Tofu Shutdown completed") assert.Contains(t, stdout, "Apply complete!") } @@ -100,7 +100,7 @@ func TestEngineRunAllOpentofuCustomPath(t *testing.T) { assert.Contains(t, stderr, "starting plugin:") assert.Contains(t, stderr, "plugin process exited:") - assert.Contains(t, stdout, "OpenTofu has been successfull") + assert.Contains(t, stdout, "OpenTofu has been successful") assert.Contains(t, stdout, "Tofu Shutdown completed") assert.Contains(t, stdout, "Apply complete!") diff --git a/test/integration_serial_test.go b/test/integration_serial_test.go index 4821a33c9..cb8f1883b 100644 --- a/test/integration_serial_test.go +++ b/test/integration_serial_test.go @@ -131,7 +131,8 @@ func TestTerragruntProviderCacheWithNetworkMirror(t *testing.T) { } filesystemProvider.CreateMirror(t, providersFilesystemMirrorPath) - // when we run NetworkMirrorServer, we override the default transport to configure the self-signed certificate, we need to restor, after finishing we need to restore this value + // When we run NetworkMirrorServer, we override the default transport to configure the self-signed certificate. + // After finishing, we need to restore this value. defaultTransport := http.DefaultTransport defer func() { http.DefaultTransport = defaultTransport @@ -392,7 +393,7 @@ func TestPriorityOrderOfArgument(t *testing.T) { injectedValue := "Injected-directly-by-argument" runTerragruntRedirectOutput(t, fmt.Sprintf("terragrunt apply -auto-approve -var extra_var=%s --terragrunt-non-interactive --terragrunt-forward-tf-stdout --terragrunt-working-dir %s", injectedValue, testFixtureExtraArgsPath), out, os.Stderr) t.Log(out.String()) - // And the result value for test should be the injected variable since the injected arguments are injected before the suplied parameters, + // And the result value for test should be the injected variable since the injected arguments are injected before the supplied parameters, // so our override of extra_var should be the last argument. assert.Contains(t, out.String(), fmt.Sprintf(`test = "%s"`, injectedValue)) } diff --git a/test/integration_test.go b/test/integration_test.go index 6f750a4f9..d8464399f 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -660,13 +660,13 @@ func TestTerraformCommandCliArgs(t *testing.T) { nil, }, { - []string{"paln"}, + []string{"paln"}, //codespell:ignore "", - expectedWrongCommandErr("paln"), + expectedWrongCommandErr("paln"), //codespell:ignore }, { - []string{"paln", "--terragrunt-disable-command-validation"}, - wrappedBinary() + " invocation failed", // error caused by running terraform with the wrong command + []string{"paln", "--terragrunt-disable-command-validation"}, //codespell:ignore + wrappedBinary() + " invocation failed", // error caused by running terraform with the wrong command nil, }, } @@ -1082,7 +1082,7 @@ func TestDependencyMockOutput(t *testing.T) { } // Test default behavior when mock_outputs_merge_with_state is not set. It should behave, as before this parameter was added -// It will fail on any command if the parent state is not applied, because the state of the parent exists and it alread has an output +// It will fail on any command if the parent state is not applied, because the state of the parent exists and it already has an output // but not the newly added output. func TestDependencyMockOutputMergeWithStateDefault(t *testing.T) { t.Parallel() @@ -1115,7 +1115,7 @@ func TestDependencyMockOutputMergeWithStateDefault(t *testing.T) { } // Test when mock_outputs_merge_with_state is explicitly set to false. It should behave, as before this parameter was added -// It will fail on any command if the parent state is not applied, because the state of the parent exists and it alread has an output +// It will fail on any command if the parent state is not applied, because the state of the parent exists and it already has an output // but not the newly added output. func TestDependencyMockOutputMergeWithStateFalse(t *testing.T) { t.Parallel() From b350dc104094c8328baa714790884febc35e8c91 Mon Sep 17 00:00:00 2001 From: Yousif Akbar <11247449+yhakbar@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:40:21 -0400 Subject: [PATCH 3/6] feat: Adding issue templates (#3416) --- .github/ISSUE_TEMPLATE/01-bug_report.md | 2 + .github/ISSUE_TEMPLATE/02-rfc.yml | 2 +- .github/ISSUE_TEMPLATE/03-feature-request.md | 36 +++++++++++++++ .../04-iac-engine-bug-report.md | 44 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/03-feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/04-iac-engine-bug-report.md diff --git a/.github/ISSUE_TEMPLATE/01-bug_report.md b/.github/ISSUE_TEMPLATE/01-bug_report.md index 5eeaa5600..a77ec8020 100644 --- a/.github/ISSUE_TEMPLATE/01-bug_report.md +++ b/.github/ISSUE_TEMPLATE/01-bug_report.md @@ -15,6 +15,8 @@ A clear and concise description of what the bug is. Steps to reproduce the behavior, code snippets and examples which can be used to reproduce the issue. +Be sure that the maintainers can actually reproduce the issue. Bug reports that are too vague or hard to reproduce are hard to troubleshoot and fix. + ```hcl // paste code snippets here ``` diff --git a/.github/ISSUE_TEMPLATE/02-rfc.yml b/.github/ISSUE_TEMPLATE/02-rfc.yml index b26f98cf5..33e845b3b 100644 --- a/.github/ISSUE_TEMPLATE/02-rfc.yml +++ b/.github/ISSUE_TEMPLATE/02-rfc.yml @@ -3,7 +3,7 @@ # - the OpenTofu RFC template: https://raw.githubusercontent.com/opentofu/opentofu/main/.github/ISSUE_TEMPLATE/rfc.yml name: RFC -description: Submit a Request For Comments (RFC). +description: Submit a Request For Comments (RFC) to significantly change Terragrunt. labels: ["rfc", "pending-decision"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/03-feature-request.md b/.github/ISSUE_TEMPLATE/03-feature-request.md new file mode 100644 index 000000000..9b9508054 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-feature-request.md @@ -0,0 +1,36 @@ +--- +name: Enhancement +about: Request a simple feature or enhancement. +title: '' +labels: enhancement +assignees: '' + +--- + +## Describe the enhancement + +A clear and concise description of what the enhancement is. + +## Additional context + +Add any other context about the enhancement here. + +Things you might want to address include: + +- [ ] Changes required. +- [ ] Implications of the feature. +- [ ] Alternatives considered. +- [ ] Level of effort. + +## PoC (Proof of Concept) + +Link to a Proof of Concept if you have one: + +- [ ] [PoC]() + +Including a PoC can help others understand the feature better and implement it faster. + +## RFC Not Needed + +- [ ] I have evaluated the complexity of this enhancement, and I believe it does not require an RFC. + diff --git a/.github/ISSUE_TEMPLATE/04-iac-engine-bug-report.md b/.github/ISSUE_TEMPLATE/04-iac-engine-bug-report.md new file mode 100644 index 000000000..6541c1a71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/04-iac-engine-bug-report.md @@ -0,0 +1,44 @@ +--- +name: IaC Engine Bug Report +about: Report a bug in the IaC Engine. +title: '' +labels: bug, iac-engine +assignees: '' + +--- + +## Describe the bug + +A clear and concise description of what the bug is. + +## Steps To Reproduce + +Steps to reproduce the behavior, code snippets and examples which can be used to reproduce the issue. + +Be sure that the maintainers can actually reproduce the issue. Bug reports that are too vague or hard to reproduce are hard to troubleshoot and fix. + +```hcl +// paste code snippets here +``` + +## Expected behavior + +A clear and concise description of what you expected to happen. + +## Nice to haves + +- [ ] Terminal output +- [ ] Screenshots + +## Versions + +- Terragrunt version: +- Engine source: +- Engine version: +- OpenTofu/Terraform version: +- Environment details (Ubuntu 20.04, Windows 10, etc.): + +## Additional context + +Add any other context about the problem here. + From 2593c5e7f9d6f42b6b596bf4b931bbcb6d8f430a Mon Sep 17 00:00:00 2001 From: Levko Burburas <62853952+levkohimins@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:08:57 +0300 Subject: [PATCH 4/6] fix: update repository for catalog command (#3415) --- cli/commands/catalog/module/repo.go | 8 +++++++- test/integration_catalog_test.go | 24 +++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/cli/commands/catalog/module/repo.go b/cli/commands/catalog/module/repo.go index 32685364a..5bc9f29b8 100644 --- a/cli/commands/catalog/module/repo.go +++ b/cli/commands/catalog/module/repo.go @@ -3,6 +3,7 @@ package module import ( "context" "fmt" + "net/url" "os" "path/filepath" "regexp" @@ -194,7 +195,12 @@ func (repo *Repo) clone(ctx context.Context) error { repo.logger.Infof("Cloning repository %q to temporary directory %q", repo.cloneURL, repo.path) - if err := getter.Get(repo.path, strings.Trim(sourceURL.String(), "/"), getter.WithContext(ctx)); err != nil { + // We need to explicitly specify the reference, otherwise we will get an error: + // "fatal: The empty string is not a valid pathspec. Use . instead if you wanted to match all paths" + // when updating an existing repository. + sourceURL.RawQuery = (url.Values{"ref": []string{"HEAD"}}).Encode() + + if err := getter.Get(repo.path, strings.Trim(sourceURL.String(), "/"), getter.WithContext(ctx), getter.WithMode(getter.ClientModeDir)); err != nil { return errors.WithStackTrace(err) } diff --git a/test/integration_catalog_test.go b/test/integration_catalog_test.go index 0a23aa40b..bc0d9abbc 100644 --- a/test/integration_catalog_test.go +++ b/test/integration_catalog_test.go @@ -2,7 +2,6 @@ package test_test import ( "context" - "os" "path/filepath" "testing" @@ -15,14 +14,27 @@ import ( "github.com/stretchr/testify/require" ) -func TestScaffoldGitRepo(t *testing.T) { +func TestCatalogGitRepoUpdate(t *testing.T) { t.Parallel() ctx := context.Background() - tempDir, err := os.MkdirTemp("", "catalog-*") + tempDir := t.TempDir() + + _, err := module.NewRepo(ctx, log.New(), "github.com/gruntwork-io/terraform-fake-modules.git", tempDir) require.NoError(t, err) + _, err = module.NewRepo(ctx, log.New(), "github.com/gruntwork-io/terraform-fake-modules.git", tempDir) + require.NoError(t, err) +} + +func TestScaffoldGitRepo(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tempDir := t.TempDir() + repo, err := module.NewRepo(ctx, log.New(), "github.com/gruntwork-io/terraform-fake-modules.git", tempDir) require.NoError(t, err) @@ -36,8 +48,7 @@ func TestScaffoldGitModule(t *testing.T) { ctx := context.Background() - tempDir, err := os.MkdirTemp("", "catalog-*") - require.NoError(t, err) + tempDir := t.TempDir() repo, err := module.NewRepo(ctx, log.New(), "https://github.com/gruntwork-io/terraform-fake-modules.git", tempDir) require.NoError(t, err) @@ -75,8 +86,7 @@ func TestScaffoldGitModuleHttps(t *testing.T) { ctx := context.Background() - tempDir, err := os.MkdirTemp("", "catalog-*") - require.NoError(t, err) + tempDir := t.TempDir() repo, err := module.NewRepo(ctx, log.New(), "https://github.com/gruntwork-io/terraform-fake-modules", tempDir) require.NoError(t, err) From 2a0eddc043fa9e9cced80fc129b2083d0583a5e1 Mon Sep 17 00:00:00 2001 From: Yousif Akbar <11247449+yhakbar@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:36:34 -0400 Subject: [PATCH 5/6] feat: Adding release announcement workflow (#3419) --- .github/scripts/announce-release.sh | 30 ++++++++++++++++++++++++++ .github/workflows/announce-release.yml | 22 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100755 .github/scripts/announce-release.sh create mode 100644 .github/workflows/announce-release.yml diff --git a/.github/scripts/announce-release.sh b/.github/scripts/announce-release.sh new file mode 100755 index 000000000..f5b7e5f56 --- /dev/null +++ b/.github/scripts/announce-release.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail + +URL="${URL:?Required environment variable URL}" +REPO="${REPO:?Required environment variable REPO}" +TAG_NAME="${TAG_NAME:?Required environment variable TAG_NAME}" +ROLE_ID="${ROLE_ID:?Required environment variable ROLE_ID}" +USERNAME="${USERNAME:?Required environment variable USERNAME}" +AVATAR_URL="${AVATAR_URL:?Required environment variable AVATAR_URL}" + +if RELEASE_JSON=$(gh -R "$REPO" release view "$TAG_NAME" --json body --json url --json name); then + RELEASE_NOTES=$(jq '.body' <<<"$RELEASE_JSON") + + PAYLOAD=$( + jq \ + --argjson release_notes "$RELEASE_NOTES" \ + --arg username "$USERNAME" \ + --arg avatar_url "$AVATAR_URL" \ + -cn '{"content": $release_notes, username: $username, avatar_url: $avatar_url, "flags": 4}' + ) + + tmpfile=$(mktemp) + jq '.content = "'"<@&$ROLE_ID> $(jq -r '.name' <<<"$RELEASE_JSON")\n"'>>> " + .content + "'"\n\n**[View release on GitHub]($(jq -r '.url' <<<"$RELEASE_JSON"))**"'"' <<<"$PAYLOAD" >"$tmpfile" + + curl -X POST \ + --data-binary "@$tmpfile" \ + -H "Content-Type: application/json" \ + "$URL" +fi diff --git a/.github/workflows/announce-release.yml b/.github/workflows/announce-release.yml new file mode 100644 index 000000000..8694986b0 --- /dev/null +++ b/.github/workflows/announce-release.yml @@ -0,0 +1,22 @@ +name: Announce Release +on: + release: + # This is intentionally not `published` to avoid announcing pre-releases + types: [released] +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Announce Release + run: ./.github/scripts/announce-release.sh + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + TAG_NAME: ${{ github.event.release.tag_name }} + URL: ${{ secrets.RELEASE_ANNOUNCEMENT_URL }} + ROLE_ID: ${{ secrets.RELEASE_ANNOUNCEMENT_ROLE_ID }} + USERNAME: ${{ secrets.RELEASE_ANNOUNCEMENT_USERNAME }} + AVATAR_URL: ${{ secrets.RELEASE_ANNOUNCEMENT_AVATAR_URL }} From 70797fd45c502221af8804266b01f958ee8b7091 Mon Sep 17 00:00:00 2001 From: Denis O Date: Fri, 20 Sep 2024 05:24:34 +0000 Subject: [PATCH 6/6] Improved errors printing in case of terraform/tofu invocation (#3423) * Improved erorr printing to incude stderr and stdout * lint fixes * Test to do output of errors * Updated handling of error message * Updated log level --- shell/run_shell_cmd.go | 1 + test/fixtures/error-print/custom-tf-script.sh | 5 +++++ test/fixtures/error-print/main.tf | 0 test/fixtures/error-print/terragrunt.hcl | 0 test/integration_test.go | 14 ++++++++++++++ 5 files changed, 20 insertions(+) create mode 100755 test/fixtures/error-print/custom-tf-script.sh create mode 100644 test/fixtures/error-print/main.tf create mode 100644 test/fixtures/error-print/terragrunt.hcl diff --git a/shell/run_shell_cmd.go b/shell/run_shell_cmd.go index fefd36254..63073aa66 100644 --- a/shell/run_shell_cmd.go +++ b/shell/run_shell_cmd.go @@ -253,6 +253,7 @@ func RunShellCommandWithOutput( } if err != nil { + opts.Logger.Warnf("Failed to execute %s in %s\n%s\n%s\n%v", command+" "+strings.Join(args, " "), cmd.Dir, stdoutBuf.String(), stderrBuf.String(), err) err = util.ProcessExecutionError{ Err: err, Stdout: stdoutBuf.String(), diff --git a/test/fixtures/error-print/custom-tf-script.sh b/test/fixtures/error-print/custom-tf-script.sh new file mode 100755 index 000000000..68317042f --- /dev/null +++ b/test/fixtures/error-print/custom-tf-script.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +echo "Custom error from script" >&2 + +exit 1 \ No newline at end of file diff --git a/test/fixtures/error-print/main.tf b/test/fixtures/error-print/main.tf new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/error-print/terragrunt.hcl b/test/fixtures/error-print/terragrunt.hcl new file mode 100644 index 000000000..e69de29bb diff --git a/test/integration_test.go b/test/integration_test.go index d8464399f..e524e5088 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -103,6 +103,7 @@ const ( testFixtureStack = "fixtures/stack/" testFixtureStdout = "fixtures/download/stdout-test" testFixtureTfTest = "fixtures/tftest/" + testFixtureErrorPrint = "fixtures/error-print" terraformFolder = ".terraform" @@ -3905,3 +3906,16 @@ func TestTerragruntJsonPlanJsonOutput(t *testing.T) { } } + +func TestErrorMessageIncludeInOutput(t *testing.T) { + t.Parallel() + + tmpEnvPath := copyEnvironment(t, testFixtureErrorPrint) + cleanupTerraformFolder(t, tmpEnvPath) + testPath := util.JoinPath(tmpEnvPath, testFixtureErrorPrint) + + _, stderr, err := runTerragruntCommandWithOutput(t, "terragrunt apply --terragrunt-non-interactive --terragrunt-working-dir "+testPath+" --terragrunt-tfpath "+testPath+"/custom-tf-script.sh --terragrunt-log-level debug") + require.Error(t, err) + + assert.Contains(t, stderr, "Custom error from script") +}