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
- b98a337ccaf9cf1fe79d50efe0ce6a8e31a40e1b
- author
- bnema · · 2023-10-29 16:54 UTC
- parent
- e31cd24
- signature
- unsigned
22 files · +384 -201
M .gitignore +1 -0
| @@ -7,3 +7,4 @@ bun.lockb | |||
| 7 | 7 | *.log | |
| 8 | 8 | *.txt | |
| 9 | 9 | *sync-conflict* | |
| 10 | + | main⏎̸ | |
| @@ -7,3 +7,4 @@ bun.lockb | |||||
| 7 | *.log | 7 | *.log | ||
| 8 | *.txt | 8 | *.txt | ||
| 9 | *sync-conflict* | 9 | *sync-conflict* | ||
| 10 | + | main⏎̸ | |||
M internal/common/initconfig.go +1 -0
| @@ -43,6 +43,7 @@ type ContainerEngineConfig struct { | |||
| 43 | 43 | Network string `yaml:"network"` | |
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | + | // Default values | |
| 46 | 47 | var ( | |
| 47 | 48 | sock = "/var/run/docker.sock" | |
| 48 | 49 | 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 { | |||
| 93 | 93 | 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 | 107 | func GetSessionExpiration(a *server.App, accountID string, sessionID string, currentTime time.Time) (time.Time, error) { | |
| 97 | 108 | var expirationTime string | |
| 98 | 109 | // with userID and sessionID we can get the expiration time | |
| @@ -93,6 +93,17 @@ func SessionCleaner(a *server.App, currentTime string) error { | |||||
| 93 | return nil | 93 | 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 string | 108 | var expirationTime string | ||
| 98 | // with userID and sessionID we can get the expiration time | 109 | // with userID and sessionID we can get the expiration time | ||
M internal/httpserve/handler/clientendpoins.go +8 -2
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/gordon/internal/common" | |
| 10 | 10 | "github.com/bnema/gordon/internal/server" | |
| 11 | + | "github.com/bnema/gordon/pkg/docker" | |
| 11 | 12 | "github.com/bnema/gordon/pkg/store" | |
| 12 | 13 | "github.com/labstack/echo/v4" | |
| 13 | 14 | ) | |
| @@ -61,7 +62,7 @@ func GetInfos(c echo.Context, a *server.App) error { | |||
| 61 | 62 | func PostPush(c echo.Context, a *server.App) error { | |
| 62 | 63 | body, _ := io.ReadAll(c.Request().Body) | |
| 63 | 64 | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |
| 64 | - | fmt.Print(body) | |
| 65 | + | ||
| 65 | 66 | payload := new(common.RequestPayload) | |
| 66 | 67 | if err := c.Bind(payload); err != nil { | |
| 67 | 68 | fmt.Println("Bind Error:", err) | |
| @@ -106,8 +107,13 @@ func PostPush(c echo.Context, a *server.App) error { | |||
| 106 | 107 | if err != nil { | |
| 107 | 108 | return c.JSON(http.StatusBadRequest, err.Error()) | |
| 108 | 109 | } | |
| 109 | - | fmt.Printf("Saved in %s", imagePath) | |
| 110 | 110 | // 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 | 118 | // 3 - Create the container with traefik labels | |
| 113 | 119 | ||
| @@ -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 body | 64 | 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 docker | 110 | // 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 labels | 118 | // 3 - Create the container with traefik labels | ||
| 113 | 119 | ||||
M internal/httpserve/handler/createcontainer.go +2 -1
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "net/http" | |
| 6 | 6 | ||
| 7 | 7 | "github.com/bnema/gordon/internal/server" | |
| 8 | + | "github.com/bnema/gordon/internal/templating/cmdparams" | |
| 8 | 9 | "github.com/bnema/gordon/internal/templating/render" | |
| 9 | 10 | "github.com/bnema/gordon/pkg/docker" | |
| 10 | 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 | 108 | if err != nil { | |
| 108 | 109 | return sendError(c, err) | |
| 109 | 110 | } | |
| @@ -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 ( | |||
| 6 | 6 | "strings" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/gordon/internal/server" | |
| 9 | + | "github.com/bnema/gordon/internal/templating/cmdparams" | |
| 9 | 10 | "github.com/bnema/gordon/internal/templating/render" | |
| 10 | 11 | "github.com/bnema/gordon/pkg/docker" | |
| 11 | 12 | "github.com/labstack/echo/v4" | |
| @@ -159,14 +160,14 @@ func ContainerManagerEditPOST(c echo.Context, a *server.App) error { | |||
| 159 | 160 | } | |
| 160 | 161 | ||
| 161 | 162 | containerConfig := c.FormValue("container_config") | |
| 162 | - | var containerParams render.YAMLContainerParams | |
| 163 | + | var containerParams cmdparams.YAMLContainerParams | |
| 163 | 164 | err = yaml.Unmarshal([]byte(containerConfig), &containerParams) | |
| 164 | 165 | if err != nil { | |
| 165 | 166 | return sendError(c, fmt.Errorf("YAML unmarshal failed: %w", err)) | |
| 166 | 167 | } | |
| 167 | 168 | ||
| 168 | 169 | // Use render.FromYAMLStructToCmdParams to convert YAMLContainerParams to ContainerCommandParams | |
| 169 | - | cmdParams, err := render.FromYAMLStructToCmdParams(containerParams) | |
| 170 | + | cmdParams, err := cmdparams.FromYAMLStructToCmdParams(containerParams) | |
| 170 | 171 | if err != nil { | |
| 171 | 172 | return fmt.Errorf("failed to convert YAML: %w", err) | |
| 172 | 173 | } | |
| @@ -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.YAMLContainerParams | 163 | + | 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 ContainerCommandParams | 169 | // 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 | |||
| 219 | 219 | sess.Values["expires"] = expires | |
| 220 | 220 | sess.Values["sessionID"] = sessionID | |
| 221 | 221 | ||
| 222 | + | fmt.Print(sess) | |
| 223 | + | ||
| 222 | 224 | if err := sess.Save(c.Request(), c.Response()); err != nil { | |
| 223 | 225 | return fmt.Errorf("could not save session: %w", err) | |
| 224 | 226 | } | |
| @@ -219,6 +219,8 @@ func setSessionValues(c echo.Context, sess *sessions.Session, user *db.User, acc | |||||
| 219 | sess.Values["expires"] = expires | 219 | sess.Values["expires"] = expires | ||
| 220 | sess.Values["sessionID"] = sessionID | 220 | 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 @@ | |||
| 1 | 1 | 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 | 3 | // 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 | + | // } | |
| 33 | 24 | ||
| 34 | - | return nil | |
| 35 | - | } | |
| 25 | + | // return nil | |
| 26 | + | // } | |
| @@ -1,35 +1,26 @@ | |||||
| 1 | package handler | 1 | 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 data | 3 | // 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 cookie | 5 | + | // // 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 immediately | 11 | + | // cookie.MaxAge = -1 // Clear immediately |
| 21 | - | c.SetCookie(cookie) | 12 | + | // c.SetCookie(cookie) |
| 22 | - | // Invalidate the existing session | 13 | + | // // 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 = -1 | 19 | + | // 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 nil | 25 | + | // return nil |
| 35 | - | } | 26 | + | // } |
M internal/httpserve/middleware/requirelogin.go +69 -19
| @@ -9,7 +9,6 @@ import ( | |||
| 9 | 9 | "github.com/bnema/gordon/internal/db/queries" | |
| 10 | 10 | "github.com/bnema/gordon/internal/server" | |
| 11 | 11 | ||
| 12 | - | // "github.com/labstack/echo-contrib/session" | |
| 13 | 12 | "github.com/labstack/echo-contrib/session" | |
| 14 | 13 | "github.com/labstack/echo/v4" | |
| 15 | 14 | ) | |
| @@ -32,29 +31,48 @@ func validateSessionAndUser(c echo.Context, a *server.App) error { | |||
| 32 | 31 | return err | |
| 33 | 32 | } | |
| 34 | 33 | ||
| 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) | |
| 42 | 36 | if !ok { | |
| 43 | 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 | 41 | if !ok { | |
| 48 | 42 | return fmt.Errorf("invalid session ID type") | |
| 49 | 43 | } | |
| 50 | 44 | ||
| 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 | + | } | |
| 54 | 64 | ||
| 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 | + | } | |
| 58 | 76 | ||
| 59 | 77 | return nil | |
| 60 | 78 | } | |
| @@ -68,10 +86,10 @@ func isCookieExpired(c echo.Context) (bool, error) { | |||
| 68 | 86 | if err != nil { | |
| 69 | 87 | return false, err | |
| 70 | 88 | } | |
| 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 | |
| 75 | 93 | } | |
| 76 | 94 | ||
| 77 | 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 | 111 | 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 | + | } | |
| @@ -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 err | 31 | 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 err | 46 | + | 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 err | 66 | + | 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 nil | 77 | 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, err | 87 | return false, err | ||
| 70 | } | 88 | } | ||
| 71 | - | if cookie.Expires.IsZero() { | 89 | + | |
| 72 | - | return false, nil // cookie has no expiration time; it's a session cookie | 90 | + | currentTime := time.Now() |
| 73 | - | } | 91 | + | expirationTime := cookie.Expires |
| 74 | - | return time.Now().After(cookie.Expires), nil | 92 | + | 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, nil | 111 | 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 @@ | |||
| 1 | 1 | package middleware | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "net/http" | |
| 4 | + | "log" | |
| 5 | 5 | "os" | |
| 6 | 6 | ||
| 7 | + | "github.com/bnema/gordon/internal/server" | |
| 7 | 8 | "github.com/gorilla/sessions" | |
| 8 | 9 | _ "github.com/joho/godotenv/autoload" | |
| 9 | 10 | "github.com/labstack/echo-contrib/session" | |
| @@ -23,14 +24,33 @@ func SecureRoutes() echo.MiddlewareFunc { | |||
| 23 | 24 | } | |
| 24 | 25 | ||
| 25 | 26 | // 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)) | |
| 28 | 34 | store.Options = &sessions.Options{ | |
| 29 | 35 | Path: "/", | |
| 30 | 36 | HttpOnly: true, | |
| 31 | - | Secure: true, | |
| 32 | - | SameSite: http.SameSiteStrictMode, | |
| 37 | + | Secure: !isDev, // Set to true in production | |
| 33 | 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 | } | |
| @@ -1,9 +1,10 @@ | |||||
| 1 | package middleware | 1 | 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 options | 26 | // 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 { | |||
| 19 | 19 | e.Use(middleware.ErrorHandler) | |
| 20 | 20 | ||
| 21 | 21 | // Initiate the session middleware | |
| 22 | - | e.Use(middleware.InitSessionMiddleware()) | |
| 22 | + | e.Use(middleware.InitSessionMiddleware(a)) | |
| 23 | 23 | // Use middlewares | |
| 24 | 24 | e.Use(middleware.SecureRoutes()) | |
| 25 | 25 | ||
| @@ -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 middleware | 21 | // Initiate the session middleware | ||
| 22 | - | e.Use(middleware.InitSessionMiddleware()) | 22 | + | e.Use(middleware.InitSessionMiddleware(a)) |
| 23 | // Use middlewares | 23 | // 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 { | |||
| 52 | 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 | 59 | func (a *App) GetUptime() string { | |
| 56 | 60 | uptime := time.Since(a.StartTime) | |
| 57 | 61 | 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) { | |||
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | 21 | // 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 | 23 | if err != nil { | |
| 24 | 24 | log.Fatalf("Failed to open strings.yml: %v", err) | |
| 25 | 25 | } | |
| @@ -19,7 +19,7 @@ func NewServerApp() (*App, error) { | |||||
| 19 | } | 19 | } | ||
| 20 | 20 | ||||
| 21 | // Open the strings.yml file containing the strings for the current language | 21 | // 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(¶ms) | |
| 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(¶ms) | |||
| 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{} | |||
| 30 | 30 | func (d *DockerClient) InitializeClient(config *Config) error { | |
| 31 | 31 | // Validate if the Sock field is not empty | |
| 32 | 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 | 36 | // Check if the socket file exists if not return an error | |
| @@ -44,7 +44,7 @@ func (d *DockerClient) InitializeClient(config *Config) error { | |||
| 44 | 44 | client.WithHost("unix://"+config.Sock), | |
| 45 | 45 | ) | |
| 46 | 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 | 49 | dockerCli = cli | |
| 50 | 50 | currentConfig = config | |
| @@ -55,17 +55,17 @@ func (d *DockerClient) InitializeClient(config *Config) error { | |||
| 55 | 55 | func CheckIfInitialized() error { | |
| 56 | 56 | // Check if there is a socket file | |
| 57 | 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 | 61 | socketPath := currentConfig.Sock // Use the package-level variable | |
| 62 | 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 | 66 | // Then we check if the dockerCli is nil | |
| 67 | 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 | 71 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) | |
| @@ -74,7 +74,7 @@ func CheckIfInitialized() error { | |||
| 74 | 74 | // More stringent check: try to list containers | |
| 75 | 75 | _, err := dockerCli.ContainerList(ctx, types.ContainerListOptions{}) | |
| 76 | 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 | 79 | return nil | |
| 80 | 80 | } | |
| @@ -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 empty | 31 | // 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 error | 36 | // 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 = cli | 49 | dockerCli = cli | ||
| 50 | currentConfig = config | 50 | 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 file | 56 | // 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 variable | 61 | 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 nil | 66 | // 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 containers | 74 | // 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 nil | 79 | return nil | ||
| 80 | } | 80 | } | ||
M pkg/docker/containers.go +47 -0
| @@ -202,6 +202,27 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) { | |||
| 202 | 202 | // Check if the Docker client has been initialized | |
| 203 | 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 | 226 | // Prepare port bindings | |
| 206 | 227 | portBindings := nat.PortMap{} | |
| 207 | 228 | for _, portMapping := range cmdParams.PortMappings { | |
| @@ -257,6 +278,32 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) { | |||
| 257 | 278 | 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 | 307 | // GetContainerInfo returns information about a container | |
| 261 | 308 | func GetContainerInfo(containerID string) (types.ContainerJSON, error) { | |
| 262 | 309 | // 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 initialized | 202 | // 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 bindings | 226 | // 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, nil | 278 | 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 container | 307 | // 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 client | 309 | // Get container info using the Docker client | ||