detached 362ea08

Refactor container port handling and improve user input parsing

- Use containerInfo.Config.ExposedPorts for preparing port mappings in ContainerManagerEditGET function.
- Update port label and guidance text in createcontainer.gohtml template.
- Add parsing logic for port mappings input in FromInputsToCmdParams function, replacing the basic string split method.
- Introduce PortMapping struct and ParsePortsSpecs function in pkg/docker/containers.go for parsing port specifications.
- Remove deprecated Ports field from ContainerCommandParams and replace it with PortMappings.
- Update CreateContainer function to use the new PortMapping struct for port bindings.
commit
362ea0889624545200456bbd455a9e57389c0d05
author
bnema · · 2023-10-09 10:54 UTC
parent
608b713
signature
unsigned
4 files · +83 -16
M internal/httpserve/handler/editcontainer.go +6 -6
@@ -51,14 +51,14 @@ func ContainerManagerEditGET(c echo.Context, a *app.App) error {
5151 networkNames = append(networkNames, networkName)
5252 }
5353
54- // Prepare Ports
54+ // Prepare Ports while using the containerInfo.Config.ExposedPorts
5555 portMappings := make([]string, 0)
56- for port, binding := range containerInfo.NetworkSettings.Ports {
57- for _, b := range binding {
58- portMappings = append(portMappings, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port))
59- }
56+ for exposedPort := range containerInfo.Config.ExposedPorts {
57+ // Here you might want to check your lastKnownPortMappings to find out what this exposedPort is mapped to on the host.
58+ hostPort := "" // Fetch this from lastKnownPortMappings or set a default
59+ portMapping := fmt.Sprintf("0.0.0.0:%s->%s", hostPort, exposedPort)
60+ portMappings = append(portMappings, portMapping)
6061 }
61-
6262 // Prepare Volumes (Mounts)
6363 volumeMappings := make([]string, 0)
6464 for _, mount := range containerInfo.Mounts {
@@ -51,14 +51,14 @@ func ContainerManagerEditGET(c echo.Context, a *app.App) error {
51 networkNames = append(networkNames, networkName)51 networkNames = append(networkNames, networkName)
52 }52 }
53 53
54- // Prepare Ports54+ // Prepare Ports while using the containerInfo.Config.ExposedPorts
55 portMappings := make([]string, 0)55 portMappings := make([]string, 0)
56- for port, binding := range containerInfo.NetworkSettings.Ports {56+ for exposedPort := range containerInfo.Config.ExposedPorts {
57- for _, b := range binding {57+ // Here you might want to check your lastKnownPortMappings to find out what this exposedPort is mapped to on the host.
58- portMappings = append(portMappings, fmt.Sprintf("%s:%s->%s", b.HostIP, b.HostPort, port))58+ hostPort := "" // Fetch this from lastKnownPortMappings or set a default
59- }59+ portMapping := fmt.Sprintf("0.0.0.0:%s->%s", hostPort, exposedPort)
60+ portMappings = append(portMappings, portMapping)
60 }61 }
61-
62 // Prepare Volumes (Mounts)62 // Prepare Volumes (Mounts)
63 volumeMappings := make([]string, 0)63 volumeMappings := make([]string, 0)
64 for _, mount := range containerInfo.Mounts {64 for _, mount := range containerInfo.Mounts {
M internal/templates/models/html/fragments/createcontainer.gohtml +2 -1
@@ -90,7 +90,8 @@
9090 <div class="pb-4">
9191 <label class="block">Ports</label>
9292 <span class="text-gray-500"
93- >e.g. 80:80 (left is host, right is container)</span
93+ >eg: 80:80/tcp (You can specify multiple ports by separating them with
94+ a comma)</span
9495 >
9596 <input
9697 type="text"
@@ -90,7 +90,8 @@
90 <div class="pb-4">90 <div class="pb-4">
91 <label class="block">Ports</label>91 <label class="block">Ports</label>
92 <span class="text-gray-500"92 <span class="text-gray-500"
93- >e.g. 80:80 (left is host, right is container)</span93+ >eg: 80:80/tcp (You can specify multiple ports by separating them with
94+ a comma)</span
94 >95 >
95 <input96 <input
96 type="text"97 type="text"
M internal/templates/render/cmdparams.go +17 -7
@@ -17,9 +17,23 @@ func FromInputsToCmdParams(inputs map[string]string) (docker.ContainerCommandPar
1717 environmentStr := inputs["environment_variables"]
1818 environmentSlice := strings.Split(environmentStr, "\n")
1919
20- fmt.Println("environmentSlice", environmentSlice)
20+ // Ports input parsing
21+ portMappingsStr := inputs["ports"]
22+ portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
23+
24+ // Trim whitespace from each port mapping string
25+ portMappingsSlice := make([]string, len(portMappingsSliceRaw))
26+ for i, spec := range portMappingsSliceRaw {
27+ portMappingsSlice[i] = strings.TrimSpace(spec)
28+ }
29+
30+ portMappings, err := docker.ParsePortsSpecs(portMappingsSlice)
31+ if err != nil {
32+ return docker.ContainerCommandParams{}, err
33+ }
2134
22- portsStr := inputs["ports"]
35+ // Get the exposed port for traefik labels
36+ exposedPort := portMappings[0].ExposedPort
2337
2438 params := docker.ContainerCommandParams{
2539 IsSSL: inputs["container_protocol"] == "https",
@@ -28,16 +42,13 @@ func FromInputsToCmdParams(inputs map[string]string) (docker.ContainerCommandPar
2842 Domain: inputs["container_domain"],
2943 ImageName: inputs["image_name"],
3044 ImageID: inputs["image_id"],
31- Ports: strings.Split(portsStr, ","),
3245 Restart: inputs["restart"],
3346 Volumes: volumeSlice,
3447 Environment: environmentSlice,
3548 Network: ("traefik"),
49+ PortMappings: portMappings,
3650 }
3751
38- // Retain the port number for the container host for the load balancer
39- exposedPort := strings.Split(params.Ports[0], ":")[0]
40-
4152 entryPoint := "web"
4253 if params.IsSSL {
4354 entryPoint = "websecure"
@@ -95,7 +106,6 @@ func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.Container
95106 ContainerName: yamlParams.Name,
96107 ImageName: yamlParams.Image,
97108 ImageID: imageID,
98- Ports: yamlParams.Ports,
99109 Volumes: yamlParams.Volumes,
100110 Environment: yamlParams.Environment,
101111 Labels: labels,
@@ -17,9 +17,23 @@ func FromInputsToCmdParams(inputs map[string]string) (docker.ContainerCommandPar
17 environmentStr := inputs["environment_variables"]17 environmentStr := inputs["environment_variables"]
18 environmentSlice := strings.Split(environmentStr, "\n")18 environmentSlice := strings.Split(environmentStr, "\n")
19 19
20- fmt.Println("environmentSlice", environmentSlice)20+ // Ports input parsing
21+ portMappingsStr := inputs["ports"]
22+ portMappingsSliceRaw := strings.Split(portMappingsStr, ",")
23+
24+ // Trim whitespace from each port mapping string
25+ portMappingsSlice := make([]string, len(portMappingsSliceRaw))
26+ for i, spec := range portMappingsSliceRaw {
27+ portMappingsSlice[i] = strings.TrimSpace(spec)
28+ }
29+
30+ portMappings, err := docker.ParsePortsSpecs(portMappingsSlice)
31+ if err != nil {
32+ return docker.ContainerCommandParams{}, err
33+ }
21 34
22- portsStr := inputs["ports"]35+ // Get the exposed port for traefik labels
36+ exposedPort := portMappings[0].ExposedPort
23 37
24 params := docker.ContainerCommandParams{38 params := docker.ContainerCommandParams{
25 IsSSL: inputs["container_protocol"] == "https",39 IsSSL: inputs["container_protocol"] == "https",
@@ -28,16 +42,13 @@ func FromInputsToCmdParams(inputs map[string]string) (docker.ContainerCommandPar
28 Domain: inputs["container_domain"],42 Domain: inputs["container_domain"],
29 ImageName: inputs["image_name"],43 ImageName: inputs["image_name"],
30 ImageID: inputs["image_id"],44 ImageID: inputs["image_id"],
31- Ports: strings.Split(portsStr, ","),
32 Restart: inputs["restart"],45 Restart: inputs["restart"],
33 Volumes: volumeSlice,46 Volumes: volumeSlice,
34 Environment: environmentSlice,47 Environment: environmentSlice,
35 Network: ("traefik"),48 Network: ("traefik"),
49+ PortMappings: portMappings,
36 }50 }
37 51
38- // Retain the port number for the container host for the load balancer
39- exposedPort := strings.Split(params.Ports[0], ":")[0]
40-
41 entryPoint := "web"52 entryPoint := "web"
42 if params.IsSSL {53 if params.IsSSL {
43 entryPoint = "websecure"54 entryPoint = "websecure"
@@ -95,7 +106,6 @@ func FromYAMLStructToCmdParams(yamlParams YAMLContainerParams) (docker.Container
95 ContainerName: yamlParams.Name,106 ContainerName: yamlParams.Name,
96 ImageName: yamlParams.Image,107 ImageName: yamlParams.Image,
97 ImageID: imageID,108 ImageID: imageID,
98- Ports: yamlParams.Ports,
99 Volumes: yamlParams.Volumes,109 Volumes: yamlParams.Volumes,
100 Environment: yamlParams.Environment,110 Environment: yamlParams.Environment,
101 Labels: labels,111 Labels: labels,
M pkg/docker/containers.go +58 -2
@@ -4,6 +4,7 @@ import (
44 "bytes"
55 "context"
66 "fmt"
7+ "strconv"
78 "strings"
89 "time"
910
@@ -22,7 +23,7 @@ type ContainerCommandParams struct {
2223 EnvVar string
2324 ImageName string
2425 ImageID string
25- Ports []string
26+ PortMappings []PortMapping
2627 Volumes []string
2728 Labels []string
2829 Network string
@@ -30,6 +31,50 @@ type ContainerCommandParams struct {
3031 Environment []string
3132 }
3233
34+type PortMapping struct {
35+ HostPort string
36+ ExposedPort string
37+ Protocol string
38+}
39+
40+// ParsePortsSpecs receives a slice of strings in the format "hostPort:containerPort/Protocol" and returns a slice of PortMapping structs
41+func ParsePortsSpecs(portsSpecs []string) ([]PortMapping, error) {
42+ portMappings := make([]PortMapping, 0, len(portsSpecs))
43+
44+ for _, portSpec := range portsSpecs {
45+ parts := strings.Split(portSpec, ":")
46+ if len(parts) != 2 {
47+ return nil, fmt.Errorf("invalid port specification: %s. Format should be hostPort:containerPort[/Protocol]", portSpec)
48+ }
49+
50+ // Validate hostPort and exposedPort are numbers
51+ hostPort := parts[0]
52+ if _, err := strconv.Atoi(hostPort); err != nil {
53+ return nil, fmt.Errorf("invalid host port: %s. Must be a number", hostPort)
54+ }
55+
56+ exposedParts := strings.Split(parts[1], "/")
57+ exposedPort := exposedParts[0]
58+ if _, err := strconv.Atoi(exposedPort); err != nil {
59+ return nil, fmt.Errorf("invalid exposed port: %s. Must be a number", exposedPort)
60+ }
61+
62+ // Default protocol is tcp
63+ protocol := "tcp"
64+ if len(exposedParts) > 1 {
65+ protocol = exposedParts[1]
66+ }
67+
68+ portMappings = append(portMappings, PortMapping{
69+ HostPort: hostPort,
70+ ExposedPort: exposedPort,
71+ Protocol: protocol,
72+ })
73+ }
74+
75+ return portMappings, nil
76+}
77+
3378 func ContainerCommandParamsToConfig(cmdParams ContainerCommandParams) (*container.Config, error) {
3479 return &container.Config{
3580 Image: cmdParams.ImageName,
@@ -152,7 +197,18 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
152197 // Check if the Docker client has been initialized
153198 CheckIfInitialized()
154199
155- portBindings := map[nat.Port][]nat.PortBinding{}
200+ // Prepare port bindings
201+ portBindings := nat.PortMap{}
202+ for _, portMapping := range cmdParams.PortMappings {
203+ key := fmt.Sprintf("%s/%s", portMapping.ExposedPort, portMapping.Protocol)
204+ portBindings[nat.Port(key)] = []nat.PortBinding{
205+ {
206+ HostIP: "0.0.0.0",
207+ HostPort: portMapping.HostPort,
208+ },
209+ }
210+
211+ }
156212
157213 // Prepare labels for Traefik
158214 labels := map[string]string{}
@@ -4,6 +4,7 @@ import (
4 "bytes"4 "bytes"
5 "context"5 "context"
6 "fmt"6 "fmt"
7+ "strconv"
7 "strings"8 "strings"
8 "time"9 "time"
9 10
@@ -22,7 +23,7 @@ type ContainerCommandParams struct {
22 EnvVar string23 EnvVar string
23 ImageName string24 ImageName string
24 ImageID string25 ImageID string
25- Ports []string26+ PortMappings []PortMapping
26 Volumes []string27 Volumes []string
27 Labels []string28 Labels []string
28 Network string29 Network string
@@ -30,6 +31,50 @@ type ContainerCommandParams struct {
30 Environment []string31 Environment []string
31 }32 }
32 33
34+type PortMapping struct {
35+ HostPort string
36+ ExposedPort string
37+ Protocol string
38+}
39+
40+// ParsePortsSpecs receives a slice of strings in the format "hostPort:containerPort/Protocol" and returns a slice of PortMapping structs
41+func ParsePortsSpecs(portsSpecs []string) ([]PortMapping, error) {
42+ portMappings := make([]PortMapping, 0, len(portsSpecs))
43+
44+ for _, portSpec := range portsSpecs {
45+ parts := strings.Split(portSpec, ":")
46+ if len(parts) != 2 {
47+ return nil, fmt.Errorf("invalid port specification: %s. Format should be hostPort:containerPort[/Protocol]", portSpec)
48+ }
49+
50+ // Validate hostPort and exposedPort are numbers
51+ hostPort := parts[0]
52+ if _, err := strconv.Atoi(hostPort); err != nil {
53+ return nil, fmt.Errorf("invalid host port: %s. Must be a number", hostPort)
54+ }
55+
56+ exposedParts := strings.Split(parts[1], "/")
57+ exposedPort := exposedParts[0]
58+ if _, err := strconv.Atoi(exposedPort); err != nil {
59+ return nil, fmt.Errorf("invalid exposed port: %s. Must be a number", exposedPort)
60+ }
61+
62+ // Default protocol is tcp
63+ protocol := "tcp"
64+ if len(exposedParts) > 1 {
65+ protocol = exposedParts[1]
66+ }
67+
68+ portMappings = append(portMappings, PortMapping{
69+ HostPort: hostPort,
70+ ExposedPort: exposedPort,
71+ Protocol: protocol,
72+ })
73+ }
74+
75+ return portMappings, nil
76+}
77+
33 func ContainerCommandParamsToConfig(cmdParams ContainerCommandParams) (*container.Config, error) {78 func ContainerCommandParamsToConfig(cmdParams ContainerCommandParams) (*container.Config, error) {
34 return &container.Config{79 return &container.Config{
35 Image: cmdParams.ImageName,80 Image: cmdParams.ImageName,
@@ -152,7 +197,18 @@ func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
152 // Check if the Docker client has been initialized197 // Check if the Docker client has been initialized
153 CheckIfInitialized()198 CheckIfInitialized()
154 199
155- portBindings := map[nat.Port][]nat.PortBinding{}200+ // Prepare port bindings
201+ portBindings := nat.PortMap{}
202+ for _, portMapping := range cmdParams.PortMappings {
203+ key := fmt.Sprintf("%s/%s", portMapping.ExposedPort, portMapping.Protocol)
204+ portBindings[nat.Port(key)] = []nat.PortBinding{
205+ {
206+ HostIP: "0.0.0.0",
207+ HostPort: portMapping.HostPort,
208+ },
209+ }
210+
211+ }
156 212
157 // Prepare labels for Traefik213 // Prepare labels for Traefik
158 labels := map[string]string{}214 labels := map[string]string{}

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