diff --git a/cmd/dagger/cmd/mod/get.go b/cmd/dagger/cmd/mod/get.go index 20df6180..bcc48d15 100644 --- a/cmd/dagger/cmd/mod/get.go +++ b/cmd/dagger/cmd/mod/get.go @@ -26,8 +26,7 @@ var getCmd = &cobra.Command{ ctx := lg.WithContext(cmd.Context()) project := common.CurrentProject(ctx) - st := common.CurrentEnvironmentState(ctx, project) - doneCh := common.TrackProjectCommand(ctx, cmd, project, st, &telemetry.Property{ + doneCh := common.TrackProjectCommand(ctx, cmd, project, nil, &telemetry.Property{ Name: "packages", Value: args, }) 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..3d62b841 100644 --- a/cmd/dagger/cmd/version.go +++ b/cmd/dagger/cmd/version.go @@ -1,46 +1,68 @@ package cmd import ( + "context" + "encoding/json" "fmt" "io/ioutil" "net/http" "os" "path" "runtime" + "sort" "strings" "time" goVersion "github.com/hashicorp/go-version" "github.com/mitchellh/go-homedir" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "go.dagger.io/dagger/cmd/dagger/cmd/common" + "go.dagger.io/dagger/mod" + "go.dagger.io/dagger/telemetry" "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) {}, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + + project := common.CurrentProject(ctx) + doneCh := common.TrackProjectCommand(ctx, cmd, project, nil, &telemetry.Property{ + Name: "version", + Value: args, + }) + fmt.Printf("dagger %s (%s) %s/%s\n", version.Version, version.Revision, runtime.GOOS, runtime.GOARCH, ) + universeVersion, err := getUniverseCurrentVersion() + if err == nil { + fmt.Printf("universe %s\n", universeVersion.Original()) + } + if check := viper.GetBool("check"); check { versionFilePath, err := homedir.Expand(versionFile) if err != nil { @@ -49,15 +71,20 @@ 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.") + } } + + <-doneCh }, } 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 +114,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 +139,136 @@ 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 +} + +// Retrieve the current universe version from `cue.mod/dagger.mod` +func getUniverseCurrentVersion() (*goVersion.Version, error) { + project := common.CurrentProject(context.Background()) + pathMod := path.Join(project.Path, mod.ModFilePath) + fileMod, err := os.Open(pathMod) + if err != nil { + return nil, err + } + + defer fileMod.Close() + data, err := ioutil.ReadAll(fileMod) + if err != nil { + return nil, err + } + + currentVersion := "" + modules := strings.Split(string(data), "\n") + for _, module := range modules { + if !strings.HasPrefix(module, "alpha.dagger.io") { + continue + } + + // Retrieve tag + tag := strings.Split(module, " ") + currentVersion = tag[1] + } + return goVersion.NewVersion(currentVersion) +} + +// 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 } @@ -132,8 +280,11 @@ func isVersionLatest() (string, error) { } func checkVersion() { + lg := log.Ctx(context.Background()).With().Logger() + if version.Version == version.DevelopmentVersion { // running devel version + lg.Debug().Msg("version checking ignored on development version") return } @@ -154,14 +305,28 @@ func checkVersion() { return } - // Check timestamp - latestVersion, err := isVersionLatest() + // Check version + lg.Debug().Msg("check for universe latest version...") + universeLatestVersion, err := isUniverseVersionLatest() if err != nil { + lg.Debug().Msg(err.Error()) 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 + lg.Debug().Msg("check for dagger latest version...") + daggerLatestVersion, err := isDaggerVersionLatest() + if err != nil { + lg.Debug().Msg(err.Error()) + 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 +334,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 +350,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 } diff --git a/mod/file.go b/mod/file.go index 1cd4d89b..67a4f9ed 100644 --- a/mod/file.go +++ b/mod/file.go @@ -17,7 +17,7 @@ import ( ) const ( - modFilePath = "./cue.mod/dagger.mod" + ModFilePath = "./cue.mod/dagger.mod" sumFilePath = "./cue.mod/dagger.sum" lockFilePath = "./cue.mod/dagger.lock" destBasePath = "./cue.mod/pkg" @@ -31,7 +31,7 @@ type file struct { } func readPath(workspacePath string) (*file, error) { - pMod := path.Join(workspacePath, modFilePath) + pMod := path.Join(workspacePath, ModFilePath) fMod, err := os.Open(pMod) if err != nil { if !errors.Is(err, fs.ErrNotExist) { @@ -276,7 +276,7 @@ func (f *file) write() error { bMod.WriteString(fmt.Sprintf("%s %s\n", r.fullPath(), r.version)) } - err := ioutil.WriteFile(path.Join(f.workspacePath, modFilePath), bMod.Bytes(), 0600) + err := ioutil.WriteFile(path.Join(f.workspacePath, ModFilePath), bMod.Bytes(), 0600) if err != nil { return err }