Add provider protocol compatibility UI err msg during registry discovery

This commit is contained in:
findkim 2019-01-10 17:09:37 -06:00
parent 21aadb0456
commit 368ac85a26
2 changed files with 159 additions and 19 deletions

View File

@ -20,6 +20,7 @@ import (
"github.com/hashicorp/terraform/registry/regsrc" "github.com/hashicorp/terraform/registry/regsrc"
"github.com/hashicorp/terraform/registry/response" "github.com/hashicorp/terraform/registry/response"
"github.com/hashicorp/terraform/svchost/disco" "github.com/hashicorp/terraform/svchost/disco"
tfversion "github.com/hashicorp/terraform/version"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -160,20 +161,23 @@ func (i *ProviderInstaller) Get(provider string, req Constraints) (PluginMeta, e
// check protocol compatibility // check protocol compatibility
if err := i.checkPluginProtocol(versionMeta); err != nil { if err := i.checkPluginProtocol(versionMeta); err != nil {
closestMatch, err := i.findProtocolCompatibleVersion(versions) closestMatch, err := i.findClosestProtocolCompatibleVersion(allVersions.Versions)
if err == nil { if err != nil {
if err := i.checkPlatformCompatibility(closestMatch); err != nil { // No operation here if we can't find a version with compatible protocol
// At this point, we have protocol compatibility but not platform, return PluginMeta{}, err
// and we give up trying to find a compatible version.
// This error message should be improved.
return PluginMeta{}, ErrorNoSuitableVersion
}
// TODO: This is a placeholder UI message. We must choose to send
// providerProtocolTooOld or providerProtocolTooNew message to the UI
i.Ui.Error(fmt.Sprintf("the most recent version of %s to match your platform is %s", provider, closestMatch))
return PluginMeta{}, ErrorNoVersionCompatible
} }
return PluginMeta{}, ErrorNoVersionCompatibleWithPlatform
// Prompt version suggestion to UI based on closest protocol match
closestVersion := VersionStr(closestMatch.Version).MustParse()
var errMsg string
if v.NewerThan(closestVersion) {
errMsg = providerProtocolTooNew
} else {
errMsg = providerProtocolTooOld
}
i.Ui.Error(fmt.Sprintf(errMsg, provider, v.String(), tfversion.String(),
closestVersion.String(), closestVersion.MinorUpgradeConstraintStr()))
return PluginMeta{}, ErrorNoVersionCompatible
} }
downloadURLs, err := i.listProviderDownloadURLs(providerSource, versionMeta.Version) downloadURLs, err := i.listProviderDownloadURLs(providerSource, versionMeta.Version)
@ -411,16 +415,48 @@ func (i *ProviderInstaller) listProviderDownloadURLs(name, version string) (*res
return urls, err return urls, err
} }
// REVIEWER QUESTION: this ends up swallowing a bunch of errors from // findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match.
// checkPluginProtocol. Do they need to be percolated up better, or would //
// debug messages would suffice in these situations? func (i *ProviderInstaller) findClosestProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) {
func (i *ProviderInstaller) findProtocolCompatibleVersion(versions []*response.TerraformProviderVersion) (*response.TerraformProviderVersion, error) { // Loop through all the provider versions to find the earliest and latest
// versions that match the installer protocol to then select the closest of the two
var latest, earliest *response.TerraformProviderVersion
for _, version := range versions { for _, version := range versions {
if err := i.checkPluginProtocol(version); err == nil { if err := i.checkPluginProtocol(version); err == nil {
return version, nil if earliest == nil {
// Found the first provider version with compatible protocol
earliest = version
}
// Update the latest protocol compatible version
latest = version
} }
} }
return nil, ErrorNoVersionCompatible if earliest == nil {
// No compatible protocol was found for any version
return nil, ErrorNoVersionCompatible
}
// Convert protocols to comparable types
protoString := strconv.Itoa(int(i.PluginProtocolVersion))
protocolVersion, err := VersionStr(protoString).Parse()
if err != nil {
return nil, fmt.Errorf("invalid plugin protocol version: %q", i.PluginProtocolVersion)
}
earliestVersionProtocol, err := VersionStr(earliest.Protocols[0]).Parse()
if err != nil {
return nil, err
}
// Compare installer protocol version with the first protocol listed of the earliest match
// [A, B] where A is assumed the earliest compatible major version of the protocol pair
if protocolVersion.NewerThan(earliestVersionProtocol) {
// Provider protocols are too old, the closest version is the earliest compatible version
return earliest, nil
}
// Provider protocols are too new, the closest version is the latest compatible version
return latest, nil
} }
func (i *ProviderInstaller) checkPluginProtocol(versionMeta *response.TerraformProviderVersion) error { func (i *ProviderInstaller) checkPluginProtocol(versionMeta *response.TerraformProviderVersion) error {

View File

@ -234,6 +234,110 @@ func TestCheckProtocolVersions(t *testing.T) {
} }
} }
func TestFindClosestProtocolCompatibleVersion(t *testing.T) {
testCases := []struct {
Name string
PluginProtocolVersion uint
ProviderVersions []*response.TerraformProviderVersion
ExpectedVersion string
Err bool
}{
{
"no compatible version",
5,
[]*response.TerraformProviderVersion{
&response.TerraformProviderVersion{
Version: "1.0.0",
Protocols: []string{"4.0"},
},
},
"",
true,
}, {
"equal, suggests latest",
4,
[]*response.TerraformProviderVersion{
&response.TerraformProviderVersion{
Version: "1.0.0",
Protocols: []string{"4.0"},
},
&response.TerraformProviderVersion{
Version: "1.5.0",
Protocols: []string{"4.0"},
},
},
"1.5.0",
false,
}, {
"provider protocol too old, suggests earliest",
5,
[]*response.TerraformProviderVersion{
&response.TerraformProviderVersion{
Version: "1.0.0",
Protocols: []string{"4.0"},
},
&response.TerraformProviderVersion{
Version: "2.0.0",
Protocols: []string{"4.0", "5.0"},
},
&response.TerraformProviderVersion{
Version: "2.5.0",
Protocols: []string{"4.0", "5.0"},
},
&response.TerraformProviderVersion{
Version: "3.0.0",
Protocols: []string{"5.0"},
},
},
"2.0.0",
false,
}, {
"provider protocol too new, suggests latest",
4,
[]*response.TerraformProviderVersion{
&response.TerraformProviderVersion{
Version: "1.0.0",
Protocols: []string{"4.0"},
},
&response.TerraformProviderVersion{
Version: "2.0.0",
Protocols: []string{"4.0", "5.0"},
},
&response.TerraformProviderVersion{
Version: "2.5.0",
Protocols: []string{"4.0", "5.0"},
},
&response.TerraformProviderVersion{
Version: "3.0.0",
Protocols: []string{"5.0"},
},
},
"2.5.0",
false,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
i := ProviderInstaller{
Ui: cli.NewMockUi(),
PluginProtocolVersion: tc.PluginProtocolVersion,
}
closestMatch, err := i.findClosestProtocolCompatibleVersion(tc.ProviderVersions)
if err != nil {
if !tc.Err {
t.Fatalf("unexpected error: %q", err)
}
return
}
if tc.ExpectedVersion != closestMatch.Version {
t.Errorf("Expected %q, got %q", tc.ExpectedVersion, closestMatch.Version)
}
})
}
}
func TestProviderInstallerGet(t *testing.T) { func TestProviderInstallerGet(t *testing.T) {
server := testReleaseServer() server := testReleaseServer()
server.Start() server.Start()