diff --git a/command/version.go b/command/version.go index 2a3c1056aed9..321340b215f2 100644 --- a/command/version.go +++ b/command/version.go @@ -2,6 +2,7 @@ package command import ( "bytes" + "encoding/json" "fmt" "sort" ) @@ -16,6 +17,13 @@ type VersionCommand struct { CheckFunc VersionCheckFunc } +type VersionOutput struct { + Version string `json:"terraform_version"` + Revision string `json:"terraform_revision"` + ProviderSelections map[string]string `json:"provider_selections"` + Outdated bool `json:"terraform_outdated"` +} + // VersionCheckFunc is the callback called by the Version command to // check if there is a new version of Terraform. type VersionCheckFunc func() (VersionCheckInfo, error) @@ -34,8 +42,17 @@ func (c *VersionCommand) Help() string { } func (c *VersionCommand) Run(args []string) int { + var outdated bool var versionString bytes.Buffer args = c.Meta.process(args) + var jsonOutput bool + cmdFlags := c.Meta.defaultFlagSet("version") + cmdFlags.BoolVar(&jsonOutput, "json", false, "json") + if err := cmdFlags.Parse(args); err != nil { + c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) + return 1 + } + fmt.Fprintf(&versionString, "Terraform v%s", c.Version) if c.VersionPrerelease != "" { fmt.Fprintf(&versionString, "-%s", c.VersionPrerelease) @@ -45,8 +62,6 @@ 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 @@ -70,29 +85,64 @@ func (c *VersionCommand) Run(args []string) int { pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider %s v%s", providerAddr, 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 { - // Check the latest version info, err := c.CheckFunc() - if err != nil { + if err != nil && !jsonOutput { c.Ui.Error(fmt.Sprintf( "\nError checking latest version: %s", err)) } if info.Outdated { - c.Ui.Output(fmt.Sprintf( - "\nYour version of Terraform is out of date! The latest version\n"+ - "is %s. You can update by downloading from https://www.terraform.io/downloads.html", - info.Latest)) + outdated = true + if !jsonOutput { + c.Ui.Output(fmt.Sprintf( + "\nYour version of Terraform is out of date! The latest version\n"+ + "is %s. You can update by downloading from https://www.terraform.io/downloads.html", + info.Latest)) + } + + } + } + + if jsonOutput { + selectionsOutput := make(map[string]string) + for providerAddr, cached := range providerSelections { + version := cached.Version.String() + selectionsOutput[providerAddr.String()] = version + } + + var versionOutput string + if c.VersionPrerelease != "" { + versionOutput = c.Version + "-" + c.VersionPrerelease + } else { + versionOutput = c.Version + } + + output := VersionOutput{ + Version: versionOutput, + Revision: c.Revision, + ProviderSelections: selectionsOutput, + Outdated: outdated, + } + + jsonOutput, err := json.MarshalIndent(output, "", " ") + if err != nil { + c.Ui.Error(fmt.Sprintf("\nError marshalling JSON: %s", err)) + return 1 + } + c.Ui.Output(string(jsonOutput)) + return 0 + } else { + c.Ui.Output(versionString.String()) + if len(pluginVersions) != 0 { + sort.Strings(pluginVersions) + for _, str := range pluginVersions { + c.Ui.Output(str) + } } + } return 0 diff --git a/command/version_test.go b/command/version_test.go index 2a645690faff..05a9a83adeab 100644 --- a/command/version_test.go +++ b/command/version_test.go @@ -1,11 +1,128 @@ package command import ( + "os" + "strings" "testing" + "github.com/hashicorp/terraform/helper/copy" "github.com/mitchellh/cli" ) func TestVersionCommand_implements(t *testing.T) { var _ cli.Command = &VersionCommand{} } + +func TestVersion(t *testing.T) { + fixtureDir := "testdata/providers-schema/basic" + td := tempDir(t) + copy.CopyDir(fixtureDir, td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + + providerSource, close := newMockProviderSource(t, map[string][]string{ + "test": []string{"1.2.3"}, + }) + defer close() + + m := Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + ProviderSource: providerSource, + } + + // `terrafrom init` + ic := &InitCommand{ + Meta: m, + } + if code := ic.Run([]string{}); code != 0 { + t.Fatalf("init failed\n%s", ui.ErrorWriter) + } + // flush the init output from the mock ui + ui.OutputWriter.Reset() + + // `terraform version` + c := &VersionCommand{ + Meta: m, + Version: "4.5.6", + VersionPrerelease: "foo", + } + if code := c.Run([]string{}); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + actual := strings.TrimSpace(ui.OutputWriter.String()) + expected := "Terraform v4.5.6-foo\n+ provider registry.terraform.io/hashicorp/test v1.2.3" + if actual != expected { + t.Fatalf("wrong output\ngot: %#v\nwant: %#v", actual, expected) + } + +} + +func TestVersion_json(t *testing.T) { + fixtureDir := "testdata/providers-schema/basic" + td := tempDir(t) + copy.CopyDir(fixtureDir, td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + ui := new(cli.MockUi) + + providerSource, close := newMockProviderSource(t, map[string][]string{ + "test": []string{"1.2.3"}, + }) + defer close() + + m := Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + ProviderSource: providerSource, + } + + // `terrafrom init` + ic := &InitCommand{ + Meta: m, + } + if code := ic.Run([]string{}); code != 0 { + t.Fatalf("init failed\n%s", ui.ErrorWriter) + } + // flush the init output from the mock ui + ui.OutputWriter.Reset() + + // `terraform version -json` without prerelease + c := &VersionCommand{ + Meta: m, + Version: "4.5.6", + } + if code := c.Run([]string{"-json"}); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + actual := strings.TrimSpace(ui.OutputWriter.String()) + expected := "{\n \"terraform_version\": \"4.5.6\",\n \"terraform_revision\": \"\",\n \"provider_selections\": {\n \"registry.terraform.io/hashicorp/test\": \"1.2.3\"\n },\n \"terraform_outdated\": false\n}" + if actual != expected { + t.Fatalf("wrong output\ngot: %#v\nwant: %#v", actual, expected) + } + + // flush the output from the mock ui + ui.OutputWriter.Reset() + + // `terraform version -json` with prerelease + c = &VersionCommand{ + Meta: m, + Version: "4.5.6", + VersionPrerelease: "foo", + } + if code := c.Run([]string{"-json"}); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + actual = strings.TrimSpace(ui.OutputWriter.String()) + expected = "{\n \"terraform_version\": \"4.5.6-foo\",\n \"terraform_revision\": \"\",\n \"provider_selections\": {\n \"registry.terraform.io/hashicorp/test\": \"1.2.3\"\n },\n \"terraform_outdated\": false\n}" + if actual != expected { + t.Fatalf("wrong output\ngot: %#v\nwant: %#v", actual, expected) + } + +}