diff --git a/checkpoint.go b/checkpoint.go new file mode 100644 index 000000000..ebe592b32 --- /dev/null +++ b/checkpoint.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "log" + "path/filepath" + + "github.com/hashicorp/go-checkpoint" + "github.com/hashicorp/terraform/command" +) + +func init() { + checkpointResult = make(chan *checkpoint.CheckResponse, 1) +} + +var checkpointResult chan *checkpoint.CheckResponse + +// runCheckpoint runs a HashiCorp Checkpoint request. You can read about +// Checkpoint here: https://github.com/hashicorp/go-checkpoint. +func runCheckpoint(c *Config) { + // If the user doesn't want checkpoint at all, then return. + if c.DisableCheckpoint { + log.Printf("[INFO] Checkpoint disabled. Not running.") + checkpointResult <- nil + return + } + + configDir, err := ConfigDir() + if err != nil { + log.Printf("[ERR] Checkpoint setup error: %s", err) + checkpointResult <- nil + return + } + + version := Version + if VersionPrerelease != "" { + version += fmt.Sprintf(".%s", VersionPrerelease) + } + + signaturePath := filepath.Join(configDir, "checkpoint_signature") + if c.DisableCheckpointSignature { + log.Printf("[INFO] Checkpoint signature disabled") + signaturePath = "" + } + + resp, err := checkpoint.Check(&checkpoint.CheckParams{ + Product: "terraform", + Version: version, + SignatureFile: signaturePath, + CacheFile: filepath.Join(configDir, "checkpoint_cache"), + }) + if err != nil { + log.Printf("[ERR] Checkpoint error: %s", err) + resp = nil + } + + checkpointResult <- resp +} + +// commandVersionCheck implements command.VersionCheckFunc and is used +// as the version checker. +func commandVersionCheck() (command.VersionCheckInfo, error) { + // Wait for the result to come through + info := <-checkpointResult + if info == nil { + var zero command.VersionCheckInfo + return zero, nil + } + + // Build the alerts that we may have received about our version + alerts := make([]string, len(info.Alerts)) + for i, a := range info.Alerts { + alerts[i] = a.Message + } + + return command.VersionCheckInfo{ + Outdated: info.Outdated, + Latest: info.CurrentVersion, + Alerts: alerts, + }, nil +} diff --git a/command/version.go b/command/version.go index 17015e604..729f55aa7 100644 --- a/command/version.go +++ b/command/version.go @@ -12,6 +12,20 @@ type VersionCommand struct { Revision string Version string VersionPrerelease string + CheckFunc VersionCheckFunc +} + +// 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 { @@ -20,7 +34,6 @@ func (c *VersionCommand) Help() string { func (c *VersionCommand) Run(args []string) int { var versionString bytes.Buffer - args = c.Meta.process(args, false) fmt.Fprintf(&versionString, "Terraform v%s", c.Version) @@ -33,6 +46,27 @@ func (c *VersionCommand) Run(args []string) int { } c.Ui.Output(versionString.String()) + + // If we have a version check function, then let's check for + // the latest version as well. + if c.CheckFunc != nil { + // Separate the prior output with a newline + c.Ui.Output("") + + // Check the latest version + info, err := c.CheckFunc() + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error checking latest version: %s", err)) + } + if info.Outdated { + c.Ui.Output(fmt.Sprintf( + "Your version of Terraform is out of date! The latest version\n"+ + "is %s. You can update by downloading from www.terraform.io", + info.Latest)) + } + } + return 0 } diff --git a/commands.go b/commands.go index b58011f1f..7c8a06eb2 100644 --- a/commands.go +++ b/commands.go @@ -96,6 +96,7 @@ func init() { Revision: GitCommit, Version: Version, VersionPrerelease: VersionPrerelease, + CheckFunc: commandVersionCheck, }, nil }, } diff --git a/config.go b/config.go index 7260f05a2..583d7ddb2 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,9 @@ import ( type Config struct { Providers map[string]string Provisioners map[string]string + + DisableCheckpoint bool `hcl:"disable_checkpoint"` + DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"` } // BuiltinConfig is the built-in defaults for the configuration. These diff --git a/main.go b/main.go index 95eaab560..46f143ba5 100644 --- a/main.go +++ b/main.go @@ -92,6 +92,9 @@ func wrappedMain() int { return 1 } + // Run checkpoint + go runCheckpoint(&config) + // Make sure we clean up any managed plugins at the end of this defer plugin.CleanupClients()