detached 97319bf

Refactor: Improve Docker client and container/image handling

- Simplify Docker client initialization and management
- Update container/image structs and processing
- Refactor OAuth and main.go configurations
- Improve error handling and code organization
commit
97319bf26bb8910bbeb2c4099b346dc51f018db0
author
bamen · · 2024-08-27 05:37 UTC
parent
5e4df08
signature
unsigned
6 files · +53 -53
M cmd/cli/main.go +6 -7
@@ -3,7 +3,6 @@ package main
33 import (
44 "fmt"
55 "log"
6- "os"
76 "regexp"
87
98 "github.com/bnema/gordon/internal/cli"
@@ -17,13 +16,13 @@ import (
1716 )
1817
1918 var (
20- build string
21- commit string
22- date string
19+ build string
20+ commit string
21+ date string
22+ rootCmd = &cobra.Command{Use: "gordon"}
23+ proxyURL = "https://gordon-proxy.bamen.dev"
2324 )
2425
25-var rootCmd = &cobra.Command{Use: "gordon"}
26-
2726 func InitializeCommands(client *cli.App, server *server.App) {
2827 rootCmd.AddCommand(cmd.NewRootCommand(client))
2928 rootCmd.AddCommand(cmd.NewServeCommand(server))
@@ -44,7 +43,7 @@ func main() {
4443 BuildVersion: build,
4544 BuildCommit: commit,
4645 BuildDate: date,
47- ProxyURL: os.Getenv("PROXY_URL"),
46+ ProxyURL: proxyURL,
4847 }
4948
5049 a, err := cli.NewClientApp(buildInfo)
@@ -3,7 +3,6 @@ package main
3 import (3 import (
4 "fmt"4 "fmt"
5 "log"5 "log"
6- "os"
7 "regexp"6 "regexp"
8 7
9 "github.com/bnema/gordon/internal/cli"8 "github.com/bnema/gordon/internal/cli"
@@ -17,13 +16,13 @@ import (
17 )16 )
18 17
19 var (18 var (
20- build string19+ build string
21- commit string20+ commit string
22- date string21+ date string
22+ rootCmd = &cobra.Command{Use: "gordon"}
23+ proxyURL = "https://gordon-proxy.bamen.dev"
23 )24 )
24 25
25-var rootCmd = &cobra.Command{Use: "gordon"}
26-
27 func InitializeCommands(client *cli.App, server *server.App) {26 func InitializeCommands(client *cli.App, server *server.App) {
28 rootCmd.AddCommand(cmd.NewRootCommand(client))27 rootCmd.AddCommand(cmd.NewRootCommand(client))
29 rootCmd.AddCommand(cmd.NewServeCommand(server))28 rootCmd.AddCommand(cmd.NewServeCommand(server))
@@ -44,7 +43,7 @@ func main() {
44 BuildVersion: build,43 BuildVersion: build,
45 BuildCommit: commit,44 BuildCommit: commit,
46 BuildDate: date,45 BuildDate: date,
47- ProxyURL: os.Getenv("PROXY_URL"),46+ ProxyURL: proxyURL,
48 }47 }
49 48
50 a, err := cli.NewClientApp(buildInfo)49 a, err := cli.NewClientApp(buildInfo)
M internal/common/initdockerclient.go +2 -3
@@ -7,10 +7,9 @@ import (
77 )
88
99 func DockerInit(cc *ContainerEngineConfig) {
10- dockerClient := docker.DockerClient{}
11- err := dockerClient.InitializeClient(NewDockerConfig(cc))
10+ err := docker.InitializeClient(NewDockerConfig(cc))
1211 if err != nil {
13- log.Printf("Error: %s", err)
12+ log.Printf("Error initializing Docker client: %s", err)
1413 }
1514 }
1615
@@ -7,10 +7,9 @@ import (
7 )7 )
8 8
9 func DockerInit(cc *ContainerEngineConfig) {9 func DockerInit(cc *ContainerEngineConfig) {
10- dockerClient := docker.DockerClient{}10+ err := docker.InitializeClient(NewDockerConfig(cc))
11- err := dockerClient.InitializeClient(NewDockerConfig(cc))
12 if err != nil {11 if err != nil {
13- log.Printf("Error: %s", err)12+ log.Printf("Error initializing Docker client: %s", err)
14 }13 }
15 }14 }
16 15
M internal/httpserve/handler/containers.go +23 -19
@@ -50,11 +50,13 @@ func safelyInteractWithIDMap(op MapOperation, key string, value ...string) (stri
5050 }
5151
5252 type HumanReadableContainerImage struct {
53- *types.ImageInspect
54- Name string
55- ShortID string
56- CreatedStr string
57- SizeStr string
53+ ID string
54+ Name string
55+ ShortID string
56+ CreatedStr string
57+ SizeStr string
58+ RepoDigests []string
59+ RepoTags []string
5860 }
5961
6062 type HumanReadableContainer struct {
@@ -98,27 +100,29 @@ func ImageManagerComponent(c echo.Context, a *server.App) error {
98100 if err != nil {
99101 return sendError(c, err)
100102 }
103+
101104 var humanReadableImages []HumanReadableContainerImage
102105
103- for _, image := range images {
104- // We need to shorten the ID because it breaks the HTML
105- ShortID := image.ID[7:19]
106- // And we store both the ShortID and the full ID in a map so we can retrieve the full ID later
107- safelyInteractWithIDMap(Update, ShortID, image.ID)
108- createdTime := time.Unix(image.Created, 0)
106+ for _, img := range images {
107+ shortID := img.ID[7:19]
108+ safelyInteractWithIDMap(Update, shortID, img.ID)
109+ createdTime := time.Unix(img.Created, 0)
109110 createdStr := humanize.TimeAgo(createdTime)
110- sizeStr := humanize.BytesToReadableSize(image.Size)
111- for _, repoTag := range image.RepoTags {
111+ sizeStr := humanize.BytesToReadableSize(img.Size)
112+
113+ for _, repoTag := range img.RepoTags {
112114 humanReadableImages = append(humanReadableImages, HumanReadableContainerImage{
113- ImageInspect: &types.ImageInspect{
114- ID: ShortID, // Set the ID to the ShortID
115- },
116- CreatedStr: createdStr,
117- SizeStr: sizeStr,
118- Name: repoTag,
115+ ID: img.ID,
116+ ShortID: shortID,
117+ CreatedStr: createdStr,
118+ SizeStr: sizeStr,
119+ Name: repoTag,
120+ RepoDigests: img.RepoDigests,
121+ RepoTags: img.RepoTags,
119122 })
120123 }
121124 }
125+
122126 data := map[string]interface{}{
123127 "Images": humanReadableImages,
124128 }
@@ -50,11 +50,13 @@ func safelyInteractWithIDMap(op MapOperation, key string, value ...string) (stri
50 }50 }
51 51
52 type HumanReadableContainerImage struct {52 type HumanReadableContainerImage struct {
53- *types.ImageInspect53+ ID string
54- Name string54+ Name string
55- ShortID string55+ ShortID string
56- CreatedStr string56+ CreatedStr string
57- SizeStr string57+ SizeStr string
58+ RepoDigests []string
59+ RepoTags []string
58 }60 }
59 61
60 type HumanReadableContainer struct {62 type HumanReadableContainer struct {
@@ -98,27 +100,29 @@ func ImageManagerComponent(c echo.Context, a *server.App) error {
98 if err != nil {100 if err != nil {
99 return sendError(c, err)101 return sendError(c, err)
100 }102 }
103+
101 var humanReadableImages []HumanReadableContainerImage104 var humanReadableImages []HumanReadableContainerImage
102 105
103- for _, image := range images {106+ for _, img := range images {
104- // We need to shorten the ID because it breaks the HTML107+ shortID := img.ID[7:19]
105- ShortID := image.ID[7:19]108+ safelyInteractWithIDMap(Update, shortID, img.ID)
106- // And we store both the ShortID and the full ID in a map so we can retrieve the full ID later109+ createdTime := time.Unix(img.Created, 0)
107- safelyInteractWithIDMap(Update, ShortID, image.ID)
108- createdTime := time.Unix(image.Created, 0)
109 createdStr := humanize.TimeAgo(createdTime)110 createdStr := humanize.TimeAgo(createdTime)
110- sizeStr := humanize.BytesToReadableSize(image.Size)111+ sizeStr := humanize.BytesToReadableSize(img.Size)
111- for _, repoTag := range image.RepoTags {112+
113+ for _, repoTag := range img.RepoTags {
112 humanReadableImages = append(humanReadableImages, HumanReadableContainerImage{114 humanReadableImages = append(humanReadableImages, HumanReadableContainerImage{
113- ImageInspect: &types.ImageInspect{115+ ID: img.ID,
114- ID: ShortID, // Set the ID to the ShortID116+ ShortID: shortID,
115- },117+ CreatedStr: createdStr,
116- CreatedStr: createdStr,118+ SizeStr: sizeStr,
117- SizeStr: sizeStr,119+ Name: repoTag,
118- Name: repoTag,120+ RepoDigests: img.RepoDigests,
121+ RepoTags: img.RepoTags,
119 })122 })
120 }123 }
121 }124 }
125+
122 data := map[string]interface{}{126 data := map[string]interface{}{
123 "Images": humanReadableImages,127 "Images": humanReadableImages,
124 }128 }
M internal/httpserve/handler/oauth.go +1 -2
@@ -6,7 +6,6 @@ import (
66 "fmt"
77 "io"
88 "net/http"
9- "os"
109 "time"
1110
1211 "github.com/bnema/gordon/internal/db"
@@ -115,7 +114,7 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
115114
116115 // initiateGithubOAuthFlow starts a new GitHub OAuth flow
117116 func initiateGithubOAuthFlow(c echo.Context, a *server.App) error {
118- clientID := os.Getenv("GITHUB_APP_ID")
117+ clientID := "" // IDK why it needs that even empty, but it does
119118 redirectDomain := a.GenerateOauthCallbackURL()
120119 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
121120
@@ -6,7 +6,6 @@ import (
6 "fmt"6 "fmt"
7 "io"7 "io"
8 "net/http"8 "net/http"
9- "os"
10 "time"9 "time"
11 10
12 "github.com/bnema/gordon/internal/db"11 "github.com/bnema/gordon/internal/db"
@@ -115,7 +114,7 @@ func StartOAuthGithub(c echo.Context, a *server.App) error {
115 114
116 // initiateGithubOAuthFlow starts a new GitHub OAuth flow115 // initiateGithubOAuthFlow starts a new GitHub OAuth flow
117 func initiateGithubOAuthFlow(c echo.Context, a *server.App) error {116 func initiateGithubOAuthFlow(c echo.Context, a *server.App) error {
118- clientID := os.Getenv("GITHUB_APP_ID")117+ clientID := "" // IDK why it needs that even empty, but it does
119 redirectDomain := a.GenerateOauthCallbackURL()118 redirectDomain := a.GenerateOauthCallbackURL()
120 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))119 encodedState := base64.StdEncoding.EncodeToString([]byte("redirectDomain:" + redirectDomain))
121 120
M internal/templating/models/html/fragments/imagelist.gohtml +3 -3
@@ -1,6 +1,6 @@
11 <div class="flex flex-col p-4">
22 <!-- Upload button aligned right of Images -->
3- <div class="flex justify-between items-center mb-1">
3+ <div class="flex justify-between items-center mb-1">
44 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1>
55 <button
66 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"
@@ -24,14 +24,14 @@
2424 }}
2525 <tr id="img-{{.ID}}" hx-ext="response-targets" class=" hover:bg-slate-100">
2626 <td>
27- <span title="{{.ImageSummary.ID}}">{{.Name}}</span>
27+ <span title="{{.ID}}">{{.Name}}</span>
2828 </td>
2929 <td>
3030 <span title="Image creation date">{{.CreatedStr}}</span>
3131 </td>
3232 <td>{{.SizeStr}}</td>
3333 <td align="right">
34-
34+
3535 <button
3636 id="add-button-img-{{.ID}}"
3737 hx-get="/htmx/create-container/{{.ID}}"
@@ -1,6 +1,6 @@
1 <div class="flex flex-col p-4">1 <div class="flex flex-col p-4">
2 <!-- Upload button aligned right of Images -->2 <!-- Upload button aligned right of Images -->
3- <div class="flex justify-between items-center mb-1"> 3+ <div class="flex justify-between items-center mb-1">
4 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1>4 <h1 class="text-lg md:text-xl font-bold text-gordonmascot-hblue">Images</h1>
5 <button5 <button
6 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"6 class="py-1 px-2 bg-blue-500 hover:bg-blue-700 text-white font-bold rounded"
@@ -24,14 +24,14 @@
24 }}24 }}
25 <tr id="img-{{.ID}}" hx-ext="response-targets" class=" hover:bg-slate-100">25 <tr id="img-{{.ID}}" hx-ext="response-targets" class=" hover:bg-slate-100">
26 <td>26 <td>
27- <span title="{{.ImageSummary.ID}}">{{.Name}}</span>27+ <span title="{{.ID}}">{{.Name}}</span>
28 </td>28 </td>
29 <td>29 <td>
30 <span title="Image creation date">{{.CreatedStr}}</span>30 <span title="Image creation date">{{.CreatedStr}}</span>
31 </td>31 </td>
32 <td>{{.SizeStr}}</td>32 <td>{{.SizeStr}}</td>
33 <td align="right">33 <td align="right">
34- 34+
35 <button35 <button
36 id="add-button-img-{{.ID}}"36 id="add-button-img-{{.ID}}"
37 hx-get="/htmx/create-container/{{.ID}}"37 hx-get="/htmx/create-container/{{.ID}}"
M pkg/docker/client.go +18 -19
@@ -2,23 +2,18 @@ package docker
22
33 import (
44 "context"
5- "crypto/tls"
65 "fmt"
76 "os"
87 "time"
98
9+ "github.com/docker/docker/api/types"
1010 "github.com/docker/docker/api/types/container"
1111 "github.com/docker/docker/client"
1212 )
1313
14-type ContainerEngineClient interface {
15- InitializeClient(*Config) error
16- // Add other methods that both Docker and Podman should implement
17-}
1814 type Config struct {
1915 Sock string
2016 PodmanEnable bool
21- TLSConfig *tls.Config
2217 }
2318
2419 var (
@@ -26,21 +21,15 @@ var (
2621 currentConfig *Config
2722 )
2823
29-// DockerClient implements the ContainerEngineClient interface for Docker
30-type DockerClient struct{}
31-
32-func (d *DockerClient) InitializeClient(config *Config) error {
33- // Validate if the Sock field is not empty
24+func InitializeClient(config *Config) error {
3425 if config.Sock == "" {
3526 return fmt.Errorf("sock field in Config is empty")
3627 }
3728
38- // Check if the socket file exists if not return an error
3929 if _, err := os.Stat(config.Sock); os.IsNotExist(err) {
4030 return err
4131 }
4232
43- // Initialize Docker client
4433 cli, err := client.NewClientWithOpts(
4534 client.WithAPIVersionNegotiation(),
4635 client.WithHost("unix://"+config.Sock),
@@ -53,19 +42,16 @@ func (d *DockerClient) InitializeClient(config *Config) error {
5342 return nil
5443 }
5544
56-// CheckIfInitialized checks if the Docker client is initialized
5745 func CheckIfInitialized() error {
58- // Check if there is a socket file
5946 if currentConfig == nil || currentConfig.Sock == "" {
6047 return fmt.Errorf("docker client is not initialized or configuration is missing")
6148 }
6249
63- socketPath := currentConfig.Sock // Use the package-level variable
50+ socketPath := currentConfig.Sock
6451 if _, err := os.Stat(socketPath); os.IsNotExist(err) {
6552 return fmt.Errorf("sock file does not exist: %s", socketPath)
6653 }
6754
68- // Then we check if the dockerCli is nil
6955 if dockerCli == nil {
7056 return fmt.Errorf("docker client is not initialized")
7157 }
@@ -73,10 +59,23 @@ func CheckIfInitialized() error {
7359 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
7460 defer cancel()
7561
76- // More stringent check: try to list containers
77- _, err := dockerCli.ContainerList(ctx, container.ListOptions{})
62+ containers, err := dockerCli.ContainerList(ctx, container.ListOptions{})
7863 if err != nil {
7964 return fmt.Errorf("cannot connect to Docker daemon: %s", err)
8065 }
66+ fmt.Printf("Found %d containers\n", len(containers))
8167 return nil
8268 }
69+
70+func ListContainers(ctx context.Context) ([]types.Container, error) {
71+ if dockerCli == nil {
72+ return nil, fmt.Errorf("Docker client is not initialized")
73+ }
74+
75+ containers, err := dockerCli.ContainerList(ctx, container.ListOptions{})
76+ if err != nil {
77+ return nil, fmt.Errorf("error listing containers: %w", err)
78+ }
79+
80+ return containers, nil
81+}
@@ -2,23 +2,18 @@ package docker
2 2
3 import (3 import (
4 "context"4 "context"
5- "crypto/tls"
6 "fmt"5 "fmt"
7 "os"6 "os"
8 "time"7 "time"
9 8
9+ "github.com/docker/docker/api/types"
10 "github.com/docker/docker/api/types/container"10 "github.com/docker/docker/api/types/container"
11 "github.com/docker/docker/client"11 "github.com/docker/docker/client"
12 )12 )
13 13
14-type ContainerEngineClient interface {
15- InitializeClient(*Config) error
16- // Add other methods that both Docker and Podman should implement
17-}
18 type Config struct {14 type Config struct {
19 Sock string15 Sock string
20 PodmanEnable bool16 PodmanEnable bool
21- TLSConfig *tls.Config
22 }17 }
23 18
24 var (19 var (
@@ -26,21 +21,15 @@ var (
26 currentConfig *Config21 currentConfig *Config
27 )22 )
28 23
29-// DockerClient implements the ContainerEngineClient interface for Docker24+func InitializeClient(config *Config) error {
30-type DockerClient struct{}
31-
32-func (d *DockerClient) InitializeClient(config *Config) error {
33- // Validate if the Sock field is not empty
34 if config.Sock == "" {25 if config.Sock == "" {
35 return fmt.Errorf("sock field in Config is empty")26 return fmt.Errorf("sock field in Config is empty")
36 }27 }
37 28
38- // Check if the socket file exists if not return an error
39 if _, err := os.Stat(config.Sock); os.IsNotExist(err) {29 if _, err := os.Stat(config.Sock); os.IsNotExist(err) {
40 return err30 return err
41 }31 }
42 32
43- // Initialize Docker client
44 cli, err := client.NewClientWithOpts(33 cli, err := client.NewClientWithOpts(
45 client.WithAPIVersionNegotiation(),34 client.WithAPIVersionNegotiation(),
46 client.WithHost("unix://"+config.Sock),35 client.WithHost("unix://"+config.Sock),
@@ -53,19 +42,16 @@ func (d *DockerClient) InitializeClient(config *Config) error {
53 return nil42 return nil
54 }43 }
55 44
56-// CheckIfInitialized checks if the Docker client is initialized
57 func CheckIfInitialized() error {45 func CheckIfInitialized() error {
58- // Check if there is a socket file
59 if currentConfig == nil || currentConfig.Sock == "" {46 if currentConfig == nil || currentConfig.Sock == "" {
60 return fmt.Errorf("docker client is not initialized or configuration is missing")47 return fmt.Errorf("docker client is not initialized or configuration is missing")
61 }48 }
62 49
63- socketPath := currentConfig.Sock // Use the package-level variable50+ socketPath := currentConfig.Sock
64 if _, err := os.Stat(socketPath); os.IsNotExist(err) {51 if _, err := os.Stat(socketPath); os.IsNotExist(err) {
65 return fmt.Errorf("sock file does not exist: %s", socketPath)52 return fmt.Errorf("sock file does not exist: %s", socketPath)
66 }53 }
67 54
68- // Then we check if the dockerCli is nil
69 if dockerCli == nil {55 if dockerCli == nil {
70 return fmt.Errorf("docker client is not initialized")56 return fmt.Errorf("docker client is not initialized")
71 }57 }
@@ -73,10 +59,23 @@ func CheckIfInitialized() error {
73 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)59 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
74 defer cancel()60 defer cancel()
75 61
76- // More stringent check: try to list containers62+ containers, err := dockerCli.ContainerList(ctx, container.ListOptions{})
77- _, err := dockerCli.ContainerList(ctx, container.ListOptions{})
78 if err != nil {63 if err != nil {
79 return fmt.Errorf("cannot connect to Docker daemon: %s", err)64 return fmt.Errorf("cannot connect to Docker daemon: %s", err)
80 }65 }
66+ fmt.Printf("Found %d containers\n", len(containers))
81 return nil67 return nil
82 }68 }
69+
70+func ListContainers(ctx context.Context) ([]types.Container, error) {
71+ if dockerCli == nil {
72+ return nil, fmt.Errorf("Docker client is not initialized")
73+ }
74+
75+ containers, err := dockerCli.ContainerList(ctx, container.ListOptions{})
76+ if err != nil {
77+ return nil, fmt.Errorf("error listing containers: %w", err)
78+ }
79+
80+ return containers, nil
81+}

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