import { PortMap } from 'docker-types/generated/1.41'; import _ from 'lodash'; import { PortMapping, Protocol, Values } from './PortsMappingField'; import { Range } from './PortsMappingField.viewModel'; type PortKey = `${string}/${Protocol}`; export function parsePortBindingRequest(portBindings: Values): PortMap { const bindings: Record< PortKey, Array<{ HostIp: string; HostPort: string }> > = {}; _.forEach(portBindings, (portBinding) => { if (!portBinding.containerPort) { return; } let { hostPort } = portBinding; const containerPortRange = parsePortRange(portBinding.containerPort); if (!isValidPortRange(containerPortRange)) { throw new Error( `Invalid port specification: ${portBinding.containerPort}` ); } const portInfo = extractPortInfo(portBinding); if (!portInfo) { return; } const { endHostPort, endPort, hostIp, startHostPort, startPort } = portInfo; _.range(startPort, endPort + 1).forEach((containerPort) => { const bindKey: PortKey = `${containerPort}/${portBinding.protocol}`; if (!bindings[bindKey]) { bindings[bindKey] = []; } if (startHostPort > 0) { hostPort = (startHostPort + containerPort - startPort).toString(); } if (startPort === endPort && startHostPort !== endHostPort) { hostPort += `-${endHostPort.toString()}`; } bindings[bindKey].push({ HostIp: hostIp, HostPort: hostPort }); }); }); return bindings; } function isValidPortRange(portRange: Range) { return portRange.start > 0 && portRange.end >= portRange.start; } function parsePortRange(portRange: string | number): Range { // Make sure we have a string const portRangeString = portRange.toString(); // Split the range and convert to integers const stringPorts = _.split(portRangeString, '-', 2); const intPorts = _.map(stringPorts, parsePort); return { start: intPorts[0], end: intPorts[1] || intPorts[0], }; } const portPattern = /^([1-9]|[1-5]?[0-9]{2,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/m; function parsePort(port: string) { if (portPattern.test(port)) { return parseInt(port, 10); } return 0; } function extractPortInfo(portBinding: PortMapping) { const containerPortRange = parsePortRange(portBinding.containerPort); if (!isValidPortRange(containerPortRange)) { throw new Error(`Invalid port specification: ${portBinding.containerPort}`); } const startPort = containerPortRange.start; const endPort = containerPortRange.end; let hostIp = ''; let startHostPort = 0; let endHostPort = 0; let { hostPort } = portBinding; if (!hostPort) { return null; } if (hostPort.includes('[')) { const hostAndPort = _.split(hostPort, ']:'); if (hostAndPort.length < 2) { throw new Error( `Invalid port specification: ${portBinding.containerPort}` ); } hostIp = hostAndPort[0].replace('[', ''); [, hostPort] = hostAndPort; } else if (hostPort.includes(':')) { [hostIp, hostPort] = _.split(hostPort, ':'); } const hostPortRange = parsePortRange(hostPort); if (!isValidPortRange(hostPortRange)) { throw new Error(`Invalid port specification: ${hostPort}`); } startHostPort = hostPortRange.start; endHostPort = hostPortRange.end; if ( endPort !== startPort && endPort - startPort !== endHostPort - startHostPort ) { throw new Error(`Invalid port specification: ${hostPort}`); } return { startPort, endPort, hostIp, startHostPort, endHostPort }; }