detached 6e11c22

feat: Enhance container creation and proxy routing with improved IP and network handling

- Add container IP detection with fallback mechanisms
- Implement automatic proxy route creation after container deployment
- Improve container startup process with error handling and cleanup
- Add hostname configuration during container creation
- Enhance logging for container and proxy route operations
- Refactor container deployment to support more robust network integration
commit
6e11c222c832a19e19c9fd64c26a20db50d50594
author
bamen · · 2025-03-09 16:31 UTC
parent
d8b24be
signature
unsigned
4 files · +89 -19
M internal/httpserve/handlers/container_crud.go +45 -11
@@ -2,14 +2,15 @@ package handlers
22
33 import (
44 "fmt"
5- "log"
65 "net/http"
6+ "strings"
77
88 "github.com/bnema/gordon/internal/server"
99 "github.com/bnema/gordon/internal/templating/cmdparams"
1010 "github.com/bnema/gordon/internal/templating/render"
1111 "github.com/bnema/gordon/pkg/docker"
1212 "github.com/bnema/gordon/pkg/sanitize"
13+ "github.com/charmbracelet/log"
1314 "github.com/labstack/echo/v4"
1415 )
1516
@@ -17,7 +18,7 @@ import (
1718 func FromShortIDToImageID(ShortID string) (string, error) {
1819 imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)
1920 if !exists {
20- log.Printf("No mapping found for short ID: %s", ShortID)
21+ log.Debug("No mapping found for short ID: %s", ShortID)
2122 return "", fmt.Errorf("image ID not found")
2223 }
2324 return imageID, nil
@@ -25,18 +26,18 @@ func FromShortIDToImageID(ShortID string) (string, error) {
2526
2627 // CreateContainerRoute is the route for creating a new container
2728 func CreateContainerGET(c echo.Context, a *server.App) error {
28- log.Printf("Full request URL: %s", c.Request().URL.String())
29+ log.Debug("Full request URL: %s", c.Request().URL.String())
2930 // Retrieve the ShortID of the image from the URL
3031 shortID := c.Param("ID")
31- log.Printf("Received ShortID: %s", shortID)
32+ log.Debug("Received ShortID: %s", shortID)
3233
3334 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)
3435 if !exists {
35- log.Printf("No mapping found for short ID: %s", shortID)
36+ log.Debug("No mapping found for short ID: %s", shortID)
3637 return c.String(http.StatusNotFound, "Image ID not found")
3738 }
3839
39- log.Printf("Found mapping: %s -> %s", shortID, fullID)
40+ log.Debug("Found mapping: %s -> %s", shortID, fullID)
4041
4142 // Get the image info
4243 imageInfo, err := docker.GetImageInfo(fullID)
@@ -78,15 +79,15 @@ func CreateContainerGET(c echo.Context, a *server.App) error {
7879 func CreateContainerFullGET(c echo.Context, a *server.App) error {
7980 // Retrieve the ShortID of the image from the URL
8081 shortID := c.Param("ID")
81- log.Printf("Received ShortID: %s", shortID)
82+ log.Debug("Received ShortID: %s", shortID)
8283
8384 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)
8485 if !exists {
85- log.Printf("No mapping found for short ID: %s", shortID)
86+ log.Debug("No mapping found for short ID: %s", shortID)
8687 return c.String(http.StatusNotFound, "Image ID not found")
8788 }
8889
89- log.Printf("Found mapping: %s -> %s", shortID, fullID)
90+ log.Debug("Found mapping: %s -> %s", shortID, fullID)
9091
9192 // Get the image info
9293 imageInfo, err := docker.GetImageInfo(fullID)
@@ -116,7 +117,7 @@ func CreateContainerFullGET(c echo.Context, a *server.App) error {
116117
117118 renderedHTML, err := rendererData.Render(data, a)
118119 if err != nil {
119- log.Printf("Error rendering template: %v", err)
120+ log.Debug("Error rendering template: %v", err)
120121 return sendError(c, err)
121122 }
122123
@@ -166,10 +167,43 @@ func CreateContainerPOST(c echo.Context, a *server.App) error {
166167 }
167168
168169 // Create the container
169- _, err = docker.CreateContainer(cmdParams)
170+ containerID, err := docker.CreateContainer(cmdParams)
170171 if err != nil {
171172 return sendError(c, err)
172173 }
173174
175+ // Start the container
176+ err = docker.StartContainer(containerID)
177+ if err != nil {
178+ // If start fails, try to cleanup the container
179+ log.Warn("Failed to start container", "error", err)
180+ _ = docker.RemoveContainer(containerID)
181+ return sendError(c, fmt.Errorf("failed to start container: %w", err))
182+ }
183+
184+ // Get the container name for logging and as fallback
185+ containerName, err := docker.GetContainerName(containerID)
186+ if err != nil {
187+ log.Warn("Failed to get container name", "error", err)
188+ } else {
189+ containerName = strings.TrimPrefix(containerName, "/")
190+ }
191+
192+ // Get container IP
193+ containerIP := GetContainerIP(a, containerID, containerName)
194+
195+ // Construct target domain from form inputs
196+ targetDomain := fmt.Sprintf("%s://%s.%s",
197+ sanitizedInputs["container_protocol"],
198+ sanitizedInputs["container_subdomain"],
199+ sanitizedInputs["container_domain"])
200+
201+ // Add proxy route for the new container
202+ err = AddProxyRoute(a, containerID, containerIP, sanitizedInputs["proxy_port"], targetDomain)
203+ if err != nil {
204+ log.Error("Failed to add proxy route", "error", err)
205+ // Continue anyway, don't fail the whole operation
206+ }
207+
174208 return c.HTML(http.StatusOK, ActionSuccess(a))
175209 }
@@ -2,14 +2,15 @@ package handlers
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 "net/http"5 "net/http"
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/cmdparams"
10 "github.com/bnema/gordon/internal/templating/render"10 "github.com/bnema/gordon/internal/templating/render"
11 "github.com/bnema/gordon/pkg/docker"11 "github.com/bnema/gordon/pkg/docker"
12 "github.com/bnema/gordon/pkg/sanitize"12 "github.com/bnema/gordon/pkg/sanitize"
13+ "github.com/charmbracelet/log"
13 "github.com/labstack/echo/v4"14 "github.com/labstack/echo/v4"
14 )15 )
15 16
@@ -17,7 +18,7 @@ import (
17 func FromShortIDToImageID(ShortID string) (string, error) {18 func FromShortIDToImageID(ShortID string) (string, error) {
18 imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)19 imageID, exists := safelyInteractWithIDMap(Fetch, ShortID)
19 if !exists {20 if !exists {
20- log.Printf("No mapping found for short ID: %s", ShortID)21+ log.Debug("No mapping found for short ID: %s", ShortID)
21 return "", fmt.Errorf("image ID not found")22 return "", fmt.Errorf("image ID not found")
22 }23 }
23 return imageID, nil24 return imageID, nil
@@ -25,18 +26,18 @@ func FromShortIDToImageID(ShortID string) (string, error) {
25 26
26 // CreateContainerRoute is the route for creating a new container27 // CreateContainerRoute is the route for creating a new container
27 func CreateContainerGET(c echo.Context, a *server.App) error {28 func CreateContainerGET(c echo.Context, a *server.App) error {
28- log.Printf("Full request URL: %s", c.Request().URL.String())29+ log.Debug("Full request URL: %s", c.Request().URL.String())
29 // Retrieve the ShortID of the image from the URL30 // Retrieve the ShortID of the image from the URL
30 shortID := c.Param("ID")31 shortID := c.Param("ID")
31- log.Printf("Received ShortID: %s", shortID)32+ log.Debug("Received ShortID: %s", shortID)
32 33
33 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)34 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)
34 if !exists {35 if !exists {
35- log.Printf("No mapping found for short ID: %s", shortID)36+ log.Debug("No mapping found for short ID: %s", shortID)
36 return c.String(http.StatusNotFound, "Image ID not found")37 return c.String(http.StatusNotFound, "Image ID not found")
37 }38 }
38 39
39- log.Printf("Found mapping: %s -> %s", shortID, fullID)40+ log.Debug("Found mapping: %s -> %s", shortID, fullID)
40 41
41 // Get the image info42 // Get the image info
42 imageInfo, err := docker.GetImageInfo(fullID)43 imageInfo, err := docker.GetImageInfo(fullID)
@@ -78,15 +79,15 @@ func CreateContainerGET(c echo.Context, a *server.App) error {
78 func CreateContainerFullGET(c echo.Context, a *server.App) error {79 func CreateContainerFullGET(c echo.Context, a *server.App) error {
79 // Retrieve the ShortID of the image from the URL80 // Retrieve the ShortID of the image from the URL
80 shortID := c.Param("ID")81 shortID := c.Param("ID")
81- log.Printf("Received ShortID: %s", shortID)82+ log.Debug("Received ShortID: %s", shortID)
82 83
83 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)84 fullID, exists := safelyInteractWithIDMap(Fetch, shortID)
84 if !exists {85 if !exists {
85- log.Printf("No mapping found for short ID: %s", shortID)86+ log.Debug("No mapping found for short ID: %s", shortID)
86 return c.String(http.StatusNotFound, "Image ID not found")87 return c.String(http.StatusNotFound, "Image ID not found")
87 }88 }
88 89
89- log.Printf("Found mapping: %s -> %s", shortID, fullID)90+ log.Debug("Found mapping: %s -> %s", shortID, fullID)
90 91
91 // Get the image info92 // Get the image info
92 imageInfo, err := docker.GetImageInfo(fullID)93 imageInfo, err := docker.GetImageInfo(fullID)
@@ -116,7 +117,7 @@ func CreateContainerFullGET(c echo.Context, a *server.App) error {
116 117
117 renderedHTML, err := rendererData.Render(data, a)118 renderedHTML, err := rendererData.Render(data, a)
118 if err != nil {119 if err != nil {
119- log.Printf("Error rendering template: %v", err)120+ log.Debug("Error rendering template: %v", err)
120 return sendError(c, err)121 return sendError(c, err)
121 }122 }
122 123
@@ -166,10 +167,43 @@ func CreateContainerPOST(c echo.Context, a *server.App) error {
166 }167 }
167 168
168 // Create the container169 // Create the container
169- _, err = docker.CreateContainer(cmdParams)170+ containerID, err := docker.CreateContainer(cmdParams)
170 if err != nil {171 if err != nil {
171 return sendError(c, err)172 return sendError(c, err)
172 }173 }
173 174
175+ // Start the container
176+ err = docker.StartContainer(containerID)
177+ if err != nil {
178+ // If start fails, try to cleanup the container
179+ log.Warn("Failed to start container", "error", err)
180+ _ = docker.RemoveContainer(containerID)
181+ return sendError(c, fmt.Errorf("failed to start container: %w", err))
182+ }
183+
184+ // Get the container name for logging and as fallback
185+ containerName, err := docker.GetContainerName(containerID)
186+ if err != nil {
187+ log.Warn("Failed to get container name", "error", err)
188+ } else {
189+ containerName = strings.TrimPrefix(containerName, "/")
190+ }
191+
192+ // Get container IP
193+ containerIP := GetContainerIP(a, containerID, containerName)
194+
195+ // Construct target domain from form inputs
196+ targetDomain := fmt.Sprintf("%s://%s.%s",
197+ sanitizedInputs["container_protocol"],
198+ sanitizedInputs["container_subdomain"],
199+ sanitizedInputs["container_domain"])
200+
201+ // Add proxy route for the new container
202+ err = AddProxyRoute(a, containerID, containerIP, sanitizedInputs["proxy_port"], targetDomain)
203+ if err != nil {
204+ log.Error("Failed to add proxy route", "error", err)
205+ // Continue anyway, don't fail the whole operation
206+ }
207+
174 return c.HTML(http.StatusOK, ActionSuccess(a))208 return c.HTML(http.StatusOK, ActionSuccess(a))
175 }209 }
M internal/httpserve/handlers/endpoint_deploy.go +6 -8
@@ -78,14 +78,11 @@ func PostDeploy(c echo.Context, a *server.App) error {
7878 }
7979
8080 // After successfully deploying a container, add a proxy route
81- err = addProxyRoute(a, containerID, containerName, payload.Port, payload.TargetDomain)
81+ containerIP := GetContainerIP(a, containerID, containerName)
82+ err = AddProxyRoute(a, containerID, containerIP, payload.Port, payload.TargetDomain)
8283 if err != nil {
8384 log.Error("Failed to add proxy route", "error", err)
84- return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{
85- Success: false,
86- Message: fmt.Sprintf("Failed to add proxy route: %v", err),
87- StatusCode: http.StatusInternalServerError,
88- })
85+ return sendDeployErrorResponse(c, "Failed to add proxy route", err)
8986 }
9087
9188 // Arrived here means deployment was successful
@@ -329,7 +326,8 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe
329326 "domain", targetDomain)
330327
331328 // After successfully deploying a container, add a proxy route
332- err = addProxyRoute(a, containerID, containerName, port, targetDomain)
329+ containerIP := GetContainerIP(a, containerID, containerName)
330+ err = AddProxyRoute(a, containerID, containerIP, port, targetDomain)
333331 if err != nil {
334332 log.Error("Failed to add proxy route", "error", err)
335333 return sendDeployErrorResponse(c, "Failed to add proxy route", err)
@@ -392,7 +390,7 @@ func sendDeployErrorResponse(c echo.Context, message string, err error) error {
392390 // }
393391
394392 // After successfully deploying a container, add a proxy route
395-func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targetDomain string) error {
393+func AddProxyRoute(a *server.App, containerID, containerIP, containerPort, targetDomain string) error {
396394 // Create a new proxy instance
397395 p, err := proxy.NewProxy(a)
398396 if err != nil {
@@ -78,14 +78,11 @@ func PostDeploy(c echo.Context, a *server.App) error {
78 }78 }
79 79
80 // After successfully deploying a container, add a proxy route80 // After successfully deploying a container, add a proxy route
81- err = addProxyRoute(a, containerID, containerName, payload.Port, payload.TargetDomain)81+ containerIP := GetContainerIP(a, containerID, containerName)
82+ err = AddProxyRoute(a, containerID, containerIP, payload.Port, payload.TargetDomain)
82 if err != nil {83 if err != nil {
83 log.Error("Failed to add proxy route", "error", err)84 log.Error("Failed to add proxy route", "error", err)
84- return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{85+ return sendDeployErrorResponse(c, "Failed to add proxy route", err)
85- Success: false,
86- Message: fmt.Sprintf("Failed to add proxy route: %v", err),
87- StatusCode: http.StatusInternalServerError,
88- })
89 }86 }
90 87
91 // Arrived here means deployment was successful88 // Arrived here means deployment was successful
@@ -329,7 +326,8 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe
329 "domain", targetDomain)326 "domain", targetDomain)
330 327
331 // After successfully deploying a container, add a proxy route328 // After successfully deploying a container, add a proxy route
332- err = addProxyRoute(a, containerID, containerName, port, targetDomain)329+ containerIP := GetContainerIP(a, containerID, containerName)
330+ err = AddProxyRoute(a, containerID, containerIP, port, targetDomain)
333 if err != nil {331 if err != nil {
334 log.Error("Failed to add proxy route", "error", err)332 log.Error("Failed to add proxy route", "error", err)
335 return sendDeployErrorResponse(c, "Failed to add proxy route", err)333 return sendDeployErrorResponse(c, "Failed to add proxy route", err)
@@ -392,7 +390,7 @@ func sendDeployErrorResponse(c echo.Context, message string, err error) error {
392 // }390 // }
393 391
394 // After successfully deploying a container, add a proxy route392 // After successfully deploying a container, add a proxy route
395-func addProxyRoute(a *server.App, containerID, containerIP, containerPort, targetDomain string) error {393+func AddProxyRoute(a *server.App, containerID, containerIP, containerPort, targetDomain string) error {
396 // Create a new proxy instance394 // Create a new proxy instance
397 p, err := proxy.NewProxy(a)395 p, err := proxy.NewProxy(a)
398 if err != nil {396 if err != nil {
M internal/httpserve/handlers/endpoint_helpers.go +37 -0
@@ -443,3 +443,40 @@ func getTransferData(transferID string) (*ChunkMetadata, map[int][]byte, error)
443443 }
444444 return metadata, chunkStore.chunks[transferID], nil
445445 }
446+
447+// GetContainerIP gets the IP address of a container within its Docker network
448+func GetContainerIP(a *server.App, containerID, containerName string) string {
449+ // Try to get container info
450+ containerInfo, err := docker.GetContainerInfo(containerID)
451+ if err != nil {
452+ log.Warn("Failed to get container info, using container name as IP", "error", err)
453+ return containerName // Use container name as fallback
454+ }
455+
456+ // Extract IP address from container info
457+ if containerInfo.NetworkSettings != nil {
458+ // Look for the network specified in the config
459+ networkName := a.Config.ContainerEngine.Network
460+ if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && networkSettings.IPAddress != "" {
461+ log.Debug("Found container IP",
462+ "containerName", containerName,
463+ "containerIP", networkSettings.IPAddress,
464+ "network", networkName)
465+ return networkSettings.IPAddress
466+ }
467+
468+ // Look for any network if the specified one isn't found
469+ for networkName, networkSettings := range containerInfo.NetworkSettings.Networks {
470+ if networkSettings.IPAddress != "" {
471+ log.Debug("Found container IP in alternative network",
472+ "containerName", containerName,
473+ "containerIP", networkSettings.IPAddress,
474+ "network", networkName)
475+ return networkSettings.IPAddress
476+ }
477+ }
478+ }
479+
480+ // Fallback to container name if no IP found
481+ return containerName
482+}
@@ -443,3 +443,40 @@ func getTransferData(transferID string) (*ChunkMetadata, map[int][]byte, error)
443 }443 }
444 return metadata, chunkStore.chunks[transferID], nil444 return metadata, chunkStore.chunks[transferID], nil
445 }445 }
446+
447+// GetContainerIP gets the IP address of a container within its Docker network
448+func GetContainerIP(a *server.App, containerID, containerName string) string {
449+ // Try to get container info
450+ containerInfo, err := docker.GetContainerInfo(containerID)
451+ if err != nil {
452+ log.Warn("Failed to get container info, using container name as IP", "error", err)
453+ return containerName // Use container name as fallback
454+ }
455+
456+ // Extract IP address from container info
457+ if containerInfo.NetworkSettings != nil {
458+ // Look for the network specified in the config
459+ networkName := a.Config.ContainerEngine.Network
460+ if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && networkSettings.IPAddress != "" {
461+ log.Debug("Found container IP",
462+ "containerName", containerName,
463+ "containerIP", networkSettings.IPAddress,
464+ "network", networkName)
465+ return networkSettings.IPAddress
466+ }
467+
468+ // Look for any network if the specified one isn't found
469+ for networkName, networkSettings := range containerInfo.NetworkSettings.Networks {
470+ if networkSettings.IPAddress != "" {
471+ log.Debug("Found container IP in alternative network",
472+ "containerName", containerName,
473+ "containerIP", networkSettings.IPAddress,
474+ "network", networkName)
475+ return networkSettings.IPAddress
476+ }
477+ }
478+ }
479+
480+ // Fallback to container name if no IP found
481+ return containerName
482+}
M pkg/docker/containers.go +1 -0
@@ -297,6 +297,7 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
297297 Labels: labels,
298298 Env: cmdParams.Environment,
299299 ExposedPorts: exposedPorts,
300+ Hostname: cmdParams.ServiceName,
300301 },
301302 &container.HostConfig{
302303 PortBindings: portBindings,
@@ -297,6 +297,7 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
297 Labels: labels,297 Labels: labels,
298 Env: cmdParams.Environment,298 Env: cmdParams.Environment,
299 ExposedPorts: exposedPorts,299 ExposedPorts: exposedPorts,
300+ Hostname: cmdParams.ServiceName,
300 },301 },
301 &container.HostConfig{302 &container.HostConfig{
302 PortBindings: portBindings,303 PortBindings: portBindings,

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