diff --git a/cmd/dagger/cmd/root.go b/cmd/dagger/cmd/root.go index 1a01a811..c129e579 100644 --- a/cmd/dagger/cmd/root.go +++ b/cmd/dagger/cmd/root.go @@ -48,7 +48,8 @@ func init() { } } rootCmd.PersistentPostRun = func(*cobra.Command, []string) { - warnVersion() + warnDaggerVersion() + warnUniverseVersion() } rootCmd.AddCommand( diff --git a/cmd/dagger/cmd/version.go b/cmd/dagger/cmd/version.go index 7979e252..14cda040 100644 --- a/cmd/dagger/cmd/version.go +++ b/cmd/dagger/cmd/version.go @@ -1,12 +1,14 @@ package cmd import ( + "encoding/json" "fmt" "io/ioutil" "net/http" "os" "path" "runtime" + "sort" "strings" "time" @@ -14,22 +16,25 @@ import ( "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" + "go.dagger.io/dagger/mod" "go.dagger.io/dagger/version" "golang.org/x/term" ) const ( - versionFile = "~/.config/dagger/version-check" - versionURL = "https://releases.dagger.io/dagger/latest_version" + versionFile = "~/.config/dagger/version-check" + versionURL = "https://releases.dagger.io/dagger/latest_version" + universeTagsURL = "https://api.github.com/repos/dagger/universe/tags" ) var ( - versionMessage = "" + daggerVersionMessage = "" + universeVersionMessage = "" ) var versionCmd = &cobra.Command{ Use: "version", - Short: "Print dagger version", + Short: "Print dagger and universe version", // Disable version hook here to avoid double version check PersistentPreRun: func(*cobra.Command, []string) {}, PersistentPostRun: func(*cobra.Command, []string) {}, @@ -41,6 +46,9 @@ var versionCmd = &cobra.Command{ runtime.GOOS, runtime.GOARCH, ) + // TODO Display universe version + // How can I retrieve it if it's not vendor ? + if check := viper.GetBool("check"); check { versionFilePath, err := homedir.Expand(versionFile) if err != nil { @@ -49,15 +57,18 @@ var versionCmd = &cobra.Command{ _ = os.Remove(versionFilePath) checkVersion() - if !warnVersion() { + if !warnDaggerVersion() { fmt.Println("dagger is up to date.") } + if !warnUniverseVersion() { + fmt.Println("universe is up to date.") + } } }, } func init() { - versionCmd.Flags().Bool("check", false, "check if dagger is up to date") + versionCmd.Flags().Bool("check", false, "check if dagger and universe are up to date") if err := viper.BindPFlags(versionCmd.Flags()); err != nil { panic(err) @@ -87,7 +98,7 @@ func isCheckOutdated(path string) bool { return !time.Now().Before(nextCheck) } -func getLatestVersion(currentVersion *goVersion.Version) (*goVersion.Version, error) { +func getDaggerLatestVersion(currentVersion *goVersion.Version) (*goVersion.Version, error) { req, err := http.NewRequest("GET", versionURL, nil) if err != nil { return nil, err @@ -112,15 +123,113 @@ func getLatestVersion(currentVersion *goVersion.Version) (*goVersion.Version, er return goVersion.NewVersion(latestVersion) } -// Compare the binary version with the latest version online -// Return the latest version if current is outdated -func isVersionLatest() (string, error) { +// Compare dagger version with the latest release online +// Return the latest dagger version if current is outdated +func isDaggerVersionLatest() (string, error) { currentVersion, err := goVersion.NewVersion(version.Version) if err != nil { return "", err } - latestVersion, err := getLatestVersion(currentVersion) + latestVersion, err := getDaggerLatestVersion(currentVersion) + if err != nil { + return "", err + } + + if currentVersion.LessThan(latestVersion) { + return latestVersion.String(), nil + } + return "", nil +} + +// Call https://api.github.com/repos/dagger/universe/tags +func listUniverseTags() ([]string, error) { + req, err := http.NewRequest("GET", universeTagsURL, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var tagsDTO []struct { + Name string `json:"name"` + } + + err = json.Unmarshal(data, &tagsDTO) + if err != nil { + return nil, err + } + + // Reduce DTO to simple string array + tags := []string{} + for _, tag := range tagsDTO { + tags = append(tags, tag.Name) + } + + return tags, nil +} + +func getUniverseLatestVersion() (*goVersion.Version, error) { + tags, err := listUniverseTags() + if err != nil { + return nil, err + } + + // Get latest available version + constraint, err := goVersion.NewConstraint(mod.UniverseVersionConstraint) + if err != nil { + return nil, err + } + + // Retrieve the latest supported universe version + var versions []*goVersion.Version + for _, tag := range tags { + if !strings.HasPrefix(tag, "v") { + continue + } + + v, err := goVersion.NewVersion(tag) + if err != nil { + continue + } + + if constraint.Check(v) { + versions = append(versions, v) + } + } + + if len(versions) == 0 { + return nil, fmt.Errorf("universe repository has no version matching the required version") + } + + sort.Sort(sort.Reverse(goVersion.Collection(versions))) + return versions[0], nil +} + +func getUniverseCurrentVersion() (*goVersion.Version, error) { + // TODO Should be replaced with the current universe version + // How I can fetch it + return goVersion.NewVersion("0.1.0") +} + +// Compare the universe version with the latest version online +// Return the latest universe version if the current is outdated +func isUniverseVersionLatest() (string, error) { + currentVersion, err := getUniverseCurrentVersion() + if err != nil { + return "", err + } + + latestVersion, err := getUniverseLatestVersion() if err != nil { return "", err } @@ -154,14 +263,24 @@ func checkVersion() { return } - // Check timestamp - latestVersion, err := isVersionLatest() + // Check version + universeLatestVersion, err := isUniverseVersionLatest() if err != nil { return } - if latestVersion != "" { - versionMessage = fmt.Sprintf("\nA new version is available (%s), please go to https://github.com/dagger/dagger/doc/install.md for instructions.", latestVersion) + if universeLatestVersion != "" { + universeVersionMessage = fmt.Sprintf("A new version of universe is available (%s), please run 'dagger mod get github.com/dagger/universe/stdlib'", universeLatestVersion) + } + + // Check timestamp + daggerLatestVersion, err := isDaggerVersionLatest() + if err != nil { + return + } + + if daggerLatestVersion != "" { + daggerVersionMessage = fmt.Sprintf("\nA new version of dagger is available (%s), please go to https://github.com/dagger/dagger/doc/install.md for instructions.", daggerLatestVersion) } // Update check timestamps file @@ -169,8 +288,8 @@ func checkVersion() { ioutil.WriteFile(path.Join(versionFilePath), []byte(now), 0600) } -func warnVersion() bool { - if versionMessage == "" { +func warnDaggerVersion() bool { + if daggerVersionMessage == "" { return false } @@ -185,6 +304,15 @@ func warnVersion() bool { } // Print default message - fmt.Println(versionMessage) + fmt.Println(daggerVersionMessage) + return true +} + +func warnUniverseVersion() bool { + if universeVersionMessage == "" { + return false + } + + fmt.Println(universeVersionMessage) return true }