mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 22:17:59 +00:00
162 lines
4.0 KiB
Go
162 lines
4.0 KiB
Go
package events
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
|
"github.com/moby/patternmatcher"
|
|
)
|
|
|
|
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, _ := patternmatcher.New(strings.Split(autoplanModuleDependants, ","))
|
|
var projects []string
|
|
err := fs.WalkDir(files, ".", func(rel string, info fs.DirEntry, err error) error {
|
|
if match, _ := filter.MatchesOrParentMatches(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()
|
|
}
|