Skip to content

Commit

Permalink
feat: Deprecating terragrunt.hcl as root (#3588)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yhakbar authored Dec 18, 2024
1 parent 0a571bf commit 5eba8e1
Show file tree
Hide file tree
Showing 259 changed files with 1,011 additions and 414 deletions.
9 changes: 9 additions & 0 deletions cli/commands/catalog/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package catalog

import (
"github.com/gruntwork-io/terragrunt/cli/commands"
"github.com/gruntwork-io/terragrunt/options"
"github.com/gruntwork-io/terragrunt/pkg/cli"
)
Expand All @@ -11,11 +12,19 @@ const (
CommandName = "catalog"
)

func NewFlags(opts *options.TerragruntOptions) cli.Flags {
return cli.Flags{
commands.NewNoIncludeRootFlag(opts),
commands.NewRootFileNameFlag(opts),
}
}

func NewCommand(opts *options.TerragruntOptions) *cli.Command {
return &cli.Command{
Name: CommandName,
DisallowUndefinedFlags: true,
Usage: "Launch the user interface for searching and managing your module catalog.",
Flags: NewFlags(opts),
Action: func(ctx *cli.Context) error {
var repoPath string

Expand Down
74 changes: 73 additions & 1 deletion cli/commands/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,15 @@ func NewGlobalFlags(opts *options.TerragruntOptions) cli.Flags {
Destination: &opts.StrictControls,
Usage: "Enables specific strict controls. For a list of available controls, see https://terragrunt.gruntwork.io/docs/reference/strict-mode .",
Action: func(ctx *cli.Context, val []string) error {
if err := strict.StrictControls.ValidateControlNames(val); err != nil {
warning, err := strict.StrictControls.ValidateControlNames(val)
if err != nil {
return cli.NewExitError(err, 1)
}

if warning != "" {
log.Warn(warning)
}

return nil
},
},
Expand Down Expand Up @@ -659,3 +664,70 @@ func NewVersionFlag(opts *options.TerragruntOptions) cli.Flag {
},
}
}

// Scaffold/Catalog shared flags

const (
RecommendedParentConfigName = "root.hcl"

RootFileNameFlagName = "root-file-name"
NoIncludeRootFlagName = "no-include-root"
)

func NewRootFileNameFlag(opts *options.TerragruntOptions) cli.Flag {
return &cli.GenericFlag[string]{
Name: RootFileNameFlagName,
Usage: "Name of the root Terragrunt configuration file, if used.",
Action: func(ctx *cli.Context, value string) error {
// The default behavior of this flag will vary depending on whether the RootTerragruntHCL
// strict control is set.
//
// If it is, the default behavior will be to use the value of the
// RecommendedParentConfigName constant.
//
// If it is not, the default behavior will be to use
// the value of the TerragruntConfigPath option for backwards compatibility.
if value == "" {
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
warn, triggered, err := control.Evaluate(opts)
if err != nil {
opts.ScaffoldRootFileName = RecommendedParentConfigName
} else {
opts.ScaffoldRootFileName = opts.TerragruntConfigPath
}

if !triggered {
opts.Logger.Warnf(warn)
}
}

return nil
}

if value == opts.TerragruntConfigPath {
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
warn, triggered, err := control.Evaluate(opts)
if err != nil {
return err
}

if !triggered {
opts.Logger.Warnf(warn)
}
}
}

opts.ScaffoldRootFileName = value

return nil
},
}
}

func NewNoIncludeRootFlag(opts *options.TerragruntOptions) cli.Flag {
return &cli.BoolFlag{
Name: NoIncludeRootFlagName,
Destination: &opts.ScaffoldNoIncludeRoot,
Usage: "Do not include root unit in scaffolding done by catalog.",
}
}
23 changes: 23 additions & 0 deletions cli/commands/scaffold/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

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

"github.com/gruntwork-io/terragrunt/cli/commands"
"github.com/gruntwork-io/terragrunt/cli/commands/hclfmt"
"github.com/gruntwork-io/terragrunt/util"

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

// TODO: Make the root configuration file name configurable
DefaultBoilerplateConfig = `
variables:
- name: EnableRootInclude
description: Should include root module
type: bool
default: true
- name: RootFileName
description: Name of the root Terragrunt configuration file
type: string
`
DefaultTerragruntTemplate = `
# This is a Terragrunt module generated by boilerplate.
Expand Down Expand Up @@ -95,6 +100,11 @@ inputs = {

var moduleURLRegex = regexp.MustCompile(moduleURLPattern)

const (
enableRootInclude = "EnableRootInclude"
rootFileName = "RootFileName"
)

func Run(ctx context.Context, opts *options.TerragruntOptions, moduleURL, templateURL string) error {
// download remote repo to local
var dirsToClean []string
Expand Down Expand Up @@ -166,6 +176,19 @@ func Run(ctx context.Context, opts *options.TerragruntOptions, moduleURL, templa

vars["sourceUrl"] = moduleURL

// Only set these if the `vars` map doesn't already have them set
if _, found := vars[enableRootInclude]; !found {
vars[enableRootInclude] = !opts.ScaffoldNoIncludeRoot
} else {
opts.Logger.Warnf("The %s variable is already set in the var flag(s). The --%s flag will be ignored.", enableRootInclude, commands.NoIncludeRootFlagName)
}

if _, found := vars[rootFileName]; !found {
vars[rootFileName] = opts.ScaffoldRootFileName
} else {
opts.Logger.Warnf("The %s variable is already set in the var flag(s). The --%s flag will be ignored.", rootFileName, commands.NoIncludeRootFlagName)
}

opts.Logger.Infof("Running boilerplate generation to %s", opts.WorkingDir)
boilerplateOpts := &boilerplate_options.BoilerplateOptions{
OutputFolder: opts.WorkingDir,
Expand Down
1 change: 1 addition & 0 deletions cli/commands/scaffold/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestDefaultTemplateVariables(t *testing.T) {
vars["sourceUrl"] = "git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/inputs?ref=v0.53.8"

vars["EnableRootInclude"] = false
vars["RootFileName"] = "root.hcl"

workDir := t.TempDir()
templateDir := util.JoinPath(workDir, "template")
Expand Down
5 changes: 4 additions & 1 deletion cli/commands/scaffold/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package scaffold

import (
"github.com/gruntwork-io/terragrunt/cli/commands"
"github.com/gruntwork-io/terragrunt/options"
"github.com/gruntwork-io/terragrunt/pkg/cli"
)
Expand All @@ -22,8 +23,10 @@ func NewFlags(opts *options.TerragruntOptions) cli.Flags {
&cli.SliceFlag[string]{
Name: VarFile,
Destination: &opts.ScaffoldVarFiles,
Usage: "Files with variables to be used in modules scaffolding.",
Usage: "Files with variables to be used in unit scaffolding.",
},
commands.NewNoIncludeRootFlag(opts),
commands.NewRootFileNameFlag(opts),
}
}

Expand Down
6 changes: 4 additions & 2 deletions cli/deprecated_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ func replaceDeprecatedCommandFunc(terragruntCommandName, terraformCommandName st

control, ok := strict.GetStrictControl(deprecatedCommandName)
if ok {
warning, err := control.Evaluate(opts)
warning, triggered, err := control.Evaluate(opts)
if err != nil {
return err //nolint:wrapcheck
}

opts.Logger.Warn(warning)
if !triggered {
opts.Logger.Warn(warning)
}

} else { //nolint:wsl,whitespace
// This else clause should never be hit, as all the commands above are accounted for.
Expand Down
18 changes: 12 additions & 6 deletions cli/deprecated_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ func NewDeprecatedFlags(opts *options.TerragruntOptions) cli.Flags {
opts.LogFormatter.SetFormat(format.NewKeyValueFormat())

if control, ok := strict.GetStrictControl(strict.DisableLogFormatting); ok {
warn, err := control.Evaluate(opts)
warn, triggered, err := control.Evaluate(opts)
if err != nil {
return err
}

opts.Logger.Warnf(warn)
if !triggered {
opts.Logger.Warnf(warn)
}
}

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

if control, ok := strict.GetStrictControl(strict.JSONLog); ok {
warn, err := control.Evaluate(opts)
warn, triggered, err := control.Evaluate(opts)
if err != nil {
return err
}

opts.Logger.Warnf(warn)
if !triggered {
opts.Logger.Warnf(warn)
}
}

return nil
Expand All @@ -86,12 +90,14 @@ func NewDeprecatedFlags(opts *options.TerragruntOptions) cli.Flags {
Hidden: true,
Action: func(_ *cli.Context, _ bool) error {
if control, ok := strict.GetStrictControl(strict.JSONLog); ok {
warn, err := control.Evaluate(opts)
warn, triggered, err := control.Evaluate(opts)
if err != nil {
return err
}

opts.Logger.Warnf(warn)
if !triggered {
opts.Logger.Warnf(warn)
}
}

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

func findCatalogConfig(ctx context.Context, opts *options.TerragruntOptions) (string, string, error) {
var (
configPath = opts.TerragruntConfigPath
configName = filepath.Base(configPath)
configPath = filepath.Join(filepath.Dir(opts.TerragruntConfigPath), opts.ScaffoldRootFileName)
configName = opts.ScaffoldRootFileName
catalogConfigPath string
)

for {
opts = &options.TerragruntOptions{
TerragruntConfigPath: filepath.Join(filepath.Dir(configPath), util.UniqueID(), configName),
MaxFoldersToCheck: opts.MaxFoldersToCheck,
Logger: opts.Logger,
}

// This allows to stop the process by pressing Ctrl-C, in case the loop is endless,
Expand Down
40 changes: 31 additions & 9 deletions config/catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ func TestCatalogParseConfigFile(t *testing.T) {
nil,
},
{
filepath.Join(basePath, "complex/terragrunt.hcl"),
filepath.Join(basePath, "complex-legacy-root/terragrunt.hcl"),
&config.CatalogConfig{
URLs: []string{
filepath.Join(basePath, "complex-legacy-root/dev/us-west-1/modules/terraform-aws-eks"),
"./terraform-aws-service-catalog",
"https://github.com/gruntwork-io/terraform-aws-utilities",
},
},
nil,
},
{
filepath.Join(basePath, "complex/root.hcl"),
&config.CatalogConfig{
URLs: []string{
filepath.Join(basePath, "complex/dev/us-west-1/modules/terraform-aws-eks"),
Expand All @@ -61,7 +72,18 @@ func TestCatalogParseConfigFile(t *testing.T) {
nil,
},
{
filepath.Join(basePath, "complex/dev/terragrunt.hcl"),
filepath.Join(basePath, "complex-legacy-root/dev/terragrunt.hcl"),
&config.CatalogConfig{
URLs: []string{
filepath.Join(basePath, "complex-legacy-root/dev/us-west-1/modules/terraform-aws-eks"),
"./terraform-aws-service-catalog",
"https://github.com/gruntwork-io/terraform-aws-utilities",
},
},
nil,
},
{
filepath.Join(basePath, "complex/dev/root.hcl"),
&config.CatalogConfig{
URLs: []string{
filepath.Join(basePath, "complex/dev/us-west-1/modules/terraform-aws-eks"),
Expand Down Expand Up @@ -106,22 +128,22 @@ func TestCatalogParseConfigFile(t *testing.T) {
},
}

for i, testCase := range testCases {
testCase := testCase

for i, tt := range testCases {
t.Run(fmt.Sprintf("testCase-%d", i), func(t *testing.T) {
t.Parallel()

opts, err := options.NewTerragruntOptionsWithConfigPath(testCase.configPath)
opts, err := options.NewTerragruntOptionsWithConfigPath(tt.configPath)
require.NoError(t, err)

opts.ScaffoldRootFileName = filepath.Base(tt.configPath)

config, err := config.ReadCatalogConfig(context.Background(), opts)

if testCase.expectedErr == nil {
if tt.expectedErr == nil {
require.NoError(t, err)
assert.Equal(t, testCase.expectedConfig, config)
assert.Equal(t, tt.expectedConfig, config)
} else {
assert.EqualError(t, err, testCase.expectedErr.Error())
assert.EqualError(t, err, tt.expectedErr.Error())
}
})

Expand Down
18 changes: 18 additions & 0 deletions config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/gruntwork-io/terragrunt/internal/cache"
"github.com/gruntwork-io/terragrunt/internal/errors"
"github.com/gruntwork-io/terragrunt/internal/locks"
"github.com/gruntwork-io/terragrunt/internal/strict"
"github.com/gruntwork-io/terragrunt/options"
"github.com/gruntwork-io/terragrunt/shell"
"github.com/gruntwork-io/terragrunt/terraform"
Expand Down Expand Up @@ -444,6 +445,23 @@ func FindInParentFolders(

previousDir = filepath.ToSlash(previousDir)

if fileToFindParam == "" || fileToFindParam == DefaultTerragruntConfigPath {
if control, ok := strict.GetStrictControl(strict.RootTerragruntHCL); ok {
warn, triggered, err := control.Evaluate(ctx.TerragruntOptions)
if err != nil {
return "", err
}

if !triggered {
ctx.TerragruntOptions.Logger.Warnf(warn)
}
}
}

// The strict control above will make this function return an error when no parameter is passed.
// When this becomes a breaking change, we can remove the strict control and
// do some validation here to ensure that users aren't using "terragrunt.hcl" as the root of their Terragrunt
// configurations.
fileToFindStr := DefaultTerragruntConfigPath
if fileToFindParam != "" {
fileToFindStr = fileToFindParam
Expand Down
Loading

0 comments on commit 5eba8e1

Please sign in to comment.