aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/config/module/versions.go
blob: 29701b931a9c91b870c01298fb9e826cf97ac30a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package module

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"

	version "github.com/hashicorp/go-version"
	"github.com/hashicorp/terraform/registry/response"
)

const anyVersion = ">=0.0.0"

var explicitEqualityConstraint = regexp.MustCompile("^=[0-9]")

// return the newest version that satisfies the provided constraint
func newest(versions []string, constraint string) (string, error) {
	if constraint == "" {
		constraint = anyVersion
	}
	cs, err := version.NewConstraint(constraint)
	if err != nil {
		return "", err
	}

	// Find any build metadata in the constraints, and
	// store whether the constraint is an explicit equality that
	// contains a build metadata requirement, so we can return a specific,
	// if requested, build metadata version
	var constraintMetas []string
	var equalsConstraint bool
	for i := range cs {
		constraintMeta := strings.SplitAfterN(cs[i].String(), "+", 2)
		if len(constraintMeta) > 1 {
			constraintMetas = append(constraintMetas, constraintMeta[1])
		}
	}

	if len(cs) == 1 {
		equalsConstraint = explicitEqualityConstraint.MatchString(cs.String())
	}

	// If the version string includes metadata, this is valid in go-version,
	// However, it's confusing as to what expected behavior should be,
	// so give an error so the user can do something more logical
	if (len(cs) > 1 || !equalsConstraint) && len(constraintMetas) > 0 {
		return "", fmt.Errorf("Constraints including build metadata must have explicit equality, or are otherwise too ambiguous: %s", cs.String())
	}

	switch len(versions) {
	case 0:
		return "", errors.New("no versions found")
	case 1:
		v, err := version.NewVersion(versions[0])
		if err != nil {
			return "", err
		}

		if !cs.Check(v) {
			return "", fmt.Errorf("no version found matching constraint %q", constraint)
		}
		return versions[0], nil
	}

	sort.Slice(versions, func(i, j int) bool {
		// versions should have already been validated
		// sort invalid version strings to the end
		iv, err := version.NewVersion(versions[i])
		if err != nil {
			return true
		}
		jv, err := version.NewVersion(versions[j])
		if err != nil {
			return true
		}
		return iv.GreaterThan(jv)
	})

	// versions are now in order, so just find the first which satisfies the
	// constraint
	for i := range versions {
		v, err := version.NewVersion(versions[i])
		if err != nil {
			continue
		}
		if cs.Check(v) {
			// Constraint has metadata and is explicit equality
			if equalsConstraint && len(constraintMetas) > 0 {
				if constraintMetas[0] != v.Metadata() {
					continue
				}
			}
			return versions[i], nil
		}
	}

	return "", nil
}

// return the newest *moduleVersion that matches the given constraint
// TODO: reconcile these two types and newest* functions
func newestVersion(moduleVersions []*response.ModuleVersion, constraint string) (*response.ModuleVersion, error) {
	var versions []string
	modules := make(map[string]*response.ModuleVersion)

	for _, m := range moduleVersions {
		versions = append(versions, m.Version)
		modules[m.Version] = m
	}

	match, err := newest(versions, constraint)
	return modules[match], err
}

// return the newest moduleRecord that matches the given constraint
func newestRecord(moduleVersions []moduleRecord, constraint string) (moduleRecord, error) {
	var versions []string
	modules := make(map[string]moduleRecord)

	for _, m := range moduleVersions {
		versions = append(versions, m.Version)
		modules[m.Version] = m
	}

	match, err := newest(versions, constraint)
	return modules[match], err
}