detached 248c044

Massive refactoring

- logger now called from one and unique point : app.App
- Refactor the traefik install script with separate functions, better
  error handling and process messages for the frontend.
- Fix tons of errors related to these changes
commit
248c0446060c43e13d33605169c51ee229830322
author
bnema · · 2023-09-06 18:39 UTC
committer
bnema · · 2023-09-06 17:55 UTC
parent
650efff
signature
unsigned
14 files · +322 -386
M cmd/main.go +1 -1
@@ -35,7 +35,7 @@ func main() {
3535 e.HideBanner = true
3636 e.HidePort = true
3737 e.Logger = utils.NewEchoLoggerWrapper(gordon.AppLogger)
38- e = routes.ConfigureRouter(e, gordon)
38+ e = routes.ConfigureRouter(e, gordon, gordon.Config)
3939
4040 startFunc := func() error {
4141 gordon.AppLogger.Info().Msgf("Server is listening on port %s", port)
@@ -35,7 +35,7 @@ func main() {
35 e.HideBanner = true35 e.HideBanner = true
36 e.HidePort = true36 e.HidePort = true
37 e.Logger = utils.NewEchoLoggerWrapper(gordon.AppLogger)37 e.Logger = utils.NewEchoLoggerWrapper(gordon.AppLogger)
38- e = routes.ConfigureRouter(e, gordon)38+ e = routes.ConfigureRouter(e, gordon, gordon.Config)
39 39
40 startFunc := func() error {40 startFunc := func() error {
41 gordon.AppLogger.Info().Msgf("Server is listening on port %s", port)41 gordon.AppLogger.Info().Msgf("Server is listening on port %s", port)
D config/config-provider.go +0 -19
@@ -1,19 +0,0 @@
1-package config
2-
3-import "io/fs"
4-
5-type Provider interface {
6- GetTemplateFS() fs.FS
7- GetPublicFS() fs.FS
8- GetModelFS() fs.FS
9-}
10-
11-type Config struct {
12- TemplateFS fs.FS
13- PublicFS fs.FS
14- ModelFS fs.FS
15-}
16-
17-func GetConfig() *Config {
18- return &Config{}
19-}
@@ -1,19 +0,0 @@
1-package config
2-
3-import "io/fs"
4-
5-type Provider interface {
6- GetTemplateFS() fs.FS
7- GetPublicFS() fs.FS
8- GetModelFS() fs.FS
9-}
10-
11-type Config struct {
12- TemplateFS fs.FS
13- PublicFS fs.FS
14- ModelFS fs.FS
15-}
16-
17-func GetConfig() *Config {
18- return &Config{}
19-}
M internal/app/app.go +0 -3
@@ -8,7 +8,6 @@ import (
88 "path/filepath"
99
1010 "github.com/rs/zerolog"
11- "gogs.bnema.dev/gordon-echo/config"
1211 "gogs.bnema.dev/gordon-echo/pkg/utils"
1312 )
1413
@@ -45,8 +44,6 @@ func (c *Config) GetModelFS() fs.FS {
4544 return c.ModelFS
4645 }
4746
48-var _ config.Provider = &Config{}
49-
5047 func defineEnv(config *Config) {
5148 // Get the environment variable
5249 env := os.Getenv(EnvVarName)
@@ -8,7 +8,6 @@ import (
8 "path/filepath"8 "path/filepath"
9 9
10 "github.com/rs/zerolog"10 "github.com/rs/zerolog"
11- "gogs.bnema.dev/gordon-echo/config"
12 "gogs.bnema.dev/gordon-echo/pkg/utils"11 "gogs.bnema.dev/gordon-echo/pkg/utils"
13 )12 )
14 13
@@ -45,8 +44,6 @@ func (c *Config) GetModelFS() fs.FS {
45 return c.ModelFS44 return c.ModelFS
46 }45 }
47 46
48-var _ config.Provider = &Config{}
49-
50 func defineEnv(config *Config) {47 func defineEnv(config *Config) {
51 // Get the environment variable48 // Get the environment variable
52 env := os.Getenv(EnvVarName)49 env := os.Getenv(EnvVarName)
M internal/http/handlers/htmx-fragment-header.go +8 -16
@@ -4,18 +4,14 @@ import (
44 "net/http"
55
66 "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/config"
7+ "gogs.bnema.dev/gordon-echo/internal/app"
88 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
99 "gogs.bnema.dev/gordon-echo/pkg/htmx"
1010 "gogs.bnema.dev/gordon-echo/pkg/utils"
1111 )
1212
13-func HTMXHandler(c echo.Context) error {
14- logger, ok := c.Get("logger").(*utils.Logger)
15- if !ok {
16- return c.String(http.StatusInternalServerError, "Failed to get logger from context")
17- }
18-
13+func HTMXHandler(c echo.Context, a *app.App, ac *app.Config) error {
14+ logger := a.AppLogger
1915 // Detect and extract HTMX data from the request
2016 htmxRequest, err := htmx.GetRequest(c)
2117 if err != nil {
@@ -31,19 +27,15 @@ func HTMXHandler(c echo.Context) error {
3127
3228 // If it's a GET request, handle the fragment
3329 if c.Request().Method == http.MethodGet {
34- cfg, ok := c.Get("config").(config.Provider)
35- if !ok {
36- logger.Error().Msg("Failed to get config provider from context")
37- return c.String(http.StatusInternalServerError, "Failed to get config provider from context")
38- }
39- return htmxFragmentHandler(c, logger, cfg)
30+ return htmxFragmentHandler(c, a, ac)
4031 }
4132
4233 // For other HTTP methods, return an error
4334 return c.String(http.StatusBadRequest, "Invalid request method")
4435 }
4536
46-func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provider) error {
37+func htmxFragmentHandler(c echo.Context, a *app.App, ac *app.Config) error {
38+ logger := a.AppLogger
4739 fragment := c.Request().Header.Get("HX-Fragment")
4840 if fragment == "" {
4941 logger.Error().Msg("Missing fragment header")
@@ -52,13 +44,13 @@ func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provide
5244
5345 lang := c.Get(middlewares.LangKey).(string)
5446 var data utils.StringsYamlData
55- err := utils.PopulateDataFromYAML(lang, fs.GetTemplateFS(), "strings.yml", &data)
47+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
5648 if err != nil {
5749 logger.Error().Err(err).Msg("Failed to populate data from YAML")
5850 return err
5951 }
6052
61- content, err := utils.GetHTMLFragmentByID(fragment, data.CurrentLang, fs)
53+ content, err := htmx.GetHTMLFragmentByID(fragment, data.CurrentLang, a, ac)
6254 if err != nil { // <-- This check was missing
6355 logger.Error().Err(err).Msg("Failed to get fragment")
6456 return c.String(http.StatusInternalServerError, err.Error())
@@ -4,18 +4,14 @@ import (
4 "net/http"4 "net/http"
5 5
6 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/config"7+ "gogs.bnema.dev/gordon-echo/internal/app"
8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9 "gogs.bnema.dev/gordon-echo/pkg/htmx"9 "gogs.bnema.dev/gordon-echo/pkg/htmx"
10 "gogs.bnema.dev/gordon-echo/pkg/utils"10 "gogs.bnema.dev/gordon-echo/pkg/utils"
11 )11 )
12 12
13-func HTMXHandler(c echo.Context) error {13+func HTMXHandler(c echo.Context, a *app.App, ac *app.Config) error {
14- logger, ok := c.Get("logger").(*utils.Logger)14+ logger := a.AppLogger
15- if !ok {
16- return c.String(http.StatusInternalServerError, "Failed to get logger from context")
17- }
18-
19 // Detect and extract HTMX data from the request15 // Detect and extract HTMX data from the request
20 htmxRequest, err := htmx.GetRequest(c)16 htmxRequest, err := htmx.GetRequest(c)
21 if err != nil {17 if err != nil {
@@ -31,19 +27,15 @@ func HTMXHandler(c echo.Context) error {
31 27
32 // If it's a GET request, handle the fragment28 // If it's a GET request, handle the fragment
33 if c.Request().Method == http.MethodGet {29 if c.Request().Method == http.MethodGet {
34- cfg, ok := c.Get("config").(config.Provider)30+ return htmxFragmentHandler(c, a, ac)
35- if !ok {
36- logger.Error().Msg("Failed to get config provider from context")
37- return c.String(http.StatusInternalServerError, "Failed to get config provider from context")
38- }
39- return htmxFragmentHandler(c, logger, cfg)
40 }31 }
41 32
42 // For other HTTP methods, return an error33 // For other HTTP methods, return an error
43 return c.String(http.StatusBadRequest, "Invalid request method")34 return c.String(http.StatusBadRequest, "Invalid request method")
44 }35 }
45 36
46-func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provider) error {37+func htmxFragmentHandler(c echo.Context, a *app.App, ac *app.Config) error {
38+ logger := a.AppLogger
47 fragment := c.Request().Header.Get("HX-Fragment")39 fragment := c.Request().Header.Get("HX-Fragment")
48 if fragment == "" {40 if fragment == "" {
49 logger.Error().Msg("Missing fragment header")41 logger.Error().Msg("Missing fragment header")
@@ -52,13 +44,13 @@ func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provide
52 44
53 lang := c.Get(middlewares.LangKey).(string)45 lang := c.Get(middlewares.LangKey).(string)
54 var data utils.StringsYamlData46 var data utils.StringsYamlData
55- err := utils.PopulateDataFromYAML(lang, fs.GetTemplateFS(), "strings.yml", &data)47+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
56 if err != nil {48 if err != nil {
57 logger.Error().Err(err).Msg("Failed to populate data from YAML")49 logger.Error().Err(err).Msg("Failed to populate data from YAML")
58 return err50 return err
59 }51 }
60 52
61- content, err := utils.GetHTMLFragmentByID(fragment, data.CurrentLang, fs)53+ content, err := htmx.GetHTMLFragmentByID(fragment, data.CurrentLang, a, ac)
62 if err != nil { // <-- This check was missing54 if err != nil { // <-- This check was missing
63 logger.Error().Err(err).Msg("Failed to get fragment")55 logger.Error().Err(err).Msg("Failed to get fragment")
64 return c.String(http.StatusInternalServerError, err.Error())56 return c.String(http.StatusInternalServerError, err.Error())
M internal/http/handlers/htmx-installer.go +7 -11
@@ -1,16 +1,14 @@
11 package handlers
22
33 import (
4- "errors"
5-
64 "github.com/labstack/echo/v4"
5+ "gogs.bnema.dev/gordon-echo/internal/app"
76 "gogs.bnema.dev/gordon-echo/pkg/scripts"
8- "gogs.bnema.dev/gordon-echo/pkg/utils"
97 )
108
119 // TraefikInstallerHandler handles the installation of Traefik
12-func TraefikInstallerHandler(c echo.Context) error {
13- logger := c.Get("logger").(*utils.Logger)
10+func TraefikInstallerHandler(c echo.Context, a *app.App) error {
11+ logger := a.AppLogger
1412 // Retrieve the logger from the context
1513 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
1614 if err := RejectPOSTPolicy(c); err != nil {
@@ -30,19 +28,17 @@ func TraefikInstallerHandler(c echo.Context) error {
3028 return err
3129 }
3230
33- // Call to create the YAML files
34- success, err := scripts.InstallTraefik(topDomain, adminEmail, logger)
35- if err != nil {
31+ // Call the InstallTraefik function from pkg/scripts/install-traefik.go
32+ if success, err := scripts.InstallTraefik(topDomain, adminEmail, a); err != nil {
3633 logger.Error().Err(err).Msg("Failed to install Traefik")
3734 return err
3835 } else if !success {
3936 logger.Error().Msg("Failed to install Traefik")
40- return errors.New("failed to install Traefik")
37+ return err
4138 } else {
4239 logger.Info().Msg("Traefik installed successfully")
40+ return c.String(200, "Traefik installed successfully")
4341 }
44- // Return success message to the user wih htmx
45- return c.HTML(200, "Traefik installed successfully")
4642 }
4743
4844 // use the handler at the route /admin/install/traefik
@@ -1,16 +1,14 @@
1 package handlers1 package handlers
2 2
3 import (3 import (
4- "errors"
5-
6 "github.com/labstack/echo/v4"4 "github.com/labstack/echo/v4"
5+ "gogs.bnema.dev/gordon-echo/internal/app"
7 "gogs.bnema.dev/gordon-echo/pkg/scripts"6 "gogs.bnema.dev/gordon-echo/pkg/scripts"
8- "gogs.bnema.dev/gordon-echo/pkg/utils"
9 )7 )
10 8
11 // TraefikInstallerHandler handles the installation of Traefik9 // TraefikInstallerHandler handles the installation of Traefik
12-func TraefikInstallerHandler(c echo.Context) error {10+func TraefikInstallerHandler(c echo.Context, a *app.App) error {
13- logger := c.Get("logger").(*utils.Logger)11+ logger := a.AppLogger
14 // Retrieve the logger from the context12 // Retrieve the logger from the context
15 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function13 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
16 if err := RejectPOSTPolicy(c); err != nil {14 if err := RejectPOSTPolicy(c); err != nil {
@@ -30,19 +28,17 @@ func TraefikInstallerHandler(c echo.Context) error {
30 return err28 return err
31 }29 }
32 30
33- // Call to create the YAML files31+ // Call the InstallTraefik function from pkg/scripts/install-traefik.go
34- success, err := scripts.InstallTraefik(topDomain, adminEmail, logger)32+ if success, err := scripts.InstallTraefik(topDomain, adminEmail, a); err != nil {
35- if err != nil {
36 logger.Error().Err(err).Msg("Failed to install Traefik")33 logger.Error().Err(err).Msg("Failed to install Traefik")
37 return err34 return err
38 } else if !success {35 } else if !success {
39 logger.Error().Msg("Failed to install Traefik")36 logger.Error().Msg("Failed to install Traefik")
40- return errors.New("failed to install Traefik")37+ return err
41 } else {38 } else {
42 logger.Info().Msg("Traefik installed successfully")39 logger.Info().Msg("Traefik installed successfully")
40+ return c.String(200, "Traefik installed successfully")
43 }41 }
44- // Return success message to the user wih htmx
45- return c.HTML(200, "Traefik installed successfully")
46 }42 }
47 43
48 // use the handler at the route /admin/install/traefik44 // use the handler at the route /admin/install/traefik
M internal/http/routes/admin-routes.go +10 -9
@@ -5,12 +5,13 @@ import (
55 "net/http"
66
77 "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/config"
8+ "gogs.bnema.dev/gordon-echo/internal/app"
99 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
10+ "gogs.bnema.dev/gordon-echo/pkg/templating"
1011 "gogs.bnema.dev/gordon-echo/pkg/utils"
1112 )
1213
13-func AdminRoute(c echo.Context, provider config.Provider) error {
14+func AdminRoute(c echo.Context, ac *app.Config, a *app.App) error {
1415 // Check if it's the first launch of the app
1516 if utils.IsConfigFilePresent() {
1617 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -22,17 +23,17 @@ func AdminRoute(c echo.Context, provider config.Provider) error {
2223
2324 // Assuming the filename is "strings.yml"
2425 var data utils.StringsYamlData
25- err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
26+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
2627 if err != nil {
2728 return err
2829 }
2930
30- renderer, err := utils.GetRenderer("index.gohtml", provider.GetPublicFS(), utils.NewLogger())
31+ renderer, err := templating.GetHTMLRenderer("index.gohtml", ac.GetPublicFS(), a)
3132 if err != nil {
3233 return err
3334 }
3435
35- html, err := renderer.Render(data.CurrentLang)
36+ html, err := renderer.Render(data.CurrentLang, a)
3637 if err != nil {
3738 // Return the error into NewLogger() to log it
3839 utils.NewLogger().Error()
@@ -42,7 +43,7 @@ func AdminRoute(c echo.Context, provider config.Provider) error {
4243 return c.HTML(http.StatusOK, html)
4344 }
4445
45-func InstallRoute(c echo.Context, provider config.Provider) error {
46+func InstallRoute(c echo.Context, ac *app.Config, a *app.App) error {
4647 // Check if it's the first launch of the app
4748 if !utils.IsConfigFilePresent() {
4849 fmt.Println("Config file already present, redirecting to /admin")
@@ -53,18 +54,18 @@ func InstallRoute(c echo.Context, provider config.Provider) error {
5354 lang := c.Get(middlewares.LangKey).(string)
5455
5556 var data utils.StringsYamlData
56- err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
57+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
5758 if err != nil {
5859 return fmt.Errorf("failed to populate data from YAML: %w", err)
5960 }
6061
61- renderer, err := utils.GetRenderer("install.gohtml", provider.GetTemplateFS(), utils.NewLogger())
62+ renderer, err := templating.GetHTMLRenderer("install.gohtml", ac.GetTemplateFS(), a)
6263 fmt.Println(renderer)
6364 if err != nil {
6465 return err
6566 }
6667
67- html, err := renderer.Render(data.CurrentLang)
68+ html, err := renderer.Render(data.CurrentLang, a)
6869 if err != nil {
6970 // Return the error into NewLogger() to log it
7071 utils.NewLogger().Error()
@@ -5,12 +5,13 @@ import (
5 "net/http"5 "net/http"
6 6
7 "github.com/labstack/echo/v4"7 "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/config"8+ "gogs.bnema.dev/gordon-echo/internal/app"
9 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"9 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
10+ "gogs.bnema.dev/gordon-echo/pkg/templating"
10 "gogs.bnema.dev/gordon-echo/pkg/utils"11 "gogs.bnema.dev/gordon-echo/pkg/utils"
11 )12 )
12 13
13-func AdminRoute(c echo.Context, provider config.Provider) error {14+func AdminRoute(c echo.Context, ac *app.Config, a *app.App) error {
14 // Check if it's the first launch of the app15 // Check if it's the first launch of the app
15 if utils.IsConfigFilePresent() {16 if utils.IsConfigFilePresent() {
16 fmt.Println("Fresh installation detected, redirecting to /admin/install")17 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -22,17 +23,17 @@ func AdminRoute(c echo.Context, provider config.Provider) error {
22 23
23 // Assuming the filename is "strings.yml"24 // Assuming the filename is "strings.yml"
24 var data utils.StringsYamlData25 var data utils.StringsYamlData
25- err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)26+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
26 if err != nil {27 if err != nil {
27 return err28 return err
28 }29 }
29 30
30- renderer, err := utils.GetRenderer("index.gohtml", provider.GetPublicFS(), utils.NewLogger())31+ renderer, err := templating.GetHTMLRenderer("index.gohtml", ac.GetPublicFS(), a)
31 if err != nil {32 if err != nil {
32 return err33 return err
33 }34 }
34 35
35- html, err := renderer.Render(data.CurrentLang)36+ html, err := renderer.Render(data.CurrentLang, a)
36 if err != nil {37 if err != nil {
37 // Return the error into NewLogger() to log it38 // Return the error into NewLogger() to log it
38 utils.NewLogger().Error()39 utils.NewLogger().Error()
@@ -42,7 +43,7 @@ func AdminRoute(c echo.Context, provider config.Provider) error {
42 return c.HTML(http.StatusOK, html)43 return c.HTML(http.StatusOK, html)
43 }44 }
44 45
45-func InstallRoute(c echo.Context, provider config.Provider) error {46+func InstallRoute(c echo.Context, ac *app.Config, a *app.App) error {
46 // Check if it's the first launch of the app47 // Check if it's the first launch of the app
47 if !utils.IsConfigFilePresent() {48 if !utils.IsConfigFilePresent() {
48 fmt.Println("Config file already present, redirecting to /admin")49 fmt.Println("Config file already present, redirecting to /admin")
@@ -53,18 +54,18 @@ func InstallRoute(c echo.Context, provider config.Provider) error {
53 lang := c.Get(middlewares.LangKey).(string)54 lang := c.Get(middlewares.LangKey).(string)
54 55
55 var data utils.StringsYamlData56 var data utils.StringsYamlData
56- err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)57+ err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
57 if err != nil {58 if err != nil {
58 return fmt.Errorf("failed to populate data from YAML: %w", err)59 return fmt.Errorf("failed to populate data from YAML: %w", err)
59 }60 }
60 61
61- renderer, err := utils.GetRenderer("install.gohtml", provider.GetTemplateFS(), utils.NewLogger())62+ renderer, err := templating.GetHTMLRenderer("install.gohtml", ac.GetTemplateFS(), a)
62 fmt.Println(renderer)63 fmt.Println(renderer)
63 if err != nil {64 if err != nil {
64 return err65 return err
65 }66 }
66 67
67- html, err := renderer.Render(data.CurrentLang)68+ html, err := renderer.Render(data.CurrentLang, a)
68 if err != nil {69 if err != nil {
69 // Return the error into NewLogger() to log it70 // Return the error into NewLogger() to log it
70 utils.NewLogger().Error()71 utils.NewLogger().Error()
M internal/http/routes/router.go +26 -18
@@ -2,52 +2,60 @@ package routes
22
33 import (
44 "github.com/labstack/echo/v4"
5- "gogs.bnema.dev/gordon-echo/config"
65 "gogs.bnema.dev/gordon-echo/internal/app"
76 "gogs.bnema.dev/gordon-echo/internal/http/handlers"
87 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
98 )
109
11-func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {
10+func ConfigureRouter(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
1211
1312 // SetLogger is a middleware that sets the logger in the context
14- e.Use(middlewares.SetLogger(app.HttpLogger))
13+ e.Use(middlewares.SetLogger(a.HttpLogger))
1514 // HTTPAccessLogger is a middleware that logs HTTP requests
16- e.Use(middlewares.HTTPAccessLogger(app.HttpLogger))
15+ e.Use(middlewares.HTTPAccessLogger(a.HttpLogger))
1716 e.Use(middlewares.LanguageDetection)
1817 e.Use(middlewares.ColorSchemeDetection)
1918 //e.Use(middlewares.SecureMiddleware()) // Need testing
2019
2120 // Register routes
22- e = bindAdminRoutes(e, app.Config)
23- e = bindHtmxRoutes(e, app.Config)
24- e = bindStaticRoutes(e, app.Config)
21+ e = bindAdminRoutes(e, a, ac)
22+ e = bindHtmxRoutes(e, a, ac)
23+ e = bindStaticRoutes(e, a.Config)
2524 e.HTTPErrorHandler = handlers.ErrorNumberHandler
2625 return e
2726 }
28-func bindAdminRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
27+func bindAdminRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
2928 // We pass the embed config to the routes so we can parse the strings.yml file
3029 e.GET("/admin", func(c echo.Context) error {
31- return AdminRoute(c, config)
30+ return AdminRoute(c, ac, a)
3231 })
3332 e.GET("/admin/install", func(c echo.Context) error {
34- return InstallRoute(c, config)
33+ return InstallRoute(c, ac, a)
3534 })
3635
37- e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)
36+ e.POST("/admin/install/traefik", func(c echo.Context) error {
37+ return handlers.TraefikInstallerHandler(c, a)
38+ })
3839 return e
3940 }
4041
41-func bindHtmxRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
42- e.GET("/htmx", handlers.HTMXHandler)
43- e.GET("/htmx/fragment", handlers.HTMXHandler)
44- e.POST("/htmx", handlers.HTMXHandler)
45- e.POST("/htmx/fragment", handlers.HTMXHandler)
42+func bindHtmxRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
43+ e.GET("/htmx", func(c echo.Context) error {
44+ return handlers.HTMXHandler(c, a, ac)
45+ })
46+ e.GET("/htmx/fragment", func(c echo.Context) error {
47+ return handlers.HTMXHandler(c, a, ac)
48+ })
49+ e.POST("/htmx", func(c echo.Context) error {
50+ return handlers.HTMXHandler(c, a, ac)
51+ })
52+ e.POST("/htmx/fragment", func(c echo.Context) error {
53+ return handlers.HTMXHandler(c, a, ac)
54+ })
4655 return e
4756 }
4857
49-func bindStaticRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
50-
58+func bindStaticRoutes(e *echo.Echo, config *app.Config) *echo.Echo {
5159 e.GET("/*", func(c echo.Context) error {
5260 return StaticRoute(c, config)
5361 })
@@ -2,52 +2,60 @@ package routes
2 2
3 import (3 import (
4 "github.com/labstack/echo/v4"4 "github.com/labstack/echo/v4"
5- "gogs.bnema.dev/gordon-echo/config"
6 "gogs.bnema.dev/gordon-echo/internal/app"5 "gogs.bnema.dev/gordon-echo/internal/app"
7 "gogs.bnema.dev/gordon-echo/internal/http/handlers"6 "gogs.bnema.dev/gordon-echo/internal/http/handlers"
8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"7 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9 )8 )
10 9
11-func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {10+func ConfigureRouter(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
12 11
13 // SetLogger is a middleware that sets the logger in the context12 // SetLogger is a middleware that sets the logger in the context
14- e.Use(middlewares.SetLogger(app.HttpLogger))13+ e.Use(middlewares.SetLogger(a.HttpLogger))
15 // HTTPAccessLogger is a middleware that logs HTTP requests14 // HTTPAccessLogger is a middleware that logs HTTP requests
16- e.Use(middlewares.HTTPAccessLogger(app.HttpLogger))15+ e.Use(middlewares.HTTPAccessLogger(a.HttpLogger))
17 e.Use(middlewares.LanguageDetection)16 e.Use(middlewares.LanguageDetection)
18 e.Use(middlewares.ColorSchemeDetection)17 e.Use(middlewares.ColorSchemeDetection)
19 //e.Use(middlewares.SecureMiddleware()) // Need testing18 //e.Use(middlewares.SecureMiddleware()) // Need testing
20 19
21 // Register routes20 // Register routes
22- e = bindAdminRoutes(e, app.Config)21+ e = bindAdminRoutes(e, a, ac)
23- e = bindHtmxRoutes(e, app.Config)22+ e = bindHtmxRoutes(e, a, ac)
24- e = bindStaticRoutes(e, app.Config)23+ e = bindStaticRoutes(e, a.Config)
25 e.HTTPErrorHandler = handlers.ErrorNumberHandler24 e.HTTPErrorHandler = handlers.ErrorNumberHandler
26 return e25 return e
27 }26 }
28-func bindAdminRoutes(e *echo.Echo, config config.Provider) *echo.Echo {27+func bindAdminRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
29 // We pass the embed config to the routes so we can parse the strings.yml file28 // We pass the embed config to the routes so we can parse the strings.yml file
30 e.GET("/admin", func(c echo.Context) error {29 e.GET("/admin", func(c echo.Context) error {
31- return AdminRoute(c, config)30+ return AdminRoute(c, ac, a)
32 })31 })
33 e.GET("/admin/install", func(c echo.Context) error {32 e.GET("/admin/install", func(c echo.Context) error {
34- return InstallRoute(c, config)33+ return InstallRoute(c, ac, a)
35 })34 })
36 35
37- e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)36+ e.POST("/admin/install/traefik", func(c echo.Context) error {
37+ return handlers.TraefikInstallerHandler(c, a)
38+ })
38 return e39 return e
39 }40 }
40 41
41-func bindHtmxRoutes(e *echo.Echo, config config.Provider) *echo.Echo {42+func bindHtmxRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
42- e.GET("/htmx", handlers.HTMXHandler)43+ e.GET("/htmx", func(c echo.Context) error {
43- e.GET("/htmx/fragment", handlers.HTMXHandler)44+ return handlers.HTMXHandler(c, a, ac)
44- e.POST("/htmx", handlers.HTMXHandler)45+ })
45- e.POST("/htmx/fragment", handlers.HTMXHandler)46+ e.GET("/htmx/fragment", func(c echo.Context) error {
47+ return handlers.HTMXHandler(c, a, ac)
48+ })
49+ e.POST("/htmx", func(c echo.Context) error {
50+ return handlers.HTMXHandler(c, a, ac)
51+ })
52+ e.POST("/htmx/fragment", func(c echo.Context) error {
53+ return handlers.HTMXHandler(c, a, ac)
54+ })
46 return e55 return e
47 }56 }
48 57
49-func bindStaticRoutes(e *echo.Echo, config config.Provider) *echo.Echo {58+func bindStaticRoutes(e *echo.Echo, config *app.Config) *echo.Echo {
50-
51 e.GET("/*", func(c echo.Context) error {59 e.GET("/*", func(c echo.Context) error {
52 return StaticRoute(c, config)60 return StaticRoute(c, config)
53 })61 })
M internal/http/routes/static-routes.go +2 -2
@@ -6,10 +6,10 @@ import (
66 "os"
77
88 "github.com/labstack/echo/v4"
9- "gogs.bnema.dev/gordon-echo/config"
9+ "gogs.bnema.dev/gordon-echo/internal/app"
1010 )
1111
12-func StaticRoute(c echo.Context, config config.Provider) error {
12+func StaticRoute(c echo.Context, config *app.Config) error {
1313
1414 // Set the cache-control header based on PROD environment variable
1515 if os.Getenv("PROD") == "true" {
@@ -6,10 +6,10 @@ import (
6 "os"6 "os"
7 7
8 "github.com/labstack/echo/v4"8 "github.com/labstack/echo/v4"
9- "gogs.bnema.dev/gordon-echo/config"9+ "gogs.bnema.dev/gordon-echo/internal/app"
10 )10 )
11 11
12-func StaticRoute(c echo.Context, config config.Provider) error {12+func StaticRoute(c echo.Context, config *app.Config) error {
13 13
14 // Set the cache-control header based on PROD environment variable14 // Set the cache-control header based on PROD environment variable
15 if os.Getenv("PROD") == "true" {15 if os.Getenv("PROD") == "true" {
M pkg/docker/commands.go +57 -145
@@ -2,247 +2,159 @@ package docker
22
33 import (
44 "bufio"
5+ "bytes"
56 "context"
67 "errors"
78 "fmt"
89 "io"
910 "os"
1011 "os/exec"
12+ "path/filepath"
1113
1214 "github.com/docker/docker/api/types"
13- "github.com/docker/docker/api/types/container"
1415 "github.com/docker/docker/client"
1516 "github.com/docker/docker/pkg/archive"
16- "github.com/docker/docker/pkg/stdcopy"
17+ "gogs.bnema.dev/gordon-echo/internal/app"
1718 "gogs.bnema.dev/gordon-echo/pkg/utils"
1819 )
1920
20-func DockertPullImageTest() {
21- ctx := context.Background()
22- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
23- if err != nil {
24- panic(err)
25- }
26- defer cli.Close()
27-
28- reader, err := cli.ImagePull(ctx, "docker.io/library/alpine", types.ImagePullOptions{})
29- if err != nil {
30- panic(err)
31- }
32- io.Copy(os.Stdout, reader)
33-
34- resp, err := cli.ContainerCreate(ctx, &container.Config{
35- Image: "alpine",
36- Cmd: []string{"echo", "hello world"},
37- }, nil, nil, nil, "")
38- if err != nil {
39- panic(err)
40- }
41-
42- if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
43- panic(err)
44- }
45-
46- statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
47- select {
48- case err := <-errCh:
49- if err != nil {
50- panic(err)
51- }
52- case <-statusCh:
53- }
54-
55- out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true})
56- if err != nil {
57- panic(err)
58- }
59-
60- stdcopy.StdCopy(os.Stdout, os.Stderr, out)
61-}
62-
63-func DockerListContainersTest() {
64- fmt.Println("Executing DockerListContainersTest()")
21+// Use the App struct to access the logger
6522
23+func CreateImageFromDockerfile(a *app.App) error {
6624 ctx := context.Background()
6725 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
6826 if err != nil {
69- panic(err)
27+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to initialize Docker client: %v", err))
28+ defer cli.Close()
7029 }
71- defer cli.Close()
72-
73- fmt.Println("Initialized Docker client.")
7430
75- containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})
76- if err != nil {
77- panic(err)
78- }
79-
80- fmt.Printf("Found %d containers.\n", len(containers))
81-
82- for _, container := range containers {
83- fmt.Printf("%s %s\n", container.ID[:10], container.Status)
84- }
85-}
86-
87-// I have a Dockerfile inside tmp/docker-test/Dockerfile and i want to create an image from it and place it at the same directory.
88-func CreateImageFromDockerfile() {
89- fmt.Println("Executing CreateImageFromDockerfile()")
90- fmt.Println("Initialized Docker client.")
91- ctx := context.Background()
92- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
93- if err != nil {
94- panic(err)
95- }
96- defer cli.Close()
97-
98- fmt.Println("Building image...")
31+ a.AppLogger.Log(utils.INFO, "Building image...")
9932 buildCtx, err := archive.TarWithOptions("tmp/docker-test/", &archive.TarOptions{})
10033 if err != nil {
101- panic(err)
34+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create build context: %v", err))
10235 }
10336 defer buildCtx.Close()
10437
105- fmt.Println("Building image...")
38+ a.AppLogger.Log(utils.INFO, "Building image...")
10639 imageBuildResponse, err := cli.ImageBuild(ctx, buildCtx, types.ImageBuildOptions{
10740 Tags: []string{"gordon-echo:latest"},
10841 })
10942 if err != nil {
110- panic(err)
43+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to build image: %v", err))
11144 }
11245 defer imageBuildResponse.Body.Close()
11346
114- fmt.Println("Saving image to file...")
47+ a.AppLogger.Log(utils.INFO, "Saving image to file...")
11548
11649 imageTar, err := cli.ImageSave(ctx, []string{"gordon-echo:latest"})
11750 if err != nil {
118- panic(err)
51+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image: %v", err))
11952 }
12053
12154 outputFile, err := os.Create("tmp/docker-test/gordon-echo.tar")
12255 if err != nil {
123- panic(err)
56+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create output file: %v", err))
12457 }
12558 defer outputFile.Close()
12659
12760 _, err = io.Copy(outputFile, imageTar)
12861 if err != nil {
129- panic(err)
130- }
131-}
132-
133-// Check if a network exists
134-func NetworkExists(name string) (bool, error) {
135- fmt.Println("Initialized Docker client.")
136- ctx := context.Background()
137- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
138- if err != nil {
139- panic(err)
140- }
141- defer cli.Close()
142-
143- fmt.Println("Checking if network exists...")
144- networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
145- if err != nil {
146- panic(err)
147- }
148-
149- for _, network := range networks {
150- if network.Name == name {
151- return true, nil
152- }
62+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image to file: %v", err))
15363 }
15464
155- return false, nil
65+ return nil
15666 }
15767
158-// Create network as a bridge
159-func CreateNetwork(name string) error {
68+func CreateNetwork(name string, a *app.App) error {
69+ a.AppLogger.Log(utils.INFO, "Entering CreateNetwork function with name: "+name)
70+
16071 ctx := context.Background()
16172 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
16273 if err != nil {
163- panic(err)
74+ errMsg := fmt.Sprintf("Failed to initialize Docker client: %v", err)
75+ a.AppLogger.Log(utils.ERROR, errMsg)
76+ return errors.New(errMsg)
16477 }
16578 defer cli.Close()
16679
167- fmt.Println("Creating network...")
80+ a.AppLogger.Log(utils.INFO, "Attempting to create network...")
16881 _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
169- Driver: "bridge",
82+ CheckDuplicate: true,
83+ Driver: "bridge",
17084 })
17185 if err != nil {
172- panic(err)
86+ errMsg := fmt.Sprintf("Failed to create network: %v", err)
87+ a.AppLogger.Log(utils.ERROR, errMsg)
88+ return errors.New(errMsg)
17389 }
17490
91+ a.AppLogger.Log(utils.INFO, "Successfully created network: "+name)
17592 return nil
17693 }
17794
17895 // Check if a container exists
179-func ContainerExists(name string) (bool, error) {
96+func ContainerExists(name string, a *app.App) error {
18097 ctx := context.Background()
18198 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
18299 if err != nil {
183- return false, errors.New("failed to initialize Docker client")
100+ return err
184101 }
185102 defer cli.Close()
186103
187104 fmt.Println("Checking if container exists...")
188105 containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
189106 if err != nil {
190- return false, errors.New("failed to list containers")
107+ return err
191108 }
192109
193110 for _, container := range containers {
194111 for _, containerName := range container.Names {
195112 if containerName == "/"+name {
196- return true, nil
113+ return errors.New("container already exists")
197114 }
198115 }
199116 }
200117
201- return false, nil
118+ return nil
202119 }
203120
204-func CheckDockerRunning() (bool, error) {
205- fmt.Println("Checking if Docker is running...")
121+func CheckIfDockerIsRunning(a *app.App) error {
206122 cmd := exec.Command("docker", "info")
207123 if err := cmd.Run(); err != nil {
208- return true, errors.New("docker is not running")
124+ return errors.New("docker is not running")
209125 }
210- return false, nil
126+ return nil
211127 }
212128
213129 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
214-func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) (bool, error) {
215- fmt.Println("Executing CreateContainerFromComposeFile()")
130+func CreateContainerFromComposeFile(composeFilePath string, a *app.App) error {
131+ a.AppLogger.Log(utils.INFO, "Executing CreateContainerFromComposeFile()")
216132
217- // Create and start containers using docker compose in the foreground
218- cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up", "-d")
133+ // Extract directory from the composeFilePath
134+ dir := filepath.Dir(composeFilePath)
219135
220- // Get stdout and stderr pipes
221- stdout, err := cmd.StdoutPipe()
222- if err != nil {
223- return false, fmt.Errorf("failed to get stdout pipe: %v", err)
224- }
225- stderr, err := cmd.StderrPipe()
226- if err != nil {
227- return false, fmt.Errorf("failed to get stderr pipe: %v", err)
136+ // Change directory to the compose file directory
137+ if err := os.Chdir(dir); err != nil {
138+ return errors.New("failed to change directory: " + err.Error())
228139 }
229140
230- // Start the command
231- if err := cmd.Start(); err != nil {
232- return false, fmt.Errorf("failed to start docker compose: %v", err)
233- }
141+ // Create the command
142+ cmd := exec.Command("docker", "compose", "up", "-d")
234143
235- // Stream stdout to logger with INFO level
236- go streamToLogger(stdout, logger, utils.INFO)
237- // Stream stderr to logger with ERROR level
238- go streamToLogger(stderr, logger, utils.ERROR)
144+ // Capture standard output and error
145+ var stdout, stderr bytes.Buffer
146+ cmd.Stdout = &stdout
147+ cmd.Stderr = &stderr
239148
240- // Wait for the command to finish
241- if err := cmd.Wait(); err != nil {
242- return false, fmt.Errorf("docker compose command finished with error: %v", err)
149+ // Execute the command
150+ if err := cmd.Run(); err != nil {
151+ // Log the captured standard output and error for better diagnostics
152+ a.AppLogger.Log(utils.ERROR, "Docker Compose Output: "+stdout.String())
153+ a.AppLogger.Log(utils.ERROR, "Docker Compose Error: "+stderr.String())
154+ return errors.New("failed to execute docker compose command: " + err.Error())
243155 }
244156
245- return true, nil
157+ return nil
246158 }
247159
248160 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
@@ -2,247 +2,159 @@ package docker
2 2
3 import (3 import (
4 "bufio"4 "bufio"
5+ "bytes"
5 "context"6 "context"
6 "errors"7 "errors"
7 "fmt"8 "fmt"
8 "io"9 "io"
9 "os"10 "os"
10 "os/exec"11 "os/exec"
12+ "path/filepath"
11 13
12 "github.com/docker/docker/api/types"14 "github.com/docker/docker/api/types"
13- "github.com/docker/docker/api/types/container"
14 "github.com/docker/docker/client"15 "github.com/docker/docker/client"
15 "github.com/docker/docker/pkg/archive"16 "github.com/docker/docker/pkg/archive"
16- "github.com/docker/docker/pkg/stdcopy"17+ "gogs.bnema.dev/gordon-echo/internal/app"
17 "gogs.bnema.dev/gordon-echo/pkg/utils"18 "gogs.bnema.dev/gordon-echo/pkg/utils"
18 )19 )
19 20
20-func DockertPullImageTest() {21+// Use the App struct to access the logger
21- ctx := context.Background()
22- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
23- if err != nil {
24- panic(err)
25- }
26- defer cli.Close()
27-
28- reader, err := cli.ImagePull(ctx, "docker.io/library/alpine", types.ImagePullOptions{})
29- if err != nil {
30- panic(err)
31- }
32- io.Copy(os.Stdout, reader)
33-
34- resp, err := cli.ContainerCreate(ctx, &container.Config{
35- Image: "alpine",
36- Cmd: []string{"echo", "hello world"},
37- }, nil, nil, nil, "")
38- if err != nil {
39- panic(err)
40- }
41-
42- if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
43- panic(err)
44- }
45-
46- statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
47- select {
48- case err := <-errCh:
49- if err != nil {
50- panic(err)
51- }
52- case <-statusCh:
53- }
54-
55- out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true})
56- if err != nil {
57- panic(err)
58- }
59-
60- stdcopy.StdCopy(os.Stdout, os.Stderr, out)
61-}
62-
63-func DockerListContainersTest() {
64- fmt.Println("Executing DockerListContainersTest()")
65 22
23+func CreateImageFromDockerfile(a *app.App) error {
66 ctx := context.Background()24 ctx := context.Background()
67 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())25 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
68 if err != nil {26 if err != nil {
69- panic(err)27+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to initialize Docker client: %v", err))
28+ defer cli.Close()
70 }29 }
71- defer cli.Close()
72-
73- fmt.Println("Initialized Docker client.")
74 30
75- containers, err := cli.ContainerList(ctx, types.ContainerListOptions{All: true})31+ a.AppLogger.Log(utils.INFO, "Building image...")
76- if err != nil {
77- panic(err)
78- }
79-
80- fmt.Printf("Found %d containers.\n", len(containers))
81-
82- for _, container := range containers {
83- fmt.Printf("%s %s\n", container.ID[:10], container.Status)
84- }
85-}
86-
87-// I have a Dockerfile inside tmp/docker-test/Dockerfile and i want to create an image from it and place it at the same directory.
88-func CreateImageFromDockerfile() {
89- fmt.Println("Executing CreateImageFromDockerfile()")
90- fmt.Println("Initialized Docker client.")
91- ctx := context.Background()
92- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
93- if err != nil {
94- panic(err)
95- }
96- defer cli.Close()
97-
98- fmt.Println("Building image...")
99 buildCtx, err := archive.TarWithOptions("tmp/docker-test/", &archive.TarOptions{})32 buildCtx, err := archive.TarWithOptions("tmp/docker-test/", &archive.TarOptions{})
100 if err != nil {33 if err != nil {
101- panic(err)34+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create build context: %v", err))
102 }35 }
103 defer buildCtx.Close()36 defer buildCtx.Close()
104 37
105- fmt.Println("Building image...")38+ a.AppLogger.Log(utils.INFO, "Building image...")
106 imageBuildResponse, err := cli.ImageBuild(ctx, buildCtx, types.ImageBuildOptions{39 imageBuildResponse, err := cli.ImageBuild(ctx, buildCtx, types.ImageBuildOptions{
107 Tags: []string{"gordon-echo:latest"},40 Tags: []string{"gordon-echo:latest"},
108 })41 })
109 if err != nil {42 if err != nil {
110- panic(err)43+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to build image: %v", err))
111 }44 }
112 defer imageBuildResponse.Body.Close()45 defer imageBuildResponse.Body.Close()
113 46
114- fmt.Println("Saving image to file...")47+ a.AppLogger.Log(utils.INFO, "Saving image to file...")
115 48
116 imageTar, err := cli.ImageSave(ctx, []string{"gordon-echo:latest"})49 imageTar, err := cli.ImageSave(ctx, []string{"gordon-echo:latest"})
117 if err != nil {50 if err != nil {
118- panic(err)51+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image: %v", err))
119 }52 }
120 53
121 outputFile, err := os.Create("tmp/docker-test/gordon-echo.tar")54 outputFile, err := os.Create("tmp/docker-test/gordon-echo.tar")
122 if err != nil {55 if err != nil {
123- panic(err)56+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create output file: %v", err))
124 }57 }
125 defer outputFile.Close()58 defer outputFile.Close()
126 59
127 _, err = io.Copy(outputFile, imageTar)60 _, err = io.Copy(outputFile, imageTar)
128 if err != nil {61 if err != nil {
129- panic(err)62+ a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image to file: %v", err))
130- }
131-}
132-
133-// Check if a network exists
134-func NetworkExists(name string) (bool, error) {
135- fmt.Println("Initialized Docker client.")
136- ctx := context.Background()
137- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
138- if err != nil {
139- panic(err)
140- }
141- defer cli.Close()
142-
143- fmt.Println("Checking if network exists...")
144- networks, err := cli.NetworkList(ctx, types.NetworkListOptions{})
145- if err != nil {
146- panic(err)
147- }
148-
149- for _, network := range networks {
150- if network.Name == name {
151- return true, nil
152- }
153 }63 }
154 64
155- return false, nil65+ return nil
156 }66 }
157 67
158-// Create network as a bridge68+func CreateNetwork(name string, a *app.App) error {
159-func CreateNetwork(name string) error {69+ a.AppLogger.Log(utils.INFO, "Entering CreateNetwork function with name: "+name)
70+
160 ctx := context.Background()71 ctx := context.Background()
161 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())72 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
162 if err != nil {73 if err != nil {
163- panic(err)74+ errMsg := fmt.Sprintf("Failed to initialize Docker client: %v", err)
75+ a.AppLogger.Log(utils.ERROR, errMsg)
76+ return errors.New(errMsg)
164 }77 }
165 defer cli.Close()78 defer cli.Close()
166 79
167- fmt.Println("Creating network...")80+ a.AppLogger.Log(utils.INFO, "Attempting to create network...")
168 _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{81 _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
169- Driver: "bridge",82+ CheckDuplicate: true,
83+ Driver: "bridge",
170 })84 })
171 if err != nil {85 if err != nil {
172- panic(err)86+ errMsg := fmt.Sprintf("Failed to create network: %v", err)
87+ a.AppLogger.Log(utils.ERROR, errMsg)
88+ return errors.New(errMsg)
173 }89 }
174 90
91+ a.AppLogger.Log(utils.INFO, "Successfully created network: "+name)
175 return nil92 return nil
176 }93 }
177 94
178 // Check if a container exists95 // Check if a container exists
179-func ContainerExists(name string) (bool, error) {96+func ContainerExists(name string, a *app.App) error {
180 ctx := context.Background()97 ctx := context.Background()
181 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())98 cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
182 if err != nil {99 if err != nil {
183- return false, errors.New("failed to initialize Docker client")100+ return err
184 }101 }
185 defer cli.Close()102 defer cli.Close()
186 103
187 fmt.Println("Checking if container exists...")104 fmt.Println("Checking if container exists...")
188 containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})105 containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
189 if err != nil {106 if err != nil {
190- return false, errors.New("failed to list containers")107+ return err
191 }108 }
192 109
193 for _, container := range containers {110 for _, container := range containers {
194 for _, containerName := range container.Names {111 for _, containerName := range container.Names {
195 if containerName == "/"+name {112 if containerName == "/"+name {
196- return true, nil113+ return errors.New("container already exists")
197 }114 }
198 }115 }
199 }116 }
200 117
201- return false, nil118+ return nil
202 }119 }
203 120
204-func CheckDockerRunning() (bool, error) {121+func CheckIfDockerIsRunning(a *app.App) error {
205- fmt.Println("Checking if Docker is running...")
206 cmd := exec.Command("docker", "info")122 cmd := exec.Command("docker", "info")
207 if err := cmd.Run(); err != nil {123 if err := cmd.Run(); err != nil {
208- return true, errors.New("docker is not running")124+ return errors.New("docker is not running")
209 }125 }
210- return false, nil126+ return nil
211 }127 }
212 128
213 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.129 // CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
214-func CreateContainerFromComposeFile(composeFilePath string, logger *utils.Logger) (bool, error) {130+func CreateContainerFromComposeFile(composeFilePath string, a *app.App) error {
215- fmt.Println("Executing CreateContainerFromComposeFile()")131+ a.AppLogger.Log(utils.INFO, "Executing CreateContainerFromComposeFile()")
216 132
217- // Create and start containers using docker compose in the foreground133+ // Extract directory from the composeFilePath
218- cmd := exec.Command("docker", "compose", "-f", composeFilePath, "up", "-d")134+ dir := filepath.Dir(composeFilePath)
219 135
220- // Get stdout and stderr pipes136+ // Change directory to the compose file directory
221- stdout, err := cmd.StdoutPipe()137+ if err := os.Chdir(dir); err != nil {
222- if err != nil {138+ return errors.New("failed to change directory: " + err.Error())
223- return false, fmt.Errorf("failed to get stdout pipe: %v", err)
224- }
225- stderr, err := cmd.StderrPipe()
226- if err != nil {
227- return false, fmt.Errorf("failed to get stderr pipe: %v", err)
228 }139 }
229 140
230- // Start the command141+ // Create the command
231- if err := cmd.Start(); err != nil {142+ cmd := exec.Command("docker", "compose", "up", "-d")
232- return false, fmt.Errorf("failed to start docker compose: %v", err)
233- }
234 143
235- // Stream stdout to logger with INFO level144+ // Capture standard output and error
236- go streamToLogger(stdout, logger, utils.INFO)145+ var stdout, stderr bytes.Buffer
237- // Stream stderr to logger with ERROR level146+ cmd.Stdout = &stdout
238- go streamToLogger(stderr, logger, utils.ERROR)147+ cmd.Stderr = &stderr
239 148
240- // Wait for the command to finish149+ // Execute the command
241- if err := cmd.Wait(); err != nil {150+ if err := cmd.Run(); err != nil {
242- return false, fmt.Errorf("docker compose command finished with error: %v", err)151+ // Log the captured standard output and error for better diagnostics
152+ a.AppLogger.Log(utils.ERROR, "Docker Compose Output: "+stdout.String())
153+ a.AppLogger.Log(utils.ERROR, "Docker Compose Error: "+stderr.String())
154+ return errors.New("failed to execute docker compose command: " + err.Error())
243 }155 }
244 156
245- return true, nil157+ return nil
246 }158 }
247 159
248 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {160 func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
A pkg/htmx/html-fragments.go +41 -0
@@ -0,0 +1,41 @@
1+package htmx
2+
3+import (
4+ "bytes"
5+
6+ "github.com/PuerkitoBio/goquery"
7+ "gogs.bnema.dev/gordon-echo/internal/app"
8+ "gogs.bnema.dev/gordon-echo/pkg/templating"
9+)
10+
11+// GetHTMLFragmentByID returns the HTML fragment with the specified id
12+func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) {
13+ // 1. Render the template
14+ renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a)
15+ if err != nil {
16+ return "", err
17+ }
18+
19+ renderedHTML, err := renderer.Render(data, a)
20+ if err != nil {
21+ return "", err
22+ }
23+
24+ // 2. Parse the rendered HTML with goquery
25+ doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26+ if err != nil {
27+ return "", err
28+ }
29+
30+ // 3. Find the div with the specified id
31+ fragmentContent, err := doc.Find("#" + id).Html()
32+ if err != nil {
33+ return "", err
34+ }
35+
36+ if fragmentContent == "" {
37+ return "", err
38+ }
39+
40+ return fragmentContent, nil
41+}
@@ -0,0 +1,41 @@
1+package htmx
2+
3+import (
4+ "bytes"
5+
6+ "github.com/PuerkitoBio/goquery"
7+ "gogs.bnema.dev/gordon-echo/internal/app"
8+ "gogs.bnema.dev/gordon-echo/pkg/templating"
9+)
10+
11+// GetHTMLFragmentByID returns the HTML fragment with the specified id
12+func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) {
13+ // 1. Render the template
14+ renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a)
15+ if err != nil {
16+ return "", err
17+ }
18+
19+ renderedHTML, err := renderer.Render(data, a)
20+ if err != nil {
21+ return "", err
22+ }
23+
24+ // 2. Parse the rendered HTML with goquery
25+ doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26+ if err != nil {
27+ return "", err
28+ }
29+
30+ // 3. Find the div with the specified id
31+ fragmentContent, err := doc.Find("#" + id).Html()
32+ if err != nil {
33+ return "", err
34+ }
35+
36+ if fragmentContent == "" {
37+ return "", err
38+ }
39+
40+ return fragmentContent, nil
41+}
M pkg/scripts/install-traefik.go +155 -101
@@ -14,159 +14,213 @@ import (
1414
1515 var path string
1616
17+const (
18+ traefikPath = "traefik"
19+ traefikVolumePath = "traefik/volume"
20+ traefikLog = "logs/traefik.log"
21+ acmeJSON = "traefik/volume/acme.json"
22+ networkName = "totototo"
23+)
24+
1725 func init() {
1826 path = app.GetBuildDir() + "/"
1927 }
2028
21-func InstallTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
22- // Create the YAML files
23- if success, err := createYAMLFiles(topDomain, adminEmail, logger); err != nil {
24- return false, err
25- } else if !success {
26- return false, errors.New("failed to create YAML files")
27- }
29+type InstallationResult struct {
30+ Success bool
31+ Message string
32+ Err error
33+}
2834
29- // Deploy the containers
30- if success, err := deployTraefik(topDomain, adminEmail, logger); err != nil {
35+func InstallTraefik(topDomain, adminEmail string, a *app.App) (bool, error) {
36+ // 1 - Files/directories creation
37+ // 1.1 - Check if directory traefik exists, if not create it
38+ if err := createDirectory(path + traefikPath); err != nil {
39+ a.AppLogger.Log(utils.ERROR, err)
3140 return false, err
32- } else if !success {
33- return false, errors.New("failed to deploy Traefik")
41+ } else {
42+ fmt.Println("traefik directory created")
3443 }
3544
36- return true, nil
37-}
38-
39-func ensureDirExists(dir string) error {
40- if _, err := os.Stat(dir); os.IsNotExist(err) {
41- return os.Mkdir(dir, 0755)
45+ // 1.2 - Check if directory traefik/volume exists, if not create it
46+ if err := createDirectory(path + traefikVolumePath); err != nil {
47+ a.AppLogger.Log(utils.ERROR, err)
48+ return false, err
49+ } else {
50+ fmt.Println("traefik/volume directory created")
4251 }
43- return nil
44-}
4552
46-func createYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
47- modelFS := root.ModelFS
48- if modelFS == nil {
49- return false, errors.New("modelFS is nil")
53+ // 1.3 - touch traefik/traefik.log
54+ if err := createTraefikLogFile(path + traefikLog); err != nil {
55+ a.AppLogger.Log(utils.ERROR, err)
56+ return false, err
57+ } else {
58+ a.AppLogger.Log(utils.INFO, "Traefik log file created")
5059 }
5160
52- // Check if directory traefik exists, if not create it
53- if err := ensureDirExists(path + "traefik"); err != nil {
61+ // 1.4 - touch traefik/volume/acme.json
62+ if err := createACMEfile(path + acmeJSON); err != nil {
63+ a.AppLogger.Log(utils.ERROR, err)
5464 return false, err
5565 }
56- // Check if directory traefik/volume exists, if not create it
57- if err := ensureDirExists(path + "traefik/volume"); err != nil {
66+
67+ a.AppLogger.Log(utils.INFO, "Traefik acme.json file created")
68+
69+ // 2 - Render the yml files
70+ // 2.1 - Render traefik-compose.yml
71+ traefikComposeContent, err := renderTemplate("traefik-compose.goyml", topDomain, adminEmail, a.AppLogger)
72+ if err != nil {
73+ a.AppLogger.Log(utils.ERROR, err)
5874 return false, err
5975 }
6076
61- // Setup the TXT renderer for traefik-compose.goyml
62- rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", modelFS, logger)
63- if err != nil {
77+ a.AppLogger.Log(utils.INFO, "Traefik compose file rendered")
78+
79+ // 2.2 - Save traefik-compose.yml
80+ if err := saveFile(path+"docker-compose.yml", traefikComposeContent); err != nil {
81+ a.AppLogger.Log(utils.ERROR, err)
6482 return false, err
83+ } else {
84+ fmt.Println("docker-compose.yml saved")
6585 }
6686
67- // Render the traefik-compose.goyml with payload data
68- composeContent, err := rendererCompose.TXTRender(map[string]string{
69- "topdomain": topDomain,
70- })
87+ // 2.3 - Render traefik.yml
88+ traefikContent, err := renderTemplate("traefik.goyml", topDomain, adminEmail, a.AppLogger)
7189 if err != nil {
90+ a.AppLogger.Log(utils.ERROR, err)
7291 return false, err
7392 }
74- // Save the rendered content to a file at the env path
75- err = os.WriteFile(path+"traefik/docker-compose.yml", []byte(composeContent), 0644)
76- if err != nil {
93+
94+ a.AppLogger.Log(utils.INFO, "Traefik file rendered")
95+
96+ // 2.4 - Save traefik.yml
97+ if err := saveFile(path+"traefik.yml", traefikContent); err != nil {
98+ a.AppLogger.Log(utils.ERROR, err)
7799 return false, err
78100 }
79101
80- // Setup the TXT renderer for traefik.goyml
81- rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", modelFS, logger)
82- if err != nil {
102+ a.AppLogger.Log(utils.INFO, "Traefik file saved")
103+
104+ // 3 - Check if traefik is running
105+ if err := checkIfTraefikIsRunning(a); err != nil {
106+ a.AppLogger.Log(utils.ERROR, err)
83107 return false, err
84108 }
85109
86- // Render the traefik.goyml with payload
87- traefikContent, err := rendererTraefik.TXTRender(map[string]string{
88- "admin.email": adminEmail,
89- })
90- if err != nil {
110+ // // 4 - Create network
111+ // if err := docker.CreateNetwork(networkName, a); err != nil {
112+ // a.AppLogger.Log(utils.ERROR, err)
113+ // return false, err
114+ // }
115+
116+ // 5 - Create traefik container
117+ if err := createTraefikContainer(path+"docker-compose.yml", a); err != nil {
118+ a.AppLogger.Log(utils.ERROR, err)
91119 return false, err
92120 }
93121
94- // Save the rendered content to a file at the env path
95- err = os.WriteFile(path+"traefik/traefik.yml", []byte(traefikContent), 0644)
96- if err != nil {
122+ // 6 - Ping traefik
123+ if err := pingWithCURL(topDomain); err != nil {
124+ a.AppLogger.Log(utils.ERROR, err)
97125 return false, err
98126 }
99127
100128 return true, nil
101-
102129 }
103130
104-func deployTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {
105- // Now that we have the YAML files, we can launch the containers
106- // Create acme.json with permissions 600
107- if f, err := os.Create(path + "traefik/volume/acme.json"); err != nil {
108- return false, err
109- } else if err = f.Chmod(0600); err != nil {
110- return false, err
111- } else {
112- f.Close() // Schedule closing of the file when function exits
131+func renderTemplate(filename, topDomain, adminEmail string, logger *utils.Logger) (string, error) {
132+ modelFS := root.ModelFS
133+ if modelFS == nil {
134+ return "", errors.New("modelFS is nil")
113135 }
114136
115- // traefik.log
116- if f, err := os.Create(path + "traefik/traefik.log"); err != nil {
117- return false, err
118- } else if err = f.Chmod(0600); err != nil {
119- return false, err
120- } else {
121- f.Close() // Schedule closing of the file when function exits
137+ renderer, err := templating.GetTXTRenderer(filename, modelFS, logger)
138+ if err != nil {
139+ return "", err
140+ }
141+
142+ var contentData map[string]interface{}
143+ switch filename {
144+ case "traefik-compose.goyml":
145+ contentData = map[string]interface{}{"topdomain": topDomain}
146+ case "traefik.goyml":
147+ contentData = map[string]interface{}{
148+ "admin": map[string]string{
149+ "email": adminEmail,
150+ },
151+ }
152+ default:
153+ return "", errors.New("invalid template filename")
122154 }
123155
124- // check if docker is running
125- if s, err := docker.CheckDockerRunning(); err != nil {
126- return false, err
127- } else if s {
128- fmt.Println("Docker is running")
129- } else {
130- fmt.Println("Docker is not running")
131- }
156+ return renderer.TXTRender(contentData)
157+}
132158
133- // check if a container named traefik already exists
134- if exists, err := docker.ContainerExists("traefik"); err != nil {
135- return false, err
136- } else if exists {
137- return false, errors.New("container traefik already exists")
138- } else {
139- fmt.Println("Container Traefik not found, creating it")
159+func createDirectory(dir string) error {
160+ if _, err := os.Stat(dir); os.IsNotExist(err) {
161+ return os.Mkdir(dir, 0755)
162+ }
163+ return nil
164+}
165+func saveFile(filePath, content string) error {
166+ f, err := os.Create(filePath)
167+ if err != nil {
168+ return err
140169 }
170+ defer f.Close()
171+ _, err = f.WriteString(content)
172+ if err != nil {
173+ fmt.Println("Error while writing to file: ", err)
174+ return err
175+ }
176+ return err
177+}
141178
142- // check if there is already a container named traefik
179+func closeFile(f *os.File) error {
180+ // defer f.Close()
181+ return f.Close()
182+}
143183
144- // Create the network "exposed" if it doesn't exist
145- // Ensure that the network "exposed" exists if not create it
146- exists, err := docker.NetworkExists("exposed")
184+func createACMEfile(filePath string) error {
185+ f, err := os.Create(filePath)
147186 if err != nil {
148- return false, err
187+ return err
149188 }
150- if exists {
151- fmt.Println("Network Exposed found")
152- } else {
153- fmt.Println("Network Exposed not found, creating it")
154- if err := docker.CreateNetwork("exposed"); err != nil {
155- return false, err
156- }
189+ defer f.Close()
190+ return f.Chmod(0600)
191+}
192+
193+func createTraefikLogFile(filePath string) error {
194+ f, err := os.Create(filePath)
195+ if err != nil {
196+ return err
157197 }
198+ defer f.Close()
199+ return f.Chmod(0600)
200+}
158201
159- fmt.Println("Network created successfully")
202+func checkIfTraefikIsRunning(a *app.App) error {
203+ // Check if docker is running
204+ if err := docker.CheckIfDockerIsRunning(a); err != nil {
205+ return err
206+ }
207+ // Check if a container named traefik already exists
208+ if err := docker.ContainerExists("traefik", a); err != nil {
209+ return err
210+ }
160211
161- // Create the containers with CreateContainerFromComposeFile (bool, error)
162- succes, err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger)
212+ return nil
213+}
214+
215+func createTraefikContainer(filePath string, a *app.App) error {
216+ err := docker.CreateContainerFromComposeFile(filePath, a)
163217 if err != nil {
164- return false, err
165- }
166- if succes {
167- fmt.Println("Containers created successfully")
168- return true, nil
218+ return err
169219 }
170220
171- return false, errors.New("failed to create containers")
221+ return nil
222+}
223+
224+func pingWithCURL(topDomain string) error {
225+ return nil
172226 }
@@ -14,159 +14,213 @@ import (
14 14
15 var path string15 var path string
16 16
17+const (
18+ traefikPath = "traefik"
19+ traefikVolumePath = "traefik/volume"
20+ traefikLog = "logs/traefik.log"
21+ acmeJSON = "traefik/volume/acme.json"
22+ networkName = "totototo"
23+)
24+
17 func init() {25 func init() {
18 path = app.GetBuildDir() + "/"26 path = app.GetBuildDir() + "/"
19 }27 }
20 28
21-func InstallTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {29+type InstallationResult struct {
22- // Create the YAML files30+ Success bool
23- if success, err := createYAMLFiles(topDomain, adminEmail, logger); err != nil {31+ Message string
24- return false, err32+ Err error
25- } else if !success {33+}
26- return false, errors.New("failed to create YAML files")
27- }
28 34
29- // Deploy the containers35+func InstallTraefik(topDomain, adminEmail string, a *app.App) (bool, error) {
30- if success, err := deployTraefik(topDomain, adminEmail, logger); err != nil {36+ // 1 - Files/directories creation
37+ // 1.1 - Check if directory traefik exists, if not create it
38+ if err := createDirectory(path + traefikPath); err != nil {
39+ a.AppLogger.Log(utils.ERROR, err)
31 return false, err40 return false, err
32- } else if !success {41+ } else {
33- return false, errors.New("failed to deploy Traefik")42+ fmt.Println("traefik directory created")
34 }43 }
35 44
36- return true, nil45+ // 1.2 - Check if directory traefik/volume exists, if not create it
37-}46+ if err := createDirectory(path + traefikVolumePath); err != nil {
38-47+ a.AppLogger.Log(utils.ERROR, err)
39-func ensureDirExists(dir string) error {48+ return false, err
40- if _, err := os.Stat(dir); os.IsNotExist(err) {49+ } else {
41- return os.Mkdir(dir, 0755)50+ fmt.Println("traefik/volume directory created")
42 }51 }
43- return nil
44-}
45 52
46-func createYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {53+ // 1.3 - touch traefik/traefik.log
47- modelFS := root.ModelFS54+ if err := createTraefikLogFile(path + traefikLog); err != nil {
48- if modelFS == nil {55+ a.AppLogger.Log(utils.ERROR, err)
49- return false, errors.New("modelFS is nil")56+ return false, err
57+ } else {
58+ a.AppLogger.Log(utils.INFO, "Traefik log file created")
50 }59 }
51 60
52- // Check if directory traefik exists, if not create it61+ // 1.4 - touch traefik/volume/acme.json
53- if err := ensureDirExists(path + "traefik"); err != nil {62+ if err := createACMEfile(path + acmeJSON); err != nil {
63+ a.AppLogger.Log(utils.ERROR, err)
54 return false, err64 return false, err
55 }65 }
56- // Check if directory traefik/volume exists, if not create it66+
57- if err := ensureDirExists(path + "traefik/volume"); err != nil {67+ a.AppLogger.Log(utils.INFO, "Traefik acme.json file created")
68+
69+ // 2 - Render the yml files
70+ // 2.1 - Render traefik-compose.yml
71+ traefikComposeContent, err := renderTemplate("traefik-compose.goyml", topDomain, adminEmail, a.AppLogger)
72+ if err != nil {
73+ a.AppLogger.Log(utils.ERROR, err)
58 return false, err74 return false, err
59 }75 }
60 76
61- // Setup the TXT renderer for traefik-compose.goyml77+ a.AppLogger.Log(utils.INFO, "Traefik compose file rendered")
62- rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", modelFS, logger)78+
63- if err != nil {79+ // 2.2 - Save traefik-compose.yml
80+ if err := saveFile(path+"docker-compose.yml", traefikComposeContent); err != nil {
81+ a.AppLogger.Log(utils.ERROR, err)
64 return false, err82 return false, err
83+ } else {
84+ fmt.Println("docker-compose.yml saved")
65 }85 }
66 86
67- // Render the traefik-compose.goyml with payload data87+ // 2.3 - Render traefik.yml
68- composeContent, err := rendererCompose.TXTRender(map[string]string{88+ traefikContent, err := renderTemplate("traefik.goyml", topDomain, adminEmail, a.AppLogger)
69- "topdomain": topDomain,
70- })
71 if err != nil {89 if err != nil {
90+ a.AppLogger.Log(utils.ERROR, err)
72 return false, err91 return false, err
73 }92 }
74- // Save the rendered content to a file at the env path93+
75- err = os.WriteFile(path+"traefik/docker-compose.yml", []byte(composeContent), 0644)94+ a.AppLogger.Log(utils.INFO, "Traefik file rendered")
76- if err != nil {95+
96+ // 2.4 - Save traefik.yml
97+ if err := saveFile(path+"traefik.yml", traefikContent); err != nil {
98+ a.AppLogger.Log(utils.ERROR, err)
77 return false, err99 return false, err
78 }100 }
79 101
80- // Setup the TXT renderer for traefik.goyml102+ a.AppLogger.Log(utils.INFO, "Traefik file saved")
81- rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", modelFS, logger)103+
82- if err != nil {104+ // 3 - Check if traefik is running
105+ if err := checkIfTraefikIsRunning(a); err != nil {
106+ a.AppLogger.Log(utils.ERROR, err)
83 return false, err107 return false, err
84 }108 }
85 109
86- // Render the traefik.goyml with payload110+ // // 4 - Create network
87- traefikContent, err := rendererTraefik.TXTRender(map[string]string{111+ // if err := docker.CreateNetwork(networkName, a); err != nil {
88- "admin.email": adminEmail,112+ // a.AppLogger.Log(utils.ERROR, err)
89- })113+ // return false, err
90- if err != nil {114+ // }
115+
116+ // 5 - Create traefik container
117+ if err := createTraefikContainer(path+"docker-compose.yml", a); err != nil {
118+ a.AppLogger.Log(utils.ERROR, err)
91 return false, err119 return false, err
92 }120 }
93 121
94- // Save the rendered content to a file at the env path122+ // 6 - Ping traefik
95- err = os.WriteFile(path+"traefik/traefik.yml", []byte(traefikContent), 0644)123+ if err := pingWithCURL(topDomain); err != nil {
96- if err != nil {124+ a.AppLogger.Log(utils.ERROR, err)
97 return false, err125 return false, err
98 }126 }
99 127
100 return true, nil128 return true, nil
101-
102 }129 }
103 130
104-func deployTraefik(topDomain, adminEmail string, logger *utils.Logger) (bool, error) {131+func renderTemplate(filename, topDomain, adminEmail string, logger *utils.Logger) (string, error) {
105- // Now that we have the YAML files, we can launch the containers132+ modelFS := root.ModelFS
106- // Create acme.json with permissions 600133+ if modelFS == nil {
107- if f, err := os.Create(path + "traefik/volume/acme.json"); err != nil {134+ return "", errors.New("modelFS is nil")
108- return false, err
109- } else if err = f.Chmod(0600); err != nil {
110- return false, err
111- } else {
112- f.Close() // Schedule closing of the file when function exits
113 }135 }
114 136
115- // traefik.log137+ renderer, err := templating.GetTXTRenderer(filename, modelFS, logger)
116- if f, err := os.Create(path + "traefik/traefik.log"); err != nil {138+ if err != nil {
117- return false, err139+ return "", err
118- } else if err = f.Chmod(0600); err != nil {140+ }
119- return false, err141+
120- } else {142+ var contentData map[string]interface{}
121- f.Close() // Schedule closing of the file when function exits143+ switch filename {
144+ case "traefik-compose.goyml":
145+ contentData = map[string]interface{}{"topdomain": topDomain}
146+ case "traefik.goyml":
147+ contentData = map[string]interface{}{
148+ "admin": map[string]string{
149+ "email": adminEmail,
150+ },
151+ }
152+ default:
153+ return "", errors.New("invalid template filename")
122 }154 }
123 155
124- // check if docker is running156+ return renderer.TXTRender(contentData)
125- if s, err := docker.CheckDockerRunning(); err != nil {157+}
126- return false, err
127- } else if s {
128- fmt.Println("Docker is running")
129- } else {
130- fmt.Println("Docker is not running")
131- }
132 158
133- // check if a container named traefik already exists159+func createDirectory(dir string) error {
134- if exists, err := docker.ContainerExists("traefik"); err != nil {160+ if _, err := os.Stat(dir); os.IsNotExist(err) {
135- return false, err161+ return os.Mkdir(dir, 0755)
136- } else if exists {162+ }
137- return false, errors.New("container traefik already exists")163+ return nil
138- } else {164+}
139- fmt.Println("Container Traefik not found, creating it")165+func saveFile(filePath, content string) error {
166+ f, err := os.Create(filePath)
167+ if err != nil {
168+ return err
140 }169 }
170+ defer f.Close()
171+ _, err = f.WriteString(content)
172+ if err != nil {
173+ fmt.Println("Error while writing to file: ", err)
174+ return err
175+ }
176+ return err
177+}
141 178
142- // check if there is already a container named traefik179+func closeFile(f *os.File) error {
180+ // defer f.Close()
181+ return f.Close()
182+}
143 183
144- // Create the network "exposed" if it doesn't exist184+func createACMEfile(filePath string) error {
145- // Ensure that the network "exposed" exists if not create it185+ f, err := os.Create(filePath)
146- exists, err := docker.NetworkExists("exposed")
147 if err != nil {186 if err != nil {
148- return false, err187+ return err
149 }188 }
150- if exists {189+ defer f.Close()
151- fmt.Println("Network Exposed found")190+ return f.Chmod(0600)
152- } else {191+}
153- fmt.Println("Network Exposed not found, creating it")192+
154- if err := docker.CreateNetwork("exposed"); err != nil {193+func createTraefikLogFile(filePath string) error {
155- return false, err194+ f, err := os.Create(filePath)
156- }195+ if err != nil {
196+ return err
157 }197 }
198+ defer f.Close()
199+ return f.Chmod(0600)
200+}
158 201
159- fmt.Println("Network created successfully")202+func checkIfTraefikIsRunning(a *app.App) error {
203+ // Check if docker is running
204+ if err := docker.CheckIfDockerIsRunning(a); err != nil {
205+ return err
206+ }
207+ // Check if a container named traefik already exists
208+ if err := docker.ContainerExists("traefik", a); err != nil {
209+ return err
210+ }
160 211
161- // Create the containers with CreateContainerFromComposeFile (bool, error)212+ return nil
162- succes, err := docker.CreateContainerFromComposeFile(path+"traefik/docker-compose.yml", logger)213+}
214+
215+func createTraefikContainer(filePath string, a *app.App) error {
216+ err := docker.CreateContainerFromComposeFile(filePath, a)
163 if err != nil {217 if err != nil {
164- return false, err218+ return err
165- }
166- if succes {
167- fmt.Println("Containers created successfully")
168- return true, nil
169 }219 }
170 220
171- return false, errors.New("failed to create containers")221+ return nil
222+}
223+
224+func pingWithCURL(topDomain string) error {
225+ return nil
172 }226 }
R pkg/utils/html-render.go → pkg/templating/html-render.go +11 -17 renamed, 57% similar
@@ -1,37 +1,33 @@
1-package utils
1+package templating
22
33 import (
44 "bytes"
5- "errors"
6- "fmt"
75 "html/template"
86 "io/fs"
7+
8+ "gogs.bnema.dev/gordon-echo/internal/app"
99 )
1010
1111 type Renderer struct {
1212 Template *template.Template
1313 ParseError error
14- Logger *Logger
1514 }
1615
1716 // Render function renders the template with the given data
18-func (r *Renderer) Render(data interface{}) (string, error) {
17+func (r *Renderer) Render(data interface{}, a *app.App) (string, error) {
1918 if r.ParseError != nil {
20- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
19+ a.AppLogger.Error().Err(r.ParseError).Msg("Failed to parse template")
2120 return "", r.ParseError
2221 }
23-
2422 if r.Template == nil {
25- err := errors.New("invalid or nil template")
26- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
27-
28- return "", err
23+ a.AppLogger.Error().Msg("Template is nil")
24+ return "", r.ParseError
2925 }
3026
3127 buf := new(bytes.Buffer)
3228 err := r.Template.Execute(buf, data)
3329 if err != nil {
34- r.Logger.Error().Err(err).Msg("Render error")
30+ a.AppLogger.Error().Err(err).Msg("Failed to execute template")
3531 return "", err
3632 }
3733
@@ -39,22 +35,20 @@ func (r *Renderer) Render(data interface{}) (string, error) {
3935 }
4036
4137 // GetRenderer function returns a new Renderer instance
42-func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
43- fmt.Println(filename)
38+func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) {
4439 // Check if the file exists in the provided fs.FS using fs.Open
4540 file, err := fs.Open(filename)
4641 if err != nil {
47- logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
42+ a.AppLogger.Error().Err(err).Msg("Failed to open file")
4843 return nil, err
4944 }
5045 file.Close() // Close the file after checking
5146 tmpl, err := template.New(filename).ParseFS(fs, filename)
5247 if err != nil {
53- logger.Error().Err(err).Msg("Failed to parse template")
48+ a.AppLogger.Error().Err(err).Msg("Failed to parse template")
5449 return nil, err
5550 }
5651 return &Renderer{
5752 Template: tmpl,
58- Logger: logger,
5953 }, nil
6054 }
@@ -1,37 +1,33 @@
1-package utils1+package templating
2 2
3 import (3 import (
4 "bytes"4 "bytes"
5- "errors"
6- "fmt"
7 "html/template"5 "html/template"
8 "io/fs"6 "io/fs"
7+
8+ "gogs.bnema.dev/gordon-echo/internal/app"
9 )9 )
10 10
11 type Renderer struct {11 type Renderer struct {
12 Template *template.Template12 Template *template.Template
13 ParseError error13 ParseError error
14- Logger *Logger
15 }14 }
16 15
17 // Render function renders the template with the given data16 // Render function renders the template with the given data
18-func (r *Renderer) Render(data interface{}) (string, error) {17+func (r *Renderer) Render(data interface{}, a *app.App) (string, error) {
19 if r.ParseError != nil {18 if r.ParseError != nil {
20- r.Logger.Error().Err(r.ParseError).Msg("Parse error")19+ a.AppLogger.Error().Err(r.ParseError).Msg("Failed to parse template")
21 return "", r.ParseError20 return "", r.ParseError
22 }21 }
23-
24 if r.Template == nil {22 if r.Template == nil {
25- err := errors.New("invalid or nil template")23+ a.AppLogger.Error().Msg("Template is nil")
26- r.Logger.Error().Err(r.ParseError).Msg("Parse error")24+ return "", r.ParseError
27-
28- return "", err
29 }25 }
30 26
31 buf := new(bytes.Buffer)27 buf := new(bytes.Buffer)
32 err := r.Template.Execute(buf, data)28 err := r.Template.Execute(buf, data)
33 if err != nil {29 if err != nil {
34- r.Logger.Error().Err(err).Msg("Render error")30+ a.AppLogger.Error().Err(err).Msg("Failed to execute template")
35 return "", err31 return "", err
36 }32 }
37 33
@@ -39,22 +35,20 @@ func (r *Renderer) Render(data interface{}) (string, error) {
39 }35 }
40 36
41 // GetRenderer function returns a new Renderer instance37 // GetRenderer function returns a new Renderer instance
42-func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {38+func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) {
43- fmt.Println(filename)
44 // Check if the file exists in the provided fs.FS using fs.Open39 // Check if the file exists in the provided fs.FS using fs.Open
45 file, err := fs.Open(filename)40 file, err := fs.Open(filename)
46 if err != nil {41 if err != nil {
47- logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)42+ a.AppLogger.Error().Err(err).Msg("Failed to open file")
48 return nil, err43 return nil, err
49 }44 }
50 file.Close() // Close the file after checking45 file.Close() // Close the file after checking
51 tmpl, err := template.New(filename).ParseFS(fs, filename)46 tmpl, err := template.New(filename).ParseFS(fs, filename)
52 if err != nil {47 if err != nil {
53- logger.Error().Err(err).Msg("Failed to parse template")48+ a.AppLogger.Error().Err(err).Msg("Failed to parse template")
54 return nil, err49 return nil, err
55 }50 }
56 return &Renderer{51 return &Renderer{
57 Template: tmpl,52 Template: tmpl,
58- Logger: logger,
59 }, nil53 }, nil
60 }54 }
M pkg/templating/models/traefik-compose.goyml +4 -3
@@ -4,6 +4,8 @@ services:
44 traefik:
55 image: traefik:v2.10
66 container_name: traefik
7+ environment:
8+ - COMPOSE_PROJECT_NAME=traefik
79 restart: unless-stopped
810 ports:
911 - "80:80"
@@ -12,7 +14,7 @@ services:
1214 - /var/run/docker.sock:/var/run/docker.sock:ro
1315 - ./traefik.yml:/etc/traefik/traefik.yml
1416 - ./volume/acme.json:/acme.json
15- - ./traefik.log:/var/log/traefik/traefik.log
17+ - ./logs/traefik.log:/var/log/traefik/traefik.log
1618 labels:
1719 - "traefik.enable=true"
1820 - "traefik.http.routers.api.rule=Host(`traefik.{{ .topdomain }}`)"
@@ -24,5 +26,4 @@ services:
2426 - exposed
2527
2628 networks:
27- exposed:
28- external: true⏎̸
29+ exposed:⏎̸
@@ -4,6 +4,8 @@ services:
4 traefik:4 traefik:
5 image: traefik:v2.105 image: traefik:v2.10
6 container_name: traefik6 container_name: traefik
7+ environment:
8+ - COMPOSE_PROJECT_NAME=traefik
7 restart: unless-stopped9 restart: unless-stopped
8 ports:10 ports:
9 - "80:80"11 - "80:80"
@@ -12,7 +14,7 @@ services:
12 - /var/run/docker.sock:/var/run/docker.sock:ro14 - /var/run/docker.sock:/var/run/docker.sock:ro
13 - ./traefik.yml:/etc/traefik/traefik.yml15 - ./traefik.yml:/etc/traefik/traefik.yml
14 - ./volume/acme.json:/acme.json16 - ./volume/acme.json:/acme.json
15- - ./traefik.log:/var/log/traefik/traefik.log17+ - ./logs/traefik.log:/var/log/traefik/traefik.log
16 labels:18 labels:
17 - "traefik.enable=true"19 - "traefik.enable=true"
18 - "traefik.http.routers.api.rule=Host(`traefik.{{ .topdomain }}`)"20 - "traefik.http.routers.api.rule=Host(`traefik.{{ .topdomain }}`)"
@@ -24,5 +26,4 @@ services:
24 - exposed26 - exposed
25 27
26 networks:28 networks:
27- exposed:29+ exposed:⏎̸
28- external: true⏎̸
D pkg/utils/html-fragments.go +0 -41
@@ -1,41 +0,0 @@
1-package utils
2-
3-import (
4- "bytes"
5- "fmt"
6-
7- "github.com/PuerkitoBio/goquery"
8- "gogs.bnema.dev/gordon-echo/config"
9-)
10-
11-// GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}, fs config.Provider) (string, error) {
13- // 1. Render the template
14- renderer, err := GetRenderer("components.gohtml", fs.GetTemplateFS(), NewLogger())
15- if err != nil {
16- return "", fmt.Errorf("failed to get renderer: %w", err)
17- }
18-
19- renderedHTML, err := renderer.Render(data)
20- if err != nil {
21- return "", fmt.Errorf("failed to render template: %w", err)
22- }
23-
24- // 2. Parse the rendered HTML with goquery
25- doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26- if err != nil {
27- return "", fmt.Errorf("failed to parse HTML document: %w", err)
28- }
29-
30- // 3. Find the div with the specified id
31- fragmentContent, err := doc.Find("#" + id).Html()
32- if err != nil {
33- return "", fmt.Errorf("failed to extract HTML for id %s: %w", id, err)
34- }
35-
36- if fragmentContent == "" {
37- return "", fmt.Errorf("element with id %s not found", id)
38- }
39-
40- return fragmentContent, nil
41-}
@@ -1,41 +0,0 @@
1-package utils
2-
3-import (
4- "bytes"
5- "fmt"
6-
7- "github.com/PuerkitoBio/goquery"
8- "gogs.bnema.dev/gordon-echo/config"
9-)
10-
11-// GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}, fs config.Provider) (string, error) {
13- // 1. Render the template
14- renderer, err := GetRenderer("components.gohtml", fs.GetTemplateFS(), NewLogger())
15- if err != nil {
16- return "", fmt.Errorf("failed to get renderer: %w", err)
17- }
18-
19- renderedHTML, err := renderer.Render(data)
20- if err != nil {
21- return "", fmt.Errorf("failed to render template: %w", err)
22- }
23-
24- // 2. Parse the rendered HTML with goquery
25- doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26- if err != nil {
27- return "", fmt.Errorf("failed to parse HTML document: %w", err)
28- }
29-
30- // 3. Find the div with the specified id
31- fragmentContent, err := doc.Find("#" + id).Html()
32- if err != nil {
33- return "", fmt.Errorf("failed to extract HTML for id %s: %w", id, err)
34- }
35-
36- if fragmentContent == "" {
37- return "", fmt.Errorf("element with id %s not found", id)
38- }
39-
40- return fragmentContent, nil
41-}

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