| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package discovery | |
| // A PluginMetaSet is a set of PluginMeta objects meeting a certain criteria. | |
| // | |
| // Methods on this type allow filtering of the set to produce subsets that | |
| // meet more restrictive criteria. | |
| type PluginMetaSet map[PluginMeta]struct{} | |
| // Add inserts the given PluginMeta into the receiving set. This is a no-op | |
| // if the given meta is already present. | |
| func (s PluginMetaSet) Add(p PluginMeta) { | |
| s[p] = struct{}{} | |
| } | |
| // Remove removes the given PluginMeta from the receiving set. This is a no-op | |
| // if the given meta is not already present. | |
| func (s PluginMetaSet) Remove(p PluginMeta) { | |
| delete(s, p) | |
| } | |
| // Has returns true if the given meta is in the receiving set, or false | |
| // otherwise. | |
| func (s PluginMetaSet) Has(p PluginMeta) bool { | |
| _, ok := s[p] | |
| return ok | |
| } | |
| // Count returns the number of metas in the set | |
| func (s PluginMetaSet) Count() int { | |
| return len(s) | |
| } | |
| // ValidateVersions returns two new PluginMetaSets, separating those with | |
| // versions that have syntax-valid semver versions from those that don't. | |
| // | |
| // Eliminating invalid versions from consideration (and possibly warning about | |
| // them) is usually the first step of working with a meta set after discovery | |
| // has completed. | |
| func (s PluginMetaSet) ValidateVersions() (valid, invalid PluginMetaSet) { | |
| valid = make(PluginMetaSet) | |
| invalid = make(PluginMetaSet) | |
| for p := range s { | |
| if _, err := p.Version.Parse(); err == nil { | |
| valid.Add(p) | |
| } else { | |
| invalid.Add(p) | |
| } | |
| } | |
| return | |
| } | |
| // WithName returns the subset of metas that have the given name. | |
| func (s PluginMetaSet) WithName(name string) PluginMetaSet { | |
| ns := make(PluginMetaSet) | |
| for p := range s { | |
| if p.Name == name { | |
| ns.Add(p) | |
| } | |
| } | |
| return ns | |
| } | |
| // WithVersion returns the subset of metas that have the given version. | |
| // | |
| // This should be used only with the "valid" result from ValidateVersions; | |
| // it will ignore any plugin metas that have invalid version strings. | |
| func (s PluginMetaSet) WithVersion(version Version) PluginMetaSet { | |
| ns := make(PluginMetaSet) | |
| for p := range s { | |
| gotVersion, err := p.Version.Parse() | |
| if err != nil { | |
| continue | |
| } | |
| if gotVersion.Equal(version) { | |
| ns.Add(p) | |
| } | |
| } | |
| return ns | |
| } | |
| // ByName groups the metas in the set by their Names, returning a map. | |
| func (s PluginMetaSet) ByName() map[string]PluginMetaSet { | |
| ret := make(map[string]PluginMetaSet) | |
| for p := range s { | |
| if _, ok := ret[p.Name]; !ok { | |
| ret[p.Name] = make(PluginMetaSet) | |
| } | |
| ret[p.Name].Add(p) | |
| } | |
| return ret | |
| } | |
| // Newest returns the one item from the set that has the newest Version value. | |
| // | |
| // The result is meaningful only if the set is already filtered such that | |
| // all of the metas have the same Name. | |
| // | |
| // If there isn't at least one meta in the set then this function will panic. | |
| // Use Count() to ensure that there is at least one value before calling. | |
| // | |
| // If any of the metas have invalid version strings then this function will | |
| // panic. Use ValidateVersions() first to filter out metas with invalid | |
| // versions. | |
| // | |
| // If two metas have the same Version then one is arbitrarily chosen. This | |
| // situation should be avoided by pre-filtering the set. | |
| func (s PluginMetaSet) Newest() PluginMeta { | |
| if len(s) == 0 { | |
| panic("can't call NewestStable on empty PluginMetaSet") | |
| } | |
| var first = true | |
| var winner PluginMeta | |
| var winnerVersion Version | |
| for p := range s { | |
| version, err := p.Version.Parse() | |
| if err != nil { | |
| panic(err) | |
| } | |
| if first || version.NewerThan(winnerVersion) { | |
| winner = p | |
| winnerVersion = version | |
| first = false | |
| } | |
| } | |
| return winner | |
| } | |
| // ConstrainVersions takes a set of requirements and attempts to | |
| // return a map from name to a set of metas that have the matching | |
| // name and an appropriate version. | |
| // | |
| // If any of the given requirements match *no* plugins then its PluginMetaSet | |
| // in the returned map will be empty. | |
| // | |
| // All viable metas are returned, so the caller can apply any desired filtering | |
| // to reduce down to a single option. For example, calling Newest() to obtain | |
| // the highest available version. | |
| // | |
| // If any of the metas in the set have invalid version strings then this | |
| // function will panic. Use ValidateVersions() first to filter out metas with | |
| // invalid versions. | |
| func (s PluginMetaSet) ConstrainVersions(reqd PluginRequirements) map[string]PluginMetaSet { | |
| ret := make(map[string]PluginMetaSet) | |
| for p := range s { | |
| name := p.Name | |
| allowedVersions, ok := reqd[name] | |
| if !ok { | |
| continue | |
| } | |
| if _, ok := ret[p.Name]; !ok { | |
| ret[p.Name] = make(PluginMetaSet) | |
| } | |
| version, err := p.Version.Parse() | |
| if err != nil { | |
| panic(err) | |
| } | |
| if allowedVersions.Allows(version) { | |
| ret[p.Name].Add(p) | |
| } | |
| } | |
| return ret | |
| } | |
| // OverridePaths returns a new set where any existing plugins with the given | |
| // names are removed and replaced with the single path given in the map. | |
| // | |
| // This is here only to continue to support the legacy way of overriding | |
| // plugin binaries in the .terraformrc file. It treats all given plugins | |
| // as pre-versioning (version 0.0.0). This mechanism will eventually be | |
| // phased out, with vendor directories being the intended replacement. | |
| func (s PluginMetaSet) OverridePaths(paths map[string]string) PluginMetaSet { | |
| ret := make(PluginMetaSet) | |
| for p := range s { | |
| if _, ok := paths[p.Name]; ok { | |
| // Skip plugins that we're overridding | |
| continue | |
| } | |
| ret.Add(p) | |
| } | |
| // Now add the metadata for overriding plugins | |
| for name, path := range paths { | |
| ret.Add(PluginMeta{ | |
| Name: name, | |
| Version: VersionZero, | |
| Path: path, | |
| }) | |
| } | |
| return ret | |
| } |