diff --git a/changelog.md b/changelog.md index 1c7c81c..f9eefe4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +## Version 0.9.11 + - Add support for port ranges in cosmos-compose + - Fix bug where multiple host port to the same container would override each other + - Port display on Servapp tab was inverted + - Fixed Network screen to support complex port mappings + - Add support for protocol in cosmos-compose port exposing logic + - Add support for relative bind path in docker-compose import + - Fix environment vars and labels containing multiple equals (@jwr1) + - Fix link to Other Setups page (@jwr1) + ## Version 0.9.10 - Never ban gateway ips - Prevent deleting networks if there's an error on disconnect diff --git a/client/src/pages/servapps/containers/docker-compose.jsx b/client/src/pages/servapps/containers/docker-compose.jsx index d79efb3..567588c 100644 --- a/client/src/pages/servapps/containers/docker-compose.jsx +++ b/client/src/pages/servapps/containers/docker-compose.jsx @@ -199,7 +199,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul let volumeObj = { source: volumeSplit[0], target: volumeSplit[1], - type: volume[0] === '/' ? 'bind' : 'volume', + type: (volume[0] === '/' || volume[0] === '.') ? 'bind' : 'volume', }; volumes.push(volumeObj); } @@ -208,6 +208,14 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul } } + if(doc.services[key].volumes) + Object.values(doc.services[key].volumes).forEach((volume) => { + if (volume.source && volume.source[0] === '.') { + let defaultPath = (config && config.DockerConfig && config.DockerConfig.DefaultDataPath) || "/usr" + volume.source = defaultPath + volume.source.replace('.', ''); + } + }); + // convert expose if (doc.services[key].expose) { doc.services[key].expose = doc.services[key].expose.map((port) => { diff --git a/client/src/pages/servapps/containers/network.jsx b/client/src/pages/servapps/containers/network.jsx index 16af040..43ffde9 100644 --- a/client/src/pages/servapps/containers/network.jsx +++ b/client/src/pages/servapps/containers/network.jsx @@ -68,25 +68,22 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O { - return { - port: port.split('/')[0], - protocol: port.split('/')[1], - hostPort: containerInfo.NetworkSettings.Ports[port] && containerInfo.NetworkSettings.Ports[port][0] ? - containerInfo.NetworkSettings.Ports[port][0].HostPort : '', - }; - }) + ports: Object.keys(containerInfo.NetworkSettings.Ports).map((contPort) => { + return containerInfo.NetworkSettings.Ports[contPort] ? containerInfo.NetworkSettings.Ports[contPort].map((hostPort) => { + return { + port: contPort.split('/')[0], + protocol: contPort.split('/')[1], + hostPort: hostPort.HostPort, + }; + }) : { + port: contPort.split('/')[0], + protocol: contPort.split('/')[1], + hostPort: '', + } + }).flat(), }} validate={(values) => { const errors = {}; - // check unique - const ports = values.ports.map((port) => { - return `${port.port}/${port.protocol}`; - }); - const unique = [...new Set(ports)]; - if (unique.length !== ports.length) { - errors.submit = 'Ports must be unique'; - } OnChange && OnChange(values); return errors; }} @@ -98,12 +95,14 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O networkMode: values.networkMode, }; values.ports.forEach((port) => { + let key = `${port.port}/${port.protocol}`; + if (port.hostPort) { - realvalues.portBindings[`${port.port}/${port.protocol}`] = [ - { - HostPort: port.hostPort, - } - ]; + if(!realvalues.portBindings[key]) realvalues.portBindings[key] = []; + + realvalues.portBindings[`${port.port}/${port.protocol}`].push({ + HostPort: port.hostPort, + }) } }); return API.docker.updateContainer(containerInfo.Name.replace('/', ''), realvalues) diff --git a/client/src/pages/servapps/servapps.jsx b/client/src/pages/servapps/servapps.jsx index ed82a0b..fc8abea 100644 --- a/client/src/pages/servapps/servapps.jsx +++ b/client/src/pages/servapps/servapps.jsx @@ -208,7 +208,7 @@ const ServApps = () => { {app.Ports.filter(p => p.IP != '::').map((port) => { return - + })} diff --git a/src/docker/api_blueprint.go b/src/docker/api_blueprint.go index f319938..dc1169f 100644 --- a/src/docker/api_blueprint.go +++ b/src/docker/api_blueprint.go @@ -206,6 +206,24 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) { } } +// generatePorts is a helper function to generate a slice of ports from a string range. +func generatePorts(portRangeStr string) []string { + portsStr := strings.Split(portRangeStr, "-") + if len(portsStr) != 2 { + return []string{ + portsStr[0], + } + } + start, _ := strconv.Atoi(portsStr[0]) + end, _ := strconv.Atoi(portsStr[1]) + + ports := make([]string, end-start+1) + for i := range ports { + ports[i] = strconv.Itoa(start + i) + } + + return ports +} func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)) error { utils.ConfigLock.Lock() @@ -408,24 +426,69 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string) } // For Expose / Ports + for _, expose := range container.Expose { exposePort := nat.Port(expose) containerConfig.ExposedPorts[exposePort] = struct{}{} } PortBindings := nat.PortMap{} + finalPorts := []string{} - for _, port := range container.Ports { - portHost := strings.Split(port, ":")[0] - portContainer := strings.Split(port, ":")[1] + for _, portRaw := range container.Ports { + portStuff := strings.Split(portRaw, "/") - containerConfig.ExposedPorts[nat.Port(portContainer)] = struct{}{} - PortBindings[nat.Port(portContainer)] = []nat.PortBinding{ - { - HostIP: "", - HostPort: portHost, - }, + if len(portStuff) == 1 { + portStuff = append(portStuff, "tcp") } + + port, protocol := portStuff[0], portStuff[1] + + ports := strings.Split(port, ":") + + hostPorts := generatePorts(ports[0]) + containerPorts := generatePorts(ports[1]) + + for i := 0; i < utils.Max(len(hostPorts), len(containerPorts)); i++ { + hostPort := hostPorts[i%len(hostPorts)] + containerPort := containerPorts[i%len(containerPorts)] + + finalPorts = append(finalPorts, fmt.Sprintf("%s:%s/%s", hostPort, containerPort, protocol)) + } + } + + utils.Debug(fmt.Sprintf("Final ports: %s", finalPorts)) + + hostPortsBound := make(map[string]bool) + + for _, portRaw := range finalPorts { + portStuff := strings.Split(portRaw, "/") + port, protocol := portStuff[0], portStuff[1] + nextPort := strings.Split(port, ":") + hostPort, contPort := nextPort[0], nextPort[1] + + contPort = contPort + "/" + protocol + + if hostPortsBound[hostPort] { + utils.Warn("Port " + hostPort + " already bound, skipping") + continue + } + + // Get the existing bindings for this container port, if any + bindings := PortBindings[nat.Port(contPort)] + + // Append a new PortBinding to the slice of bindings + bindings = append(bindings, nat.PortBinding{ + HostPort: hostPort, + }) + + // Update the port bindings for this container port + PortBindings[nat.Port(contPort)] = bindings + + // Mark the container port as exposed + containerConfig.ExposedPorts[nat.Port(contPort)] = struct{}{} + + hostPortsBound[hostPort] = true } // Create missing folders for bind mounts diff --git a/src/docker/api_updateContainer.go b/src/docker/api_updateContainer.go index 5f203a6..aa528de 100644 --- a/src/docker/api_updateContainer.go +++ b/src/docker/api_updateContainer.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/http" "os" + "fmt" "github.com/azukaar/cosmos-server/src/utils" containerType "github.com/docker/docker/api/types/container" @@ -77,6 +78,7 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) { container.Config.Labels = form.Labels } if(form.PortBindings != nil) { + utils.Debug(fmt.Sprintf("UpdateContainer: PortBindings: %v", form.PortBindings)) container.HostConfig.PortBindings = form.PortBindings container.Config.ExposedPorts = make(map[nat.Port]struct{}) for port := range form.PortBindings {