github.com/opentofu/opentofu@v1.7.1/internal/moduledeps/module.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package moduledeps 7 8 import ( 9 "sort" 10 "strings" 11 12 "github.com/opentofu/opentofu/internal/plugin/discovery" 13 ) 14 15 // Module represents the dependencies of a single module, as well being 16 // a node in a tree of such structures representing the dependencies of 17 // an entire configuration. 18 type Module struct { 19 Name string 20 Providers Providers 21 Children []*Module 22 } 23 24 // WalkFunc is a callback type for use with Module.WalkTree 25 type WalkFunc func(path []string, parent *Module, current *Module) error 26 27 // WalkTree calls the given callback once for the receiver and then 28 // once for each descendent, in an order such that parents are called 29 // before their children and siblings are called in the order they 30 // appear in the Children slice. 31 // 32 // When calling the callback, parent will be nil for the first call 33 // for the receiving module, and then set to the direct parent of 34 // each module for the subsequent calls. 35 // 36 // The path given to the callback is valid only until the callback 37 // returns, after which it will be mutated and reused. Callbacks must 38 // therefore copy the path slice if they wish to retain it. 39 // 40 // If the given callback returns an error, the walk will be aborted at 41 // that point and that error returned to the caller. 42 // 43 // This function is not thread-safe for concurrent modifications of the 44 // data structure, so it's the caller's responsibility to arrange for that 45 // should it be needed. 46 // 47 // It is safe for a callback to modify the descendents of the "current" 48 // module, including the ordering of the Children slice itself, but the 49 // callback MUST NOT modify the parent module. 50 func (m *Module) WalkTree(cb WalkFunc) error { 51 return walkModuleTree(make([]string, 0, 1), nil, m, cb) 52 } 53 54 func walkModuleTree(path []string, parent *Module, current *Module, cb WalkFunc) error { 55 path = append(path, current.Name) 56 err := cb(path, parent, current) 57 if err != nil { 58 return err 59 } 60 61 for _, child := range current.Children { 62 err := walkModuleTree(path, current, child, cb) 63 if err != nil { 64 return err 65 } 66 } 67 return nil 68 } 69 70 // SortChildren sorts the Children slice into lexicographic order by 71 // name, in-place. 72 // 73 // This is primarily useful prior to calling WalkTree so that the walk 74 // will proceed in a consistent order. 75 func (m *Module) SortChildren() { 76 sort.Sort(sortModules{m.Children}) 77 } 78 79 // SortDescendents is a convenience wrapper for calling SortChildren on 80 // the receiver and all of its descendent modules. 81 func (m *Module) SortDescendents() { 82 m.WalkTree(func(path []string, parent *Module, current *Module) error { 83 current.SortChildren() 84 return nil 85 }) 86 } 87 88 type sortModules struct { 89 modules []*Module 90 } 91 92 func (s sortModules) Len() int { 93 return len(s.modules) 94 } 95 96 func (s sortModules) Less(i, j int) bool { 97 cmp := strings.Compare(s.modules[i].Name, s.modules[j].Name) 98 return cmp < 0 99 } 100 101 func (s sortModules) Swap(i, j int) { 102 s.modules[i], s.modules[j] = s.modules[j], s.modules[i] 103 } 104 105 // ProviderRequirements produces a PluginRequirements structure that can 106 // be used with discovery.PluginMetaSet.ConstrainVersions to identify 107 // suitable plugins to satisfy the module's provider dependencies. 108 // 109 // This method only considers the direct requirements of the receiver. 110 // Use AllPluginRequirements to flatten the dependencies for the 111 // entire tree of modules. 112 // 113 // Requirements returned by this method include only version constraints, 114 // and apply no particular SHA256 hash constraint. 115 func (m *Module) ProviderRequirements() discovery.PluginRequirements { 116 ret := make(discovery.PluginRequirements) 117 for pFqn, dep := range m.Providers { 118 providerType := pFqn.Type 119 if existing, exists := ret[providerType]; exists { 120 ret[providerType].Versions = existing.Versions.Append(dep.Constraints) 121 } else { 122 ret[providerType] = &discovery.PluginConstraints{ 123 Versions: dep.Constraints, 124 } 125 } 126 } 127 return ret 128 } 129 130 // AllProviderRequirements calls ProviderRequirements for the receiver and all 131 // of its descendents, and merges the result into a single PluginRequirements 132 // structure that would satisfy all of the modules together. 133 // 134 // Requirements returned by this method include only version constraints, 135 // and apply no particular SHA256 hash constraint. 136 func (m *Module) AllProviderRequirements() discovery.PluginRequirements { 137 var ret discovery.PluginRequirements 138 m.WalkTree(func(path []string, parent *Module, current *Module) error { 139 ret = ret.Merge(current.ProviderRequirements()) 140 return nil 141 }) 142 return ret 143 } 144 145 // Equal returns true if the receiver is the root of an identical tree 146 // to the other given Module. This is a deep comparison that considers 147 // the equality of all downstream modules too. 148 // 149 // The children are considered to be ordered, so callers may wish to use 150 // SortDescendents first to normalize the order of the slices of child nodes. 151 // 152 // The implementation of this function is not optimized since it is provided 153 // primarily for use in tests. 154 func (m *Module) Equal(other *Module) bool { 155 // take care of nils first 156 if m == nil && other == nil { 157 return true 158 } else if (m == nil && other != nil) || (m != nil && other == nil) { 159 return false 160 } 161 162 if m.Name != other.Name { 163 return false 164 } 165 166 if len(m.Providers) != len(other.Providers) { 167 return false 168 } 169 if len(m.Children) != len(other.Children) { 170 return false 171 } 172 173 // Can't use reflect.DeepEqual on this provider structure because 174 // the nested Constraints objects contain function pointers that 175 // never compare as equal. So we'll need to walk it the long way. 176 for inst, dep := range m.Providers { 177 if _, exists := other.Providers[inst]; !exists { 178 return false 179 } 180 181 if dep.Reason != other.Providers[inst].Reason { 182 return false 183 } 184 185 // Constraints are not too easy to compare robustly, so 186 // we'll just use their string representations as a proxy 187 // for now. 188 if dep.Constraints.String() != other.Providers[inst].Constraints.String() { 189 return false 190 } 191 } 192 193 // Above we already checked that we have the same number of children 194 // in each module, so now we just need to check that they are 195 // recursively equal. 196 for i := range m.Children { 197 if !m.Children[i].Equal(other.Children[i]) { 198 return false 199 } 200 } 201 202 // If we fall out here then they are equal 203 return true 204 }