diff --git a/apply/apply.go b/apply/apply.go index 802163435..c7a0dd3d5 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -73,13 +73,6 @@ func Apply(fs afero.Fs, conf *v2.Config, tmpl *templates.T, upgrade bool) error } } - if plan.Atlantis.Enabled { - err = applyTree(fs, tmpl.Atlantis, tmpl.Common, "", plan.Atlantis) - if err != nil { - return errs.WrapUser(err, "unable to apply Atlantis") - } - } - tfBox := tmpl.Components[v2.ComponentKindTerraform] err = applyAccounts(fs, plan, tfBox, tmpl.Common) if err != nil { @@ -91,11 +84,18 @@ func Apply(fs afero.Fs, conf *v2.Config, tmpl *templates.T, upgrade bool) error return errs.WrapUser(err, "unable to apply modules") } - err = applyEnvs(fs, plan, tmpl.Env, tmpl.Components, tmpl.Common) + pathModuleConfigs, err := applyEnvs(fs, plan, tmpl.Env, tmpl.Components, tmpl.Common) if err != nil { return errs.WrapUser(err, "unable to apply envs") } + if plan.Atlantis.Enabled { + err = applyAtlantisConfig(fs, tmpl.Atlantis, tmpl.Common, "", &plan.Atlantis, pathModuleConfigs) + if err != nil { + return errs.WrapUser(err, "unable to apply Atlantis") + } + } + tfBox = tmpl.Components[v2.ComponentKindTerraform] err = applyGlobal(fs, plan.Global, tfBox, tmpl.Common) if err != nil { @@ -270,7 +270,7 @@ func applyTFE(fs afero.Fs, plan *plan.Plan, tmpl *templates.T) error { if err != nil { return errs.WrapUser(err, "unable to make a downloader") } - err = applyModuleInvocation(fs, path, templates.Templates.ModuleInvocation, tmpl.Common, mi, nil) + _, err = applyModuleInvocation(fs, path, templates.Templates.ModuleInvocation, tmpl.Common, mi, nil) if err != nil { return errs.WrapUser(err, "unable to apply module invocation") } @@ -361,30 +361,33 @@ func applyModules(fs afero.Fs, p map[string]plan.Module, moduleBox, commonBox fs return nil } +type PathModuleConfigs map[string]ModuleConfigMap + func applyEnvs( fs afero.Fs, p *plan.Plan, envBox fs.FS, componentBoxes map[v2.ComponentKind]fs.FS, - commonBox fs.FS) (err error) { + commonBox fs.FS) (pathModuleConfigs PathModuleConfigs, err error) { logrus.Debug("applying envs") + pathModuleConfigs = make(PathModuleConfigs) for env, envPlan := range p.Envs { logrus.Debugf("applying %s", env) path := fmt.Sprintf("%s/envs/%s", rootPath, env) err = fs.MkdirAll(path, 0755) if err != nil { - return errs.WrapUserf(err, "unable to make directory %s", path) + return nil, errs.WrapUserf(err, "unable to make directory %s", path) } err := applyTree(fs, envBox, commonBox, path, envPlan) if err != nil { - return errs.WrapUser(err, "unable to apply templates to env") + return nil, errs.WrapUser(err, "unable to apply templates to env") } reg := registry.NewClient(nil, nil) for component, componentPlan := range envPlan.Components { path = fmt.Sprintf("%s/envs/%s/%s", rootPath, env, component) err = fs.MkdirAll(path, 0755) if err != nil { - return errs.WrapUser(err, "unable to make directories for component") + return nil, errs.WrapUser(err, "unable to make directories for component") } // NOTE(el): component kind only support TF now @@ -392,19 +395,19 @@ func applyEnvs( kind := componentPlan.Kind.GetOrDefault() componentBox, ok := componentBoxes[kind] if !ok { - return errs.NewUserf("component of kind '%s' not suppoerted, must be 'terraform'", kind) + return nil, errs.NewUserf("component of kind '%s' not supported, must be 'terraform'", kind) } err := applyTree(fs, componentBox, commonBox, path, componentPlan) if err != nil { - return errs.WrapUser(err, "unable to apply templates for component") + return nil, errs.WrapUser(err, "unable to apply templates for component") } mi := make([]moduleInvocation, 0) if componentPlan.ModuleSource != nil { downloader, err := util.MakeDownloader(*componentPlan.ModuleSource, "", reg) if err != nil { - return errs.WrapUser(err, "unable to make a downloader") + return nil, errs.WrapUser(err, "unable to make a downloader") } mi = append(mi, moduleInvocation{ module: v2.ComponentModule{ @@ -428,22 +431,121 @@ func applyEnvs( } downloader, err := util.MakeDownloader(*m.Source, moduleVersion, reg) if err != nil { - return errs.WrapUser(err, "unable to make a downloader") + return nil, errs.WrapUser(err, "unable to make a downloader") } mi = append(mi, moduleInvocation{ module: m, downloadFunc: downloader, }) } - err = applyModuleInvocation(fs, path, templates.Templates.ModuleInvocation, commonBox, mi, componentPlan.IntegrationRegistry) + pathModuleConfigs[path], err = applyModuleInvocation(fs, path, templates.Templates.ModuleInvocation, commonBox, mi, componentPlan.IntegrationRegistry) if err != nil { - return errs.WrapUser(err, "unable to apply module invocation") + return nil, errs.WrapUser(err, "unable to apply module invocation") } } } + return pathModuleConfigs, nil +} + +func applyAtlantisConfig(base afero.Fs, atlantisFs fs.FS, common fs.FS, targetBasePath string, config *plan.AtlantisConfig, pathModuleConfigs PathModuleConfigs) (e error) { + // add autoplan triggers based on moduleConfigs + for _, project := range config.RepoCfg.Projects { + uniqueModuleSources := []string{} + for moduleSource, module := range pathModuleConfigs[*project.Dir] { + if _, err := base.Stat(moduleSource); err != nil { + continue + } + logrus.Debugf(" >> project %s depends on local %s", *project.Name, moduleSource) + + if !slices.Contains(uniqueModuleSources, moduleSource) { + uniqueModuleSources = append(uniqueModuleSources, moduleSource) + } + for _, call := range module.ModuleCalls { + fullPath := filepath.Join(moduleSource, call.Source) + if _, err := base.Stat(fullPath); err != nil { + logrus.Debugf(" %s sources remote %s", moduleSource, call.Source) + continue + } + logrus.Debugf(" %s sources local %s", moduleSource, call.Source) + childModuleSource := filepath.Clean(fullPath) + if !slices.Contains(uniqueModuleSources, childModuleSource) { + uniqueModuleSources = append(uniqueModuleSources, childModuleSource) + var err error + uniqueModuleSources, err = loadAndRecurseModule(base, childModuleSource, uniqueModuleSources) + if err != nil { + return errs.WrapUser(err, "unable to recurse modules") + } + } + } + } + // take whenModified from plan + whenModified := project.Autoplan.WhenModified + // add uniqueModuleSources to whenModified array + for _, moduleSource := range uniqueModuleSources { + moduleAddressForSource, _ := calculateModuleAddressForSource(*project.Dir, moduleSource, "") + whenModified = append(whenModified, + fmt.Sprintf( + "%s/**/*.tf", + moduleAddressForSource, + ), fmt.Sprintf( + "%s/**/*.tf.json", + moduleAddressForSource, + ), + ) + } + project.Autoplan.WhenModified = whenModified + } + err := applyTree(base, atlantisFs, common, targetBasePath, config) + if err != nil { + return errs.WrapUser(err, "unable to apply Atlantis") + } return nil } +func loadAndRecurseModule(base afero.Fs, source string, uniqueModuleSources []string) ([]string, error) { + logrus.Debugf(" recurse: loading %s", source) + // tfconfig.LoadModule fails in tests only :( + module, err := DownloadAndParseLocalModule(base, source) + if err != nil { + return uniqueModuleSources, errs.WrapUser(err, "Failed to load module from source") + } + for _, call := range module.ModuleCalls { + fullPath := filepath.Join(source, call.Source) + if _, err := base.Stat(fullPath); err != nil { + logrus.Debugf(" %s sources remote %s", source, call.Source) + continue + } + logrus.Debugf(" %s sources local %s", source, call.Source) + childModuleSource := filepath.Clean(fullPath) + if !slices.Contains(uniqueModuleSources, childModuleSource) { + uniqueModuleSources = append(uniqueModuleSources, childModuleSource) + return loadAndRecurseModule(base, childModuleSource, uniqueModuleSources) + } + } + return uniqueModuleSources, nil +} + +// HACK HACK HACK copy of util/module_storage.downloader +// +// this function is needed to ensure a copy of the local module exists in the testdataFS +func DownloadAndParseLocalModule(fs afero.Fs, source string) (*tfconfig.Module, error) { + dir, err := util.GetFoggCachePath() + if err != nil { + return nil, err + } + // registry client can be nil for local modules + d, err := util.DownloadModule(fs, dir, source, "", nil) + if err != nil { + return nil, errs.WrapUser(err, "unable to download module") + } + // ensures module source is loaded from testdataFS, else tests fail :( + module, diag := tfconfig.LoadModule(d) + if diag.HasErrors() { + return nil, errs.WrapInternal(diag.Err(), "There was an issue loading the module") + } + return module, nil +} + func applyTree(dest afero.Fs, source fs.FS, common fs.FS, targetBasePath string, subst interface{}) (e error) { return fs.WalkDir(source, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -631,6 +733,8 @@ type moduleInvocation struct { downloadFunc util.ModuleDownloader } +type ModuleConfigMap map[string]*tfconfig.Module + func applyModuleInvocation( fs afero.Fs, path string, @@ -638,17 +742,19 @@ func applyModuleInvocation( commonBox fs.FS, moduleInvocations []moduleInvocation, integrationRegistry *string, -) error { +) (ModuleConfigMap, error) { + moduleConfigs := make(ModuleConfigMap, len(moduleInvocations)) e := fs.MkdirAll(path, 0755) if e != nil { - return errs.WrapUserf(e, "couldn't create %s directory", path) + return nil, errs.WrapUserf(e, "couldn't create %s directory", path) } arr := make([]*moduleData, 0) // TODO: parallel downloads with go routines for _, mi := range moduleInvocations { moduleConfig, e := mi.downloadFunc.DownloadAndParseModule(fs) + moduleConfigs[*mi.module.Source] = moduleConfig if e != nil { - return errs.WrapUser(e, "could not download or parse module") + return nil, errs.WrapUser(e, "could not download or parse module") } // This should really be part of the plan stage, not apply. But going to @@ -728,7 +834,7 @@ func applyModuleInvocation( if mi.module.Version != nil { moduleVersion = *mi.module.Version } - moduleAddressForSource, moduleVersion, _ := calculateModuleAddressForSource(path, *mi.module.Source, moduleVersion) + moduleAddressForSource, _ := calculateModuleAddressForSource(path, *mi.module.Source, moduleVersion) arr = append(arr, &moduleData{ ModuleName: moduleName, ModuleSource: moduleAddressForSource, @@ -746,7 +852,7 @@ func applyModuleInvocation( // MAIN f, e := box.Open("main.tf.tmpl") if e != nil { - return errs.WrapUser(e, "could not open template file") + return nil, errs.WrapUser(e, "could not open template file") } e = applyTemplate( f, @@ -755,48 +861,48 @@ func applyModuleInvocation( filepath.Join(path, "main.tf"), &modulesData{arr}) if e != nil { - return errs.WrapUser(e, "unable to apply template for main.tf") + return nil, errs.WrapUser(e, "unable to apply template for main.tf") } e = fmtHcl(fs, filepath.Join(path, "main.tf"), false) if e != nil { - return errs.WrapUser(e, "unable to format main.tf") + return nil, errs.WrapUser(e, "unable to format main.tf") } // OUTPUTS f, e = box.Open("outputs.tf.tmpl") if e != nil { - return errs.WrapUser(e, "could not open template file") + return nil, errs.WrapUser(e, "could not open template file") } e = applyTemplate(f, commonBox, fs, filepath.Join(path, "outputs.tf"), &modulesData{arr}) if e != nil { - return errs.WrapUser(e, "unable to apply template for outputs.tf") + return nil, errs.WrapUser(e, "unable to apply template for outputs.tf") } e = fmtHcl(fs, filepath.Join(path, "outputs.tf"), false) if e != nil { - return errs.WrapUser(e, "unable to format outputs.tf") + return nil, errs.WrapUser(e, "unable to format outputs.tf") } if integrationRegistry != nil && *integrationRegistry == "ssm" { // Integration Registry Entries - ssm parameter store f, e = box.Open("ssm-parameter-store.tf.tmpl") if e != nil { - return errs.WrapUser(e, "could not open template file") + return nil, errs.WrapUser(e, "could not open template file") } e = applyTemplate(f, commonBox, fs, filepath.Join(path, "ssm-parameter-store.tf"), &modulesData{arr}) if e != nil { - return errs.WrapUser(e, "unable to apply template for ssm-parameter-store.tf") + return nil, errs.WrapUser(e, "unable to apply template for ssm-parameter-store.tf") } e = fmtHcl(fs, filepath.Join(path, "ssm-parameter-store.tf"), false) if e != nil { - return errs.WrapUser(e, "unable to format ssm-parameter-store.tf") + return nil, errs.WrapUser(e, "unable to format ssm-parameter-store.tf") } } - return nil + return moduleConfigs, nil } // Evaluate integrationRegistry configuration and return updated list of integrationRegistryEntries @@ -876,9 +982,12 @@ func integrateOutput( return integrationRegistryEntries } -func calculateModuleAddressForSource(path, moduleAddress string, moduleVersion string) (string, string, error) { +// Convert moduleAddress relative to path. +// +// moduleVersion is only needed to test if moduleAddress is a Registry Source Address +func calculateModuleAddressForSource(path, moduleAddress string, moduleVersion string) (string, error) { if moduleVersion != "" && util.IsRegistrySourceAddr(moduleAddress) { - return moduleAddress, moduleVersion, nil + return moduleAddress, nil } else { // For cases where the module is a local path, we need to calculate the // relative path from the component to the module. @@ -888,7 +997,7 @@ func calculateModuleAddressForSource(path, moduleAddress string, moduleVersion s // wrong for local file paths, so we need to calculate that ourselves below s, e := getter.Detect(moduleAddress, path, getter.Detectors) if e != nil { - return "", "", e + return "", e } u, e := url.Parse(s) if e != nil || u.Scheme == "file" { @@ -896,12 +1005,12 @@ func calculateModuleAddressForSource(path, moduleAddress string, moduleVersion s // It is possible that this test is unreliable. moduleAddressForSource, e = filepath.Rel(path, moduleAddress) if e != nil { - return "", "", e + return "", e } } else { moduleAddressForSource = moduleAddress } - return moduleAddressForSource, "", nil + return moduleAddressForSource, nil } } diff --git a/apply/apply_test.go b/apply/apply_test.go index 2c8b69d6a..dbaab5e07 100644 --- a/apply/apply_test.go +++ b/apply/apply_test.go @@ -417,7 +417,7 @@ func TestApplyModuleInvocation(t *testing.T) { downloadFunc: downloader, }, } - e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) + _, e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) r.NoError(e) s, e := fs.Stat("mymodule") @@ -460,7 +460,7 @@ func TestApplyModuleInvocationWithEmptyVariables(t *testing.T) { downloadFunc: downloader, }, } - e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) + _, e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) r.NoError(e) s, e := fs.Stat("mymodule") @@ -503,7 +503,7 @@ func TestApplyModuleInvocationWithOneDefaultVariable(t *testing.T) { downloadFunc: downloader, }, } - e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) + _, e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) r.NoError(e) s, e := fs.Stat("mymodule") @@ -549,7 +549,7 @@ func TestApplyModuleInvocationWithModuleName(t *testing.T) { downloadFunc: downloader, }, } - e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) + _, e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) r.NoError(e) s, e := fs.Stat("mymodule") @@ -597,7 +597,7 @@ func TestApplyModuleInvocationWithModulePrefix(t *testing.T) { downloadFunc: downloader, }, } - e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) + _, e := applyModuleInvocation(fs, "mymodule", templates.Templates.ModuleInvocation, templates.Templates.Common, mi, nil) r.NoError(e) s, e := fs.Stat("mymodule") @@ -690,7 +690,7 @@ func TestCalculateLocalPath(t *testing.T) { t.Run("", func(t *testing.T) { r := require.New(t) - p, _, e := calculateModuleAddressForSource(tt.path, tt.moduleAddress, "") + p, e := calculateModuleAddressForSource(tt.path, tt.moduleAddress, "") r.Nil(e) r.Equal(tt.expected, p) }) diff --git a/config/v2/config.go b/config/v2/config.go index 6f1f57711..7732b4a05 100644 --- a/config/v2/config.go +++ b/config/v2/config.go @@ -212,6 +212,8 @@ type Atlantis struct { Enabled *bool `yaml:"enabled,omitempty"` // list of module source prefixes for auto plan when modified // default: "terraform/modules/" + // + // Deprecated: autoplan now auto detects if module is local ModulePrefixes []string `yaml:"module_prefixes,omitempty"` // autoplan remote-states (only if depends_on is provided) // default: false diff --git a/plan/ci.go b/plan/ci.go index 9532b8a3e..a376f57f8 100644 --- a/plan/ci.go +++ b/plan/ci.go @@ -4,12 +4,10 @@ import ( "fmt" "slices" "sort" - "strings" v2 "github.com/chanzuckerberg/fogg/config/v2" "github.com/chanzuckerberg/fogg/util" atlantis "github.com/runatlantis/atlantis/server/core/config/raw" - "github.com/sirupsen/logrus" ) type CIProject struct { @@ -381,19 +379,15 @@ func (p *Plan) buildGitHubActionsConfig(c *v2.Config, foggVersion string) GitHub } // buildAtlantisConfig must be build after Envs -func (p *Plan) buildAtlantisConfig(c *v2.Config, foggVersion string) AtlantisConfig { +func (p *Plan) buildAtlantisConfig(c *v2.Config) AtlantisConfig { enabled := false autoplanRemoteStates := false repoCfg := atlantis.RepoCfg{} if c.Defaults.Tools != nil && c.Defaults.Tools.Atlantis != nil { enabled = *c.Defaults.Tools.Atlantis.Enabled - modulePrefixes := c.Defaults.Tools.Atlantis.ModulePrefixes if c.Defaults.Tools.Atlantis.AutoplanRemoteStates != nil { autoplanRemoteStates = *c.Defaults.Tools.Atlantis.AutoplanRemoteStates } - if len(modulePrefixes) == 0 { - modulePrefixes = append(modulePrefixes, "terraform/modules/") - } repoCfg = c.Defaults.Tools.Atlantis.RepoCfg projects := []atlantis.Project{} for envName, env := range p.Envs { @@ -407,11 +401,15 @@ func (p *Plan) buildAtlantisConfig(c *v2.Config, foggVersion string) AtlantisCon uniqueModuleSources = append(uniqueModuleSources, *m.Source) } } - autoplanRemoteState := autoplanRemoteStates && d.HasDependsOn - whenModified := generateWhenModified(uniqueModuleSources, d.PathToRepoRoot, modulePrefixes, autoplanRemoteState) + whenModified := []string{} if d.AutoplanRelativeGlobs != nil { whenModified = append(whenModified, d.AutoplanRelativeGlobs...) } + // if global autoplan remote states is disabled or + // the component has no dependencies defined, explicitly ignore `remote-states.tf` + if !autoplanRemoteStates || !d.HasDependsOn { + whenModified = append(whenModified, "!remote-states.tf") + } projects = append(projects, atlantis.Project{ Name: util.Ptr(fmt.Sprintf("%s_%s", envName, cName)), Dir: util.Ptr(fmt.Sprintf("terraform/envs/%s/%s", envName, cName)), @@ -419,7 +417,8 @@ func (p *Plan) buildAtlantisConfig(c *v2.Config, foggVersion string) AtlantisCon Workspace: util.Ptr(atlantis.DefaultWorkspace), ApplyRequirements: []string{atlantis.ApprovedRequirement}, Autoplan: &atlantis.Autoplan{ - Enabled: util.Ptr(true), + Enabled: util.Ptr(true), + // Additional whenModified entries are added during module inspection in apply phase WhenModified: whenModified, }, }) @@ -439,42 +438,6 @@ func (p *Plan) buildAtlantisConfig(c *v2.Config, foggVersion string) AtlantisCon } } -func generateWhenModified(moduleSources []string, pathToRepoRoot string, modulePrefixes []string, autoplanRemoteState bool) []string { - whenModified := []string{ - "*.tf", - } - if !autoplanRemoteState { - whenModified = append(whenModified, "!remote-states.tf") - } - for _, moduleSource := range moduleSources { - if startsWithPrefix(moduleSource, modulePrefixes) { - modulePath := pathToRepoRoot + moduleSource - whenModified = append(whenModified, - fmt.Sprintf( - "%s/**/*.tf", - modulePath, - ), fmt.Sprintf( - "%s/**/*.tf.json", - modulePath, - ), - ) - } else { - logrus.Debugf("atlantis: moduleSource %q is not part of module_prefix list: %q", moduleSource, modulePrefixes) - } - } - return whenModified -} - -// startsWithPrefix checks if the given string s starts with any of the prefixes in the array. -func startsWithPrefix(s string, prefixes []string) bool { - for _, prefix := range prefixes { - if strings.HasPrefix(s, prefix) { - return true - } - } - return false -} - func (p *Plan) buildGithubActionsPreCommitConfig(c *v2.Config, foggVersion string) PreCommitConfig { // defaults enabled := false diff --git a/plan/plan.go b/plan/plan.go index 216d25277..76b9effdc 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -399,7 +399,7 @@ func Eval(c *v2.Config) (*Plan, error) { p.TravisCI = p.buildTravisCIConfig(c, v) p.CircleCI = p.buildCircleCIConfig(c, v) p.GitHubActionsCI = p.buildGitHubActionsConfig(c, v) - p.Atlantis = p.buildAtlantisConfig(c, v) + p.Atlantis = p.buildAtlantisConfig(c) p.TFE, err = p.buildTFE(c) if err != nil { return p, err diff --git a/testdata/v2_atlantis_depends_on/atlantis.yaml b/testdata/v2_atlantis_depends_on/atlantis.yaml index c5f41eddb..59a09c303 100644 --- a/testdata/v2_atlantis_depends_on/atlantis.yaml +++ b/testdata/v2_atlantis_depends_on/atlantis.yaml @@ -9,10 +9,27 @@ projects: terraform_version: 0.100.0 autoplan: when_modified: - - '*.tf' - - ../../../../terraform/modules/my_module/**/*.tf - - ../../../../terraform/modules/my_module/**/*.tf.json - ../../foo.yaml + - ../../../modules/my_module/**/*.tf + - ../../../modules/my_module/**/*.tf.json + enabled: true + apply_requirements: + - approved + - name: test_recurse_test + dir: terraform/envs/test/recurse_test + workspace: default + terraform_version: 0.100.0 + autoplan: + when_modified: + - '!remote-states.tf' + - ../../../../foo_modules/parent_module/**/*.tf + - ../../../../foo_modules/parent_module/**/*.tf.json + - ../../../../foo_modules/nested_module1/**/*.tf + - ../../../../foo_modules/nested_module1/**/*.tf.json + - ../../../../foo_modules/nested_module2/**/*.tf + - ../../../../foo_modules/nested_module2/**/*.tf.json + - ../../../../foo_modules/nested_module3/**/*.tf + - ../../../../foo_modules/nested_module3/**/*.tf.json enabled: true apply_requirements: - approved @@ -22,10 +39,9 @@ projects: terraform_version: 0.100.0 autoplan: when_modified: - - '*.tf' - '!remote-states.tf' - - ../../../../terraform/modules/my_module/**/*.tf - - ../../../../terraform/modules/my_module/**/*.tf.json + - ../../../modules/my_module/**/*.tf + - ../../../modules/my_module/**/*.tf.json - ../../../../foo_modules/bar/**/*.tf - ../../../../foo_modules/bar/**/*.tf.json enabled: true diff --git a/testdata/v2_atlantis_depends_on/fogg.yml b/testdata/v2_atlantis_depends_on/fogg.yml index e0d405f91..9601a4de8 100644 --- a/testdata/v2_atlantis_depends_on/fogg.yml +++ b/testdata/v2_atlantis_depends_on/fogg.yml @@ -13,9 +13,6 @@ defaults: enabled: true # if depends_on is defined, don't ignore `remote-state.tf` autoplan_remote_states: true - module_prefixes: - - terraform/modules/ - - foo_modules/ version: 3 automerge: true parallel_plan: true @@ -46,12 +43,14 @@ envs: - banana - source: "terraform/modules/my_module" name: "my_module" - depends_on: + depends_on: - "vpc" - source: "foo_modules/bar" depends_on: - "vpc" - "my_module" + recurse_test: + module_source: "foo_modules/parent_module" db: depends_on: components: diff --git a/testdata/v2_atlantis_depends_on/foo_modules/nested_module1/main.tf b/testdata/v2_atlantis_depends_on/foo_modules/nested_module1/main.tf new file mode 100644 index 000000000..38f9a85d0 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/foo_modules/nested_module1/main.tf @@ -0,0 +1,3 @@ +module "nested_module" { + source = "../nested_module2" +} diff --git a/testdata/v2_atlantis_depends_on/foo_modules/nested_module2/main.tf b/testdata/v2_atlantis_depends_on/foo_modules/nested_module2/main.tf new file mode 100644 index 000000000..35ca28938 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/foo_modules/nested_module2/main.tf @@ -0,0 +1,3 @@ +module "nested_module" { + source = "../nested_module3" +} diff --git a/testdata/v2_atlantis_depends_on/foo_modules/nested_module3/main.tf b/testdata/v2_atlantis_depends_on/foo_modules/nested_module3/main.tf new file mode 100644 index 000000000..dc41541c2 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/foo_modules/nested_module3/main.tf @@ -0,0 +1,5 @@ +provider "null" { + +} +resource "null_resource" "nested_resource" { +} diff --git a/testdata/v2_atlantis_depends_on/foo_modules/parent_module/recurse_test.tf b/testdata/v2_atlantis_depends_on/foo_modules/parent_module/recurse_test.tf new file mode 100644 index 000000000..3704257d6 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/foo_modules/parent_module/recurse_test.tf @@ -0,0 +1,3 @@ +module "nested_module" { + source = "../nested_module1" +} diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/Makefile b/testdata/v2_atlantis_depends_on/terraform/envs/test/Makefile index c38c3a2eb..e1bfc17dc 100644 --- a/testdata/v2_atlantis_depends_on/terraform/envs/test/Makefile +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/Makefile @@ -1,7 +1,7 @@ # Auto-generated by fogg. Do not edit # Make improvements in fogg, so that everyone can benefit. -COMPONENTS=db vpc +COMPONENTS=db recurse_test vpc all: .PHONY: all diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/Makefile b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/Makefile new file mode 100644 index 000000000..79de617b8 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/Makefile @@ -0,0 +1,24 @@ +# Auto-generated by fogg. Do not edit +# Make improvements in fogg, so that everyone can benefit. + + +export TERRAFORM_VERSION := 0.100.0 +export TFLINT_ENABLED := 0 +export TF_PLUGIN_CACHE_DIR := ../../../..//.terraform.d/plugin-cache +export TF_BACKEND_KIND := s3 + + +export AWS_BACKEND_ROLE_ARN := arn:aws:iam::12345:role/role + +export AWS_PROVIDER_PROFILE := profile + + + + +include ../../../..//scripts/component.mk + + +help: ## display help for this makefile + @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +.PHONY: help + diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/README.md b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/fogg.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/fogg.tf new file mode 100644 index 000000000..e434f12dd --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/fogg.tf @@ -0,0 +1,117 @@ +# Auto-generated by fogg. Do not edit +# Make improvements in fogg, so that everyone can benefit. +provider "aws" { + + region = "us-west-2" + profile = "profile" + + allowed_account_ids = ["00456"] +} +# Aliased Providers (for doing things in every region). + + +provider "assert" {} +terraform { + required_version = "=0.100.0" + + backend "s3" { + + bucket = "buck" + + key = "terraform/proj/envs/test/components/recurse_test.tfstate" + encrypt = true + region = "us-west-2" + + assume_role = { + role_arn = "arn:aws:iam::12345:role/role" + } + + } + required_providers { + archive = { + source = "hashicorp/archive" + version = "~> 2.0" + } + assert = { + source = "bwoznicki/assert" + version = "0.0.1" + } + aws = { + source = "hashicorp/aws" + version = "0.12.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + okta-head = { + source = "okta/okta" + version = "~> 3.30" + } + random = { + source = "hashicorp/random" + version = "~> 3.4" + } + tls = { + source = "hashicorp/tls" + version = "~> 3.0" + } + } +} +# tflint-ignore: terraform_unused_declarations +variable "env" { + type = string + default = "test" +} +# tflint-ignore: terraform_unused_declarations +variable "project" { + type = string + default = "proj" +} +# tflint-ignore: terraform_unused_declarations +variable "region" { + type = string + default = "us-west-2" +} +# tflint-ignore: terraform_unused_declarations +variable "component" { + type = string + default = "recurse_test" +} +variable "aws_profile" { + type = string + default = "profile" +} +# tflint-ignore: terraform_unused_declarations +variable "owner" { + type = string + default = "foo@example.com" +} +# tflint-ignore: terraform_unused_declarations +variable "tags" { + type = object({ project : string, env : string, service : string, owner : string, managedBy : string, tfstateKey : string }) + default = { + project = "proj" + env = "test" + service = "recurse_test" + owner = "foo@example.com" + tfstateKey = "terraform/proj/envs/test/components/recurse_test.tfstate" + + managedBy = "terraform" + } +} +variable "foo" { + type = string + default = "bar1" +} +# tflint-ignore: terraform_unused_declarations +variable "aws_accounts" { + type = map(string) + default = { + + } +} diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/main.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/main.tf new file mode 100644 index 000000000..0b93e43b4 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/main.tf @@ -0,0 +1,6 @@ +# Auto-generated by fogg. Do not edit +# Make improvements in fogg, so that everyone can benefit. + +module "parent_module" { + source = "../../../../foo_modules/parent_module" +} diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/outputs.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/outputs.tf new file mode 100644 index 000000000..ef7b05491 --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/outputs.tf @@ -0,0 +1,4 @@ +# Auto-generated by fogg. Do not edit +# Make improvements in fogg, so that everyone can benefit. + +# module "parent_module" outputs diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/remote-states.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/remote-states.tf new file mode 100644 index 000000000..796301ffa --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/remote-states.tf @@ -0,0 +1,53 @@ +# Auto-generated by fogg. Do not edit +# Make improvements in fogg, so that everyone can benefit. +# tflint-ignore: terraform_unused_declarations +data "terraform_remote_state" "global" { + backend = "s3" + config = { + + + bucket = "buck" + + key = "terraform/proj/global.tfstate" + region = "us-west-2" + + assume_role = { + role_arn = "arn:aws:iam::12345:role/role" + } + + } +} +# tflint-ignore: terraform_unused_declarations +data "terraform_remote_state" "db" { + backend = "s3" + config = { + + + bucket = "buck" + + key = "terraform/proj/envs/test/components/db.tfstate" + region = "us-west-2" + + assume_role = { + role_arn = "arn:aws:iam::12345:role/role" + } + + } +} +# tflint-ignore: terraform_unused_declarations +data "terraform_remote_state" "vpc" { + backend = "s3" + config = { + + + bucket = "buck" + + key = "terraform/proj/envs/test/components/vpc.tfstate" + region = "us-west-2" + + assume_role = { + role_arn = "arn:aws:iam::12345:role/role" + } + + } +} diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/terraform.d b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/terraform.d new file mode 120000 index 000000000..b482d75bb --- /dev/null +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/terraform.d @@ -0,0 +1 @@ +../../../../terraform.d \ No newline at end of file diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/variables.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/recurse_test/variables.tf new file mode 100644 index 000000000..e69de29bb diff --git a/testdata/v2_atlantis_depends_on/terraform/envs/test/vpc/remote-states.tf b/testdata/v2_atlantis_depends_on/terraform/envs/test/vpc/remote-states.tf index ffb8f0d61..ea84d9334 100644 --- a/testdata/v2_atlantis_depends_on/terraform/envs/test/vpc/remote-states.tf +++ b/testdata/v2_atlantis_depends_on/terraform/envs/test/vpc/remote-states.tf @@ -34,3 +34,20 @@ data "terraform_remote_state" "db" { } } +# tflint-ignore: terraform_unused_declarations +data "terraform_remote_state" "recurse_test" { + backend = "s3" + config = { + + + bucket = "buck" + + key = "terraform/proj/envs/test/components/recurse_test.tfstate" + region = "us-west-2" + + assume_role = { + role_arn = "arn:aws:iam::12345:role/role" + } + + } +} diff --git a/testdata/v2_tf_registry_module_atlantis/atlantis.yaml b/testdata/v2_tf_registry_module_atlantis/atlantis.yaml index 9daa7dbbb..e722a80ae 100644 --- a/testdata/v2_tf_registry_module_atlantis/atlantis.yaml +++ b/testdata/v2_tf_registry_module_atlantis/atlantis.yaml @@ -9,10 +9,9 @@ projects: terraform_version: 0.100.0 autoplan: when_modified: - - '*.tf' - '!remote-states.tf' - - ../../../../terraform/modules/my_module/**/*.tf - - ../../../../terraform/modules/my_module/**/*.tf.json + - ../../../modules/my_module/**/*.tf + - ../../../modules/my_module/**/*.tf.json - ../../../../foo_modules/bar/**/*.tf - ../../../../foo_modules/bar/**/*.tf.json enabled: true diff --git a/testdata/v2_tf_registry_module_atlantis_dup_module/atlantis.yaml b/testdata/v2_tf_registry_module_atlantis_dup_module/atlantis.yaml index 1d377e8a9..1a11ffad0 100644 --- a/testdata/v2_tf_registry_module_atlantis_dup_module/atlantis.yaml +++ b/testdata/v2_tf_registry_module_atlantis_dup_module/atlantis.yaml @@ -9,10 +9,9 @@ projects: terraform_version: 0.100.0 autoplan: when_modified: - - '*.tf' - '!remote-states.tf' - - ../../../../terraform/modules/my_module/**/*.tf - - ../../../../terraform/modules/my_module/**/*.tf.json + - ../../../modules/my_module/**/*.tf + - ../../../modules/my_module/**/*.tf.json enabled: true apply_requirements: - approved