terraform/internal/moduleref/resolver.go
Daniel Banck 834c2f4c21 Fix modules command
To be able to show version constraint from modules, we now store them
during configuration loading.
2026-03-04 11:45:59 +01:00

95 lines
2.8 KiB
Go

// Copyright IBM Corp. 2014, 2026
// 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, child := range cfg.Parent.Children {
if key == *parentKey {
name = key
versionConstraints = child.VersionConstraint.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)
}
}