github.com/opentofu/opentofu@v1.7.1/internal/plugin/discovery/meta_set.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 discovery 7 8 // A PluginMetaSet is a set of PluginMeta objects meeting a certain criteria. 9 // 10 // MethodConfigs on this type allow filtering of the set to produce subsets that 11 // meet more restrictive criteria. 12 type PluginMetaSet map[PluginMeta]struct{} 13 14 // Add inserts the given PluginMeta into the receiving set. This is a no-op 15 // if the given meta is already present. 16 func (s PluginMetaSet) Add(p PluginMeta) { 17 s[p] = struct{}{} 18 } 19 20 // Remove removes the given PluginMeta from the receiving set. This is a no-op 21 // if the given meta is not already present. 22 func (s PluginMetaSet) Remove(p PluginMeta) { 23 delete(s, p) 24 } 25 26 // Has returns true if the given meta is in the receiving set, or false 27 // otherwise. 28 func (s PluginMetaSet) Has(p PluginMeta) bool { 29 _, ok := s[p] 30 return ok 31 } 32 33 // Count returns the number of metas in the set 34 func (s PluginMetaSet) Count() int { 35 return len(s) 36 } 37 38 // ValidateVersions returns two new PluginMetaSets, separating those with 39 // versions that have syntax-valid semver versions from those that don't. 40 // 41 // Eliminating invalid versions from consideration (and possibly warning about 42 // them) is usually the first step of working with a meta set after discovery 43 // has completed. 44 func (s PluginMetaSet) ValidateVersions() (valid, invalid PluginMetaSet) { 45 valid = make(PluginMetaSet) 46 invalid = make(PluginMetaSet) 47 for p := range s { 48 if _, err := p.Version.Parse(); err == nil { 49 valid.Add(p) 50 } else { 51 invalid.Add(p) 52 } 53 } 54 return 55 } 56 57 // WithName returns the subset of metas that have the given name. 58 func (s PluginMetaSet) WithName(name string) PluginMetaSet { 59 ns := make(PluginMetaSet) 60 for p := range s { 61 if p.Name == name { 62 ns.Add(p) 63 } 64 } 65 return ns 66 } 67 68 // WithVersion returns the subset of metas that have the given version. 69 // 70 // This should be used only with the "valid" result from ValidateVersions; 71 // it will ignore any plugin metas that have invalid version strings. 72 func (s PluginMetaSet) WithVersion(version Version) PluginMetaSet { 73 ns := make(PluginMetaSet) 74 for p := range s { 75 gotVersion, err := p.Version.Parse() 76 if err != nil { 77 continue 78 } 79 if gotVersion.Equal(version) { 80 ns.Add(p) 81 } 82 } 83 return ns 84 } 85 86 // ByName groups the metas in the set by their Names, returning a map. 87 func (s PluginMetaSet) ByName() map[string]PluginMetaSet { 88 ret := make(map[string]PluginMetaSet) 89 for p := range s { 90 if _, ok := ret[p.Name]; !ok { 91 ret[p.Name] = make(PluginMetaSet) 92 } 93 ret[p.Name].Add(p) 94 } 95 return ret 96 } 97 98 // Newest returns the one item from the set that has the newest Version value. 99 // 100 // The result is meaningful only if the set is already filtered such that 101 // all of the metas have the same Name. 102 // 103 // If there isn't at least one meta in the set then this function will panic. 104 // Use Count() to ensure that there is at least one value before calling. 105 // 106 // If any of the metas have invalid version strings then this function will 107 // panic. Use ValidateVersions() first to filter out metas with invalid 108 // versions. 109 // 110 // If two metas have the same Version then one is arbitrarily chosen. This 111 // situation should be avoided by pre-filtering the set. 112 func (s PluginMetaSet) Newest() PluginMeta { 113 if len(s) == 0 { 114 panic("can't call NewestStable on empty PluginMetaSet") 115 } 116 117 var first = true 118 var winner PluginMeta 119 var winnerVersion Version 120 for p := range s { 121 version, err := p.Version.Parse() 122 if err != nil { 123 panic(err) 124 } 125 126 if first || version.NewerThan(winnerVersion) { 127 winner = p 128 winnerVersion = version 129 first = false 130 } 131 } 132 133 return winner 134 } 135 136 // ConstrainVersions takes a set of requirements and attempts to 137 // return a map from name to a set of metas that have the matching 138 // name and an appropriate version. 139 // 140 // If any of the given requirements match *no* plugins then its PluginMetaSet 141 // in the returned map will be empty. 142 // 143 // All viable metas are returned, so the caller can apply any desired filtering 144 // to reduce down to a single option. For example, calling Newest() to obtain 145 // the highest available version. 146 // 147 // If any of the metas in the set have invalid version strings then this 148 // function will panic. Use ValidateVersions() first to filter out metas with 149 // invalid versions. 150 func (s PluginMetaSet) ConstrainVersions(reqd PluginRequirements) map[string]PluginMetaSet { 151 ret := make(map[string]PluginMetaSet) 152 for p := range s { 153 name := p.Name 154 allowedVersions, ok := reqd[name] 155 if !ok { 156 continue 157 } 158 if _, ok := ret[p.Name]; !ok { 159 ret[p.Name] = make(PluginMetaSet) 160 } 161 version, err := p.Version.Parse() 162 if err != nil { 163 panic(err) 164 } 165 if allowedVersions.Allows(version) { 166 ret[p.Name].Add(p) 167 } 168 } 169 return ret 170 } 171 172 // OverridePaths returns a new set where any existing plugins with the given 173 // names are removed and replaced with the single path given in the map. 174 // 175 // This is here only to continue to support the legacy way of overriding 176 // plugin binaries in the .opentofurc file. It treats all given plugins 177 // as pre-versioning (version 0.0.0). This mechanism will eventually be 178 // phased out, with vendor directories being the intended replacement. 179 func (s PluginMetaSet) OverridePaths(paths map[string]string) PluginMetaSet { 180 ret := make(PluginMetaSet) 181 for p := range s { 182 if _, ok := paths[p.Name]; ok { 183 // Skip plugins that we're overridding 184 continue 185 } 186 187 ret.Add(p) 188 } 189 190 // Now add the metadata for overriding plugins 191 for name, path := range paths { 192 ret.Add(PluginMeta{ 193 Name: name, 194 Version: VersionZero, 195 Path: path, 196 }) 197 } 198 199 return ret 200 }