1package common
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "log"
8 "net/http"
9 "runtime"
10 "strings"
11 "time"
12
13 "github.com/bnema/gordon/pkg/docker"
14 "github.com/fatih/color"
15)
16
17type CurrentVersion struct {
18 isContainer bool
19 Version string `json:"version"`
20}
21
22type ArchVersion struct {
23 Name string `json:"name"`
24}
25
26type VersionInfo struct {
27 Name string `json:"name"`
28}
29
30type VersionsResponse struct {
31 Name string `json:"name"`
32 AMD64 VersionInfo `json:"amd64"`
33 ARM64 VersionInfo `json:"arm64"`
34}
35
36func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {
37 return CurrentVersion{
38 isContainer: docker.IsRunningInContainer(),
39 Version: c.GetVersion(),
40 }
41}
42
43func CheckForNewVersion(localVersion, remoteVersion string) string {
44 if localVersion != remoteVersion {
45 return "A new version is available"
46 }
47 return ""
48}
49
50func getArch() string {
51 return runtime.GOARCH
52}
53
54func checkAndUpdateVersion(c *Config) (string, error) {
55 localVersion := GetCurrentBuildVersionInfo(c)
56 remoteVersions, err := getRemoteVersion(c)
57 if err != nil {
58 return "", err
59 }
60
61 var remoteVersion string
62 switch getArch() {
63 case "amd64":
64 remoteVersion = remoteVersions.AMD64.Name
65 case "arm64":
66 remoteVersion = remoteVersions.ARM64.Name
67 default:
68 remoteVersion = remoteVersions.Name
69 }
70
71 // Check if remoteVersion is empty or doesn't contain the architecture suffix
72 if remoteVersion == "" || !strings.Contains(remoteVersion, getArch()) {
73 return "", fmt.Errorf("remote version not available for architecture %s", getArch())
74 }
75
76 remoteVersion = remoteVersion[:len(remoteVersion)-len(getArch())-1]
77 message := CheckForNewVersion(localVersion.Version, remoteVersion)
78
79 // if local version is empty that means dev mode is on so we don't need to check for updates
80 if localVersion.Version == "" {
81 return "", nil
82 }
83
84 if message != "" {
85 // Create a new color attribute
86 green := color.New(color.FgGreen).SprintFunc()
87 // Use the color attribute to format the string with ANSI codes
88 coloredMessage := green(fmt.Sprintf("New version %s is available", remoteVersion))
89 return coloredMessage, nil
90 }
91
92 return "", nil
93}
94
95func CheckVersionPeriodically(c *Config) (string, error) {
96 // Perform an immediate check.
97 newVersionMessage, err := checkAndUpdateVersion(c)
98
99 if err != nil {
100 return "", err
101 }
102
103 if newVersionMessage != "" {
104 fmt.Println(newVersionMessage)
105 return "", nil
106 }
107
108 // Set up a ticker to check every 5 minutes.
109 timer := time.NewTicker(5 * time.Minute)
110 defer timer.Stop()
111
112 for range timer.C {
113 newVersionMessage, err = checkAndUpdateVersion(c)
114 if err != nil {
115 log.Printf("Error fetching remote version: %s", err)
116 continue
117 }
118
119 if newVersionMessage != "" {
120 fmt.Println(newVersionMessage)
121 return "", nil //We return so the loop stops in case of a new version
122 }
123 }
124
125 return "", nil
126}
127
128func getRemoteVersion(c *Config) (VersionsResponse, error) {
129 proxyURL := c.Build.ProxyURL
130 if proxyURL == "" {
131 return VersionsResponse{}, fmt.Errorf("proxy URL is empty")
132 }
133
134 checkVersionEndpoint := proxyURL + "/version"
135 resp, err := http.Get(checkVersionEndpoint)
136 if err != nil {
137 return VersionsResponse{}, err
138 }
139
140 defer resp.Body.Close()
141
142 body, err := io.ReadAll(resp.Body)
143 if err != nil {
144 return VersionsResponse{}, err
145 }
146
147 var remoteVersionInfo VersionsResponse
148 err = json.Unmarshal(body, &remoteVersionInfo)
149 if err != nil {
150 return VersionsResponse{}, err
151 }
152
153 return remoteVersionInfo, nil
154
155}