Skip to content

Commit 5eba8e1

Browse files
authored
feat: Deprecating terragrunt.hcl as root (#3588)
* feat: Deprecating `terragrunt.hcl` as root * fix: Passing in Logger to catalog * fix: Updating all references to `find_in_parent_config()` to `find_in_parent_config("root")` * fix: Addressing markdownlint errors * fix: Addressing deprecated CircleCI configs. See this: https://circleci.com/docs/configuration-reference/#using-when-in-workflows * fix: Adjusting usage of `find_in_parent_folders("terragrunt.hcl")` to `find_in_parent_folders("root.hcl")` * Revert "fix: Addressing deprecated CircleCI configs." This reverts commit c0ecfb9. * fix: Renaming more root `terragrunt.hcl` files * fix: Test fixes * fix: Addressing some hidden dependencies on root `terragrunt.hcl` * fix: Addressing more hidden root `terragrunt.hcl` usage * fix: Using non-deprecated resource instead of data * fix: Using non-deprecated run-all instead of apply-all * fix: Fixing more tests * fix: Setting root as `root.hcl` in AWS and GCP tests * fix: Removing unneeded config * fix: Adjusting which tests use `root.hcl` and which use `terragrunt.hcl` * fix: Hopefully last config adjustment * fix: Another unit test fix * fix: Adding debounce to strict controls * wip: Working on getting scaffold to allow for non-terragrunt.hcl roots * fix: Explicitly ignoring triggered status during tests * fix: Fixing more tests * fix: Fixing `TestDefaultTemplateVariables` * fix: Fixing more tests * fix: Remove strict mode integration tests from parallel * fix: Fixing manually setting the variable for scaffold * feat: More documentation and testing * fix: Made an adjustment I'm not sure about * feat: Trying things out the way Levko recommended * fix: More like Levko recommended * feat: Adding more docs for the deprecation * fix: Using `errors` instead of `fmt` * fix: Moving shared logic to `cli/commands` * fix: Reduce, Reuse, Recycle * fix: Fixing reference left for `config.RecommendedParentConfigName` * fix: It's easier to add it than to argue
1 parent 0a571bf commit 5eba8e1

File tree

259 files changed

+1011
-414
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

259 files changed

+1011
-414
lines changed

cli/commands/catalog/command.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package catalog
44

55
import (
6+
"github.com/gruntwork-io/terragrunt/cli/commands"
67
"github.com/gruntwork-io/terragrunt/options"
78
"github.com/gruntwork-io/terragrunt/pkg/cli"
89
)
@@ -11,11 +12,19 @@ const (
1112
CommandName = "catalog"
1213
)
1314

15+
func NewFlags(opts *options.TerragruntOptions) cli.Flags {
16+
return cli.Flags{
17+
commands.NewNoIncludeRootFlag(opts),
18+
commands.NewRootFileNameFlag(opts),
19+
}
20+
}
21+
1422
func NewCommand(opts *options.TerragruntOptions) *cli.Command {
1523
return &cli.Command{
1624
Name: CommandName,
1725
DisallowUndefinedFlags: true,
1826
Usage: "Launch the user interface for searching and managing your module catalog.",
27+
Flags: NewFlags(opts),
1928
Action: func(ctx *cli.Context) error {
2029
var repoPath string
2130

cli/commands/flags.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,10 +512,15 @@ func NewGlobalFlags(opts *options.TerragruntOptions) cli.Flags {
512512
Destination: &opts.StrictControls,
513513
Usage: "Enables specific strict controls. For a list of available controls, see https://terragrunt.gruntwork.io/docs/reference/strict-mode .",
514514
Action: func(ctx *cli.Context, val []string) error {
515-
if err := strict.StrictControls.ValidateControlNames(val); err != nil {
515+
warning, err := strict.StrictControls.ValidateControlNames(val)
516+
if err != nil {
516517
return cli.NewExitError(err, 1)
517518
}
518519

520+
if warning != "" {
521+
log.Warn(warning)
522+
}
523+
519524
return nil
520525
},
521526
},
@@ -659,3 +664,70 @@ func NewVersionFlag(opts *options.TerragruntOptions) cli.Flag {
659664
},
660665
}
661666
}
667+
668+
// Scaffold/Catalog shared flags
669+
670+
const (
671+
RecommendedParentConfigName = "root.hcl"
672+
673+
RootFileNameFlagName = "root-file-name"
674+
NoIncludeRootFlagName = "no-include-root"
675+
)
676+
677+
func NewRootFileNameFlag(opts *options.TerragruntOptions) cli.Flag {
678+
return &cli.GenericFlag[string]{
679+
Name: RootFileNameFlagName,
680+
Usage: "Name of the root Terragrunt configuration file, if used.",
681+
Action: func(ctx *cli.Context, value string) error {
682+
// The default behavior of this flag will vary depending on whether the RootTerragruntHCL
683+
// strict control is set.
684+
//
685+
// If it is, the default behavior will be to use the value of the
686+
// RecommendedParentConfigName constant.
687+
//
688+
// If it is not, the default behavior will be to use
689+
// the value of the TerragruntConfigPath option for backwards compatibility.
690+
if value == "" {
691+
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
692+
warn, triggered, err := control.Evaluate(opts)
693+
if err != nil {
694+
opts.ScaffoldRootFileName = RecommendedParentConfigName
695+
} else {
696+
opts.ScaffoldRootFileName = opts.TerragruntConfigPath
697+
}
698+
699+
if !triggered {
700+
opts.Logger.Warnf(warn)
701+
}
702+
}
703+
704+
return nil
705+
}
706+
707+
if value == opts.TerragruntConfigPath {
708+
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
709+
warn, triggered, err := control.Evaluate(opts)
710+
if err != nil {
711+
return err
712+
}
713+
714+
if !triggered {
715+
opts.Logger.Warnf(warn)
716+
}
717+
}
718+
}
719+
720+
opts.ScaffoldRootFileName = value
721+
722+
return nil
723+
},
724+
}
725+
}
726+
727+
func NewNoIncludeRootFlag(opts *options.TerragruntOptions) cli.Flag {
728+
return &cli.BoolFlag{
729+
Name: NoIncludeRootFlagName,
730+
Destination: &opts.ScaffoldNoIncludeRoot,
731+
Usage: "Do not include root unit in scaffolding done by catalog.",
732+
}
733+
}

cli/commands/scaffold/action.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/gruntwork-io/terragrunt/terraform"
1515

16+
"github.com/gruntwork-io/terragrunt/cli/commands"
1617
"github.com/gruntwork-io/terragrunt/cli/commands/hclfmt"
1718
"github.com/gruntwork-io/terragrunt/util"
1819

@@ -39,12 +40,16 @@ const (
3940
moduleURLPattern = `(?:git|hg|s3|gcs)::([^:]+)://([^/]+)(/.*)`
4041
moduleURLParts = 4
4142

43+
// TODO: Make the root configuration file name configurable
4244
DefaultBoilerplateConfig = `
4345
variables:
4446
- name: EnableRootInclude
4547
description: Should include root module
4648
type: bool
4749
default: true
50+
- name: RootFileName
51+
description: Name of the root Terragrunt configuration file
52+
type: string
4853
`
4954
DefaultTerragruntTemplate = `
5055
# This is a Terragrunt module generated by boilerplate.
@@ -95,6 +100,11 @@ inputs = {
95100

96101
var moduleURLRegex = regexp.MustCompile(moduleURLPattern)
97102

103+
const (
104+
enableRootInclude = "EnableRootInclude"
105+
rootFileName = "RootFileName"
106+
)
107+
98108
func Run(ctx context.Context, opts *options.TerragruntOptions, moduleURL, templateURL string) error {
99109
// download remote repo to local
100110
var dirsToClean []string
@@ -166,6 +176,19 @@ func Run(ctx context.Context, opts *options.TerragruntOptions, moduleURL, templa
166176

167177
vars["sourceUrl"] = moduleURL
168178

179+
// Only set these if the `vars` map doesn't already have them set
180+
if _, found := vars[enableRootInclude]; !found {
181+
vars[enableRootInclude] = !opts.ScaffoldNoIncludeRoot
182+
} else {
183+
opts.Logger.Warnf("The %s variable is already set in the var flag(s). The --%s flag will be ignored.", enableRootInclude, commands.NoIncludeRootFlagName)
184+
}
185+
186+
if _, found := vars[rootFileName]; !found {
187+
vars[rootFileName] = opts.ScaffoldRootFileName
188+
} else {
189+
opts.Logger.Warnf("The %s variable is already set in the var flag(s). The --%s flag will be ignored.", rootFileName, commands.NoIncludeRootFlagName)
190+
}
191+
169192
opts.Logger.Infof("Running boilerplate generation to %s", opts.WorkingDir)
170193
boilerplateOpts := &boilerplate_options.BoilerplateOptions{
171194
OutputFolder: opts.WorkingDir,

cli/commands/scaffold/action_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func TestDefaultTemplateVariables(t *testing.T) {
4444
vars["sourceUrl"] = "git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/inputs?ref=v0.53.8"
4545

4646
vars["EnableRootInclude"] = false
47+
vars["RootFileName"] = "root.hcl"
4748

4849
workDir := t.TempDir()
4950
templateDir := util.JoinPath(workDir, "template")

cli/commands/scaffold/command.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package scaffold
33

44
import (
5+
"github.com/gruntwork-io/terragrunt/cli/commands"
56
"github.com/gruntwork-io/terragrunt/options"
67
"github.com/gruntwork-io/terragrunt/pkg/cli"
78
)
@@ -22,8 +23,10 @@ func NewFlags(opts *options.TerragruntOptions) cli.Flags {
2223
&cli.SliceFlag[string]{
2324
Name: VarFile,
2425
Destination: &opts.ScaffoldVarFiles,
25-
Usage: "Files with variables to be used in modules scaffolding.",
26+
Usage: "Files with variables to be used in unit scaffolding.",
2627
},
28+
commands.NewNoIncludeRootFlag(opts),
29+
commands.NewRootFileNameFlag(opts),
2730
}
2831
}
2932

cli/deprecated_commands.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,14 @@ func replaceDeprecatedCommandFunc(terragruntCommandName, terraformCommandName st
5050

5151
control, ok := strict.GetStrictControl(deprecatedCommandName)
5252
if ok {
53-
warning, err := control.Evaluate(opts)
53+
warning, triggered, err := control.Evaluate(opts)
5454
if err != nil {
5555
return err //nolint:wrapcheck
5656
}
5757

58-
opts.Logger.Warn(warning)
58+
if !triggered {
59+
opts.Logger.Warn(warning)
60+
}
5961

6062
} else { //nolint:wsl,whitespace
6163
// This else clause should never be hit, as all the commands above are accounted for.

cli/deprecated_flags.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ func NewDeprecatedFlags(opts *options.TerragruntOptions) cli.Flags {
4747
opts.LogFormatter.SetFormat(format.NewKeyValueFormat())
4848

4949
if control, ok := strict.GetStrictControl(strict.DisableLogFormatting); ok {
50-
warn, err := control.Evaluate(opts)
50+
warn, triggered, err := control.Evaluate(opts)
5151
if err != nil {
5252
return err
5353
}
5454

55-
opts.Logger.Warnf(warn)
55+
if !triggered {
56+
opts.Logger.Warnf(warn)
57+
}
5658
}
5759

5860
return nil
@@ -68,12 +70,14 @@ func NewDeprecatedFlags(opts *options.TerragruntOptions) cli.Flags {
6870
opts.LogFormatter.SetFormat(format.NewJSONFormat())
6971

7072
if control, ok := strict.GetStrictControl(strict.JSONLog); ok {
71-
warn, err := control.Evaluate(opts)
73+
warn, triggered, err := control.Evaluate(opts)
7274
if err != nil {
7375
return err
7476
}
7577

76-
opts.Logger.Warnf(warn)
78+
if !triggered {
79+
opts.Logger.Warnf(warn)
80+
}
7781
}
7882

7983
return nil
@@ -86,12 +90,14 @@ func NewDeprecatedFlags(opts *options.TerragruntOptions) cli.Flags {
8690
Hidden: true,
8791
Action: func(_ *cli.Context, _ bool) error {
8892
if control, ok := strict.GetStrictControl(strict.JSONLog); ok {
89-
warn, err := control.Evaluate(opts)
93+
warn, triggered, err := control.Evaluate(opts)
9094
if err != nil {
9195
return err
9296
}
9397

94-
opts.Logger.Warnf(warn)
98+
if !triggered {
99+
opts.Logger.Warnf(warn)
100+
}
95101
}
96102

97103
return nil

config/catalog.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (cfg *CatalogConfig) normalize(configPath string) {
6767
// config contains `include{...find_in_parent_folders()...}` block to determine if it is the root configuration.
6868
// If it finds `terragrunt.hcl` that already has `include`, then read that configuration as is,
6969
// otherwise generate a stub child `terragrunt.hcl` in memory with an `include` to pull in the one we found.
70-
// Unlike "RoadTerragruntConfig" func, it ignores any configuration errors not related to the "catalog" block.
70+
// Unlike the "ReadTerragruntConfig" func, it ignores any configuration errors not related to the "catalog" block.
7171
func ReadCatalogConfig(parentCtx context.Context, opts *options.TerragruntOptions) (*CatalogConfig, error) {
7272
configPath, configString, err := findCatalogConfig(parentCtx, opts)
7373
if err != nil || configPath == "" {
@@ -91,15 +91,16 @@ func ReadCatalogConfig(parentCtx context.Context, opts *options.TerragruntOption
9191

9292
func findCatalogConfig(ctx context.Context, opts *options.TerragruntOptions) (string, string, error) {
9393
var (
94-
configPath = opts.TerragruntConfigPath
95-
configName = filepath.Base(configPath)
94+
configPath = filepath.Join(filepath.Dir(opts.TerragruntConfigPath), opts.ScaffoldRootFileName)
95+
configName = opts.ScaffoldRootFileName
9696
catalogConfigPath string
9797
)
9898

9999
for {
100100
opts = &options.TerragruntOptions{
101101
TerragruntConfigPath: filepath.Join(filepath.Dir(configPath), util.UniqueID(), configName),
102102
MaxFoldersToCheck: opts.MaxFoldersToCheck,
103+
Logger: opts.Logger,
103104
}
104105

105106
// This allows to stop the process by pressing Ctrl-C, in case the loop is endless,

config/catalog_test.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ func TestCatalogParseConfigFile(t *testing.T) {
5050
nil,
5151
},
5252
{
53-
filepath.Join(basePath, "complex/terragrunt.hcl"),
53+
filepath.Join(basePath, "complex-legacy-root/terragrunt.hcl"),
54+
&config.CatalogConfig{
55+
URLs: []string{
56+
filepath.Join(basePath, "complex-legacy-root/dev/us-west-1/modules/terraform-aws-eks"),
57+
"./terraform-aws-service-catalog",
58+
"https://github.com/gruntwork-io/terraform-aws-utilities",
59+
},
60+
},
61+
nil,
62+
},
63+
{
64+
filepath.Join(basePath, "complex/root.hcl"),
5465
&config.CatalogConfig{
5566
URLs: []string{
5667
filepath.Join(basePath, "complex/dev/us-west-1/modules/terraform-aws-eks"),
@@ -61,7 +72,18 @@ func TestCatalogParseConfigFile(t *testing.T) {
6172
nil,
6273
},
6374
{
64-
filepath.Join(basePath, "complex/dev/terragrunt.hcl"),
75+
filepath.Join(basePath, "complex-legacy-root/dev/terragrunt.hcl"),
76+
&config.CatalogConfig{
77+
URLs: []string{
78+
filepath.Join(basePath, "complex-legacy-root/dev/us-west-1/modules/terraform-aws-eks"),
79+
"./terraform-aws-service-catalog",
80+
"https://github.com/gruntwork-io/terraform-aws-utilities",
81+
},
82+
},
83+
nil,
84+
},
85+
{
86+
filepath.Join(basePath, "complex/dev/root.hcl"),
6587
&config.CatalogConfig{
6688
URLs: []string{
6789
filepath.Join(basePath, "complex/dev/us-west-1/modules/terraform-aws-eks"),
@@ -106,22 +128,22 @@ func TestCatalogParseConfigFile(t *testing.T) {
106128
},
107129
}
108130

109-
for i, testCase := range testCases {
110-
testCase := testCase
111-
131+
for i, tt := range testCases {
112132
t.Run(fmt.Sprintf("testCase-%d", i), func(t *testing.T) {
113133
t.Parallel()
114134

115-
opts, err := options.NewTerragruntOptionsWithConfigPath(testCase.configPath)
135+
opts, err := options.NewTerragruntOptionsWithConfigPath(tt.configPath)
116136
require.NoError(t, err)
117137

138+
opts.ScaffoldRootFileName = filepath.Base(tt.configPath)
139+
118140
config, err := config.ReadCatalogConfig(context.Background(), opts)
119141

120-
if testCase.expectedErr == nil {
142+
if tt.expectedErr == nil {
121143
require.NoError(t, err)
122-
assert.Equal(t, testCase.expectedConfig, config)
144+
assert.Equal(t, tt.expectedConfig, config)
123145
} else {
124-
assert.EqualError(t, err, testCase.expectedErr.Error())
146+
assert.EqualError(t, err, tt.expectedErr.Error())
125147
}
126148
})
127149

config/config_helpers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/gruntwork-io/terragrunt/internal/cache"
2626
"github.com/gruntwork-io/terragrunt/internal/errors"
2727
"github.com/gruntwork-io/terragrunt/internal/locks"
28+
"github.com/gruntwork-io/terragrunt/internal/strict"
2829
"github.com/gruntwork-io/terragrunt/options"
2930
"github.com/gruntwork-io/terragrunt/shell"
3031
"github.com/gruntwork-io/terragrunt/terraform"
@@ -444,6 +445,23 @@ func FindInParentFolders(
444445

445446
previousDir = filepath.ToSlash(previousDir)
446447

448+
if fileToFindParam == "" || fileToFindParam == DefaultTerragruntConfigPath {
449+
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
450+
warn, triggered, err := control.Evaluate(ctx.TerragruntOptions)
451+
if err != nil {
452+
return "", err
453+
}
454+
455+
if !triggered {
456+
ctx.TerragruntOptions.Logger.Warnf(warn)
457+
}
458+
}
459+
}
460+
461+
// The strict control above will make this function return an error when no parameter is passed.
462+
// When this becomes a breaking change, we can remove the strict control and
463+
// do some validation here to ensure that users aren't using "terragrunt.hcl" as the root of their Terragrunt
464+
// configurations.
447465
fileToFindStr := DefaultTerragruntConfigPath
448466
if fileToFindParam != "" {
449467
fileToFindStr = fileToFindParam

0 commit comments

Comments
 (0)