| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package command | |
| import ( | |
| "bytes" | |
| "encoding/json" | |
| "fmt" | |
| "sort" | |
| "strings" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/depsfile" | |
| "github.com/hashicorp/terraform/internal/getproviders" | |
| ) | |
| // VersionCommand is a Command implementation prints the version. | |
| type VersionCommand struct { | |
| Meta | |
| Version string | |
| VersionPrerelease string | |
| CheckFunc VersionCheckFunc | |
| Platform getproviders.Platform | |
| } | |
| type VersionOutput struct { | |
| Version string `json:"terraform_version"` | |
| Platform string `json:"platform"` | |
| 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) | |
| // VersionCheckInfo is the return value for the VersionCheckFunc callback | |
| // and tells the Version command information about the latest version | |
| // of Terraform. | |
| type VersionCheckInfo struct { | |
| Outdated bool | |
| Latest string | |
| Alerts []string | |
| } | |
| func (c *VersionCommand) Help() string { | |
| helpText := ` | |
| Usage: terraform [global options] version [options] | |
| Displays the version of Terraform and all installed plugins | |
| Options: | |
| -json Output the version information as a JSON object. | |
| ` | |
| return strings.TrimSpace(helpText) | |
| } | |
| func (c *VersionCommand) Run(args []string) int { | |
| var outdated bool | |
| var latest string | |
| var versionString bytes.Buffer | |
| args = c.Meta.process(args) | |
| var jsonOutput bool | |
| cmdFlags := c.Meta.defaultFlagSet("version") | |
| cmdFlags.BoolVar(&jsonOutput, "json", false, "json") | |
| // Enable but ignore the global version flags. In main.go, if any of the | |
| // arguments are -v, -version, or --version, this command will be called | |
| // with the rest of the arguments, so we need to be able to cope with | |
| // those. | |
| cmdFlags.Bool("v", true, "version") | |
| cmdFlags.Bool("version", true, "version") | |
| cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } | |
| 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) | |
| } | |
| // We'll also attempt to print out the selected plugin versions. We do | |
| // this based on the dependency lock file, and so the result might be | |
| // empty or incomplete if the user hasn't successfully run "terraform init" | |
| // since the most recent change to dependencies. | |
| // | |
| // 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. | |
| var providerVersions []string | |
| var providerLocks map[addrs.Provider]*depsfile.ProviderLock | |
| if locks, err := c.lockedDependencies(); err == nil { | |
| providerLocks = locks.AllProviders() | |
| for providerAddr, lock := range providerLocks { | |
| version := lock.Version().String() | |
| if version == "0.0.0" { | |
| providerVersions = append(providerVersions, fmt.Sprintf("+ provider %s (unversioned)", providerAddr)) | |
| } else { | |
| providerVersions = append(providerVersions, fmt.Sprintf("+ provider %s v%s", providerAddr, version)) | |
| } | |
| } | |
| } | |
| // 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 && !jsonOutput { | |
| c.Ui.Error(fmt.Sprintf( | |
| "\nError checking latest version: %s", err)) | |
| } | |
| if info.Outdated { | |
| outdated = true | |
| latest = info.Latest | |
| } | |
| } | |
| if jsonOutput { | |
| selectionsOutput := make(map[string]string) | |
| for providerAddr, lock := range providerLocks { | |
| version := lock.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, | |
| Platform: c.Platform.String(), | |
| 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()) | |
| c.Ui.Output(fmt.Sprintf("on %s", c.Platform)) | |
| if len(providerVersions) != 0 { | |
| sort.Strings(providerVersions) | |
| for _, str := range providerVersions { | |
| c.Ui.Output(str) | |
| } | |
| } | |
| if 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://developer.hashicorp.com/terraform/install", | |
| latest)) | |
| } | |
| } | |
| return 0 | |
| } | |
| func (c *VersionCommand) Synopsis() string { | |
| return "Show the current Terraform version" | |
| } |