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 } // 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 == true || 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: "0.0.0", Path: path, }) } return ret }