Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add path_overrides autodetect configuration #2853

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/infracost/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (g *generateConfigCommand) run(cmd *cobra.Command, args []string) error {
ctx.Config.Autodetect.ExcludeDirs = partialConfig.Autodetect.ExcludeDirs
ctx.Config.Autodetect.IncludeDirs = partialConfig.Autodetect.IncludeDirs
ctx.Config.Autodetect.EnvNames = partialConfig.Autodetect.EnvNames
ctx.Config.Autodetect.PathOverrides = partialConfig.Autodetect.PathOverrides

_, _ = reader.Seek(0, io.SeekStart)
definedProjects = hasLineStartingWith(reader, "projects:")
Expand Down
35 changes: 35 additions & 0 deletions cmd/infracost/testdata/generate/path_overrides/expected.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: 0.1

projects:
- path: infra/components/bar
name: infra-components-bar-bip
terraform_var_files:
- ../../prod.tfvars
- ../../dev.tfvars
- ../../default.tfvars
- ../../bip.tfvars
skip_autodetect: true
- path: infra/components/blah
name: infra-components-blah-bat
terraform_var_files:
- ../../prod.tfvars
- ../../dev.tfvars
- ../../default.tfvars
- ../../network-bat.tfvars
- ../../bat.tfvars
skip_autodetect: true
- path: infra/components/blah
name: infra-components-blah-bip
terraform_var_files:
- ../../prod.tfvars
- ../../dev.tfvars
- ../../default.tfvars
- ../../bip.tfvars
skip_autodetect: true
- path: infra/components/foo
name: infra-components-foo-baz
terraform_var_files:
- ../../network-baz.tfvars
- ../../baz.tfvars
skip_autodetect: true

16 changes: 16 additions & 0 deletions cmd/infracost/testdata/generate/path_overrides/infracost.yml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 0.1
autodetect:
env_names:
- baz
- bat
- bip
path_overrides:
- path: "**/**"
exclude:
- baz
- path: infra/components/foo
only:
- baz
- path: infra/**/bar
exclude:
- bat
17 changes: 17 additions & 0 deletions cmd/infracost/testdata/generate/path_overrides/tree.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.
└── infra
├── components
│ ├── foo
│ │ └── main.tf
│ ├── blah
│ │ └── main.tf
│ └── bar
│ └── main.tf
├── baz.tfvars
├── bat.tfvars
├── bip.tfvars
├── network-baz.tfvars
├── network-bat.tfvars
├── dev.tfvars
├── prod.tfvars
└── default.tfvars
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ require (
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bmatcuk/doublestar v1.3.4
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/glob v0.2.3
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
Expand Down
9 changes: 9 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ type AutodetectConfig struct {
// IncludeDirs is a list of directories that the autodetect should append
// to the already detected directories.
IncludeDirs []string `yaml:"include_dirs,omitempty" ignored:"true"`
// PathOverrides defines paths that should be overridden with specific
// environment variable grouping.
PathOverrides []PathOverride `yaml:"path_overrides,omitempty" ignored:"true"`
}

type PathOverride struct {
Path string `yaml:"path"`
Exclude []string `yaml:"exclude"`
Only []string `yaml:"only"`
}

// Project defines a specific terraform project config. This can be used
Expand Down
102 changes: 98 additions & 4 deletions internal/hcl/project_locator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sort"
"strings"

"github.com/gobwas/glob"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -196,6 +197,7 @@ type ProjectLocator struct {

shouldSkipDir func(string) bool
shouldIncludeDir func(string) bool
pathOverrides []pathOverride
}

// ProjectLocatorConfig provides configuration options on how the locator functions.
Expand All @@ -206,6 +208,37 @@ type ProjectLocatorConfig struct {
SkipAutoDetection bool
IncludedDirs []string
EnvNames []string
PathOverrides []PathOverrideConfig
}

type PathOverrideConfig struct {
Path string
Exclude []string
Only []string
}

type pathOverride struct {
glob glob.Glob

exclude map[string]struct{}
only map[string]struct{}
}

func newPathOverride(override PathOverrideConfig) pathOverride {
exclude := make(map[string]struct{}, len(override.Exclude))
for _, s := range override.Exclude {
exclude[s] = struct{}{}
}
only := make(map[string]struct{}, len(override.Only))
for _, s := range override.Only {
only[s] = struct{}{}
}

return pathOverride{
glob: glob.MustCompile(override.Path),
exclude: exclude,
only: only,
}
}

// NewProjectLocator returns safely initialized ProjectLocator.
Expand All @@ -216,13 +249,19 @@ func NewProjectLocator(logger zerolog.Logger, config *ProjectLocatorConfig) *Pro
matcher = CreateEnvFileMatcher(config.EnvNames)
}

overrides := make([]pathOverride, len(config.PathOverrides))
for i, override := range config.PathOverrides {
overrides[i] = newPathOverride(override)
}

return &ProjectLocator{
modules: make(map[string]struct{}),
moduleCalls: make(map[string][]string),
discoveredVarFiles: make(map[string][]RootPathVarFile),
excludedDirs: config.ExcludedDirs,
changedObjects: config.ChangedObjects,
includedDirs: config.IncludedDirs,
pathOverrides: overrides,
logger: logger,
envMatcher: matcher,
useAllPaths: config.UseAllPaths,
Expand Down Expand Up @@ -373,7 +412,7 @@ func buildVarFileEnvNames(root *TreeNode, e *EnvFileMatcher) {
}
}

t.TerraformVarFiles.Files[i].EnvName = envName
t.TerraformVarFiles.Files[i].EnvName = e.EnvName(envName)
if envName != "" {
t.TerraformVarFiles.Files[i].IsGlobal = false
}
Expand Down Expand Up @@ -805,6 +844,11 @@ type RootPath struct {
IsTerragrunt bool
}

func (r *RootPath) RelPath() string {
rel, _ := filepath.Rel(r.RepoPath, r.Path)
return rel
}

// GlobalFiles returns a list of any global var files defined in the project.
func (r *RootPath) GlobalFiles() RootPathVarFiles {
var files RootPathVarFiles
Expand Down Expand Up @@ -984,7 +1028,57 @@ func (p *ProjectLocator) FindRootModules(fullPath string) []RootPath {
node.AssociateParentVarFiles()
node.AssociateAuntVarFiles()

return node.CollectRootPaths()
paths := node.CollectRootPaths()
p.excludeEnvFromPaths(paths)

return paths
}

// excludeEnvFromPaths filters car files from the paths based on the path overrides.
func (p *ProjectLocator) excludeEnvFromPaths(paths []RootPath) {
// filter the "only" paths first. This is done as "only" rules take precedence
// over exclude rules. So if a env is defined in both only and exclude and
// matches the same path, the "only" rule is the only one to apply.
onlyPaths := map[string]struct{}{}
for _, override := range p.pathOverrides {
if len(override.only) > 0 {
for i, path := range paths {
relPath := path.RelPath()
if override.glob.Match(relPath) {
var filtered RootPathVarFiles
for _, varFile := range path.TerraformVarFiles {
if _, ok := override.only[varFile.EnvName]; ok {
onlyPaths[relPath+varFile.EnvName] = struct{}{}
filtered = append(filtered, varFile)
}
}
paths[i].TerraformVarFiles = filtered
}
}
}
}

for _, override := range p.pathOverrides {
if len(override.exclude) > 0 {
for i, path := range paths {
relPath := path.RelPath()
if override.glob.Match(relPath) {
var filtered RootPathVarFiles
for _, varFile := range path.TerraformVarFiles {
_, excluded := override.exclude[varFile.EnvName]
_, only := onlyPaths[relPath+varFile.EnvName]
if excluded && !only {
continue
}

filtered = append(filtered, varFile)
}

paths[i].TerraformVarFiles = filtered
}
}
}
}
}

func (p *ProjectLocator) discoveredProjectsWithModulesFiltered() []discoveredProject {
Expand Down Expand Up @@ -1099,15 +1193,15 @@ func (p *ProjectLocator) walkPaths(fullPath string, level int) {
if !ok {
v = []RootPathVarFile{{
Name: name,
EnvName: name,
EnvName: p.envMatcher.EnvName(name),
RelPath: name,
IsGlobal: p.envMatcher.IsGlobalVarFile(name),
FullPath: filepath.Join(fullPath, name),
}}
} else {
v = append(v, RootPathVarFile{
Name: name,
EnvName: name,
EnvName: p.envMatcher.EnvName(name),
RelPath: name,
IsGlobal: p.envMatcher.IsGlobalVarFile(name),
FullPath: filepath.Join(fullPath, name),
Expand Down
10 changes: 10 additions & 0 deletions internal/providers/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,19 @@ func Detect(ctx *config.RunContext, project *config.Project, includePastResource
return []schema.Provider{h}, nil
}

pathOverrides := make([]hcl.PathOverrideConfig, len(ctx.Config.Autodetect.PathOverrides))
for i, override := range ctx.Config.Autodetect.PathOverrides {
pathOverrides[i] = hcl.PathOverrideConfig{
Path: override.Path,
Only: override.Only,
Exclude: override.Exclude,
}
}

locatorConfig := &hcl.ProjectLocatorConfig{
ExcludedDirs: append(project.ExcludePaths, ctx.Config.Autodetect.ExcludeDirs...),
IncludedDirs: ctx.Config.Autodetect.IncludeDirs,
PathOverrides: pathOverrides,
EnvNames: ctx.Config.Autodetect.EnvNames,
ChangedObjects: ctx.VCSMetadata.Commit.ChangedObjects,
UseAllPaths: project.IncludeAllPaths,
Expand Down