diff --git a/command/e2etest/version_test.go b/command/e2etest/version_test.go index 174bf1829d..8131155cb3 100644 --- a/command/e2etest/version_test.go +++ b/command/e2etest/version_test.go @@ -33,8 +33,64 @@ func TestVersion(t *testing.T) { wantVersion := fmt.Sprintf("Terraform v%s", tfcore.VersionString()) if !strings.Contains(stdout, wantVersion) { - wantVersion := fmt.Sprintf("Terraform %s", tfcore.VersionString()) - if strings.Contains(stdout, wantVersion) { t.Errorf("output does not contain our current version %q:\n%s", wantVersion, stdout) } } + +func TestVersionWithProvider(t *testing.T) { + // This is a more elaborate use of "version" that shows the selected + // versions of plugins too. + t.Parallel() + + // This test reaches out to releases.hashicorp.com to download the + // template and null providers, so it can only run if network access is + // allowed. + skipIfCannotAccessNetwork(t) + + fixturePath := filepath.Join("test-fixtures", "template-provider") + tf := e2e.NewBinary(terraformBin, fixturePath) + defer tf.Close() + + // Initial run (before "init") should work without error but will not + // include the provider version, since we've not "locked" one yet. + { + stdout, stderr, err := tf.Run("version") + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + if stderr != "" { + t.Errorf("unexpected stderr output:\n%s", stderr) + } + + wantVersion := fmt.Sprintf("Terraform v%s", tfcore.VersionString()) + if !strings.Contains(stdout, wantVersion) { + t.Errorf("output does not contain our current version %q:\n%s", wantVersion, stdout) + } + } + + { + _, _, err := tf.Run("init") + if err != nil { + t.Errorf("unexpected error: %s", err) + } + } + + // After running init, we additionally include information about the + // selected version of the "template" provider. + { + stdout, stderr, err := tf.Run("version") + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + if stderr != "" { + t.Errorf("unexpected stderr output:\n%s", stderr) + } + + wantMsg := "+ provider.template v" // we don't know which version we'll get here + if !strings.Contains(stdout, wantMsg) { + t.Errorf("output does not contain provider information %q:\n%s", wantMsg, stdout) + } + } +} diff --git a/command/version.go b/command/version.go index 34be3ceedd..b62da93bfc 100644 --- a/command/version.go +++ b/command/version.go @@ -3,6 +3,7 @@ package command import ( "bytes" "fmt" + "sort" ) // VersionCommand is a Command implementation prints the version. @@ -50,6 +51,50 @@ func (c *VersionCommand) Run(args []string) int { c.Ui.Output(versionString.String()) + // We'll also attempt to print out the selected plugin versions. We can + // do this only if "terraform init" was already run and thus we've committed + // to a specific set of plugins. If not, the plugins lock will be empty + // and so we'll show _no_ providers. + // + // Generally-speaking this is a best-effort thing that will give us a good + // result in the usual case where the user successfully ran "terraform init" + // and then hit a problem running _another_ command. + providerPlugins := c.providerPluginSet() + pluginsLockFile := c.providerPluginsLock() + pluginsLock := pluginsLockFile.Read() + var pluginVersions []string + for meta := range providerPlugins { + name := meta.Name + wantHash, wanted := pluginsLock[name] + if !wanted { + // Ignore providers that aren't used by the current config at all + continue + } + gotHash, err := meta.SHA256() + if err != nil { + // if we can't read the file to hash it, ignore it. + continue + } + if !bytes.Equal(gotHash, wantHash) { + // Not the plugin we've locked, so ignore it. + continue + } + + // If we get here then we've found a selected plugin, so we'll print + // out its details. + if meta.Version == "0.0.0" { + pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider.%s (unversioned)", name)) + } else { + pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider.%s v%s", name, meta.Version)) + } + } + if len(pluginVersions) != 0 { + sort.Strings(pluginVersions) + for _, str := range pluginVersions { + c.Ui.Output(str) + } + } + // If we have a version check function, then let's check for // the latest version as well. if c.CheckFunc != nil {