From 0405da4f91cd157db1c04188eba590d8f3a40833 Mon Sep 17 00:00:00 2001 From: vinay121314 <100485512+vinay121314@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:50:41 +0530 Subject: [PATCH 1/7] fix(cli): skip reinstall in rill upgrade when already on latest version Fixes #9984. rill upgrade now exits early when the current version is already the latest release. Adds --force to reinstall anyway. --- cli/cmd/upgrade/upgrade.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index d7805be3f289..911f3db1987b 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -1,6 +1,8 @@ package upgrade import ( + "fmt" + goversion "github.com/hashicorp/go-version" "github.com/rilldata/rill/cli/pkg/cmdutil" "github.com/rilldata/rill/cli/pkg/installscript" @@ -10,6 +12,7 @@ import ( func UpgradeCmd(ch *cmdutil.Helper) *cobra.Command { var version string var nightly bool + var force bool upgradeCmd := &cobra.Command{ Use: "upgrade", @@ -31,12 +34,47 @@ func UpgradeCmd(ch *cmdutil.Helper) *cobra.Command { return installscript.Install(cmd.Context(), "nightly") } + // Skip the download if the current version is already the latest release. + if !force { + upToDate, latest, err := isUpToDate(cmd, ch) + if err == nil && upToDate { + fmt.Printf("Rill is already up to date (%s). Use --force to reinstall.\n", latest) + return nil + } + } + return installscript.Install(cmd.Context(), "") }, } upgradeCmd.Flags().StringVar(&version, "version", "", "Install a specific version of Rill") upgradeCmd.Flags().BoolVar(&nightly, "nightly", false, "Install the latest nightly build") + upgradeCmd.Flags().BoolVar(&force, "force", false, "Reinstall even if the current version is already the latest") return upgradeCmd } + +// isUpToDate reports whether the running CLI version is greater than or equal to the latest released version. +// It returns false (without error) for development builds, where the current version is unknown. +func isUpToDate(cmd *cobra.Command, ch *cmdutil.Helper) (bool, string, error) { + if ch.Version.Number == "" { + return false, "", nil + } + + latest, err := ch.LatestVersion(cmd.Context()) + if err != nil { + return false, "", err + } + + current, err := goversion.NewVersion(ch.Version.Number) + if err != nil { + return false, "", err + } + + latestV, err := goversion.NewVersion(latest) + if err != nil { + return false, "", err + } + + return current.GreaterThanOrEqual(latestV), latest, nil +} From ae6d36701f2a691de9e0e011dbc90b276a8c549f Mon Sep 17 00:00:00 2001 From: vinay121314 <100485512+vinay121314@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:56:29 +0530 Subject: [PATCH 2/7] test(cli): add unit tests for versionUpToDate --- cli/cmd/upgrade/upgrade_test.go | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 cli/cmd/upgrade/upgrade_test.go diff --git a/cli/cmd/upgrade/upgrade_test.go b/cli/cmd/upgrade/upgrade_test.go new file mode 100644 index 000000000000..5a93ec9fc2db --- /dev/null +++ b/cli/cmd/upgrade/upgrade_test.go @@ -0,0 +1,38 @@ +package upgrade + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestVersionUpToDate(t *testing.T) { + tests := []struct { + name string + current string + latest string + want bool + wantErr bool + }{ + {name: "same version", current: "v0.90.2", latest: "v0.90.2", want: true}, + {name: "same version without v prefix", current: "0.90.2", latest: "v0.90.2", want: true}, + {name: "older patch", current: "v0.90.1", latest: "v0.90.2", want: false}, + {name: "older minor", current: "v0.89.5", latest: "v0.90.0", want: false}, + {name: "newer than latest", current: "v0.91.0", latest: "v0.90.2", want: true}, + {name: "prerelease is older than release", current: "v0.90.2-rc1", latest: "v0.90.2", want: false}, + {name: "invalid current", current: "dev", latest: "v0.90.2", wantErr: true}, + {name: "invalid latest", current: "v0.90.2", latest: "", wantErr: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := versionUpToDate(tt.current, tt.latest) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} From 2bd174c73a20a94510ee782f8e9a0a19a06b24b6 Mon Sep 17 00:00:00 2001 From: vinay121314 <100485512+vinay121314@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:57:35 +0530 Subject: [PATCH 3/7] refactor(cli): extract versionUpToDate for testability --- cli/cmd/upgrade/upgrade.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index 911f3db1987b..b9f8d6bca7b7 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -66,15 +66,25 @@ func isUpToDate(cmd *cobra.Command, ch *cmdutil.Helper) (bool, string, error) { return false, "", err } - current, err := goversion.NewVersion(ch.Version.Number) + upToDate, err := versionUpToDate(ch.Version.Number, latest) if err != nil { return false, "", err } + return upToDate, latest, nil +} + +// versionUpToDate reports whether current is greater than or equal to latest. +func versionUpToDate(current, latest string) (bool, error) { + currentV, err := goversion.NewVersion(current) + if err != nil { + return false, err + } + latestV, err := goversion.NewVersion(latest) if err != nil { - return false, "", err + return false, err } - return current.GreaterThanOrEqual(latestV), latest, nil + return currentV.GreaterThanOrEqual(latestV), nil } From 2ff83680879fac2131d944c31d52d2d7de2669d0 Mon Sep 17 00:00:00 2001 From: vinay121314 <100485512+vinay121314@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:26:58 +0530 Subject: [PATCH 4/7] fix(cli): recheck latest version when the cache says we're up to date --- cli/cmd/upgrade/upgrade.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index b9f8d6bca7b7..6da1dca35b20 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -71,6 +71,20 @@ func isUpToDate(cmd *cobra.Command, ch *cmdutil.Helper) (bool, string, error) { return false, "", err } + // The cached latest version can be stale. Only trust it when it says an upgrade is needed; + // otherwise refetch from Github so a new release is not missed. + if upToDate { + latest, err = ch.RefreshLatestVersion(cmd.Context()) + if err != nil { + return false, "", err + } + + upToDate, err = versionUpToDate(ch.Version.Number, latest) + if err != nil { + return false, "", err + } + } + return upToDate, latest, nil } From b33668c12842eb60128afcf1be88707228d0733a Mon Sep 17 00:00:00 2001 From: vinay121314 <100485512+vinay121314@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:29:41 +0530 Subject: [PATCH 5/7] feat(cli): add RefreshLatestVersion to fetch the latest release without the cache --- cli/pkg/cmdutil/update.go | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/cli/pkg/cmdutil/update.go b/cli/pkg/cmdutil/update.go index 450737584d8c..6fe8807d7944 100644 --- a/cli/pkg/cmdutil/update.go +++ b/cli/pkg/cmdutil/update.go @@ -56,6 +56,166 @@ func (h *Helper) CheckVersion(ctx context.Context) error { return nil } +// LatestVersion returns the latest available version of Rill (cached for up to 24 hours). +func (h *Helper) LatestVersion(ctx context.Context) (string, error) { + cachedVersion, err := h.DotRill.GetVersion() + if err != nil { + return "", err + } + + cachedVersionUpdatedAt, err := h.DotRill.GetVersionUpdatedAt() + if err != nil { + return "", err + } + + if cachedVersion != "" && cachedVersionUpdatedAt != "" { + updatedAt, err := time.Parse(time.RFC3339, cachedVersionUpdatedAt) + if err != nil { + // Set versionTs as empty if any parse errors + _ = h.DotRill.SetVersionUpdatedAt("") + return "", err + } + + if time.Since(updatedAt) < versionCheckTTL { + return cachedVersion, nil + } + } + + // Check with latest release on github if cached version is not available + return h.RefreshLatestVersion(ctx) +} + +// RefreshLatestVersion fetches the latest available version of Rill from Github, bypassing the cache, and updates the cache. +func (h *Helper) RefreshLatestVersion(ctx context.Context) (string, error) { + info, err := fetchLatestVersion(ctx) + if err != nil { + return "", err + } + + err = h.DotRill.SetVersionUpdatedAt(time.Now().Format(time.RFC3339)) + if err != nil { + return "", err + } + + err = h.DotRill.SetVersion(info.Version) + if err != nil { + return "", err + } + + return info.Version, nil +} + +// githubReleaseInfo represents information about a release. +type githubReleaseInfo struct { + Version string `json:"tag_name"` + URL string `json:"html_url"` + PublishedAt time.Time `json:"published_at"` +} + +// fetchLatestVersion fetches the latest version of Rill from Github releases. +func fetchLatestVersion(ctx context.Context) (*githubReleaseInfo, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionCheckURI, http.NoBody) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/vnd.github.v3+json") + + getToken := func() string { + if t := os.Getenv("GH_TOKEN"); t != "" { + return t + } + return os.Getenv("GITHUB_TOKEN") + } + + if token := getToken(); token != "" { + req.Header.Set("Authorization", fmt.Sprintf("token %s", token)) + } + + client := &http.Client{Timeout: time.Second * 15} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + out, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + success := resp.StatusCode >= 200 && resp.StatusCode < 300 + if !success { + return nil, fmt.Errorf("error fetching latest release: %s", string(out)) + } + + var info *githubReleaseInfo + err = json.Unmarshal(out, &info) + if err != nil { + return nil, err + } + + return info, nil +} +package cmdutil + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/fatih/color" + "github.com/hashicorp/go-version" +) + +const ( + versionCheckURI = "https://api.github.com/repos/rilldata/rill/releases/latest" + versionCheckTTL = 24 * time.Hour +) + +func (h *Helper) CheckVersion(ctx context.Context) error { + // Check if build from source + if h.Version.Number == "" { + return nil + } + + // Add a timeout + ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + + latestVersion, err := h.LatestVersion(ctx) + if err != nil { + return err + } + + v1, err := version.NewVersion(h.Version.Number) + if err != nil { + return err + } + + v2, err := version.NewVersion(latestVersion) + if err != nil { + // Set version as empty if any parse errors + _ = h.DotRill.SetVersion("") + return err + } + + if v1.LessThan(v2) { + fmt.Printf("%s %s → %s\n\n", + color.YellowString("A new version of rill is available (run `rill upgrade`):"), + color.CyanString(h.Version.Number), + color.CyanString(latestVersion)) + return nil + } + + return nil +} + // LatestVersion returns the latest available version of Rill (cached for up to 24 hours). func (h *Helper) LatestVersion(ctx context.Context) (string, error) { cachedVersion, err := h.DotRill.GetVersion() From 4f2a5c37857086fbee84a0a688c8ee776859b9ce Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:24:05 +0530 Subject: [PATCH 6/7] review --- cli/cmd/upgrade/upgrade.go | 50 +++-------- cli/cmd/upgrade/upgrade_test.go | 2 + cli/pkg/cmdutil/update.go | 155 -------------------------------- 3 files changed, 12 insertions(+), 195 deletions(-) diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index 6da1dca35b20..1c0af37491b5 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -1,8 +1,6 @@ package upgrade import ( - "fmt" - goversion "github.com/hashicorp/go-version" "github.com/rilldata/rill/cli/pkg/cmdutil" "github.com/rilldata/rill/cli/pkg/installscript" @@ -35,10 +33,12 @@ func UpgradeCmd(ch *cmdutil.Helper) *cobra.Command { } // Skip the download if the current version is already the latest release. - if !force { - upToDate, latest, err := isUpToDate(cmd, ch) - if err == nil && upToDate { - fmt.Printf("Rill is already up to date (%s). Use --force to reinstall.\n", latest) + if !force && !ch.IsDev() { + latest, err := ch.RefreshLatestVersion(cmd.Context()) + if err != nil { + ch.PrintfWarn("Could not check latest version: %v\n", err) + } else if upToDate, err := versionUpToDate(ch.Version.Number, latest); err == nil && upToDate { + ch.Printf("Rill is already up to date (%s). Use --force to reinstall.\n", latest) return nil } } @@ -54,46 +54,16 @@ func UpgradeCmd(ch *cmdutil.Helper) *cobra.Command { return upgradeCmd } -// isUpToDate reports whether the running CLI version is greater than or equal to the latest released version. -// It returns false (without error) for development builds, where the current version is unknown. -func isUpToDate(cmd *cobra.Command, ch *cmdutil.Helper) (bool, string, error) { - if ch.Version.Number == "" { - return false, "", nil - } - - latest, err := ch.LatestVersion(cmd.Context()) - if err != nil { - return false, "", err - } - - upToDate, err := versionUpToDate(ch.Version.Number, latest) - if err != nil { - return false, "", err - } - - // The cached latest version can be stale. Only trust it when it says an upgrade is needed; - // otherwise refetch from Github so a new release is not missed. - if upToDate { - latest, err = ch.RefreshLatestVersion(cmd.Context()) - if err != nil { - return false, "", err - } - - upToDate, err = versionUpToDate(ch.Version.Number, latest) - if err != nil { - return false, "", err - } - } - - return upToDate, latest, nil -} - // versionUpToDate reports whether current is greater than or equal to latest. +// Prerelease builds (e.g. nightly) are never up to date, so upgrading moves them to the latest stable release. func versionUpToDate(current, latest string) (bool, error) { currentV, err := goversion.NewVersion(current) if err != nil { return false, err } + if currentV.Prerelease() != "" { + return false, nil + } latestV, err := goversion.NewVersion(latest) if err != nil { diff --git a/cli/cmd/upgrade/upgrade_test.go b/cli/cmd/upgrade/upgrade_test.go index 5a93ec9fc2db..d13421bfcb22 100644 --- a/cli/cmd/upgrade/upgrade_test.go +++ b/cli/cmd/upgrade/upgrade_test.go @@ -20,6 +20,8 @@ func TestVersionUpToDate(t *testing.T) { {name: "older minor", current: "v0.89.5", latest: "v0.90.0", want: false}, {name: "newer than latest", current: "v0.91.0", latest: "v0.90.2", want: true}, {name: "prerelease is older than release", current: "v0.90.2-rc1", latest: "v0.90.2", want: false}, + {name: "nightly is never up to date", current: "v0.91.0-nightly", latest: "v0.90.2", want: false}, + {name: "prerelease ahead of release", current: "v0.91.0-rc1", latest: "v0.90.2", want: false}, {name: "invalid current", current: "dev", latest: "v0.90.2", wantErr: true}, {name: "invalid latest", current: "v0.90.2", latest: "", wantErr: true}, } diff --git a/cli/pkg/cmdutil/update.go b/cli/pkg/cmdutil/update.go index 6fe8807d7944..e03598228cd1 100644 --- a/cli/pkg/cmdutil/update.go +++ b/cli/pkg/cmdutil/update.go @@ -158,158 +158,3 @@ func fetchLatestVersion(ctx context.Context) (*githubReleaseInfo, error) { return info, nil } -package cmdutil - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "time" - - "github.com/fatih/color" - "github.com/hashicorp/go-version" -) - -const ( - versionCheckURI = "https://api.github.com/repos/rilldata/rill/releases/latest" - versionCheckTTL = 24 * time.Hour -) - -func (h *Helper) CheckVersion(ctx context.Context) error { - // Check if build from source - if h.Version.Number == "" { - return nil - } - - // Add a timeout - ctx, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - - latestVersion, err := h.LatestVersion(ctx) - if err != nil { - return err - } - - v1, err := version.NewVersion(h.Version.Number) - if err != nil { - return err - } - - v2, err := version.NewVersion(latestVersion) - if err != nil { - // Set version as empty if any parse errors - _ = h.DotRill.SetVersion("") - return err - } - - if v1.LessThan(v2) { - fmt.Printf("%s %s → %s\n\n", - color.YellowString("A new version of rill is available (run `rill upgrade`):"), - color.CyanString(h.Version.Number), - color.CyanString(latestVersion)) - return nil - } - - return nil -} - -// LatestVersion returns the latest available version of Rill (cached for up to 24 hours). -func (h *Helper) LatestVersion(ctx context.Context) (string, error) { - cachedVersion, err := h.DotRill.GetVersion() - if err != nil { - return "", err - } - - cachedVersionUpdatedAt, err := h.DotRill.GetVersionUpdatedAt() - if err != nil { - return "", err - } - - if cachedVersion != "" && cachedVersionUpdatedAt != "" { - updatedAt, err := time.Parse(time.RFC3339, cachedVersionUpdatedAt) - if err != nil { - // Set versionTs as empty if any parse errors - _ = h.DotRill.SetVersionUpdatedAt("") - return "", err - } - - if time.Since(updatedAt) < versionCheckTTL { - return cachedVersion, nil - } - } - - // Check with latest release on github if cached version is not available - info, err := fetchLatestVersion(ctx) - if err != nil { - return "", err - } - - err = h.DotRill.SetVersionUpdatedAt(time.Now().Format(time.RFC3339)) - if err != nil { - return "", err - } - - err = h.DotRill.SetVersion(info.Version) - if err != nil { - return "", err - } - - return info.Version, nil -} - -// githubReleaseInfo represents information about a release. -type githubReleaseInfo struct { - Version string `json:"tag_name"` - URL string `json:"html_url"` - PublishedAt time.Time `json:"published_at"` -} - -// fetchLatestVersion fetches the latest version of Rill from Github releases. -func fetchLatestVersion(ctx context.Context) (*githubReleaseInfo, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, versionCheckURI, http.NoBody) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - req.Header.Set("Accept", "application/vnd.github.v3+json") - - getToken := func() string { - if t := os.Getenv("GH_TOKEN"); t != "" { - return t - } - return os.Getenv("GITHUB_TOKEN") - } - - if token := getToken(); token != "" { - req.Header.Set("Authorization", fmt.Sprintf("token %s", token)) - } - - client := &http.Client{Timeout: time.Second * 15} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - out, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - success := resp.StatusCode >= 200 && resp.StatusCode < 300 - if !success { - return nil, fmt.Errorf("error fetching latest release: %s", string(out)) - } - - var info *githubReleaseInfo - err = json.Unmarshal(out, &info) - if err != nil { - return nil, err - } - - return info, nil -} From ac03cb5810ea954eb5385f6744564dfc2751d5c9 Mon Sep 17 00:00:00 2001 From: Anshul Khandelwal <12948312+k-anshul@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:24:49 +0530 Subject: [PATCH 7/7] move check to install script --- cli/cmd/upgrade/upgrade.go | 38 +++----------------- cli/cmd/upgrade/upgrade_test.go | 40 --------------------- cli/pkg/cmdutil/update.go | 5 --- cli/pkg/installscript/installscript.go | 13 ++++--- docs/docs/developers/get-started/install.md | 5 +++ docs/docs/reference/cli/upgrade.md | 1 + scripts/install.sh | 24 ++++++++++--- 7 files changed, 38 insertions(+), 88 deletions(-) delete mode 100644 cli/cmd/upgrade/upgrade_test.go diff --git a/cli/cmd/upgrade/upgrade.go b/cli/cmd/upgrade/upgrade.go index 1c0af37491b5..06a17f7341bf 100644 --- a/cli/cmd/upgrade/upgrade.go +++ b/cli/cmd/upgrade/upgrade.go @@ -25,50 +25,20 @@ func UpgradeCmd(ch *cmdutil.Helper) *cobra.Command { } version = "v" + v.String() - return installscript.Install(cmd.Context(), version) + return installscript.Install(cmd.Context(), version, force) } if nightly { - return installscript.Install(cmd.Context(), "nightly") + return installscript.Install(cmd.Context(), "nightly", force) } - // Skip the download if the current version is already the latest release. - if !force && !ch.IsDev() { - latest, err := ch.RefreshLatestVersion(cmd.Context()) - if err != nil { - ch.PrintfWarn("Could not check latest version: %v\n", err) - } else if upToDate, err := versionUpToDate(ch.Version.Number, latest); err == nil && upToDate { - ch.Printf("Rill is already up to date (%s). Use --force to reinstall.\n", latest) - return nil - } - } - - return installscript.Install(cmd.Context(), "") + return installscript.Install(cmd.Context(), "", force) }, } upgradeCmd.Flags().StringVar(&version, "version", "", "Install a specific version of Rill") upgradeCmd.Flags().BoolVar(&nightly, "nightly", false, "Install the latest nightly build") - upgradeCmd.Flags().BoolVar(&force, "force", false, "Reinstall even if the current version is already the latest") + upgradeCmd.Flags().BoolVar(&force, "force", false, "Reinstall even if the latest version is already installed") return upgradeCmd } - -// versionUpToDate reports whether current is greater than or equal to latest. -// Prerelease builds (e.g. nightly) are never up to date, so upgrading moves them to the latest stable release. -func versionUpToDate(current, latest string) (bool, error) { - currentV, err := goversion.NewVersion(current) - if err != nil { - return false, err - } - if currentV.Prerelease() != "" { - return false, nil - } - - latestV, err := goversion.NewVersion(latest) - if err != nil { - return false, err - } - - return currentV.GreaterThanOrEqual(latestV), nil -} diff --git a/cli/cmd/upgrade/upgrade_test.go b/cli/cmd/upgrade/upgrade_test.go deleted file mode 100644 index d13421bfcb22..000000000000 --- a/cli/cmd/upgrade/upgrade_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package upgrade - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestVersionUpToDate(t *testing.T) { - tests := []struct { - name string - current string - latest string - want bool - wantErr bool - }{ - {name: "same version", current: "v0.90.2", latest: "v0.90.2", want: true}, - {name: "same version without v prefix", current: "0.90.2", latest: "v0.90.2", want: true}, - {name: "older patch", current: "v0.90.1", latest: "v0.90.2", want: false}, - {name: "older minor", current: "v0.89.5", latest: "v0.90.0", want: false}, - {name: "newer than latest", current: "v0.91.0", latest: "v0.90.2", want: true}, - {name: "prerelease is older than release", current: "v0.90.2-rc1", latest: "v0.90.2", want: false}, - {name: "nightly is never up to date", current: "v0.91.0-nightly", latest: "v0.90.2", want: false}, - {name: "prerelease ahead of release", current: "v0.91.0-rc1", latest: "v0.90.2", want: false}, - {name: "invalid current", current: "dev", latest: "v0.90.2", wantErr: true}, - {name: "invalid latest", current: "v0.90.2", latest: "", wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := versionUpToDate(tt.current, tt.latest) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - require.Equal(t, tt.want, got) - }) - } -} diff --git a/cli/pkg/cmdutil/update.go b/cli/pkg/cmdutil/update.go index e03598228cd1..450737584d8c 100644 --- a/cli/pkg/cmdutil/update.go +++ b/cli/pkg/cmdutil/update.go @@ -82,11 +82,6 @@ func (h *Helper) LatestVersion(ctx context.Context) (string, error) { } // Check with latest release on github if cached version is not available - return h.RefreshLatestVersion(ctx) -} - -// RefreshLatestVersion fetches the latest available version of Rill from Github, bypassing the cache, and updates the cache. -func (h *Helper) RefreshLatestVersion(ctx context.Context) (string, error) { info, err := fetchLatestVersion(ctx) if err != nil { return "", err diff --git a/cli/pkg/installscript/installscript.go b/cli/pkg/installscript/installscript.go index a997405593f2..5431b8e67063 100644 --- a/cli/pkg/installscript/installscript.go +++ b/cli/pkg/installscript/installscript.go @@ -9,15 +9,19 @@ import ( "os/exec" ) -func Install(ctx context.Context, version string) error { - return execScript(ctx, version, "--version", version) +func Install(ctx context.Context, version string, force bool) error { + var env []string + if force { + env = append(os.Environ(), "RILL_INSTALL_FORCE=1") + } + return execScript(ctx, version, env, "--version", version) } func Uninstall(ctx context.Context) error { - return execScript(ctx, "", "--uninstall") + return execScript(ctx, "", nil, "--uninstall") } -func execScript(ctx context.Context, version string, args ...string) error { +func execScript(ctx context.Context, version string, env []string, args ...string) error { script, err := createScriptFile(ctx, version) if err != nil { return err @@ -26,6 +30,7 @@ func execScript(ctx context.Context, version string, args ...string) error { scriptArgs := append([]string{script}, args...) cmd := exec.CommandContext(ctx, "/bin/sh", scriptArgs...) + cmd.Env = env cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/docs/docs/developers/get-started/install.md b/docs/docs/developers/get-started/install.md index 2758ffbb3762..8aefcd710193 100644 --- a/docs/docs/developers/get-started/install.md +++ b/docs/docs/developers/get-started/install.md @@ -44,6 +44,11 @@ To ensure you're on the latest version of Rill and enjoying all of our newest fe rill upgrade ``` +If the latest version is already installed, the upgrade is skipped. To reinstall anyway, run `rill upgrade --force`, or set `RILL_INSTALL_FORCE=1` when using the installation script: +```bash +curl https://rill.sh | RILL_INSTALL_FORCE=1 sh +``` + :::info What about Rill Cloud? Rill Cloud is always on the latest stable version of Rill. To check the latest version available, please see our [Releases](https://github.com/rilldata/rill/releases) page. diff --git a/docs/docs/reference/cli/upgrade.md b/docs/docs/reference/cli/upgrade.md index 1d8382b2c1c4..072ddc7cada7 100644 --- a/docs/docs/reference/cli/upgrade.md +++ b/docs/docs/reference/cli/upgrade.md @@ -13,6 +13,7 @@ rill upgrade [flags] ### Flags ``` + --force Reinstall even if the latest version is already installed --nightly Install the latest nightly build --version string Install a specific version of Rill ``` diff --git a/scripts/install.sh b/scripts/install.sh index 063ad8fbaf83..ea0ba1136b26 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -62,11 +62,6 @@ sha256Verify() { # Download the binary and check the integrity using the SHA256 checksum downloadBinary() { CDN="cdn.rilldata.com" - - LATEST_URL="https://${CDN}/rill/latest.txt" - if [ "${VERSION}" = "latest" ]; then - VERSION=$(curl --silent --show-error "${LATEST_URL}") - fi BINARY_URL="https://${CDN}/rill/${VERSION}/rill_${PLATFORM}.zip" CHECKSUM_URL="https://${CDN}/rill/${VERSION}/checksums.txt" @@ -91,6 +86,24 @@ downloadBinary() { unzip -q "rill_${PLATFORM}.zip" } +# Resolve 'latest' to a concrete version and exit if it is already installed. +resolveLatestVersion() { + if [ "${VERSION}" != "latest" ]; then + return + fi + VERSION=$(curl --silent --show-error "https://cdn.rilldata.com/rill/latest.txt") + + if [ -n "$RILL_INSTALL_FORCE" ] || ! [ -x "$INSTALL_DIR/rill" ]; then + return + fi + # Output format: 'rill version v0.90.3 (build commit: ... date: ...)' + INSTALLED_VERSION=$("$INSTALL_DIR"/rill version 2>/dev/null | cut -d' ' -f3) || true + if [ "$INSTALLED_VERSION" = "$VERSION" ]; then + printf "Rill %s is already installed. To reinstall, run 'rill upgrade --force' or set RILL_INSTALL_FORCE=1.\n" "$VERSION" + exit 0 + fi +} + # Print install options printInstallOptions() { printf "\nWhere would you like to install rill? (Default [1])\n\n" @@ -298,6 +311,7 @@ installRill() { checkDependencies initPlatform resolveInstallDir + resolveLatestVersion initTmpDir downloadBinary installBinary