You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
portainer/app/docker/helpers/containerHelper.js

269 lines
8.7 KiB

import _ from 'lodash-es';
import splitargs from 'splitargs/src/splitargs';
refactor(app): introduce webpack and babel (#2407) * feat(agent): add new host page * feat(agent): convert volume-browser to files-datatable * fix(agent): browse folders in file-datatable * feat(engine-details): replace engine view with host view * feat(engine-details): remove old panels * feat(engine-details): add basic engine-details-panel component * feat(engine-details): pass details to the different components * feat(engine-details): replace host-view with host-overview * feat(engine-details): add commaseperated filter * feat(engine-details): add host-view container component * feat(engine-details): add host-details component * feat(engine-details): build host details object * feat(engine-details): format engine version * feat(engine-details): get details for one node * feat(engine-details): pass is-agent from view * feat(engine-details): replace old node view with a new component * feat(engine-details): add swarm-node-details component * feat(engine-details): remove isSwarm binding * feat(engine-details): remove node-details and include in parent * feat(engine-details): add labels-table component * feat(engine-details): add update node service * feat(engine-details): add update label functionality * style(engine-details): remove whitespaces * feat(engine-details): remove old node page * feat(engine-details): pass is agent to host details * feat(host-details): hide missing info * feat(host-details): update node availability * style(host-details): remove obsolete event object * feat(host-details): fix labels not sending * feat(host-details): remove flags for hiding data * feat(host-details): create mock call to server for agent host info * style(host-details): fix spelling mistake in filter's name * feat(host-details): get info from agent * feat(host-details): hide engine labels when empty * feat(node-details): move labels table and save button * feat(host-info): add different urls for refresh * feat(host-details): show disk/devices info for agent * feat(host-view): add loading indicator to devices-panel * feat(host-details): add loading indicator to disks panel * feat(agent): fix browse volume * feat(agent): browse files * feat(agent): enable rename * feat(agent): download file * fix(agent): download file from root * feat(agent): delete file * style(agent): remove whitespaces * fix(agent): fix link on node browser * feat(agent): basic file uploader * feat(agent): add basic file upload * fix(volume-browser): move volume id to query params * feat(node-browser): moved uploader into browser * feat(node-browser): add upload spinner * feat(agent): browse files relative to root * feat(build): add webpack build config * feat(build): add missing imports * feat(webpack): add missing imports * feat(build): enable eslint on build * feat(build): add webpack notifier * feat(build): clean terminal on build * feat(build): import all globals * feat(build): add angular import * feat(build): fix styles * feat(build): load favicons * feat(build): load css before script * feat(webpack): split vendors css and js to a different bundle * feat(webpack): import angular in all files * feat(webpack): remove eslint global config * feat(webpack): add webpack clean dist * feat(webpack): fix styling issues * refactor(webpack): remove empty controllers * refactor(webpack): optimize moment * refactor(webpack): add bundle analyzer * feat(webpack): add babel * refactor(webpack): optimize lodash * refactor(toastr): update toastr * feat(webpack): create basic production and dev config * fix(webpack): fix production config * fix(webpack): fix html templates url * refactor(webpack): remove angular imports * refactor(webpack): remove more angular imports * refactor(webpack): return angular to entry file * style(webpack): remove comments from config * fix(hosts): remove browse button * fix(webpack): import lodash * fix(webpack): import missing htmls * feat(webpack): reduce lodash size * feat(webpack): config grunt to use webpack * feat(webpack): add postcss * chore(codeclimate): use eslint-5 channel * feat(deps): upgrade from lodash to lodash-es * fix(webpack): fix bug with lodash * chore(build): add build client script * fix(webpack): fix missing jsyaml reference * refactor(webpack): seperate builds of img files * chore(build): add a way to check times of webpack build * feat(webpack): add dev server * fix(webpack): fix css output name * chore(webpack): optimize images * chore(webpack): add node env * fix(build): copy templates on release * chore(webpack): set env NODE_ENV * feat(webpack): set NODE_ENV on production builds * fix(extensions): set image path * refactor(css): move vendor css to js import * style(app): remove whitespaces * fix(build-system): allow DevOps pipeline to leverage webpack (#2670) * Update devopsbuild task to use webpack & remove AppVeyor environment var * Added -Force to replace the existing dist folder * Removed Test-Path * dep(build-system): add angularjs-annotate to webpack + fix on imports * Merge branch 'develop' into webpack * refactor(app): webpack aliases for imports + async / await dep + start refactor * style(extensions): use develop version of the view * fix(app): fix several issues introduced by webpack migration * fix(webpack): fix ng-include not loading templates with webpack * Fix Windows CI with Webpack (#2782) * fix(configs): refactor broke configs creation and list views * fix(build-system): update build_binary_devops for Windows
6 years ago
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) {
if (portPattern.test(port)) {
return parseInt(port, 10);
} else {
return 0;
}
}
function parsePortRange(portRange) {
if (typeof portRange !== 'string') {
portRange = portRange.toString();
}
// Split the range and convert to integers
const stringPorts = _.split(portRange, '-', 2);
const intPorts = _.map(stringPorts, parsePort);
// If it's not a range, we still make sure that we return two ports (start & end)
if (intPorts.length == 1) {
intPorts.push(intPorts[0]);
}
return intPorts;
}
function isValidPortRange(portRange) {
if (typeof portRange === 'string') {
portRange = parsePortRange();
}
return Array.isArray(portRange) && portRange.length === 2 && portRange[0] > 0 && portRange[1] >= portRange[0];
}
function createPortRange(portRangeText, port) {
if (typeof portRangeText !== 'string') {
portRangeText = portRangeText.toString();
}
let hostIp = null;
const colonIndex = portRangeText.indexOf(':');
if (colonIndex >= 0) {
hostIp = portRangeText.substr(0, colonIndex);
portRangeText = portRangeText.substr(colonIndex + 1);
}
port = typeof port === 'number' ? port : parsePort(port);
const portRange = parsePortRange(portRangeText);
const startPort = Math.min(portRange[0], port);
const endPort = Math.max(portRange[1], port);
if (hostIp) {
return hostIp + ':' + startPort + '-' + endPort;
} else {
return startPort + '-' + endPort;
}
}
angular.module('portainer.docker').factory('ContainerHelper', [
function ContainerHelperFactory() {
'use strict';
var helper = {};
helper.commandStringToArray = function (command) {
return splitargs(command);
};
helper.commandArrayToString = function (array) {
return array
.map(function (elem) {
return "'" + elem + "'";
})
.join(' ');
};
helper.configFromContainer = function (container) {
var config = container.Config;
// HostConfig
config.HostConfig = container.HostConfig;
// Name
config.name = container.Name.replace(/^\//g, '');
// Network
var mode = config.HostConfig.NetworkMode;
config.NetworkingConfig = {
EndpointsConfig: {},
};
config.NetworkingConfig.EndpointsConfig = container.NetworkSettings.Networks;
if (config.ExposedPorts === undefined) {
config.ExposedPorts = {};
}
if (mode.indexOf('container:') !== -1) {
delete config.Hostname;
delete config.ExposedPorts;
}
// Set volumes
var binds = [];
var volumes = {};
for (var v in container.Mounts) {
if ({}.hasOwnProperty.call(container.Mounts, v)) {
var mount = container.Mounts[v];
var name = mount.Name || mount.Source;
var containerPath = mount.Destination;
if (name && containerPath) {
var bind = name + ':' + containerPath;
volumes[containerPath] = {};
if (mount.RW === false) {
bind += ':ro';
}
binds.push(bind);
}
}
}
config.HostConfig.Binds = binds;
config.Volumes = volumes;
return config;
};
helper.preparePortBindings = function (portBindings) {
const bindings = {};
_.forEach(portBindings, (portBinding) => {
if (!portBinding.containerPort) {
return;
}
let hostPort = portBinding.hostPort;
const containerPortRange = parsePortRange(portBinding.containerPort);
if (!isValidPortRange(containerPortRange)) {
throw new Error('Invalid port specification: ' + portBinding.containerPort);
}
const startPort = containerPortRange[0];
const endPort = containerPortRange[1];
let hostIp = undefined;
let startHostPort = 0;
let endHostPort = 0;
if (hostPort) {
if (hostPort.indexOf('[') > -1) {
const hostAndPort = _.split(hostPort, ']:');
if (hostAndPort.length < 2) {
throw new Error('Invalid port specification: ' + portBinding.containerPort);
}
hostIp = hostAndPort[0].replace('[', '');
hostPort = hostAndPort[1];
} else {
if (hostPort.indexOf(':') > -1) {
const hostAndPort = _.split(hostPort, ':');
hostIp = hostAndPort[0];
hostPort = hostAndPort[1];
}
}
const hostPortRange = parsePortRange(hostPort);
if (!isValidPortRange(hostPortRange)) {
throw new Error('Invalid port specification: ' + hostPort);
}
startHostPort = hostPortRange[0];
endHostPort = hostPortRange[1];
if (endPort !== startPort && endPort - startPort !== endHostPort - startHostPort) {
throw new Error('Invalid port specification: ' + hostPort);
}
}
for (let i = 0; i <= endPort - startPort; i++) {
const containerPort = (startPort + i).toString();
if (startHostPort > 0) {
hostPort = (startHostPort + i).toString();
}
if (startPort === endPort && startHostPort !== endHostPort) {
hostPort += '-' + endHostPort.toString();
}
const bindKey = containerPort + '/' + portBinding.protocol;
if (bindings[bindKey]) {
bindings[bindKey].push({ HostIp: hostIp, HostPort: hostPort });
} else {
bindings[bindKey] = [{ HostIp: hostIp, HostPort: hostPort }];
}
}
});
return bindings;
};
helper.sortAndCombinePorts = function (portBindings) {
const bindings = [];
const portBindingKeys = _.keys(portBindings);
// Group the port bindings by protocol
const portBindingKeysByProtocol = _.groupBy(portBindingKeys, (portKey) => {
return _.split(portKey, '/')[1];
});
_.forEach(portBindingKeysByProtocol, (portBindingKeys, protocol) => {
// Group the port bindings by host IP
const portBindingKeysByHostIp = {};
for (const portKey of portBindingKeys) {
for (const portBinding of portBindings[portKey]) {
portBindingKeysByHostIp[portBinding.HostIp] = portBindingKeysByHostIp[portBinding.HostIp] || [];
portBindingKeysByHostIp[portBinding.HostIp].push(portKey);
}
}
_.forEach(portBindingKeysByHostIp, (portBindingKeys, ip) => {
// Sort by host port
const sortedPortBindingKeys = _.orderBy(portBindingKeys, (portKey) => {
return parseInt(_.split(portKey, '/')[0], 10);
});
let previousHostPort = -1;
let previousContainerPort = -1;
_.forEach(sortedPortBindingKeys, (portKey) => {
const portKeySplit = _.split(portKey, '/');
const containerPort = parseInt(portKeySplit[0], 10);
const portBinding = portBindings[portKey][0];
portBindings[portKey].shift();
const hostPort = parsePort(portBinding.HostPort);
// We only combine single ports, and skip the host port ranges on one container port
if (hostPort > 0) {
// If we detect consecutive ports, we create a range of them
if (bindings.length > 0 && previousHostPort === hostPort - 1 && previousContainerPort === containerPort - 1) {
bindings[bindings.length - 1].hostPort = createPortRange(bindings[bindings.length - 1].hostPort, hostPort);
bindings[bindings.length - 1].containerPort = createPortRange(bindings[bindings.length - 1].containerPort, containerPort);
previousHostPort = hostPort;
previousContainerPort = containerPort;
return;
}
previousHostPort = hostPort;
previousContainerPort = containerPort;
} else {
previousHostPort = -1;
previousContainerPort = -1;
}
let bindingHostPort = portBinding.HostPort.toString();
if (ip) {
bindingHostPort = `${ip}:${bindingHostPort}`;
}
const binding = {
hostPort: bindingHostPort,
containerPort: containerPort,
protocol: protocol,
};
bindings.push(binding);
});
});
});
return bindings;
};
helper.getContainerNames = function (containers) {
return _.map(_.flatten(_.map(containers, 'Names')), (name) => _.trimStart(name, '/'));
};
return helper;
},
]);