Skip to content

Commit 99978ee

Browse files
authored
feat: support for automatically updating to prereleases (#147)
* feat: support for automatically updating to prereleases * fix: skip non pre-releases in pre-release filter
1 parent 6b980c2 commit 99978ee

File tree

6 files changed

+99
-46
lines changed

6 files changed

+99
-46
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Makefile for runtipi CLI
22

3+
# Build values
4+
VERSION := nightly
5+
COMMIT := $(shell git rev-parse HEAD)
6+
DATE := $(shell date +%Y-%m-%dT%H:%M:%S%z)
7+
38
# Define the root folder path
49
ROOT_FOLDER_HOST := ~/temp/runtipi
510

611
# Main target that creates the directory and runs the program
712
.PHONY: run
813
run:
914
@mkdir -p $(ROOT_FOLDER_HOST)
10-
@ROOT_FOLDER_HOST=$(ROOT_FOLDER_HOST) go run -ldflags="-X main.version=nightly -X main.commit=$(git rev-parse HEAD) -X main.buildDate=$(date +%Y-%m-%dT%H:%M:%S%z)" cmd/runtipi/main.go $(ARGS)
15+
@ROOT_FOLDER_HOST=$(ROOT_FOLDER_HOST) go run -ldflags="-X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.buildDate=$(DATE)" cmd/runtipi/main.go $(ARGS)
1116

1217
# Build the program
1318
.PHONY: build

cmd/runtipi/main.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import (
1313
)
1414

1515
var (
16-
version string
17-
commit string
18-
buildDate string
16+
version = "dev"
17+
commit = "unknown"
18+
buildDate = "unknown"
1919
)
2020

2121
func init() {
@@ -45,16 +45,6 @@ func init() {
4545
config.RootFolder = envRootFolder
4646
}
4747

48-
if version == "" {
49-
version = "dev"
50-
}
51-
if commit == "" {
52-
commit = "unknown"
53-
}
54-
if buildDate == "" {
55-
buildDate = "unknown"
56-
}
57-
5848
config.Info = config.AppInfo{
5949
Version: version,
6050
Commit: commit,

internal/commands/debug.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ func RunDebug() {
8383
envExists = color.YellowString("Yes")
8484
}
8585

86-
fmt.Printf("\n--- %s ---\n", color.BlueString("Tipi configuration"))
86+
fmt.Printf("\n--- %s ---\n", color.BlueString("Runtipi configuration"))
8787
configTable := tablewriter.NewWriter(os.Stdout)
88-
configTable.Append([]string{"Custom tipi docker config", configExists})
88+
configTable.Append([]string{"Custom runtipi docker config", configExists})
8989
configTable.Append([]string{"Custom environment file", envExists})
9090
configTable.Render()
9191

internal/commands/update.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,51 @@ import (
1313
"github.com/runtipi/cli/internal/utils"
1414
)
1515

16+
const PreReleaseWarning = "You are updating to pre-release version which may contain bugs, we are not responsible for any issues that may arise"
17+
1618
func RunUpdate(args types.UpdateArgs) {
1719
spin := components.NewSpinner("")
1820
spin.SetMessage("Grabbing releases from GitHub")
1921

2022
var wantedVersion string
21-
if args.Version.IsLatest() {
22-
latest, err := utils.GetLatestRelease()
23+
24+
switch args.Version.String() {
25+
case "latest":
26+
latest, err := utils.GetReleases("https://api.github.com/repos/runtipi/runtipi/releases/latest")
2327
if err != nil {
2428
spin.Fail("Failed to fetch latest release")
2529
spin.Finish()
2630
fmt.Printf("\nError: %v\n", err)
2731
return
2832
}
29-
wantedVersion = latest
30-
} else if args.Version.IsNightly() {
33+
if len(latest) == 0 {
34+
spin.Fail("Failed to fetch latest release")
35+
spin.Finish()
36+
fmt.Printf("\nError: No releases found\n")
37+
return
38+
}
39+
wantedVersion = latest[0].TagName
40+
case "nightly":
41+
spin.Warn(PreReleaseWarning)
3142
wantedVersion = "nightly"
32-
} else {
43+
case "prerelease":
44+
spin.Warn(PreReleaseWarning)
45+
releases, err := utils.GetReleases("https://api.github.com/repos/runtipi/runtipi/releases")
46+
if err != nil {
47+
spin.Fail("Failed to fetch latest prerelease")
48+
spin.Finish()
49+
fmt.Printf("\nError: %v\n", err)
50+
return
51+
}
52+
filtered := utils.FilterNonPreReleases(releases)
53+
if len(filtered) == 0 {
54+
spin.Fail("Failed to fetch latest prerelease")
55+
spin.Finish()
56+
fmt.Printf("\nError: No prerelease releases found\n")
57+
return
58+
}
59+
wantedVersion = filtered[0].TagName
60+
default:
3361
wantedVersion = args.Version.String()
3462
}
3563

@@ -47,7 +75,7 @@ func RunUpdate(args types.UpdateArgs) {
4775
return
4876
}
4977

50-
spin.Succeed("Tipi updated successfully. Starting new CLI")
78+
spin.Succeed("Runtipi updated successfully. Starting new CLI")
5179
spin.Finish()
5280

5381
newExecutablePath := filepath.Join(config.RootFolder, "runtipi-cli")

internal/types/version.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,29 @@ import (
99

1010
type VersionType struct {
1111
version *semver.Version
12-
kind string // "latest", "nightly", or "version"
12+
kind string // "latest", "nightly", "prerelease", or "version"
1313
}
1414

1515
func NewVersion(s string) (*VersionType, error) {
1616
s = strings.TrimSpace(s)
17-
if s == "latest" {
17+
18+
switch s {
19+
case "latest":
1820
return &VersionType{kind: "latest"}, nil
19-
}
20-
if s == "nightly" {
21+
case "nightly":
2122
return &VersionType{kind: "nightly"}, nil
23+
case "prerelease":
24+
return &VersionType{kind: "prerelease"}, nil
25+
default:
26+
version, err := semver.NewVersion(s)
27+
if err != nil {
28+
return nil, fmt.Errorf("invalid version format: %w", err)
29+
}
30+
return &VersionType{
31+
version: version,
32+
kind: "version",
33+
}, nil
2234
}
23-
24-
version, err := semver.NewVersion(s)
25-
if err != nil {
26-
return nil, fmt.Errorf("invalid version format: %w", err)
27-
}
28-
29-
return &VersionType{
30-
version: version,
31-
kind: "version",
32-
}, nil
3335
}
3436

3537
// The string representation of the version
@@ -39,6 +41,8 @@ func (v *VersionType) String() string {
3941
return "latest"
4042
case "nightly":
4143
return "nightly"
44+
case "prerelease":
45+
return "prerelease"
4246
default:
4347
return fmt.Sprintf("v%s", v.version.String())
4448
}
@@ -52,6 +56,10 @@ func (v *VersionType) IsNightly() bool {
5256
return v.kind == "nightly"
5357
}
5458

59+
func (v *VersionType) IsPrerelease() bool {
60+
return v.kind == "prerelease"
61+
}
62+
5563
func (v *VersionType) Version() *semver.Version {
5664
return v.version
5765
}

internal/utils/release.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import (
88
"os"
99
"path/filepath"
1010
"runtime"
11+
"slices"
1112
"strings"
1213
"time"
1314

1415
"github.com/Masterminds/semver/v3"
1516
"github.com/runtipi/cli/internal/config"
1617
)
1718

19+
var NonPreReleaseTags = []string{"nightly", "e2e"}
20+
1821
type GitHubRelease struct {
1922
TagName string `json:"tag_name"`
2023
Name string `json:"name"`
@@ -28,32 +31,40 @@ type GitHubRelease struct {
2831
} `json:"assets"`
2932
}
3033

31-
func GetLatestRelease() (string, error) {
32-
url := "https://api.github.com/repos/runtipi/runtipi/releases/latest"
33-
34+
func GetReleases(url string) ([]GitHubRelease, error) {
3435
req, err := http.NewRequest("GET", url, nil)
3536
if err != nil {
36-
return "", fmt.Errorf("failed to create request: %v", err)
37+
return nil, fmt.Errorf("failed to create request: %v", err)
3738
}
3839

3940
req.Header.Set("User-Agent", "Runtipi-CLI")
4041
client := &http.Client{Timeout: 10 * time.Second}
4142
resp, err := client.Do(req)
4243
if err != nil {
43-
return "", fmt.Errorf("failed to send request: %v", err)
44+
return nil, fmt.Errorf("failed to send request: %v", err)
4445
}
4546
defer resp.Body.Close()
4647

4748
if resp.StatusCode != http.StatusOK {
48-
return "", fmt.Errorf("failed to fetch latest release. Status code: %d", resp.StatusCode)
49+
return nil, fmt.Errorf("failed to fetch releases. Status code: %d", resp.StatusCode)
4950
}
5051

51-
var release GitHubRelease
52-
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
53-
return "", fmt.Errorf("failed to parse latest release: %v", err)
52+
body, err := io.ReadAll(resp.Body)
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to read response body: %v", err)
5455
}
5556

56-
return release.TagName, nil
57+
// The latest release is not returned in an array so we will try to decode it as a single release
58+
var releases []GitHubRelease
59+
if err := json.Unmarshal(body, &releases); err != nil {
60+
var release GitHubRelease
61+
if err := json.Unmarshal(body, &release); err != nil {
62+
return nil, fmt.Errorf("failed to parse releases: %v", err)
63+
}
64+
releases = append(releases, release)
65+
}
66+
67+
return releases, nil
5768
}
5869

5970
func IsMajorBump(currentVersion, newVersion string) bool {
@@ -190,3 +201,14 @@ func FindReleaseByVersion(version string) (GitHubRelease, error) {
190201

191202
return GitHubRelease{}, fmt.Errorf("release not found. Did you forget the v prefix? (e.g. v4.0.0 instead of 4.0.0)")
192203
}
204+
205+
func FilterNonPreReleases(releases []GitHubRelease) []GitHubRelease {
206+
var filtered []GitHubRelease
207+
for _, release := range releases {
208+
if slices.Contains(NonPreReleaseTags, release.TagName) || !release.Prerelease {
209+
continue
210+
}
211+
filtered = append(filtered, release)
212+
}
213+
return filtered
214+
}

0 commit comments

Comments
 (0)