diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index 32dea121..70995e55 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -25,6 +25,9 @@ func init() { rootCmd.PersistentFlags().StringP("log-level", "l", "info", "Log level") rootCmd.PersistentFlags().StringP("environment", "e", "", "Select an environment") + rootCmd.PersistentPreRun = checkVersionHook + rootCmd.PersistentPostRun = warnVersionHook + rootCmd.AddCommand( computeCmd, newCmd, @@ -50,6 +53,14 @@ func init() { viper.AutomaticEnv() } +func checkVersionHook(cmd *cobra.Command, args []string) { + go checkVersion() +} + +func warnVersionHook(cmd *cobra.Command, args []string) { + warnVersion() +} + func Execute() { var ( ctx = appcontext.Context() diff --git a/cmd/dagger/cmd/version.go b/cmd/dagger/cmd/version.go index 5d85b9e4..2faf3c68 100644 --- a/cmd/dagger/cmd/version.go +++ b/cmd/dagger/cmd/version.go @@ -1,27 +1,41 @@ package cmd import ( + "errors" "fmt" + "io/ioutil" + "net/http" + "os" "runtime" "runtime/debug" + "strings" + "time" + goVersion "github.com/hashicorp/go-version" "github.com/spf13/cobra" + "github.com/spf13/viper" ) const ( - defaultVersion = "devel" + defaultVersion = "devel" + outdatedMessage = "dagger binary is outdated, go to https://github.com/dagger/dagger/doc/update.md to update dagger." ) // set by goreleaser or other builder using // -ldflags='-X dagger.io/go/cmd/dagger/cmd.version=' var ( - version = defaultVersion + version = defaultVersion + versionMessage = "" ) +// Disable version hook here +// It can lead to a double check if --check flag is enable var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print dagger version", - Args: cobra.NoArgs, + Use: "version", + Short: "Print dagger version", + PreRun: nil, + PostRun: nil, + Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { if bi, ok := debug.ReadBuildInfo(); ok && version == defaultVersion { // No specific version provided via version @@ -31,5 +45,131 @@ var versionCmd = &cobra.Command{ version, runtime.GOOS, runtime.GOARCH, ) + + if check := viper.GetBool("check"); check != false { + upToDate, err := isVersionLatest() + if err != nil { + fmt.Println("error: could not check version.") + return + } + + if !upToDate { + fmt.Println(outdatedMessage) + } else { + fmt.Println("dagger is up to date.") + } + } }, } + +func init() { + versionCmd.Flags().Bool("check", false, "check if dagger is up to date") + + if err := viper.BindPFlags(versionCmd.Flags()); err != nil { + panic(err) + } +} + +func isCheckOutdated(path string) bool { + data, err := ioutil.ReadFile(path) + if err != nil { + return true + } + lastCheck, err := time.Parse(time.RFC3339, string(data)) + if err != nil { + return true + } + nextCheck := lastCheck.Add(24 * time.Hour) + return !time.Now().Before(nextCheck) +} + +func getVersion() (*goVersion.Version, error) { + if version != defaultVersion { + return goVersion.NewVersion(version) + } + + if build, ok := debug.ReadBuildInfo(); ok { + return goVersion.NewVersion(build.Main.Version) + } + return nil, errors.New("could not read dagger version") +} + +func getOnlineVersion(currentVersion *goVersion.Version) (*goVersion.Version, error) { + req, err := http.NewRequest("GET", "https://releases.dagger.io/dagger/latest_version", nil) + if err != nil { + return nil, err + } + + // dagger/ (; ) + agent := fmt.Sprintf("dagger/%s (%s; %s)", currentVersion.String(), runtime.GOOS, runtime.GOARCH) + req.Header.Set("User-Agent", agent) + req.Header.Set("X-Dagger-Version", currentVersion.String()) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + latestVersion := strings.TrimSuffix(string(data), "\n") + return goVersion.NewVersion(latestVersion) +} + +// Compare the binary version with the latest version online +func isVersionLatest() (bool, error) { + currentVersion, err := getVersion() + if err != nil { + return false, err + } + + latestVersion, err := getOnlineVersion(currentVersion) + if err != nil { + return false, err + } + + if currentVersion.LessThan(latestVersion) { + return false, nil + } + return true, nil +} + +func checkVersion() { + home, err := os.UserHomeDir() + if err != nil { + return + } + + daggerDirectory := home + "/.dagger" + if folder, err := os.Stat(daggerDirectory); !os.IsNotExist(err) { + if !folder.IsDir() { + return + } + + if !isCheckOutdated(daggerDirectory + "/version_check.txt") { + return + } + + // Check timestamp + upToDate, err := isVersionLatest() + if err != nil { + return + } + + if !upToDate { + versionMessage = outdatedMessage + } + + // Update check timestamps file + now := time.Now().Format(time.RFC3339) + ioutil.WriteFile(daggerDirectory+"/version_check.txt", []byte(now), 0644) + } +} + +func warnVersion() { + if versionMessage != "" { + fmt.Println(versionMessage) + } +} diff --git a/go.mod b/go.mod index b5aa5632..0dedc4dc 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/docker/distribution v2.7.1+incompatible github.com/emicklei/proto v1.9.0 // indirect github.com/google/uuid v1.2.0 + github.com/hashicorp/go-version v1.2.0 github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/moby/buildkit v0.8.2 diff --git a/go.sum b/go.sum index 7665ee52..b531fadf 100644 --- a/go.sum +++ b/go.sum @@ -587,6 +587,7 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=