diff --git a/config/module/versions.go b/config/module/versions.go index 8348d4b19535..29701b931a9c 100644 --- a/config/module/versions.go +++ b/config/module/versions.go @@ -3,7 +3,9 @@ package module import ( "errors" "fmt" + "regexp" "sort" + "strings" version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/registry/response" @@ -11,6 +13,8 @@ import ( 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 == "" { @@ -21,6 +25,30 @@ func newest(versions []string, constraint string) (string, error) { 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") @@ -58,6 +86,12 @@ func newest(versions []string, constraint string) (string, error) { 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 } } diff --git a/config/module/versions_test.go b/config/module/versions_test.go index b7ff6e6271e9..a9d4941eb08a 100644 --- a/config/module/versions_test.go +++ b/config/module/versions_test.go @@ -58,3 +58,33 @@ func TestNewestInvalidModuleVersion(t *testing.T) { t.Fatalf("expected version %q, got %q", expected, m.Version) } } + +func TestNewestModulesWithMetadata(t *testing.T) { + mpv := &response.ModuleProviderVersions{ + Source: "registry/test/module", + Versions: []*response.ModuleVersion{ + {Version: "0.9.0"}, + {Version: "0.9.0+def"}, + {Version: "0.9.0+abc"}, + {Version: "0.9.0+xyz"}, + }, + } + + // with metadata and explicit version request + expected := "0.9.0+def" + m, _ := newestVersion(mpv.Versions, "=0.9.0+def") + if m.Version != expected { + t.Fatalf("expected version %q, got %q", expected, m.Version) + } + + // respect explicit equality, but >/0.9.0+abc") + if err == nil { + t.Fatalf("expected an error, but did not get one") + } + + _, err = newestVersion(mpv.Versions, ">0.8.0+abc, <1.0.0") + if err == nil { + t.Fatalf("expected an error, but did not get one") + } +}