blob: 94d80c40238a732f042016b73666e1474fa5db94 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package moduleref
import (
"maps"
"strings"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/modsdir"
)
// Resolver is the struct responsible for finding all modules references in
// Terraform configuration for a given internal module manifest.
type Resolver struct {
manifest *Manifest
internalManifest modsdir.Manifest
}
// NewResolver creates a new Resolver, storing a copy of the internal manifest
// that is passed.
func NewResolver(internalManifest modsdir.Manifest) *Resolver {
// Since maps are pointers, create a copy of the internal manifest to
// prevent introducing side effects to the original
internalManifestCopy := maps.Clone(internalManifest)
// Remove the root module entry from the internal manifest as it is
// never directly referenced.
delete(internalManifestCopy, "")
return &Resolver{
internalManifest: internalManifestCopy,
manifest: &Manifest{
FormatVersion: FormatVersion,
Records: Records{},
},
}
}
// Resolve will attempt to find all module references for the passed configuration
// and return a new manifest encapsulating this information.
func (r *Resolver) Resolve(cfg *configs.Config) *Manifest {
// First find all the referenced modules.
r.findAndTrimReferencedEntries(cfg, nil, nil)
return r.manifest
}
// findAndTrimReferencedEntries will traverse a given Terraform configuration
// and attempt find a caller for every entry in the internal module manifest.
// If an entry is found, it will be removed from the internal manifest and
// appended to the manifest that records this new information in a nested heirarchy.
func (r *Resolver) findAndTrimReferencedEntries(cfg *configs.Config, parentRecord *Record, parentKey *string) {
var name string
var versionConstraints version.Constraints
if parentKey != nil {
for key := range cfg.Parent.Children {
if key == *parentKey {
name = key
if cfg.Parent.Module.ModuleCalls[key] != nil {
versionConstraints = cfg.Parent.Module.ModuleCalls[key].Version.Required
}
break
}
}
}
childRecord := &Record{
Key: name,
Source: cfg.SourceAddr,
VersionConstraints: versionConstraints,
}
key := strings.Join(cfg.Path, ".")
for entryKey, entry := range r.internalManifest {
if entryKey == key {
// Use resolved version from manifest
childRecord.Version = entry.Version
if parentRecord.Source != nil {
parentRecord.addChild(childRecord)
} else {
r.manifest.addModuleEntry(childRecord)
}
// "Trim" the entry from the internal manifest, saving us cycles
// as we descend into the module tree.
delete(r.internalManifest, entryKey)
break
}
}
// Traverse the child configurations
for childKey, childCfg := range cfg.Children {
r.findAndTrimReferencedEntries(childCfg, childRecord, &childKey)
}
}