mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
b40a4fb741
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
200 lines
6.1 KiB
Go
200 lines
6.1 KiB
Go
package moduledeps
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/terraform/internal/plugin/discovery"
|
|
)
|
|
|
|
// Module represents the dependencies of a single module, as well being
|
|
// a node in a tree of such structures representing the dependencies of
|
|
// an entire configuration.
|
|
type Module struct {
|
|
Name string
|
|
Providers Providers
|
|
Children []*Module
|
|
}
|
|
|
|
// WalkFunc is a callback type for use with Module.WalkTree
|
|
type WalkFunc func(path []string, parent *Module, current *Module) error
|
|
|
|
// WalkTree calls the given callback once for the receiver and then
|
|
// once for each descendent, in an order such that parents are called
|
|
// before their children and siblings are called in the order they
|
|
// appear in the Children slice.
|
|
//
|
|
// When calling the callback, parent will be nil for the first call
|
|
// for the receiving module, and then set to the direct parent of
|
|
// each module for the subsequent calls.
|
|
//
|
|
// The path given to the callback is valid only until the callback
|
|
// returns, after which it will be mutated and reused. Callbacks must
|
|
// therefore copy the path slice if they wish to retain it.
|
|
//
|
|
// If the given callback returns an error, the walk will be aborted at
|
|
// that point and that error returned to the caller.
|
|
//
|
|
// This function is not thread-safe for concurrent modifications of the
|
|
// data structure, so it's the caller's responsibility to arrange for that
|
|
// should it be needed.
|
|
//
|
|
// It is safe for a callback to modify the descendents of the "current"
|
|
// module, including the ordering of the Children slice itself, but the
|
|
// callback MUST NOT modify the parent module.
|
|
func (m *Module) WalkTree(cb WalkFunc) error {
|
|
return walkModuleTree(make([]string, 0, 1), nil, m, cb)
|
|
}
|
|
|
|
func walkModuleTree(path []string, parent *Module, current *Module, cb WalkFunc) error {
|
|
path = append(path, current.Name)
|
|
err := cb(path, parent, current)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, child := range current.Children {
|
|
err := walkModuleTree(path, current, child, cb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SortChildren sorts the Children slice into lexicographic order by
|
|
// name, in-place.
|
|
//
|
|
// This is primarily useful prior to calling WalkTree so that the walk
|
|
// will proceed in a consistent order.
|
|
func (m *Module) SortChildren() {
|
|
sort.Sort(sortModules{m.Children})
|
|
}
|
|
|
|
// SortDescendents is a convenience wrapper for calling SortChildren on
|
|
// the receiver and all of its descendent modules.
|
|
func (m *Module) SortDescendents() {
|
|
m.WalkTree(func(path []string, parent *Module, current *Module) error {
|
|
current.SortChildren()
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type sortModules struct {
|
|
modules []*Module
|
|
}
|
|
|
|
func (s sortModules) Len() int {
|
|
return len(s.modules)
|
|
}
|
|
|
|
func (s sortModules) Less(i, j int) bool {
|
|
cmp := strings.Compare(s.modules[i].Name, s.modules[j].Name)
|
|
return cmp < 0
|
|
}
|
|
|
|
func (s sortModules) Swap(i, j int) {
|
|
s.modules[i], s.modules[j] = s.modules[j], s.modules[i]
|
|
}
|
|
|
|
// ProviderRequirements produces a PluginRequirements structure that can
|
|
// be used with discovery.PluginMetaSet.ConstrainVersions to identify
|
|
// suitable plugins to satisfy the module's provider dependencies.
|
|
//
|
|
// This method only considers the direct requirements of the receiver.
|
|
// Use AllPluginRequirements to flatten the dependencies for the
|
|
// entire tree of modules.
|
|
//
|
|
// Requirements returned by this method include only version constraints,
|
|
// and apply no particular SHA256 hash constraint.
|
|
func (m *Module) ProviderRequirements() discovery.PluginRequirements {
|
|
ret := make(discovery.PluginRequirements)
|
|
for pFqn, dep := range m.Providers {
|
|
providerType := pFqn.Type
|
|
if existing, exists := ret[providerType]; exists {
|
|
ret[providerType].Versions = existing.Versions.Append(dep.Constraints)
|
|
} else {
|
|
ret[providerType] = &discovery.PluginConstraints{
|
|
Versions: dep.Constraints,
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// AllProviderRequirements calls ProviderRequirements for the receiver and all
|
|
// of its descendents, and merges the result into a single PluginRequirements
|
|
// structure that would satisfy all of the modules together.
|
|
//
|
|
// Requirements returned by this method include only version constraints,
|
|
// and apply no particular SHA256 hash constraint.
|
|
func (m *Module) AllProviderRequirements() discovery.PluginRequirements {
|
|
var ret discovery.PluginRequirements
|
|
m.WalkTree(func(path []string, parent *Module, current *Module) error {
|
|
ret = ret.Merge(current.ProviderRequirements())
|
|
return nil
|
|
})
|
|
return ret
|
|
}
|
|
|
|
// Equal returns true if the receiver is the root of an identical tree
|
|
// to the other given Module. This is a deep comparison that considers
|
|
// the equality of all downstream modules too.
|
|
//
|
|
// The children are considered to be ordered, so callers may wish to use
|
|
// SortDescendents first to normalize the order of the slices of child nodes.
|
|
//
|
|
// The implementation of this function is not optimized since it is provided
|
|
// primarily for use in tests.
|
|
func (m *Module) Equal(other *Module) bool {
|
|
// take care of nils first
|
|
if m == nil && other == nil {
|
|
return true
|
|
} else if (m == nil && other != nil) || (m != nil && other == nil) {
|
|
return false
|
|
}
|
|
|
|
if m.Name != other.Name {
|
|
return false
|
|
}
|
|
|
|
if len(m.Providers) != len(other.Providers) {
|
|
return false
|
|
}
|
|
if len(m.Children) != len(other.Children) {
|
|
return false
|
|
}
|
|
|
|
// Can't use reflect.DeepEqual on this provider structure because
|
|
// the nested Constraints objects contain function pointers that
|
|
// never compare as equal. So we'll need to walk it the long way.
|
|
for inst, dep := range m.Providers {
|
|
if _, exists := other.Providers[inst]; !exists {
|
|
return false
|
|
}
|
|
|
|
if dep.Reason != other.Providers[inst].Reason {
|
|
return false
|
|
}
|
|
|
|
// Constraints are not too easy to compare robustly, so
|
|
// we'll just use their string representations as a proxy
|
|
// for now.
|
|
if dep.Constraints.String() != other.Providers[inst].Constraints.String() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Above we already checked that we have the same number of children
|
|
// in each module, so now we just need to check that they are
|
|
// recursively equal.
|
|
for i := range m.Children {
|
|
if !m.Children[i].Equal(other.Children[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// If we fall out here then they are equal
|
|
return true
|
|
}
|