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