forked from runatlantis/atlantis
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: auto-plan projects when modules change (runatlantis#2507)
* feat: auto-plan projects when modules change Fixes runatlantis#920 * rename Downstream->Dependent; docs cleanup * chore: gofmt -s
- Loading branch information
Showing
29 changed files
with
423 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package events | ||
|
||
import ( | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"path" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform-config-inspect/tfconfig" | ||
"github.com/moby/moby/pkg/fileutils" | ||
) | ||
|
||
type module struct { | ||
// path to the module | ||
path string | ||
// dependencies of this module | ||
dependencies map[string]bool | ||
// projects that depend on this module | ||
projects map[string]bool | ||
} | ||
|
||
func (m *module) String() string { | ||
if m == nil { | ||
return "nil" | ||
} | ||
return fmt.Sprintf("%+v", *m) | ||
} | ||
|
||
type ModuleProjects interface { | ||
// DependentProjects returns all projects that depend on the module at moduleDir | ||
DependentProjects(moduleDir string) []string | ||
} | ||
|
||
type moduleInfo map[string]*module | ||
|
||
var _ ModuleProjects = moduleInfo{} | ||
|
||
func (m moduleInfo) String() string { | ||
return fmt.Sprintf("%+v", map[string]*module(m)) | ||
} | ||
|
||
func (m moduleInfo) DependentProjects(moduleDir string) (projectPaths []string) { | ||
if m == nil || m[moduleDir] == nil { | ||
return nil | ||
} | ||
for project := range m[moduleDir].projects { | ||
projectPaths = append(projectPaths, project) | ||
} | ||
return projectPaths | ||
} | ||
|
||
type tfFs struct { | ||
fs.FS | ||
} | ||
|
||
func (t tfFs) Open(name string) (tfconfig.File, error) { | ||
return t.FS.Open(name) | ||
} | ||
|
||
func (t tfFs) ReadFile(name string) ([]byte, error) { | ||
return fs.ReadFile(t.FS, name) | ||
} | ||
|
||
func (t tfFs) ReadDir(dirname string) ([]os.FileInfo, error) { | ||
ls, err := fs.ReadDir(t.FS, dirname) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var infos []os.FileInfo | ||
for _, l := range ls { | ||
info, err := l.Info() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get info for %s: %w", l.Name(), err) | ||
} | ||
infos = append(infos, info) | ||
} | ||
return infos, err | ||
} | ||
|
||
var _ tfconfig.FS = tfFs{} | ||
|
||
func (m moduleInfo) load(files fs.FS, dir string, projects ...string) (_ *module, diags tfconfig.Diagnostics) { | ||
if _, set := m[dir]; !set { | ||
tfFiles := tfFs{files} | ||
var mod *tfconfig.Module | ||
mod, diags = tfconfig.LoadModuleFromFilesystem(tfFiles, dir) | ||
|
||
deps := make(map[string]bool) | ||
if mod != nil { | ||
for _, c := range mod.ModuleCalls { | ||
mPath := path.Join(dir, c.Source) | ||
if !tfconfig.IsModuleDirOnFilesystem(tfFiles, mPath) { | ||
continue | ||
} | ||
deps[mPath] = true | ||
} | ||
} | ||
|
||
m[dir] = &module{ | ||
path: dir, | ||
dependencies: deps, | ||
projects: make(map[string]bool), | ||
} | ||
} | ||
// set projects on my dependencies | ||
for dep := range m[dir].dependencies { | ||
_, err := m.load(files, dep, projects...) | ||
if err != nil { | ||
diags = append(diags, err...) | ||
} | ||
} | ||
// add projects to the list of dependant projects | ||
for _, p := range projects { | ||
m[dir].projects[p] = true | ||
} | ||
return m[dir], diags | ||
} | ||
|
||
// FindModuleProjects returns a mapping of modules to projects that depend on them. | ||
func FindModuleProjects(absRepoDir string, autoplanModuleDependants string) (ModuleProjects, error) { | ||
return findModuleDependants(os.DirFS(absRepoDir), autoplanModuleDependants) | ||
} | ||
|
||
func findModuleDependants(files fs.FS, autoplanModuleDependants string) (ModuleProjects, error) { | ||
if autoplanModuleDependants == "" { | ||
return moduleInfo{}, nil | ||
} | ||
// find all the projects matching autoplanModuleDependants | ||
filter, _ := fileutils.NewPatternMatcher(strings.Split(autoplanModuleDependants, ",")) | ||
var projects []string | ||
err := fs.WalkDir(files, ".", func(rel string, info fs.DirEntry, err error) error { | ||
if match, _ := filter.Matches(rel); match { | ||
if projectDir := getProjectDirFromFs(files, rel); projectDir != "" { | ||
projects = append(projects, projectDir) | ||
} | ||
} | ||
return err | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("find projects for module dependants: %w", err) | ||
} | ||
|
||
result := make(moduleInfo) | ||
var diags tfconfig.Diagnostics | ||
// for each project, find the modules it depends on, their deps, etc. | ||
for _, projectDir := range projects { | ||
if _, err := result.load(files, projectDir, projectDir); err != nil { | ||
diags = append(diags, err...) | ||
} | ||
} | ||
// if there are any errors, prefer one with a source location | ||
if diags.HasErrors() { | ||
for _, d := range diags { | ||
if d.Pos != nil { | ||
return nil, fmt.Errorf("%s:%d - %s: %s", d.Pos.Filename, d.Pos.Line, d.Summary, d.Detail) | ||
} | ||
} | ||
} | ||
return result, diags.Err() | ||
} |
Oops, something went wrong.