detached 2d35eb3

Implement Push Command and Resolve Session Issues. Can't go further than this without proper building and testing.

- Add support for the push command in clientendpoins.go
- Introduce session deletion method in sessions.go
- Refactor templating logic, moving cmdparams to its own package
- Fixe session validation in middleware requirelogin.go
- Update .gitignore to ignore 'main' build output
- Add app context to InitSessionMiddleware
- Implement IsDevEnvironment method in App struct
- Refactor locstrings.yml file path and deletion
- Remove unused cmdparams.go in templating
- Refactor Docker client initialization and error messages
commit
2d35eb3124859979712c48e274960e98ba9048dc
author
bnema · · 2023-10-29 16:54 UTC
parent
fc7b2c6
signature
unsigned
22 files · +384 -201
M .gitignore +1 -0
@@ -7,3 +7,4 @@ bun.lockb
77 *.log
88 *.txt
99 *sync-conflict*
10+main⏎̸
@@ -7,3 +7,4 @@ bun.lockb
7 *.log7 *.log
8 *.txt8 *.txt
9 *sync-conflict*9 *sync-conflict*
10+main⏎̸
M internal/common/initconfig.go +1 -0
@@ -43,6 +43,7 @@ type ContainerEngineConfig struct {
4343 Network string `yaml:"network"`
4444 }
4545
46+// Default values
4647 var (
4748 sock = "/var/run/docker.sock"
4849 podmansock = "/run/user/1000/podman/podman.sock"
@@ -43,6 +43,7 @@ type ContainerEngineConfig struct {
43 Network string `yaml:"network"`43 Network string `yaml:"network"`
44 }44 }
45 45
46+// Default values
46 var (47 var (
47 sock = "/var/run/docker.sock"48 sock = "/var/run/docker.sock"
48 podmansock = "/run/user/1000/podman/podman.sock"49 podmansock = "/run/user/1000/podman/podman.sock"
M internal/db/queries/sessions.go +11 -0
@@ -93,6 +93,17 @@ func SessionCleaner(a *server.App, currentTime string) error {
9393 return nil
9494 }
9595
96+// DeleteSession deletes the session from the database.
97+func DeleteDBSession(a *server.App, accountID string, sessionID string) error {
98+ query := "DELETE FROM sessions WHERE account_id = ? AND id = ?"
99+ _, err := a.DB.Exec(query, accountID, sessionID)
100+ if err != nil {
101+ return err
102+ }
103+
104+ return nil
105+}
106+
96107 func GetSessionExpiration(a *server.App, accountID string, sessionID string, currentTime time.Time) (time.Time, error) {
97108 var expirationTime string
98109 // with userID and sessionID we can get the expiration time
@@ -93,6 +93,17 @@ func SessionCleaner(a *server.App, currentTime string) error {
93 return nil93 return nil
94 }94 }
95 95
96+// DeleteSession deletes the session from the database.
97+func DeleteDBSession(a *server.App, accountID string, sessionID string) error {
98+ query := "DELETE FROM sessions WHERE account_id = ? AND id = ?"
99+ _, err := a.DB.Exec(query, accountID, sessionID)
100+ if err != nil {
101+ return err
102+ }
103+
104+ return nil
105+}
106+
96 func GetSessionExpiration(a *server.App, accountID string, sessionID string, currentTime time.Time) (time.Time, error) {107 func GetSessionExpiration(a *server.App, accountID string, sessionID string, currentTime time.Time) (time.Time, error) {
97 var expirationTime string108 var expirationTime string
98 // with userID and sessionID we can get the expiration time109 // with userID and sessionID we can get the expiration time
M internal/httpserve/handler/clientendpoins.go +8 -2
@@ -8,6 +8,7 @@ import (
88
99 "github.com/bnema/gordon/internal/common"
1010 "github.com/bnema/gordon/internal/server"
11+ "github.com/bnema/gordon/pkg/docker"
1112 "github.com/bnema/gordon/pkg/store"
1213 "github.com/labstack/echo/v4"
1314 )
@@ -61,7 +62,7 @@ func GetInfos(c echo.Context, a *server.App) error {
6162 func PostPush(c echo.Context, a *server.App) error {
6263 body, _ := io.ReadAll(c.Request().Body)
6364 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
64- fmt.Print(body)
65+
6566 payload := new(common.RequestPayload)
6667 if err := c.Bind(payload); err != nil {
6768 fmt.Println("Bind Error:", err)
@@ -106,8 +107,13 @@ func PostPush(c echo.Context, a *server.App) error {
106107 if err != nil {
107108 return c.JSON(http.StatusBadRequest, err.Error())
108109 }
109- fmt.Printf("Saved in %s", imagePath)
110110 // 2 - Import the tar in docker
111+ imageID, err := docker.ImportImageToEngine(imagePath)
112+ if err != nil {
113+ return c.JSON(http.StatusBadRequest, err.Error())
114+ }
115+
116+ fmt.Println("Import successful, ImageID:", imageID)
111117
112118 // 3 - Create the container with traefik labels
113119
@@ -8,6 +8,7 @@ import (
8 8
9 "github.com/bnema/gordon/internal/common"9 "github.com/bnema/gordon/internal/common"
10 "github.com/bnema/gordon/internal/server"10 "github.com/bnema/gordon/internal/server"
11+ "github.com/bnema/gordon/pkg/docker"
11 "github.com/bnema/gordon/pkg/store"12 "github.com/bnema/gordon/pkg/store"
12 "github.com/labstack/echo/v4"13 "github.com/labstack/echo/v4"
13 )14 )
@@ -61,7 +62,7 @@ func GetInfos(c echo.Context, a *server.App) error {
61 func PostPush(c echo.Context, a *server.App) error {62 func PostPush(c echo.Context, a *server.App) error {
62 body, _ := io.ReadAll(c.Request().Body)63 body, _ := io.ReadAll(c.Request().Body)
63 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body64 c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body
64- fmt.Print(body)65+
65 payload := new(common.RequestPayload)66 payload := new(common.RequestPayload)
66 if err := c.Bind(payload); err != nil {67 if err := c.Bind(payload); err != nil {
67 fmt.Println("Bind Error:", err)68 fmt.Println("Bind Error:", err)
@@ -106,8 +107,13 @@ func PostPush(c echo.Context, a *server.App) error {
106 if err != nil {107 if err != nil {
107 return c.JSON(http.StatusBadRequest, err.Error())108 return c.JSON(http.StatusBadRequest, err.Error())
108 }109 }
109- fmt.Printf("Saved in %s", imagePath)
110 // 2 - Import the tar in docker110 // 2 - Import the tar in docker
111+ imageID, err := docker.ImportImageToEngine(imagePath)
112+ if err != nil {
113+ return c.JSON(http.StatusBadRequest, err.Error())
114+ }
115+
116+ fmt.Println("Import successful, ImageID:", imageID)
111 117
112 // 3 - Create the container with traefik labels118 // 3 - Create the container with traefik labels
113 119
M internal/httpserve/handler/createcontainer.go +2 -1
@@ -5,6 +5,7 @@ import (
55 "net/http"
66
77 "github.com/bnema/gordon/internal/server"
8+ "github.com/bnema/gordon/internal/templating/cmdparams"
89 "github.com/bnema/gordon/internal/templating/render"
910 "github.com/bnema/gordon/pkg/docker"
1011 "github.com/bnema/gordon/pkg/sanitize"
@@ -103,7 +104,7 @@ func CreateContainerPOST(c echo.Context, a *server.App) error {
103104 }
104105 }
105106
106- cmdParams, err := render.FromInputsToCmdParams(sanitizedInputs, a)
107+ cmdParams, err := cmdparams.FromInputsToCmdParams(sanitizedInputs, a)
107108 if err != nil {
108109 return sendError(c, err)
109110 }
@@ -5,6 +5,7 @@ import (
5 "net/http"5 "net/http"
6 6
7 "github.com/bnema/gordon/internal/server"7 "github.com/bnema/gordon/internal/server"
8+ "github.com/bnema/gordon/internal/templating/cmdparams"
8 "github.com/bnema/gordon/internal/templating/render"9 "github.com/bnema/gordon/internal/templating/render"
9 "github.com/bnema/gordon/pkg/docker"10 "github.com/bnema/gordon/pkg/docker"
10 "github.com/bnema/gordon/pkg/sanitize"11 "github.com/bnema/gordon/pkg/sanitize"
@@ -103,7 +104,7 @@ func CreateContainerPOST(c echo.Context, a *server.App) error {
103 }104 }
104 }105 }
105 106
106- cmdParams, err := render.FromInputsToCmdParams(sanitizedInputs, a)107+ cmdParams, err := cmdparams.FromInputsToCmdParams(sanitizedInputs, a)
107 if err != nil {108 if err != nil {
108 return sendError(c, err)109 return sendError(c, err)
109 }110 }
M internal/httpserve/handler/editcontainer.go +3 -2
@@ -6,6 +6,7 @@ import (
66 "strings"
77
88 "github.com/bnema/gordon/internal/server"
9+ "github.com/bnema/gordon/internal/templating/cmdparams"
910 "github.com/bnema/gordon/internal/templating/render"
1011 "github.com/bnema/gordon/pkg/docker"
1112 "github.com/labstack/echo/v4"
@@ -159,14 +160,14 @@ func ContainerManagerEditPOST(c echo.Context, a *server.App) error {
159160 }
160161
161162 containerConfig := c.FormValue("container_config")
162- var containerParams render.YAMLContainerParams
163+ var containerParams cmdparams.YAMLContainerParams
163164 err = yaml.Unmarshal([]byte(containerConfig), &containerParams)
164165 if err != nil {
165166 return sendError(c, fmt.Errorf("YAML unmarshal failed: %w", err))
166167 }
167168
168169 // Use render.FromYAMLStructToCmdParams to convert YAMLContainerParams to ContainerCommandParams
169- cmdParams, err := render.FromYAMLStructToCmdParams(containerParams)
170+ cmdParams, err := cmdparams.FromYAMLStructToCmdParams(containerParams)
170171 if err != nil {
171172 return fmt.Errorf("failed to convert YAML: %w", err)
172173 }
@@ -6,6 +6,7 @@ import (
6 "strings"6 "strings"
7 7
8 "github.com/bnema/gordon/internal/server"8 "github.com/bnema/gordon/internal/server"
9+ "github.com/bnema/gordon/internal/templating/cmdparams"
9 "github.com/bnema/gordon/internal/templating/render"10 "github.com/bnema/gordon/internal/templating/render"
10 "github.com/bnema/gordon/pkg/docker"11 "github.com/bnema/gordon/pkg/docker"
11 "github.com/labstack/echo/v4"12 "github.com/labstack/echo/v4"
@@ -159,14 +160,14 @@ func ContainerManagerEditPOST(c echo.Context, a *server.App) error {
159 }160 }
160 161
161 containerConfig := c.FormValue("container_config")162 containerConfig := c.FormValue("container_config")
162- var containerParams render.YAMLContainerParams163+ var containerParams cmdparams.YAMLContainerParams
163 err = yaml.Unmarshal([]byte(containerConfig), &containerParams)164 err = yaml.Unmarshal([]byte(containerConfig), &containerParams)
164 if err != nil {165 if err != nil {
165 return sendError(c, fmt.Errorf("YAML unmarshal failed: %w", err))166 return sendError(c, fmt.Errorf("YAML unmarshal failed: %w", err))
166 }167 }
167 168
168 // Use render.FromYAMLStructToCmdParams to convert YAMLContainerParams to ContainerCommandParams169 // Use render.FromYAMLStructToCmdParams to convert YAMLContainerParams to ContainerCommandParams
169- cmdParams, err := render.FromYAMLStructToCmdParams(containerParams)170+ cmdParams, err := cmdparams.FromYAMLStructToCmdParams(containerParams)
170 if err != nil {171 if err != nil {
171 return fmt.Errorf("failed to convert YAML: %w", err)172 return fmt.Errorf("failed to convert YAML: %w", err)
172 }173 }
M internal/httpserve/handler/oauth.go +2 -0
@@ -219,6 +219,8 @@ func setSessionValues(c echo.Context, sess *sessions.Session, user *db.User, acc
219219 sess.Values["expires"] = expires
220220 sess.Values["sessionID"] = sessionID
221221
222+ fmt.Print(sess)
223+
222224 if err := sess.Save(c.Request(), c.Response()); err != nil {
223225 return fmt.Errorf("could not save session: %w", err)
224226 }
@@ -219,6 +219,8 @@ func setSessionValues(c echo.Context, sess *sessions.Session, user *db.User, acc
219 sess.Values["expires"] = expires219 sess.Values["expires"] = expires
220 sess.Values["sessionID"] = sessionID220 sess.Values["sessionID"] = sessionID
221 221
222+ fmt.Print(sess)
223+
222 if err := sess.Save(c.Request(), c.Response()); err != nil {224 if err := sess.Save(c.Request(), c.Response()); err != nil {
223 return fmt.Errorf("could not save session: %w", err)225 return fmt.Errorf("could not save session: %w", err)
224 }226 }
M internal/httpserve/handler/session.go +22 -31
@@ -1,35 +1,26 @@
11 package handler
22
3-import (
4- "log"
5- "net/http"
6- "time"
7-
8- "github.com/labstack/echo-contrib/session"
9- "github.com/labstack/echo/v4"
10-)
11-
123 // ResetSession resets the session cookie and session data
13-func ResetSession(c echo.Context) error {
14- // Manually expire the cookie
15- cookie := new(http.Cookie)
16- cookie.Name = "session"
17- cookie.Value = ""
18- cookie.Path = "/"
19- cookie.Expires = time.Unix(0, 0)
20- cookie.MaxAge = -1 // Clear immediately
21- c.SetCookie(cookie)
22- // Invalidate the existing session
23- sess, err := session.Get("session", c)
24- if err != nil {
25- log.Println(err)
26- return echo.NewHTTPError(http.StatusInternalServerError, "Could not get session")
27- }
28- sess.Options.MaxAge = -1
29- if err := sess.Save(c.Request(), c.Response()); err != nil {
30- log.Println("Could not delete session:", err)
31- return echo.NewHTTPError(http.StatusInternalServerError, "Could not delete session")
32- }
4+// func ResetSession(c echo.Context) error {
5+// // Manually expire the cookie
6+// cookie := new(http.Cookie)
7+// cookie.Name = "session"
8+// cookie.Value = ""
9+// cookie.Path = "/"
10+// cookie.Expires = time.Unix(0, 0)
11+// cookie.MaxAge = -1 // Clear immediately
12+// c.SetCookie(cookie)
13+// // Invalidate the existing session
14+// sess, err := session.Get("session", c)
15+// if err != nil {
16+// log.Println(err)
17+// return echo.NewHTTPError(http.StatusInternalServerError, "Could not get session")
18+// }
19+// sess.Options.MaxAge = -1
20+// if err := sess.Save(c.Request(), c.Response()); err != nil {
21+// log.Println("Could not delete session:", err)
22+// return echo.NewHTTPError(http.StatusInternalServerError, "Could not delete session")
23+// }
3324
34- return nil
35-}
25+// return nil
26+// }
@@ -1,35 +1,26 @@
1 package handler1 package handler
2 2
3-import (
4- "log"
5- "net/http"
6- "time"
7-
8- "github.com/labstack/echo-contrib/session"
9- "github.com/labstack/echo/v4"
10-)
11-
12 // ResetSession resets the session cookie and session data3 // ResetSession resets the session cookie and session data
13-func ResetSession(c echo.Context) error {4+// func ResetSession(c echo.Context) error {
14- // Manually expire the cookie5+// // Manually expire the cookie
15- cookie := new(http.Cookie)6+// cookie := new(http.Cookie)
16- cookie.Name = "session"7+// cookie.Name = "session"
17- cookie.Value = ""8+// cookie.Value = ""
18- cookie.Path = "/"9+// cookie.Path = "/"
19- cookie.Expires = time.Unix(0, 0)10+// cookie.Expires = time.Unix(0, 0)
20- cookie.MaxAge = -1 // Clear immediately11+// cookie.MaxAge = -1 // Clear immediately
21- c.SetCookie(cookie)12+// c.SetCookie(cookie)
22- // Invalidate the existing session13+// // Invalidate the existing session
23- sess, err := session.Get("session", c)14+// sess, err := session.Get("session", c)
24- if err != nil {15+// if err != nil {
25- log.Println(err)16+// log.Println(err)
26- return echo.NewHTTPError(http.StatusInternalServerError, "Could not get session")17+// return echo.NewHTTPError(http.StatusInternalServerError, "Could not get session")
27- }18+// }
28- sess.Options.MaxAge = -119+// sess.Options.MaxAge = -1
29- if err := sess.Save(c.Request(), c.Response()); err != nil {20+// if err := sess.Save(c.Request(), c.Response()); err != nil {
30- log.Println("Could not delete session:", err)21+// log.Println("Could not delete session:", err)
31- return echo.NewHTTPError(http.StatusInternalServerError, "Could not delete session")22+// return echo.NewHTTPError(http.StatusInternalServerError, "Could not delete session")
32- }23+// }
33 24
34- return nil25+// return nil
35-}26+// }
M internal/httpserve/middleware/requirelogin.go +69 -19
@@ -9,7 +9,6 @@ import (
99 "github.com/bnema/gordon/internal/db/queries"
1010 "github.com/bnema/gordon/internal/server"
1111
12- // "github.com/labstack/echo-contrib/session"
1312 "github.com/labstack/echo-contrib/session"
1413 "github.com/labstack/echo/v4"
1514 )
@@ -32,29 +31,48 @@ func validateSessionAndUser(c echo.Context, a *server.App) error {
3231 return err
3332 }
3433
35- fmt.Print(sess)
36-
37- if cookieExpired, err := isCookieExpired(c); err != nil || cookieExpired {
38- return err
39- }
40-
41- _, ok := sess.Values["accountID"].(string)
34+ // Retrieve accountID and sessionID from session values
35+ accountID, ok := sess.Values["accountID"].(string)
4236 if !ok {
4337 return fmt.Errorf("invalid account ID type")
4438 }
4539
46- _, ok = sess.Values["sessionID"].(string)
40+ sessionID, ok := sess.Values["sessionID"].(string)
4741 if !ok {
4842 return fmt.Errorf("invalid session ID type")
4943 }
5044
51- // if accountCheck, err := isAccountIDInDB(a, accountID); err != nil || !accountCheck {
52- // return err
53- // }
45+ // Retrieve expires from session values and parse it to time.Time
46+ expiresStr, ok := sess.Values["expires"].(string)
47+ if !ok {
48+ return fmt.Errorf("invalid expires type")
49+ }
50+ expires, err := time.Parse(time.RFC3339, expiresStr)
51+ if err != nil {
52+ return fmt.Errorf("could not parse expires: %v", err)
53+ }
54+
55+ if accountCheck, err := isAccountIDInDB(a, accountID); err != nil || !accountCheck {
56+ fmt.Println("accountCheck:", accountCheck)
57+ return err
58+ }
59+
60+ if sessionExpired, err := isSessionExpiredInDB(a, accountID, sessionID); err != nil || sessionExpired {
61+ fmt.Println("sessionExpired:", sessionExpired)
62+ return err
63+ }
5464
55- // if sessionExpired, err := isSessionExpiredInDB(a, accountID, sessionID); err != nil || sessionExpired {
56- // return err
57- // }
65+ if cookieExpired, err := isCookieExpired(c); err != nil || cookieExpired {
66+ fmt.Println("cookieExpired:", cookieExpired)
67+ return err
68+ }
69+
70+ if time.Now().After(expires) {
71+ fmt.Println("session expired")
72+ queries.SessionCleaner(a, time.Now().Format(time.RFC3339))
73+ return fmt.Errorf("session expired")
74+
75+ }
5876
5977 return nil
6078 }
@@ -68,10 +86,10 @@ func isCookieExpired(c echo.Context) (bool, error) {
6886 if err != nil {
6987 return false, err
7088 }
71- if cookie.Expires.IsZero() {
72- return false, nil // cookie has no expiration time; it's a session cookie
73- }
74- return time.Now().After(cookie.Expires), nil
89+
90+ currentTime := time.Now()
91+ expirationTime := cookie.Expires
92+ return currentTime.After(expirationTime), nil
7593 }
7694
7795 func isSessionExpiredInDB(a *server.App, userID string, sessionID string) (bool, error) {
@@ -92,3 +110,35 @@ func isAccountIDInDB(a *server.App, accountID string) (bool, error) {
92110
93111 return accountExists, nil
94112 }
113+
114+func DeleteAndRedirectToLogin(c echo.Context, a *server.App) error {
115+ sess, err := session.Get("session", c)
116+ if err != nil {
117+ return err
118+ }
119+
120+ // Retrieve accountID and sessionID from session values
121+ accountID, ok := sess.Values["accountID"].(string)
122+ if !ok {
123+ return fmt.Errorf("invalid account ID type")
124+ }
125+
126+ sessionID, ok := sess.Values["sessionID"].(string)
127+ if !ok {
128+ return fmt.Errorf("invalid session ID type")
129+ }
130+
131+ // Delete session from DB
132+ if err := queries.DeleteDBSession(a, accountID, sessionID); err != nil {
133+ return err
134+ }
135+
136+ // Delete session cookie
137+ cookie := new(http.Cookie)
138+ cookie.Name = "session"
139+ cookie.Value = ""
140+ cookie.Expires = time.Now().Add(-1 * time.Hour)
141+ c.SetCookie(cookie)
142+
143+ return redirectToLogin(c, a)
144+}
@@ -9,7 +9,6 @@ import (
9 "github.com/bnema/gordon/internal/db/queries"9 "github.com/bnema/gordon/internal/db/queries"
10 "github.com/bnema/gordon/internal/server"10 "github.com/bnema/gordon/internal/server"
11 11
12- // "github.com/labstack/echo-contrib/session"
13 "github.com/labstack/echo-contrib/session"12 "github.com/labstack/echo-contrib/session"
14 "github.com/labstack/echo/v4"13 "github.com/labstack/echo/v4"
15 )14 )
@@ -32,29 +31,48 @@ func validateSessionAndUser(c echo.Context, a *server.App) error {
32 return err31 return err
33 }32 }
34 33
35- fmt.Print(sess)34+ // Retrieve accountID and sessionID from session values
36-35+ accountID, ok := sess.Values["accountID"].(string)
37- if cookieExpired, err := isCookieExpired(c); err != nil || cookieExpired {
38- return err
39- }
40-
41- _, ok := sess.Values["accountID"].(string)
42 if !ok {36 if !ok {
43 return fmt.Errorf("invalid account ID type")37 return fmt.Errorf("invalid account ID type")
44 }38 }
45 39
46- _, ok = sess.Values["sessionID"].(string)40+ sessionID, ok := sess.Values["sessionID"].(string)
47 if !ok {41 if !ok {
48 return fmt.Errorf("invalid session ID type")42 return fmt.Errorf("invalid session ID type")
49 }43 }
50 44
51- // if accountCheck, err := isAccountIDInDB(a, accountID); err != nil || !accountCheck {45+ // Retrieve expires from session values and parse it to time.Time
52- // return err46+ expiresStr, ok := sess.Values["expires"].(string)
53- // }47+ if !ok {
48+ return fmt.Errorf("invalid expires type")
49+ }
50+ expires, err := time.Parse(time.RFC3339, expiresStr)
51+ if err != nil {
52+ return fmt.Errorf("could not parse expires: %v", err)
53+ }
54+
55+ if accountCheck, err := isAccountIDInDB(a, accountID); err != nil || !accountCheck {
56+ fmt.Println("accountCheck:", accountCheck)
57+ return err
58+ }
59+
60+ if sessionExpired, err := isSessionExpiredInDB(a, accountID, sessionID); err != nil || sessionExpired {
61+ fmt.Println("sessionExpired:", sessionExpired)
62+ return err
63+ }
54 64
55- // if sessionExpired, err := isSessionExpiredInDB(a, accountID, sessionID); err != nil || sessionExpired {65+ if cookieExpired, err := isCookieExpired(c); err != nil || cookieExpired {
56- // return err66+ fmt.Println("cookieExpired:", cookieExpired)
57- // }67+ return err
68+ }
69+
70+ if time.Now().After(expires) {
71+ fmt.Println("session expired")
72+ queries.SessionCleaner(a, time.Now().Format(time.RFC3339))
73+ return fmt.Errorf("session expired")
74+
75+ }
58 76
59 return nil77 return nil
60 }78 }
@@ -68,10 +86,10 @@ func isCookieExpired(c echo.Context) (bool, error) {
68 if err != nil {86 if err != nil {
69 return false, err87 return false, err
70 }88 }
71- if cookie.Expires.IsZero() {89+
72- return false, nil // cookie has no expiration time; it's a session cookie90+ currentTime := time.Now()
73- }91+ expirationTime := cookie.Expires
74- return time.Now().After(cookie.Expires), nil92+ return currentTime.After(expirationTime), nil
75 }93 }
76 94
77 func isSessionExpiredInDB(a *server.App, userID string, sessionID string) (bool, error) {95 func isSessionExpiredInDB(a *server.App, userID string, sessionID string) (bool, error) {
@@ -92,3 +110,35 @@ func isAccountIDInDB(a *server.App, accountID string) (bool, error) {
92 110
93 return accountExists, nil111 return accountExists, nil
94 }112 }
113+
114+func DeleteAndRedirectToLogin(c echo.Context, a *server.App) error {
115+ sess, err := session.Get("session", c)
116+ if err != nil {
117+ return err
118+ }
119+
120+ // Retrieve accountID and sessionID from session values
121+ accountID, ok := sess.Values["accountID"].(string)
122+ if !ok {
123+ return fmt.Errorf("invalid account ID type")
124+ }
125+
126+ sessionID, ok := sess.Values["sessionID"].(string)
127+ if !ok {
128+ return fmt.Errorf("invalid session ID type")
129+ }
130+
131+ // Delete session from DB
132+ if err := queries.DeleteDBSession(a, accountID, sessionID); err != nil {
133+ return err
134+ }
135+
136+ // Delete session cookie
137+ cookie := new(http.Cookie)
138+ cookie.Name = "session"
139+ cookie.Value = ""
140+ cookie.Expires = time.Now().Add(-1 * time.Hour)
141+ c.SetCookie(cookie)
142+
143+ return redirectToLogin(c, a)
144+}
M internal/httpserve/middleware/secure.go +26 -6
@@ -1,9 +1,10 @@
11 package middleware
22
33 import (
4- "net/http"
4+ "log"
55 "os"
66
7+ "github.com/bnema/gordon/internal/server"
78 "github.com/gorilla/sessions"
89 _ "github.com/joho/godotenv/autoload"
910 "github.com/labstack/echo-contrib/session"
@@ -23,14 +24,33 @@ func SecureRoutes() echo.MiddlewareFunc {
2324 }
2425
2526 // InitSessionMiddleware initializes the session middleware with secure options
26-func InitSessionMiddleware() echo.MiddlewareFunc {
27- store := sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))
27+func InitSessionMiddleware(a *server.App) echo.MiddlewareFunc {
28+ isDev := a.IsDevEnvironment()
29+ secret := os.Getenv("SESSION_SECRET")
30+ if secret == "" {
31+ log.Fatal("Environment variable SESSION_SECRET is not set or cannot be read")
32+ }
33+ store := sessions.NewCookieStore([]byte(secret))
2834 store.Options = &sessions.Options{
2935 Path: "/",
3036 HttpOnly: true,
31- Secure: true,
32- SameSite: http.SameSiteStrictMode,
37+ Secure: !isDev, // Set to true in production
3338 MaxAge: 86400,
3439 }
35- return session.Middleware(store)
40+ return func(next echo.HandlerFunc) echo.HandlerFunc {
41+ return func(c echo.Context) error {
42+ err := session.Middleware(store)(next)(c)
43+ if err != nil {
44+ log.Printf("Error in session middleware: %v", err)
45+ return err
46+ }
47+ sess, err := session.Get("session", c)
48+ if err != nil {
49+ log.Printf("Could not retrieve session: %v", err)
50+ } else {
51+ log.Printf("Session values: %+v", sess.Values)
52+ }
53+ return nil
54+ }
55+ }
3656 }
@@ -1,9 +1,10 @@
1 package middleware1 package middleware
2 2
3 import (3 import (
4- "net/http"4+ "log"
5 "os"5 "os"
6 6
7+ "github.com/bnema/gordon/internal/server"
7 "github.com/gorilla/sessions"8 "github.com/gorilla/sessions"
8 _ "github.com/joho/godotenv/autoload"9 _ "github.com/joho/godotenv/autoload"
9 "github.com/labstack/echo-contrib/session"10 "github.com/labstack/echo-contrib/session"
@@ -23,14 +24,33 @@ func SecureRoutes() echo.MiddlewareFunc {
23 }24 }
24 25
25 // InitSessionMiddleware initializes the session middleware with secure options26 // InitSessionMiddleware initializes the session middleware with secure options
26-func InitSessionMiddleware() echo.MiddlewareFunc {27+func InitSessionMiddleware(a *server.App) echo.MiddlewareFunc {
27- store := sessions.NewCookieStore([]byte(os.Getenv("SESSION_SECRET")))28+ isDev := a.IsDevEnvironment()
29+ secret := os.Getenv("SESSION_SECRET")
30+ if secret == "" {
31+ log.Fatal("Environment variable SESSION_SECRET is not set or cannot be read")
32+ }
33+ store := sessions.NewCookieStore([]byte(secret))
28 store.Options = &sessions.Options{34 store.Options = &sessions.Options{
29 Path: "/",35 Path: "/",
30 HttpOnly: true,36 HttpOnly: true,
31- Secure: true,37+ Secure: !isDev, // Set to true in production
32- SameSite: http.SameSiteStrictMode,
33 MaxAge: 86400,38 MaxAge: 86400,
34 }39 }
35- return session.Middleware(store)40+ return func(next echo.HandlerFunc) echo.HandlerFunc {
41+ return func(c echo.Context) error {
42+ err := session.Middleware(store)(next)(c)
43+ if err != nil {
44+ log.Printf("Error in session middleware: %v", err)
45+ return err
46+ }
47+ sess, err := session.Get("session", c)
48+ if err != nil {
49+ log.Printf("Could not retrieve session: %v", err)
50+ } else {
51+ log.Printf("Session values: %+v", sess.Values)
52+ }
53+ return nil
54+ }
55+ }
36 }56 }
M internal/httpserve/router.go +1 -1
@@ -19,7 +19,7 @@ func RegisterRoutes(e *echo.Echo, a *server.App) *echo.Echo {
1919 e.Use(middleware.ErrorHandler)
2020
2121 // Initiate the session middleware
22- e.Use(middleware.InitSessionMiddleware())
22+ e.Use(middleware.InitSessionMiddleware(a))
2323 // Use middlewares
2424 e.Use(middleware.SecureRoutes())
2525
@@ -19,7 +19,7 @@ func RegisterRoutes(e *echo.Echo, a *server.App) *echo.Echo {
19 e.Use(middleware.ErrorHandler)19 e.Use(middleware.ErrorHandler)
20 20
21 // Initiate the session middleware21 // Initiate the session middleware
22- e.Use(middleware.InitSessionMiddleware())22+ e.Use(middleware.InitSessionMiddleware(a))
23 // Use middlewares23 // Use middlewares
24 e.Use(middleware.SecureRoutes())24 e.Use(middleware.SecureRoutes())
25 25
M internal/server/appserver.go +4 -0
@@ -52,6 +52,10 @@ func (a *App) GenerateOauthCallbackURL() string {
5252 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
5353 }
5454
55+func (a *App) IsDevEnvironment() bool {
56+ return a.Config.General.RunEnv == "dev"
57+}
58+
5559 func (a *App) GetUptime() string {
5660 uptime := time.Since(a.StartTime)
5761 return uptime.String()
@@ -52,6 +52,10 @@ func (a *App) GenerateOauthCallbackURL() string {
52 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)52 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
53 }53 }
54 54
55+func (a *App) IsDevEnvironment() bool {
56+ return a.Config.General.RunEnv == "dev"
57+}
58+
55 func (a *App) GetUptime() string {59 func (a *App) GetUptime() string {
56 uptime := time.Since(a.StartTime)60 uptime := time.Since(a.StartTime)
57 return uptime.String()61 return uptime.String()
M internal/server/newserver.go +1 -1
@@ -19,7 +19,7 @@ func NewServerApp() (*App, error) {
1919 }
2020
2121 // Open the strings.yml file containing the strings for the current language
22- file, err := templating.TemplateFS.Open("locstrings.yml")
22+ file, err := templating.TemplateFS.Open("txt/locstrings.yml")
2323 if err != nil {
2424 log.Fatalf("Failed to open strings.yml: %v", err)
2525 }
@@ -19,7 +19,7 @@ func NewServerApp() (*App, error) {
19 }19 }
20 20
21 // Open the strings.yml file containing the strings for the current language21 // Open the strings.yml file containing the strings for the current language
22- file, err := templating.TemplateFS.Open("locstrings.yml")22+ file, err := templating.TemplateFS.Open("txt/locstrings.yml")
23 if err != nil {23 if err != nil {
24 log.Fatalf("Failed to open strings.yml: %v", err)24 log.Fatalf("Failed to open strings.yml: %v", err)
25 }25 }
A internal/templating/cmdparams/_fromcli.go +24 -0
@@ -0,0 +1,24 @@
1+package cmdparams
2+
3+import (
4+ "github.com/bnema/gordon/internal/common"
5+ "github.com/bnema/gordon/pkg/docker"
6+)
7+
8+// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
9+func FromPayloadStructToCmdParams(PushPayload *common.PushPayload) (docker.ContainerCommandParams, error) {
10+
11+ params := docker.ContainerCommandParams{
12+ ContainerName: yamlParams.Name,
13+ ImageName: yamlParams.Image,
14+ ImageID: imageID,
15+ Volumes: yamlParams.Volumes,
16+ Environment: yamlParams.Environment,
17+ Labels: labels,
18+ Network: network,
19+ Restart: yamlParams.Restart,
20+ PortMappings: portMappings,
21+ }
22+
23+ return params, nil
24+}
@@ -0,0 +1,24 @@
1+package cmdparams
2+
3+import (
4+ "github.com/bnema/gordon/internal/common"
5+ "github.com/bnema/gordon/pkg/docker"
6+)
7+
8+// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
9+func FromPayloadStructToCmdParams(PushPayload *common.PushPayload) (docker.ContainerCommandParams, error) {
10+
11+ params := docker.ContainerCommandParams{
12+ ContainerName: yamlParams.Name,
13+ ImageName: yamlParams.Image,
14+ ImageID: imageID,
15+ Volumes: yamlParams.Volumes,
16+ Environment: yamlParams.Environment,
17+ Labels: labels,
18+ Network: network,
19+ Restart: yamlParams.Restart,
20+ PortMappings: portMappings,
21+ }
22+
23+ return params, nil
24+}
A internal/templating/cmdparams/frominputs.go +34 -0
@@ -0,0 +1,34 @@
1+package cmdparams
2+
3+import (
4+ "github.com/bnema/gordon/internal/server"
5+ "github.com/bnema/gordon/pkg/docker"
6+)
7+
8+// FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct
9+func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
10+ volumeSlice := ParseVolumeSlice(inputs["volumes"])
11+ environmentSlice := ParseEnvironmentSlice(inputs["environment_variables"])
12+ portMappings, err := ParsePortMappingsSlice(inputs["ports"])
13+ if err != nil {
14+ return docker.ContainerCommandParams{}, err
15+ }
16+
17+ params := docker.ContainerCommandParams{
18+ IsSSL: inputs["container_protocol"] == "https",
19+ ContainerName: inputs["container_name"],
20+ ServiceName: inputs["container_subdomain"],
21+ Domain: inputs["container_domain"],
22+ ImageName: inputs["image_name"],
23+ ImageID: inputs["image_id"],
24+ Restart: inputs["restart"],
25+ Volumes: volumeSlice,
26+ Environment: environmentSlice,
27+ Network: a.Config.ContainerEngine.Network,
28+ PortMappings: portMappings,
29+ }
30+
31+ CreateTraefikLabels(&params)
32+
33+ return params, nil
34+}
@@ -0,0 +1,34 @@
1+package cmdparams
2+
3+import (
4+ "github.com/bnema/gordon/internal/server"
5+ "github.com/bnema/gordon/pkg/docker"
6+)
7+
8+// FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct
9+func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
10+ volumeSlice := ParseVolumeSlice(inputs["volumes"])
11+ environmentSlice := ParseEnvironmentSlice(inputs["environment_variables"])
12+ portMappings, err := ParsePortMappingsSlice(inputs["ports"])
13+ if err != nil {
14+ return docker.ContainerCommandParams{}, err
15+ }
16+
17+ params := docker.ContainerCommandParams{
18+ IsSSL: inputs["container_protocol"] == "https",
19+ ContainerName: inputs["container_name"],
20+ ServiceName: inputs["container_subdomain"],
21+ Domain: inputs["container_domain"],
22+ ImageName: inputs["image_name"],
23+ ImageID: inputs["image_id"],
24+ Restart: inputs["restart"],
25+ Volumes: volumeSlice,
26+ Environment: environmentSlice,
27+ Network: a.Config.ContainerEngine.Network,
28+ PortMappings: portMappings,
29+ }
30+
31+ CreateTraefikLabels(&params)
32+
33+ return params, nil
34+}
A internal/templating/cmdparams/fromyaml.go +58 -0
@@ -0,0 +1,58 @@
1+package cmdparams
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/bnema/gordon/pkg/docker"
8+)
9+
10+type YAMLContainerParams struct {
11+ Name string `yaml:"Name"`
12+ Image string `yaml:"Image"`
13+ Hostname string `yaml:"Hostname"`
14+ Ports []string `yaml:"Ports"`
15+ Volumes []string `yaml:"Volumes"`
16+ Environment []string `yaml:"Environment"`
17+ Labels map[string]string `yaml:"Labels"`
18+ Network []string `yaml:"Network"`
19+ Restart string `yaml:"Restart"`
20+}
21+
22+// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
23+func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) {
24+ // with the image name find the image id
25+ imageID, err := docker.GetImageID(yamlParams.Image)
26+ if err != nil {
27+ return docker.ContainerCommandParams{}, err
28+ }
29+
30+ // Convert the map of labels to a slice of strings
31+ var labels []string
32+ for k, v := range yamlParams.Labels {
33+ labels = append(labels, fmt.Sprintf("%s=%s", k, v))
34+ }
35+
36+ // Ports input parsing
37+ portMappings, err := docker.ParsePortsSpecs(yamlParams.Ports)
38+ if err != nil {
39+ return docker.ContainerCommandParams{}, err
40+ }
41+
42+ // Convert the slice of strings to a comma separated string
43+ network := strings.Join(yamlParams.Network, ",")
44+
45+ params := docker.ContainerCommandParams{
46+ ContainerName: yamlParams.Name,
47+ ImageName: yamlParams.Image,
48+ ImageID: imageID,
49+ Volumes: yamlParams.Volumes,
50+ Environment: yamlParams.Environment,
51+ Labels: labels,
52+ Network: network,
53+ Restart: yamlParams.Restart,
54+ PortMappings: portMappings,
55+ }
56+
57+ return params, nil
58+}
@@ -0,0 +1,58 @@
1+package cmdparams
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/bnema/gordon/pkg/docker"
8+)
9+
10+type YAMLContainerParams struct {
11+ Name string `yaml:"Name"`
12+ Image string `yaml:"Image"`
13+ Hostname string `yaml:"Hostname"`
14+ Ports []string `yaml:"Ports"`
15+ Volumes []string `yaml:"Volumes"`
16+ Environment []string `yaml:"Environment"`
17+ Labels map[string]string `yaml:"Labels"`
18+ Network []string `yaml:"Network"`
19+ Restart string `yaml:"Restart"`
20+}
21+
22+// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
23+func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) {
24+ // with the image name find the image id
25+ imageID, err := docker.GetImageID(yamlParams.Image)
26+ if err != nil {
27+ return docker.ContainerCommandParams{}, err
28+ }
29+
30+ // Convert the map of labels to a slice of strings
31+ var labels []string
32+ for k, v := range yamlParams.Labels {
33+ labels = append(labels, fmt.Sprintf("%s=%s", k, v))
34+ }
35+
36+ // Ports input parsing
37+ portMappings, err := docker.ParsePortsSpecs(yamlParams.Ports)
38+ if err != nil {
39+ return docker.ContainerCommandParams{}, err
40+ }
41+
42+ // Convert the slice of strings to a comma separated string
43+ network := strings.Join(yamlParams.Network, ",")
44+
45+ params := docker.ContainerCommandParams{
46+ ContainerName: yamlParams.Name,
47+ ImageName: yamlParams.Image,
48+ ImageID: imageID,
49+ Volumes: yamlParams.Volumes,
50+ Environment: yamlParams.Environment,
51+ Labels: labels,
52+ Network: network,
53+ Restart: yamlParams.Restart,
54+ PortMappings: portMappings,
55+ }
56+
57+ return params, nil
58+}
A internal/templating/cmdparams/parsing.go +35 -0
@@ -0,0 +1,35 @@
1+package cmdparams
2+
3+import (
4+ "strings"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+// ParseVolumeSlice parses the volumes string into a slice.
10+func ParseVolumeSlice(volumeStr string) []string {
11+ volumeSlice := strings.Split(volumeStr, ",")
12+ if len(volumeSlice) == 1 && volumeSlice[0] == "" {
13+ return []string{}
14+ }
15+ return volumeSlice
16+}
17+
18+// ParseEnvironmentSlice parses the environment variables string into a slice.
19+func ParseEnvironmentSlice(environmentStr string) []string {
20+ environmentSlice := strings.Split(environmentStr, "\n")
21+ if len(environmentSlice) == 1 && environmentSlice[0] == "" {
22+ return []string{}
23+ }
24+ return environmentSlice
25+}
26+
27+// ParsePortMappingsSlice parses the port mappings string into a slice and a struct.
28+func ParsePortMappingsSlice(portMappingsStr string) ([]docker.PortMapping, error) {
29+ portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
30+ portMappingsSlice := make([]string, len(portMappingsSliceRaw))
31+ for i, spec := range portMappingsSliceRaw {
32+ portMappingsSlice[i] = strings.TrimSpace(spec)
33+ }
34+ return docker.ParsePortsSpecs(portMappingsSlice)
35+}
@@ -0,0 +1,35 @@
1+package cmdparams
2+
3+import (
4+ "strings"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+// ParseVolumeSlice parses the volumes string into a slice.
10+func ParseVolumeSlice(volumeStr string) []string {
11+ volumeSlice := strings.Split(volumeStr, ",")
12+ if len(volumeSlice) == 1 && volumeSlice[0] == "" {
13+ return []string{}
14+ }
15+ return volumeSlice
16+}
17+
18+// ParseEnvironmentSlice parses the environment variables string into a slice.
19+func ParseEnvironmentSlice(environmentStr string) []string {
20+ environmentSlice := strings.Split(environmentStr, "\n")
21+ if len(environmentSlice) == 1 && environmentSlice[0] == "" {
22+ return []string{}
23+ }
24+ return environmentSlice
25+}
26+
27+// ParsePortMappingsSlice parses the port mappings string into a slice and a struct.
28+func ParsePortMappingsSlice(portMappingsStr string) ([]docker.PortMapping, error) {
29+ portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
30+ portMappingsSlice := make([]string, len(portMappingsSliceRaw))
31+ for i, spec := range portMappingsSliceRaw {
32+ portMappingsSlice[i] = strings.TrimSpace(spec)
33+ }
34+ return docker.ParsePortsSpecs(portMappingsSlice)
35+}
A internal/templating/cmdparams/traefik.go +29 -0
@@ -0,0 +1,29 @@
1+package cmdparams
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+// CreateTraefikLabels creates Traefik labels based on the provided parameters.
10+func CreateTraefikLabels(params *docker.ContainerCommandParams) {
11+ entryPoint := "web"
12+ if params.IsSSL {
13+ entryPoint = "websecure"
14+ }
15+ baseRouter := "traefik.http.routers." + params.ContainerName
16+ baseService := "traefik.http.services." + params.ContainerName
17+ exposedPort := params.PortMappings[0].ContainerPort
18+
19+ params.Labels = []string{
20+ "traefik.enable=true",
21+ fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
22+ fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
23+ fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, exposedPort),
24+ }
25+
26+ if params.IsSSL {
27+ params.Labels = append(params.Labels, fmt.Sprintf("%s.tls.certresolver=letsencrypt", baseRouter))
28+ }
29+}
@@ -0,0 +1,29 @@
1+package cmdparams
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/pkg/docker"
7+)
8+
9+// CreateTraefikLabels creates Traefik labels based on the provided parameters.
10+func CreateTraefikLabels(params *docker.ContainerCommandParams) {
11+ entryPoint := "web"
12+ if params.IsSSL {
13+ entryPoint = "websecure"
14+ }
15+ baseRouter := "traefik.http.routers." + params.ContainerName
16+ baseService := "traefik.http.services." + params.ContainerName
17+ exposedPort := params.PortMappings[0].ContainerPort
18+
19+ params.Labels = []string{
20+ "traefik.enable=true",
21+ fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
22+ fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
23+ fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, exposedPort),
24+ }
25+
26+ if params.IsSSL {
27+ params.Labels = append(params.Labels, fmt.Sprintf("%s.tls.certresolver=letsencrypt", baseRouter))
28+ }
29+}
R internal/templating/models/locstrings.yml → internal/templating/models/txt/locstrings.yml +0 -0 renamed, 100% similar

renamed, 100% similar

D internal/templating/render/cmdparams.go +0 -132
@@ -1,132 +0,0 @@
1-package render
2-
3-import (
4- "fmt"
5- "strings"
6-
7- "github.com/bnema/gordon/internal/server"
8- "github.com/bnema/gordon/pkg/docker"
9-)
10-
11-// FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct
12-func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
13- volumeStr := inputs["volumes"]
14- volumeSlice := strings.Split(volumeStr, ",")
15-
16- // If there is no volume value we just resturn an empty strings ""
17- if len(volumeSlice) == 1 && volumeSlice[0] == "" {
18- volumeSlice = []string{}
19- }
20-
21- // Retreive the content of the textarea environment_variables and create a slice of strings
22- environmentStr := inputs["environment_variables"]
23- environmentSlice := strings.Split(environmentStr, "\n")
24-
25- // If there is no environment value we just resturn an empty strings ""
26- if len(environmentSlice) == 1 && environmentSlice[0] == "" {
27- environmentSlice = []string{}
28- }
29-
30- // Ports input parsing
31- portMappingsStr := inputs["ports"]
32- portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
33-
34- // Trim whitespace from each port mapping string
35- portMappingsSlice := make([]string, len(portMappingsSliceRaw))
36- for i, spec := range portMappingsSliceRaw {
37- portMappingsSlice[i] = strings.TrimSpace(spec)
38- }
39-
40- portMappings, err := docker.ParsePortsSpecs(portMappingsSlice)
41- if err != nil {
42- return docker.ContainerCommandParams{}, err
43- }
44-
45- // Get the exposed port for traefik labels
46- exposedPort := portMappings[0].ContainerPort
47-
48- params := docker.ContainerCommandParams{
49- IsSSL: inputs["container_protocol"] == "https",
50- ContainerName: inputs["container_name"],
51- ServiceName: inputs["container_subdomain"],
52- Domain: inputs["container_domain"],
53- ImageName: inputs["image_name"],
54- ImageID: inputs["image_id"],
55- Restart: inputs["restart"],
56- Volumes: volumeSlice,
57- Environment: environmentSlice,
58- Network: a.Config.ContainerEngine.Network,
59- PortMappings: portMappings,
60- }
61-
62- entryPoint := "web"
63- if params.IsSSL {
64- entryPoint = "websecure"
65- }
66-
67- baseRouter := "traefik.http.routers." + params.ContainerName
68- baseService := "traefik.http.services." + params.ContainerName
69-
70- params.Labels = []string{
71- "traefik.enable=true",
72- fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
73- fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
74- fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, exposedPort),
75- }
76-
77- if params.IsSSL {
78- params.Labels = append(params.Labels, fmt.Sprintf("%s.tls.certresolver=letsencrypt", baseRouter))
79- }
80-
81- return params, nil
82-}
83-
84-type YAMLContainerParams struct {
85- Name string `yaml:"Name"`
86- Image string `yaml:"Image"`
87- Hostname string `yaml:"Hostname"`
88- Ports []string `yaml:"Ports"`
89- Volumes []string `yaml:"Volumes"`
90- Environment []string `yaml:"Environment"`
91- Labels map[string]string `yaml:"Labels"`
92- Network []string `yaml:"Network"`
93- Restart string `yaml:"Restart"`
94-}
95-
96-// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
97-func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) {
98- // with the image name find the image id
99- imageID, err := docker.GetImageID(yamlParams.Image)
100- if err != nil {
101- return docker.ContainerCommandParams{}, err
102- }
103-
104- // Convert the map of labels to a slice of strings
105- var labels []string
106- for k, v := range yamlParams.Labels {
107- labels = append(labels, fmt.Sprintf("%s=%s", k, v))
108- }
109-
110- // Ports input parsing
111- portMappings, err := docker.ParsePortsSpecs(yamlParams.Ports)
112- if err != nil {
113- return docker.ContainerCommandParams{}, err
114- }
115-
116- // Convert the slice of strings to a comma separated string
117- network := strings.Join(yamlParams.Network, ",")
118-
119- params := docker.ContainerCommandParams{
120- ContainerName: yamlParams.Name,
121- ImageName: yamlParams.Image,
122- ImageID: imageID,
123- Volumes: yamlParams.Volumes,
124- Environment: yamlParams.Environment,
125- Labels: labels,
126- Network: network,
127- Restart: yamlParams.Restart,
128- PortMappings: portMappings,
129- }
130-
131- return params, nil
132-}
@@ -1,132 +0,0 @@
1-package render
2-
3-import (
4- "fmt"
5- "strings"
6-
7- "github.com/bnema/gordon/internal/server"
8- "github.com/bnema/gordon/pkg/docker"
9-)
10-
11-// FromInputsToCmdParams transforms the inputs map into a ContainerCommandParams struct
12-func FromInputsToCmdParams(inputs map[string]string, a *server.App) (docker.ContainerCommandParams, error) {
13- volumeStr := inputs["volumes"]
14- volumeSlice := strings.Split(volumeStr, ",")
15-
16- // If there is no volume value we just resturn an empty strings ""
17- if len(volumeSlice) == 1 && volumeSlice[0] == "" {
18- volumeSlice = []string{}
19- }
20-
21- // Retreive the content of the textarea environment_variables and create a slice of strings
22- environmentStr := inputs["environment_variables"]
23- environmentSlice := strings.Split(environmentStr, "\n")
24-
25- // If there is no environment value we just resturn an empty strings ""
26- if len(environmentSlice) == 1 && environmentSlice[0] == "" {
27- environmentSlice = []string{}
28- }
29-
30- // Ports input parsing
31- portMappingsStr := inputs["ports"]
32- portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
33-
34- // Trim whitespace from each port mapping string
35- portMappingsSlice := make([]string, len(portMappingsSliceRaw))
36- for i, spec := range portMappingsSliceRaw {
37- portMappingsSlice[i] = strings.TrimSpace(spec)
38- }
39-
40- portMappings, err := docker.ParsePortsSpecs(portMappingsSlice)
41- if err != nil {
42- return docker.ContainerCommandParams{}, err
43- }
44-
45- // Get the exposed port for traefik labels
46- exposedPort := portMappings[0].ContainerPort
47-
48- params := docker.ContainerCommandParams{
49- IsSSL: inputs["container_protocol"] == "https",
50- ContainerName: inputs["container_name"],
51- ServiceName: inputs["container_subdomain"],
52- Domain: inputs["container_domain"],
53- ImageName: inputs["image_name"],
54- ImageID: inputs["image_id"],
55- Restart: inputs["restart"],
56- Volumes: volumeSlice,
57- Environment: environmentSlice,
58- Network: a.Config.ContainerEngine.Network,
59- PortMappings: portMappings,
60- }
61-
62- entryPoint := "web"
63- if params.IsSSL {
64- entryPoint = "websecure"
65- }
66-
67- baseRouter := "traefik.http.routers." + params.ContainerName
68- baseService := "traefik.http.services." + params.ContainerName
69-
70- params.Labels = []string{
71- "traefik.enable=true",
72- fmt.Sprintf("%s.rule=Host(`%s.%s`)", baseRouter, params.ServiceName, params.Domain),
73- fmt.Sprintf("%s.entrypoints=%s", baseRouter, entryPoint),
74- fmt.Sprintf("%s.loadbalancer.server.port=%s", baseService, exposedPort),
75- }
76-
77- if params.IsSSL {
78- params.Labels = append(params.Labels, fmt.Sprintf("%s.tls.certresolver=letsencrypt", baseRouter))
79- }
80-
81- return params, nil
82-}
83-
84-type YAMLContainerParams struct {
85- Name string `yaml:"Name"`
86- Image string `yaml:"Image"`
87- Hostname string `yaml:"Hostname"`
88- Ports []string `yaml:"Ports"`
89- Volumes []string `yaml:"Volumes"`
90- Environment []string `yaml:"Environment"`
91- Labels map[string]string `yaml:"Labels"`
92- Network []string `yaml:"Network"`
93- Restart string `yaml:"Restart"`
94-}
95-
96-// FromYAMLStructToCmdParams converts a YAMLContainerParams struct to a ContainerCommandParams struct
97-func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.ContainerCommandParams, error) {
98- // with the image name find the image id
99- imageID, err := docker.GetImageID(yamlParams.Image)
100- if err != nil {
101- return docker.ContainerCommandParams{}, err
102- }
103-
104- // Convert the map of labels to a slice of strings
105- var labels []string
106- for k, v := range yamlParams.Labels {
107- labels = append(labels, fmt.Sprintf("%s=%s", k, v))
108- }
109-
110- // Ports input parsing
111- portMappings, err := docker.ParsePortsSpecs(yamlParams.Ports)
112- if err != nil {
113- return docker.ContainerCommandParams{}, err
114- }
115-
116- // Convert the slice of strings to a comma separated string
117- network := strings.Join(yamlParams.Network, ",")
118-
119- params := docker.ContainerCommandParams{
120- ContainerName: yamlParams.Name,
121- ImageName: yamlParams.Image,
122- ImageID: imageID,
123- Volumes: yamlParams.Volumes,
124- Environment: yamlParams.Environment,
125- Labels: labels,
126- Network: network,
127- Restart: yamlParams.Restart,
128- PortMappings: portMappings,
129- }
130-
131- return params, nil
132-}
M pkg/docker/client.go +6 -6
@@ -30,7 +30,7 @@ type DockerClient struct{}
3030 func (d *DockerClient) InitializeClient(config *Config) error {
3131 // Validate if the Sock field is not empty
3232 if config.Sock == "" {
33- return fmt.Errorf("Sock field in Config is empty")
33+ return fmt.Errorf("sock field in Config is empty")
3434 }
3535
3636 // Check if the socket file exists if not return an error
@@ -44,7 +44,7 @@ func (d *DockerClient) InitializeClient(config *Config) error {
4444 client.WithHost("unix://"+config.Sock),
4545 )
4646 if err != nil {
47- return fmt.Errorf("Error initializing Docker client: %s", err)
47+ return fmt.Errorf("error initializing Docker client: %s", err)
4848 }
4949 dockerCli = cli
5050 currentConfig = config
@@ -55,17 +55,17 @@ func (d *DockerClient) InitializeClient(config *Config) error {
5555 func CheckIfInitialized() error {
5656 // Check if there is a socket file
5757 if currentConfig == nil || currentConfig.Sock == "" {
58- return fmt.Errorf("Docker client is not initialized or configuration is missing")
58+ return fmt.Errorf("docker client is not initialized or configuration is missing")
5959 }
6060
6161 socketPath := currentConfig.Sock // Use the package-level variable
6262 if _, err := os.Stat(socketPath); os.IsNotExist(err) {
63- return fmt.Errorf("Sock file does not exist: %s", socketPath)
63+ return fmt.Errorf("sock file does not exist: %s", socketPath)
6464 }
6565
6666 // Then we check if the dockerCli is nil
6767 if dockerCli == nil {
68- return fmt.Errorf("Docker client is not initialized")
68+ return fmt.Errorf("docker client is not initialized")
6969 }
7070
7171 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -74,7 +74,7 @@ func CheckIfInitialized() error {
7474 // More stringent check: try to list containers
7575 _, err := dockerCli.ContainerList(ctx, types.ContainerListOptions{})
7676 if err != nil {
77- return fmt.Errorf("Cannot connect to Docker daemon: %s", err)
77+ return fmt.Errorf("cannot connect to Docker daemon: %s", err)
7878 }
7979 return nil
8080 }
@@ -30,7 +30,7 @@ type DockerClient struct{}
30 func (d *DockerClient) InitializeClient(config *Config) error {30 func (d *DockerClient) InitializeClient(config *Config) error {
31 // Validate if the Sock field is not empty31 // Validate if the Sock field is not empty
32 if config.Sock == "" {32 if config.Sock == "" {
33- return fmt.Errorf("Sock field in Config is empty")33+ return fmt.Errorf("sock field in Config is empty")
34 }34 }
35 35
36 // Check if the socket file exists if not return an error36 // Check if the socket file exists if not return an error
@@ -44,7 +44,7 @@ func (d *DockerClient) InitializeClient(config *Config) error {
44 client.WithHost("unix://"+config.Sock),44 client.WithHost("unix://"+config.Sock),
45 )45 )
46 if err != nil {46 if err != nil {
47- return fmt.Errorf("Error initializing Docker client: %s", err)47+ return fmt.Errorf("error initializing Docker client: %s", err)
48 }48 }
49 dockerCli = cli49 dockerCli = cli
50 currentConfig = config50 currentConfig = config
@@ -55,17 +55,17 @@ func (d *DockerClient) InitializeClient(config *Config) error {
55 func CheckIfInitialized() error {55 func CheckIfInitialized() error {
56 // Check if there is a socket file56 // Check if there is a socket file
57 if currentConfig == nil || currentConfig.Sock == "" {57 if currentConfig == nil || currentConfig.Sock == "" {
58- return fmt.Errorf("Docker client is not initialized or configuration is missing")58+ return fmt.Errorf("docker client is not initialized or configuration is missing")
59 }59 }
60 60
61 socketPath := currentConfig.Sock // Use the package-level variable61 socketPath := currentConfig.Sock // Use the package-level variable
62 if _, err := os.Stat(socketPath); os.IsNotExist(err) {62 if _, err := os.Stat(socketPath); os.IsNotExist(err) {
63- return fmt.Errorf("Sock file does not exist: %s", socketPath)63+ return fmt.Errorf("sock file does not exist: %s", socketPath)
64 }64 }
65 65
66 // Then we check if the dockerCli is nil66 // Then we check if the dockerCli is nil
67 if dockerCli == nil {67 if dockerCli == nil {
68- return fmt.Errorf("Docker client is not initialized")68+ return fmt.Errorf("docker client is not initialized")
69 }69 }
70 70
71 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)71 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -74,7 +74,7 @@ func CheckIfInitialized() error {
74 // More stringent check: try to list containers74 // More stringent check: try to list containers
75 _, err := dockerCli.ContainerList(ctx, types.ContainerListOptions{})75 _, err := dockerCli.ContainerList(ctx, types.ContainerListOptions{})
76 if err != nil {76 if err != nil {
77- return fmt.Errorf("Cannot connect to Docker daemon: %s", err)77+ return fmt.Errorf("cannot connect to Docker daemon: %s", err)
78 }78 }
79 return nil79 return nil
80 }80 }
M pkg/docker/containers.go +47 -0
@@ -202,6 +202,27 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
202202 // Check if the Docker client has been initialized
203203 CheckIfInitialized()
204204
205+ isNetworkCreated, err := CheckIfNetworkExists(cmdParams.Network)
206+ if err != nil {
207+ return "", err
208+ }
209+
210+ if !isNetworkCreated {
211+ _, err := dockerCli.NetworkCreate(context.Background(), cmdParams.Network, types.NetworkCreate{})
212+ if err != nil {
213+ return "", err
214+ }
215+ }
216+
217+ // if the network exist return its id
218+ // networkInfo, err := GetNetworkInfo(cmdParams.Network)
219+ // if err != nil {
220+ // return "", err
221+
222+ // }
223+
224+ // fmt.Print(networkInfo.ID)
225+
205226 // Prepare port bindings
206227 portBindings := nat.PortMap{}
207228 for _, portMapping := range cmdParams.PortMappings {
@@ -257,6 +278,32 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
257278 return resp.ID, nil
258279 }
259280
281+func CheckIfNetworkExists(networkName string) (bool, error) {
282+ networks, err := dockerCli.NetworkList(context.Background(), types.NetworkListOptions{})
283+ if err != nil {
284+ return false, err
285+ }
286+
287+ for _, network := range networks {
288+ if network.Name == networkName {
289+ return true, nil
290+ }
291+ }
292+
293+ return false, nil
294+}
295+
296+// GetNetworkInfo returns information about a network
297+func GetNetworkInfo(networkName string) (types.NetworkResource, error) {
298+ // Get network info using the Docker client
299+ networkInfo, err := dockerCli.NetworkInspect(context.Background(), networkName, types.NetworkInspectOptions{})
300+ if err != nil {
301+ return types.NetworkResource{}, err
302+ }
303+
304+ return networkInfo, nil
305+}
306+
260307 // GetContainerInfo returns information about a container
261308 func GetContainerInfo(containerID string) (types.ContainerJSON, error) {
262309 // Get container info using the Docker client
@@ -202,6 +202,27 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
202 // Check if the Docker client has been initialized202 // Check if the Docker client has been initialized
203 CheckIfInitialized()203 CheckIfInitialized()
204 204
205+ isNetworkCreated, err := CheckIfNetworkExists(cmdParams.Network)
206+ if err != nil {
207+ return "", err
208+ }
209+
210+ if !isNetworkCreated {
211+ _, err := dockerCli.NetworkCreate(context.Background(), cmdParams.Network, types.NetworkCreate{})
212+ if err != nil {
213+ return "", err
214+ }
215+ }
216+
217+ // if the network exist return its id
218+ // networkInfo, err := GetNetworkInfo(cmdParams.Network)
219+ // if err != nil {
220+ // return "", err
221+
222+ // }
223+
224+ // fmt.Print(networkInfo.ID)
225+
205 // Prepare port bindings226 // Prepare port bindings
206 portBindings := nat.PortMap{}227 portBindings := nat.PortMap{}
207 for _, portMapping := range cmdParams.PortMappings {228 for _, portMapping := range cmdParams.PortMappings {
@@ -257,6 +278,32 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
257 return resp.ID, nil278 return resp.ID, nil
258 }279 }
259 280
281+func CheckIfNetworkExists(networkName string) (bool, error) {
282+ networks, err := dockerCli.NetworkList(context.Background(), types.NetworkListOptions{})
283+ if err != nil {
284+ return false, err
285+ }
286+
287+ for _, network := range networks {
288+ if network.Name == networkName {
289+ return true, nil
290+ }
291+ }
292+
293+ return false, nil
294+}
295+
296+// GetNetworkInfo returns information about a network
297+func GetNetworkInfo(networkName string) (types.NetworkResource, error) {
298+ // Get network info using the Docker client
299+ networkInfo, err := dockerCli.NetworkInspect(context.Background(), networkName, types.NetworkInspectOptions{})
300+ if err != nil {
301+ return types.NetworkResource{}, err
302+ }
303+
304+ return networkInfo, nil
305+}
306+
260 // GetContainerInfo returns information about a container307 // GetContainerInfo returns information about a container
261 func GetContainerInfo(containerID string) (types.ContainerJSON, error) {308 func GetContainerInfo(containerID string) (types.ContainerJSON, error) {
262 // Get container info using the Docker client309 // Get container info using the Docker client

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