opentofu/internal/plugin/discovery/meta_set.go
Martin Atkins b40a4fb741 Move plugin/ and plugin6/ to internal/plugin{,6}/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

196 lines
5.4 KiB
Go

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
}