github.com/opentofu/opentofu@v1.7.1/internal/command/version.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package command
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"fmt"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/depsfile"
    17  	"github.com/opentofu/opentofu/internal/getproviders"
    18  )
    19  
    20  // VersionCommand is a Command implementation prints the version.
    21  type VersionCommand struct {
    22  	Meta
    23  
    24  	Version           string
    25  	VersionPrerelease string
    26  	Platform          getproviders.Platform
    27  }
    28  
    29  type VersionOutput struct {
    30  	Version            string            `json:"terraform_version"`
    31  	Platform           string            `json:"platform"`
    32  	ProviderSelections map[string]string `json:"provider_selections"`
    33  }
    34  
    35  func (c *VersionCommand) Help() string {
    36  	helpText := `
    37  Usage: tofu [global options] version [options]
    38  
    39    Displays the version of OpenTofu and all installed plugins
    40  
    41  Options:
    42  
    43    -json       Output the version information as a JSON object.
    44  `
    45  	return strings.TrimSpace(helpText)
    46  }
    47  
    48  func (c *VersionCommand) Run(args []string) int {
    49  	var versionString bytes.Buffer
    50  	args = c.Meta.process(args)
    51  	var jsonOutput bool
    52  	cmdFlags := c.Meta.defaultFlagSet("version")
    53  	cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
    54  	// Enable but ignore the global version flags. In main.go, if any of the
    55  	// arguments are -v, -version, or --version, this command will be called
    56  	// with the rest of the arguments, so we need to be able to cope with
    57  	// those.
    58  	cmdFlags.Bool("v", true, "version")
    59  	cmdFlags.Bool("version", true, "version")
    60  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    61  	if err := cmdFlags.Parse(args); err != nil {
    62  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    63  		return 1
    64  	}
    65  
    66  	fmt.Fprintf(&versionString, "OpenTofu v%s", c.Version)
    67  	if c.VersionPrerelease != "" {
    68  		fmt.Fprintf(&versionString, "-%s", c.VersionPrerelease)
    69  	}
    70  
    71  	// We'll also attempt to print out the selected plugin versions. We do
    72  	// this based on the dependency lock file, and so the result might be
    73  	// empty or incomplete if the user hasn't successfully run "tofu init"
    74  	// since the most recent change to dependencies.
    75  	//
    76  	// Generally-speaking this is a best-effort thing that will give us a good
    77  	// result in the usual case where the user successfully ran "tofu init"
    78  	// and then hit a problem running _another_ command.
    79  	var providerVersions []string
    80  	var providerLocks map[addrs.Provider]*depsfile.ProviderLock
    81  	if locks, err := c.lockedDependencies(); err == nil {
    82  		providerLocks = locks.AllProviders()
    83  		for providerAddr, lock := range providerLocks {
    84  			version := lock.Version().String()
    85  			if version == "0.0.0" {
    86  				providerVersions = append(providerVersions, fmt.Sprintf("+ provider %s (unversioned)", providerAddr))
    87  			} else {
    88  				providerVersions = append(providerVersions, fmt.Sprintf("+ provider %s v%s", providerAddr, version))
    89  			}
    90  		}
    91  	}
    92  
    93  	if jsonOutput {
    94  		selectionsOutput := make(map[string]string)
    95  		for providerAddr, lock := range providerLocks {
    96  			version := lock.Version().String()
    97  			selectionsOutput[providerAddr.String()] = version
    98  		}
    99  
   100  		var versionOutput string
   101  		if c.VersionPrerelease != "" {
   102  			versionOutput = c.Version + "-" + c.VersionPrerelease
   103  		} else {
   104  			versionOutput = c.Version
   105  		}
   106  
   107  		output := VersionOutput{
   108  			Version:            versionOutput,
   109  			Platform:           c.Platform.String(),
   110  			ProviderSelections: selectionsOutput,
   111  		}
   112  
   113  		jsonOutput, err := json.MarshalIndent(output, "", "  ")
   114  		if err != nil {
   115  			c.Ui.Error(fmt.Sprintf("\nError marshalling JSON: %s", err))
   116  			return 1
   117  		}
   118  		c.Ui.Output(string(jsonOutput))
   119  		return 0
   120  	} else {
   121  		c.Ui.Output(versionString.String())
   122  		c.Ui.Output(fmt.Sprintf("on %s", c.Platform))
   123  
   124  		if len(providerVersions) != 0 {
   125  			sort.Strings(providerVersions)
   126  			for _, str := range providerVersions {
   127  				c.Ui.Output(str)
   128  			}
   129  		}
   130  	}
   131  
   132  	return 0
   133  }
   134  
   135  func (c *VersionCommand) Synopsis() string {
   136  	return "Show the current OpenTofu version"
   137  }