| 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 |
| } |