128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
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 };
|
|
}
|