detached 38e97fb

Testfeature new version notification

commit
38e97fbb47ab6add5cc58f5a6716647f5a4724d9
author
bnema · · 2023-11-03 16:23 UTC
parent
2f5e3b7
signature
unsigned
6 files · +227 -13
A .vscode/launch.json +18 -0
@@ -0,0 +1,18 @@
1+{
2+ // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
3+ // Pointez pour afficher la description des attributs existants.
4+ // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
5+ "version": "0.2.0",
6+ "configurations": [
7+ {
8+ "name": "Launch file",
9+ "type": "go",
10+ "request": "launch",
11+ "mode": "debug",
12+ "program": "./cmd/cli/main.go",
13+ }
14+
15+
16+ ]
17+
18+}⏎̸
@@ -0,0 +1,18 @@
1+{
2+ // Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
3+ // Pointez pour afficher la description des attributs existants.
4+ // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
5+ "version": "0.2.0",
6+ "configurations": [
7+ {
8+ "name": "Launch file",
9+ "type": "go",
10+ "request": "launch",
11+ "mode": "debug",
12+ "program": "./cmd/cli/main.go",
13+ }
14+
15+
16+ ]
17+
18+}⏎̸
M cmd/cli/main.go +32 -5
@@ -2,16 +2,22 @@ package main
22
33 import (
44 "fmt"
5+ "log"
56 "regexp"
67
78 "github.com/bnema/gordon/internal/cli"
89 "github.com/bnema/gordon/internal/cli/cmd"
910 "github.com/bnema/gordon/internal/common"
1011 "github.com/bnema/gordon/internal/server"
12+ "github.com/bnema/gordon/pkg/docker"
1113 "github.com/spf13/cobra"
1214 )
1315
14-var build string
16+var (
17+ build string
18+ commit string
19+ date string
20+)
1521
1622 var rootCmd = &cobra.Command{Use: "gordon"}
1723
@@ -38,11 +44,32 @@ func main() {
3844
3945 common.DockerInit(&s.Config.ContainerEngine)
4046
41- // build looks like this: "0.0.901-b98a337"
42- //use regex to only get the version number
4347 build = regexp.MustCompile(`\d+\.\d+\.\d+`).FindString(build)
44- // Set the BuildVersion
45- s.Config.Build.BuildVersion = build
48+ s.Config.Build = common.BuildConfig{
49+ BuildVersion: build,
50+ BuildCommit: commit,
51+ BuildDate: date,
52+ ProxyURL: "https://gordon-proxy.bnema.dev",
53+ }
54+
55+ digest, err := docker.WhoAmI()
56+ if err != nil {
57+ log.Println(err)
58+ }
59+
60+ fmt.Printf("Gordon version %s (%s) built on %s\n", s.Config.Build.BuildVersion, digest, s.Config.Build.BuildDate)
61+
62+ go func() {
63+ msg, err := common.CheckVersionPeriodically(&s.Config)
64+ if err != nil {
65+ log.Println(err)
66+ }
67+
68+ if msg != "" {
69+ log.Println(msg)
70+ }
71+
72+ }()
4673
4774 Execute(a, s)
4875 }
@@ -2,16 +2,22 @@ package main
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5+ "log"
5 "regexp"6 "regexp"
6 7
7 "github.com/bnema/gordon/internal/cli"8 "github.com/bnema/gordon/internal/cli"
8 "github.com/bnema/gordon/internal/cli/cmd"9 "github.com/bnema/gordon/internal/cli/cmd"
9 "github.com/bnema/gordon/internal/common"10 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"11 "github.com/bnema/gordon/internal/server"
12+ "github.com/bnema/gordon/pkg/docker"
11 "github.com/spf13/cobra"13 "github.com/spf13/cobra"
12 )14 )
13 15
14-var build string16+var (
17+ build string
18+ commit string
19+ date string
20+)
15 21
16 var rootCmd = &cobra.Command{Use: "gordon"}22 var rootCmd = &cobra.Command{Use: "gordon"}
17 23
@@ -38,11 +44,32 @@ func main() {
38 44
39 common.DockerInit(&s.Config.ContainerEngine)45 common.DockerInit(&s.Config.ContainerEngine)
40 46
41- // build looks like this: "0.0.901-b98a337"
42- //use regex to only get the version number
43 build = regexp.MustCompile(`\d+\.\d+\.\d+`).FindString(build)47 build = regexp.MustCompile(`\d+\.\d+\.\d+`).FindString(build)
44- // Set the BuildVersion48+ s.Config.Build = common.BuildConfig{
45- s.Config.Build.BuildVersion = build49+ BuildVersion: build,
50+ BuildCommit: commit,
51+ BuildDate: date,
52+ ProxyURL: "https://gordon-proxy.bnema.dev",
53+ }
54+
55+ digest, err := docker.WhoAmI()
56+ if err != nil {
57+ log.Println(err)
58+ }
59+
60+ fmt.Printf("Gordon version %s (%s) built on %s\n", s.Config.Build.BuildVersion, digest, s.Config.Build.BuildDate)
61+
62+ go func() {
63+ msg, err := common.CheckVersionPeriodically(&s.Config)
64+ if err != nil {
65+ log.Println(err)
66+ }
67+
68+ if msg != "" {
69+ log.Println(msg)
70+ }
71+
72+ }()
46 73
47 Execute(a, s)74 Execute(a, s)
48 }75 }
M internal/common/initconfig.go +3 -0
@@ -23,6 +23,9 @@ type Config struct {
2323 type BuildConfig struct {
2424 RunEnv string `yaml:"-"` // come from env
2525 BuildVersion string `yaml:"-"` // come from build ldflags
26+ BuildCommit string `yaml:"-"` // come from build ldflags
27+ BuildDate string `yaml:"-"` // come from build ldflags
28+ ProxyURL string `yaml:"-"`
2629 }
2730
2831 type AdminConfig struct {
@@ -23,6 +23,9 @@ type Config struct {
23 type BuildConfig struct {23 type BuildConfig struct {
24 RunEnv string `yaml:"-"` // come from env24 RunEnv string `yaml:"-"` // come from env
25 BuildVersion string `yaml:"-"` // come from build ldflags25 BuildVersion string `yaml:"-"` // come from build ldflags
26+ BuildCommit string `yaml:"-"` // come from build ldflags
27+ BuildDate string `yaml:"-"` // come from build ldflags
28+ ProxyURL string `yaml:"-"`
26 }29 }
27 30
28 type AdminConfig struct {31 type AdminConfig struct {
M internal/common/version.go +158 -6
@@ -1,16 +1,168 @@
11 package common
22
3-import "time"
3+import (
4+ "encoding/json"
5+ "fmt"
6+ "io"
7+ "log"
8+ "net/http"
9+ "runtime"
10+ "strings"
11+ "time"
412
5-const checkVersionURL = "https://gordon.bnema.dev/version"
6-
7-var timer = time.NewTicker(1 * time.Minute)
13+ "github.com/bnema/gordon/pkg/docker"
14+)
815
916 type CurrentVersion struct {
1017 isDocker bool
1118 Version string `json:"version"`
1219 Commit string `json:"commit"`
13- Sha256ID string `json:"sha256id"`
20+ Date string `json:"date"`
21+ Digest string `json:"digest"`
22+}
23+
24+// {
25+// "amd64": {
26+// "name": "0.0.951-amd64",
27+// "digest": "sha256:8486058cea46c5a18bc73d19446d6590b8b49cfadd7128ea443abbd69c11ea83"
28+// },
29+// "arm64": {
30+// "name": "0.0.951-arm64",
31+// "digest": "sha256:d9c2749117711e0bccd9cf3a3ac36ef7c4aa5487046e584640f2467add57a829"
32+// }
33+// }
34+// VersionInfo represents the JSON structure of the version endpoint response.
35+
36+// ArchVersion represents the architecture-specific version details.
37+type ArchVersion struct {
38+ Name string `json:"name"`
39+ Digest string `json:"digest"`
40+}
41+
42+type VersionInfo struct {
43+ Name string `json:"name"`
44+ Digest string `json:"digest"`
45+}
46+
47+type VersionsResponse struct {
48+ AMD64 VersionInfo `json:"amd64"`
49+ ARM64 VersionInfo `json:"arm64"`
50+}
51+
52+func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {
53+ // Check if the Docker client has been initialized .iscontainer should be at root level
54+ isContainer, err := docker.IsRunningInContainer()
55+ if err != nil {
56+ log.Println(err)
57+ }
58+
59+ digest, err := docker.WhoAmI()
60+ if err != nil {
61+ log.Println(err)
62+ }
63+
64+ return CurrentVersion{
65+ isDocker: isContainer,
66+ Version: c.Build.BuildVersion,
67+ Commit: c.Build.BuildCommit,
68+ Date: c.Build.BuildDate,
69+ Digest: digest,
70+ }
71+}
72+
73+// CheckForNewVersion compares the local and remote version.
74+// If the remote version is newer, it returns a message stating that a new version is available.
75+func CheckForNewVersion(local CurrentVersion, remote VersionsResponse) string {
76+ arch := getArch()
77+
78+ var remoteVersionInfo VersionInfo
79+ switch arch {
80+ case "amd64":
81+ remoteVersionInfo = remote.AMD64
82+ case "arm64":
83+ remoteVersionInfo = remote.ARM64
84+ default:
85+ log.Printf("Unknown architecture: %s", arch)
86+ return ""
87+ }
88+
89+ localVersion := normalizeVersion(local.Version, arch)
90+ remoteVersion := normalizeVersion(remoteVersionInfo.Name, arch)
91+
92+ // Compare the normalized local version with the remote version
93+ if localVersion != remoteVersion {
94+ return "a new version is available"
95+ }
96+ return ""
1497 }
1598
16-type Response interface{}
99+// normalizeVersion ensures that the version strings are comparable.
100+func normalizeVersion(version, arch string) string {
101+ // Assuming the version string might not contain the architecture.
102+ if !strings.Contains(version, arch) {
103+ version = fmt.Sprintf("%s-%s", version, arch)
104+ }
105+ return version
106+}
107+
108+func getArch() string {
109+ return runtime.GOARCH
110+}
111+
112+func CheckVersionPeriodically(c *Config) (string, error) {
113+ timer := time.NewTicker(1 * time.Minute)
114+ defer timer.Stop()
115+
116+ for range timer.C {
117+ localVersion := getLocalVersion(c)
118+ remoteVersion, err := getRemoteVersion(c)
119+ if err != nil {
120+ log.Printf("Error fetching remote version: %s", err)
121+ continue
122+ }
123+
124+ message := CheckForNewVersion(localVersion, remoteVersion)
125+ if message != "" {
126+ log.Printf("New version available: %s", message)
127+ // TODO: Add a notification
128+ }
129+ }
130+
131+ return "", nil
132+}
133+
134+func getLocalVersion(c *Config) CurrentVersion {
135+ return CurrentVersion{
136+ Version: c.Build.BuildVersion,
137+ Commit: c.Build.BuildCommit,
138+ Date: c.Build.BuildDate,
139+ }
140+}
141+
142+func getRemoteVersion(c *Config) (VersionsResponse, error) {
143+ proxyURL := c.Build.ProxyURL
144+ if proxyURL == "" {
145+ return VersionsResponse{}, fmt.Errorf("proxy URL is empty")
146+ }
147+
148+ checkVersionEndpoint := proxyURL + "/version"
149+ resp, err := http.Get(checkVersionEndpoint)
150+ if err != nil {
151+ return VersionsResponse{}, err
152+ }
153+
154+ defer resp.Body.Close()
155+
156+ body, err := io.ReadAll(resp.Body)
157+ if err != nil {
158+ return VersionsResponse{}, err
159+ }
160+
161+ var remoteVersionInfo VersionsResponse
162+ err = json.Unmarshal(body, &remoteVersionInfo)
163+ if err != nil {
164+ return VersionsResponse{}, err
165+ }
166+
167+ return remoteVersionInfo, nil
168+}
@@ -1,16 +1,168 @@
1 package common1 package common
2 2
3-import "time"3+import (
4+ "encoding/json"
5+ "fmt"
6+ "io"
7+ "log"
8+ "net/http"
9+ "runtime"
10+ "strings"
11+ "time"
4 12
5-const checkVersionURL = "https://gordon.bnema.dev/version"13+ "github.com/bnema/gordon/pkg/docker"
6-14+)
7-var timer = time.NewTicker(1 * time.Minute)
8 15
9 type CurrentVersion struct {16 type CurrentVersion struct {
10 isDocker bool17 isDocker bool
11 Version string `json:"version"`18 Version string `json:"version"`
12 Commit string `json:"commit"`19 Commit string `json:"commit"`
13- Sha256ID string `json:"sha256id"`20+ Date string `json:"date"`
21+ Digest string `json:"digest"`
22+}
23+
24+// {
25+// "amd64": {
26+// "name": "0.0.951-amd64",
27+// "digest": "sha256:8486058cea46c5a18bc73d19446d6590b8b49cfadd7128ea443abbd69c11ea83"
28+// },
29+// "arm64": {
30+// "name": "0.0.951-arm64",
31+// "digest": "sha256:d9c2749117711e0bccd9cf3a3ac36ef7c4aa5487046e584640f2467add57a829"
32+// }
33+// }
34+// VersionInfo represents the JSON structure of the version endpoint response.
35+
36+// ArchVersion represents the architecture-specific version details.
37+type ArchVersion struct {
38+ Name string `json:"name"`
39+ Digest string `json:"digest"`
40+}
41+
42+type VersionInfo struct {
43+ Name string `json:"name"`
44+ Digest string `json:"digest"`
45+}
46+
47+type VersionsResponse struct {
48+ AMD64 VersionInfo `json:"amd64"`
49+ ARM64 VersionInfo `json:"arm64"`
50+}
51+
52+func GetCurrentBuildVersionInfo(c *Config) CurrentVersion {
53+ // Check if the Docker client has been initialized .iscontainer should be at root level
54+ isContainer, err := docker.IsRunningInContainer()
55+ if err != nil {
56+ log.Println(err)
57+ }
58+
59+ digest, err := docker.WhoAmI()
60+ if err != nil {
61+ log.Println(err)
62+ }
63+
64+ return CurrentVersion{
65+ isDocker: isContainer,
66+ Version: c.Build.BuildVersion,
67+ Commit: c.Build.BuildCommit,
68+ Date: c.Build.BuildDate,
69+ Digest: digest,
70+ }
71+}
72+
73+// CheckForNewVersion compares the local and remote version.
74+// If the remote version is newer, it returns a message stating that a new version is available.
75+func CheckForNewVersion(local CurrentVersion, remote VersionsResponse) string {
76+ arch := getArch()
77+
78+ var remoteVersionInfo VersionInfo
79+ switch arch {
80+ case "amd64":
81+ remoteVersionInfo = remote.AMD64
82+ case "arm64":
83+ remoteVersionInfo = remote.ARM64
84+ default:
85+ log.Printf("Unknown architecture: %s", arch)
86+ return ""
87+ }
88+
89+ localVersion := normalizeVersion(local.Version, arch)
90+ remoteVersion := normalizeVersion(remoteVersionInfo.Name, arch)
91+
92+ // Compare the normalized local version with the remote version
93+ if localVersion != remoteVersion {
94+ return "a new version is available"
95+ }
96+ return ""
14 }97 }
15 98
16-type Response interface{}99+// normalizeVersion ensures that the version strings are comparable.
100+func normalizeVersion(version, arch string) string {
101+ // Assuming the version string might not contain the architecture.
102+ if !strings.Contains(version, arch) {
103+ version = fmt.Sprintf("%s-%s", version, arch)
104+ }
105+ return version
106+}
107+
108+func getArch() string {
109+ return runtime.GOARCH
110+}
111+
112+func CheckVersionPeriodically(c *Config) (string, error) {
113+ timer := time.NewTicker(1 * time.Minute)
114+ defer timer.Stop()
115+
116+ for range timer.C {
117+ localVersion := getLocalVersion(c)
118+ remoteVersion, err := getRemoteVersion(c)
119+ if err != nil {
120+ log.Printf("Error fetching remote version: %s", err)
121+ continue
122+ }
123+
124+ message := CheckForNewVersion(localVersion, remoteVersion)
125+ if message != "" {
126+ log.Printf("New version available: %s", message)
127+ // TODO: Add a notification
128+ }
129+ }
130+
131+ return "", nil
132+}
133+
134+func getLocalVersion(c *Config) CurrentVersion {
135+ return CurrentVersion{
136+ Version: c.Build.BuildVersion,
137+ Commit: c.Build.BuildCommit,
138+ Date: c.Build.BuildDate,
139+ }
140+}
141+
142+func getRemoteVersion(c *Config) (VersionsResponse, error) {
143+ proxyURL := c.Build.ProxyURL
144+ if proxyURL == "" {
145+ return VersionsResponse{}, fmt.Errorf("proxy URL is empty")
146+ }
147+
148+ checkVersionEndpoint := proxyURL + "/version"
149+ resp, err := http.Get(checkVersionEndpoint)
150+ if err != nil {
151+ return VersionsResponse{}, err
152+ }
153+
154+ defer resp.Body.Close()
155+
156+ body, err := io.ReadAll(resp.Body)
157+ if err != nil {
158+ return VersionsResponse{}, err
159+ }
160+
161+ var remoteVersionInfo VersionsResponse
162+ err = json.Unmarshal(body, &remoteVersionInfo)
163+ if err != nil {
164+ return VersionsResponse{}, err
165+ }
166+
167+ return remoteVersionInfo, nil
168+}
M internal/httpserve/handler/oauth.go +3 -1
@@ -75,8 +75,10 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
7575 redirectDomain := a.GenerateOauthCallbackURL()
7676 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
7777 // Redirect to Gordon's Proxy to grab the oauth access
78+ proxyURL := a.Config.Build.ProxyURL
7879 oauthURL := fmt.Sprintf(
79- "https://gordon.bnema.dev/github-proxy/authorize?client_id=%s&redirect_uri=%s&state=%s",
80+ "%s/github/authorize?client_id=%s&redirect_uri=%s&state=%s",
81+ proxyURL,
8082 clientID,
8183 redirectDomain,
8284 encodedState,
@@ -75,8 +75,10 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
75 redirectDomain := a.GenerateOauthCallbackURL()75 redirectDomain := a.GenerateOauthCallbackURL()
76 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))76 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
77 // Redirect to Gordon's Proxy to grab the oauth access77 // Redirect to Gordon's Proxy to grab the oauth access
78+ proxyURL := a.Config.Build.ProxyURL
78 oauthURL := fmt.Sprintf(79 oauthURL := fmt.Sprintf(
79- "https://gordon.bnema.dev/github-proxy/authorize?client_id=%s&redirect_uri=%s&state=%s",80+ "%s/github/authorize?client_id=%s&redirect_uri=%s&state=%s",
81+ proxyURL,
80 clientID,82 clientID,
81 redirectDomain,83 redirectDomain,
82 encodedState,84 encodedState,
M pkg/docker/images.go +13 -1
@@ -78,7 +78,6 @@ func ImportImageToEngine(imageFilePath string) error {
7878 // ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
7979 func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {
8080 // Check if the Docker client has been initialized
81- CheckIfInitialized()
8281
8382 // Get the image information using the Docker client
8483 imageInfo, err := GetImageInfo(imageID)
@@ -142,3 +141,16 @@ func GetImageName(imageID string) (string, error) {
142141
143142 return imageInfo.RepoDigests[0], nil
144143 }
144+
145+// WhoAmI attempts to identify the Docker image digest of the container running this code.
146+func WhoAmI() (string, error) {
147+ gordonImage := "ghcr.io/bnema/gordon:latest"
148+
149+ // Get the image information using the Docker client
150+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), gordonImage)
151+ if err != nil {
152+ return "", err
153+ }
154+
155+ return imageInfo.ID, nil
156+}
@@ -78,7 +78,6 @@ func ImportImageToEngine(imageFilePath string) error {
78 // ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader78 // ExportImageFromEngine exports an image from the Docker engine and returns it as an io.Reader
79 func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {79 func ExportImageFromEngine(imageID string) (io.ReadCloser, error) {
80 // Check if the Docker client has been initialized80 // Check if the Docker client has been initialized
81- CheckIfInitialized()
82 81
83 // Get the image information using the Docker client82 // Get the image information using the Docker client
84 imageInfo, err := GetImageInfo(imageID)83 imageInfo, err := GetImageInfo(imageID)
@@ -142,3 +141,16 @@ func GetImageName(imageID string) (string, error) {
142 141
143 return imageInfo.RepoDigests[0], nil142 return imageInfo.RepoDigests[0], nil
144 }143 }
144+
145+// WhoAmI attempts to identify the Docker image digest of the container running this code.
146+func WhoAmI() (string, error) {
147+ gordonImage := "ghcr.io/bnema/gordon:latest"
148+
149+ // Get the image information using the Docker client
150+ imageInfo, _, err := dockerCli.ImageInspectWithRaw(context.Background(), gordonImage)
151+ if err != nil {
152+ return "", err
153+ }
154+
155+ return imageInfo.ID, nil
156+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close