dockerode-compose
Version:
docker-compose in nodejs using dockerode
677 lines (654 loc) • 22.4 kB
JavaScript
const fs = require('fs');
const tar = require('tar-fs');
const path = require('path');
const stream = require('stream');
/**
* if host path in the volume string (e.g. `./mylocal/file:/container/file`) is not absolute path,
* this function will convert it to an absolute path using `cwd` (current working directory) parameter.
* Otherwise, it will return volume string as it is.
*
* @param {*} volumeStr
* @param {*} cwd
*/
function standardizeVolumeStr(volumeStr, cwd) {
if (typeof volumeStr !== 'string' || volumeStr.length < 1) {
return volumeStr;
}
volumeStr = volumeStr.trim();
if (
volumeStr.substring(0, 2) !== './' &&
volumeStr.substring(0, 3) !== '../'
) {
return volumeStr;
}
const parts = volumeStr.split(':');
if (parts.length !== 2) {
return volumeStr;
}
if (!cwd) {
throw new Error(
'Current working dir path not available when local path is a relative path: ' +
parts[0]
);
}
const localPath = parts[0];
return path.resolve(cwd, localPath) + ':' + parts[1];
}
module.exports = {
buildPorts: function (servicePorts, output) {
var ports = {};
if (typeof servicePorts[0] === 'object') {
// LONG SYNTAX
// !!! INCOMPLETE - NOT USING DIFFERENT MODES - `mode`: `host` for publishing a host port on each node, or `ingress` for a port to be load balanced.
for (let port of servicePorts) {
ports[port.target + '/' + port.protocol] = [
{ HostPort: port.published.toString() },
];
}
output['PortBindings'] = ports;
} else {
// SHORT SYNTAX
// TODO: SIMPLIFY THIS BLOCK OF CODE! MAYBE!
for (let port of servicePorts) {
var port_split = port.split(':');
if (port_split.length == 2) {
// "xxxx:xxxx"
if (port_split[1].includes('-')) {
// "9090-9091:8080-8081"
let split_port_split0 = port_split[0].split('-');
let split_port_split0_array = [];
split_port_split0_array = fillPortArray(
parseInt(split_port_split0[0]),
parseInt(split_port_split0[1])
);
let split_port_split1 = port_split[1].split('-');
let split_port_split1_array = [];
split_port_split1_array = fillPortArray(
parseInt(split_port_split1[0]),
parseInt(split_port_split1[1])
);
for (let index in split_port_split0_array) {
ports[split_port_split1_array[index] + '/tcp'] = [
{
HostPort: split_port_split0_array[index].toString(),
},
];
}
} else if (port_split[0].includes('-')) {
// "3000-3005"
let split_port_split = port_split[0].split('-');
ports[port_split[1] + '/tcp'] = [];
for (let i = split_port_split[0]; i <= split_port_split[1]; i++) {
ports[port_split[1] + '/tcp'].push({
HostPort: i.toString(),
});
}
} else if (port_split[1].includes('/')) {
// "6060:6060/udp"
ports[port_split[1]] = [{ HostPort: port_split[0] }];
} else {
// "8000:8000"
ports[port_split[1] + '/tcp'] = [{ HostPort: port_split[0] }];
}
} else if (port_split.length == 3) {
// "x.x.x.x:xxxx:xxxx"
if (port_split[2].includes('-')) {
// "127.0.0.1:5000-5010:5000-5010"
let split_port_split1 = port_split[1].split('-');
let split_port_split1_array = [];
split_port_split1_array = fillPortArray(
parseInt(split_port_split1[0]),
parseInt(split_port_split1[1])
);
let split_port_split2 = port_split[2].split('-');
let split_port_split2_array = [];
split_port_split2_array = fillPortArray(
parseInt(split_port_split2[0]),
parseInt(split_port_split2[1])
);
for (let index in split_port_split1_array) {
ports[split_port_split2_array[index] + '/tcp'] = [
{
HostPort: split_port_split1_array[index].toString(),
HostIp: port_split[0],
},
];
}
} else if (port_split[1] == '') {
// "127.0.0.1::5000
ports[port_split[2] + '/tcp'] = [
{ HostPort: port_split[2], HostIp: port_split[0] },
];
} else {
// "127.0.0.1:8001:8001"
ports[port_split[2] + '/tcp'] = [
{ HostPort: port_split[1], HostIp: port_split[0] },
];
}
} else {
// "xxxx"
if (port_split[0].includes('-')) {
// "3000-3005"
let split_port_split = port_split[0].split('-');
for (let i = split_port_split[0]; i <= split_port_split[1]; i++) {
ports[i + '/tcp'] = [{ HostPort: i.toString() }];
}
} else {
// "3000"
ports[port + '/tcp'] = [{ HostPort: port }];
}
}
}
output['PortBindings'] = ports;
}
},
//ToDo: complete the compose specification
buildHostConfig: function (projectName, service, recipe, cwd) {
var output = {
RestartPolicy: { Name: service.restart },
};
if (service.volumes_from !== undefined) {
for (var volume_from of service.volumes_from) {
var vf = volume_from.split(':');
var svf = recipe.services[vf[0]];
this.buildVolumesHostconfig(
projectName,
svf.volumes,
output,
vf[1],
cwd
);
}
}
if (service.volumes !== undefined) {
this.buildVolumesHostconfig(
projectName,
service.volumes,
output,
undefined,
cwd
);
}
if (service.ports !== undefined) {
this.buildPorts(service.ports, output);
}
if (service.devices !== undefined) {
output.Devices = service.devices.map((device) => ({
CgroupPermissions: "mrw",
PathInContainer: device,
PathOnHost: device,
}));
}
if (service.cpu_count !== undefined) {
output.CpuCount = service.cpu_count;
}
if (service.cpu_percent !== undefined) {
output.CpuPercent = service.cpu_percent;
}
if (service.cpu_shares !== undefined) {
output.CpuShares = service.cpu_shares;
}
if (service.cpu_period !== undefined) {
output.CpuPeriod = service.cpu_period;
}
if (service.cpu_quota !== undefined) {
output.CpuQuota = service.cpu_quota;
}
if (service.cpu_rt_runtime !== undefined) {
output.CpuRealtimeRuntime = service.cpu_rt_runtime;
}
if (service.cpu_rt_period !== undefined) {
output.CpuRealtimePeriod = service.cpu_rt_period;
}
if (service.cpuset !== undefined) {
output.CpusetCpus = service.cpuset;
}
if (service.cap_add !== undefined) {
output.CapAdd = service.cap_add;
}
if (service.cap_drop !== undefined) {
output.CapDrop = service.cap_drop;
}
if (service.cgroup_parent !== undefined) {
output.CgroupParent = service.cgroup_parent;
}
if (service.device_cgroup_rules !== undefined) {
output.DeviceCgroupRules = service.device_cgroup_rules;
}
if (service.dns !== undefined) {
output.Dns = service.dns;
}
if (service.dns_opt !== undefined) {
output.DnsOptions = service.dns_opt;
}
if (service.dns_search !== undefined) {
output.DnsSearch = service.dns_search;
}
if (service.extra_hosts !== undefined) {
output.ExtraHosts = service.extra_hosts;
}
if (service.group_add !== undefined) {
output.GroupAdd = service.group_add;
}
if (service.init !== undefined) {
output.Init = service.init;
}
if (service.ipc !== undefined) {
output.IpcMode = service.ipc;
}
if (service.isolation !== undefined) {
output.Isolation = service.isolation;
}
if (service.mem_swappiness !== undefined) {
output.MemorySwappiness = service.mem_swappiness;
}
if (service.oom_kill_disable !== undefined) {
output.OomKillDisable = service.oom_kill_disable;
}
if (service.oom_score_adj !== undefined) {
output.OomScoreAdj = service.oom_score_adj;
}
if (service.pid !== undefined) {
output.PidMode = service.pid;
}
if (service.pids_limit !== undefined) {
output.PidsLimit = service.pids_limit;
}
if (service.privileged !== undefined) {
output.Privileged = service.privileged;
}
if (service.read_only !== undefined) {
output.ReadonlyRootfs = service.read_only;
}
if (service.runtime !== undefined) {
output.Runtime = service.runtime;
}
if (service.security_opt !== undefined) {
output.SecurityOpt = service.security_opt;
}
if (service.shm_size !== undefined) {
output.ShmSize = service.shm_size;
}
if (service.storage_opt !== undefined) {
output.StorageOpt = service.storage_opt;
}
if (service.sysctls !== undefined) {
if (service.sysctls.length > 0) {
var sysctls = {};
for (var sysctlsb of service.sysctls) {
let p = sysctlsb.split('=');
sysctls[p[0]] = p[1];
}
output.Sysctls = sysctls;
} else {
let sysctlKeys = Object.keys(service.sysctls);
let newSysctls = {};
for (let key of sysctlKeys) {
newSysctls[key] = service.sysctls[key].toString();
}
output.Sysctls = newSysctls;
}
}
if (service.userns_mode !== undefined) {
output.UsernsMode = service.userns_mode;
}
if (service.tmpfs !== undefined) {
var tmpfs = {};
if (Array.isArray(service.tmpfs)) {
for (var tmpfsb of service.tmpfs) {
let p = tmpfsb.split(':');
if (p[1] === undefined) {
p[1] = '';
}
tmpfs[p[0]] = p[1];
}
output.Tmpfs = tmpfs;
} else {
let p = service.tmpfs.split(':');
if (p[1] === undefined) {
p[1] = '';
}
tmpfs[p[0]] = p[1];
output.Tmpfs = tmpfs;
}
}
if (service.ulimits !== undefined) {
let ulimitsKeys = Object.keys(service.ulimits);
let ulimitsArray = [];
for (let key of ulimitsKeys) {
let ulimitsObject = {};
if (typeof service.ulimits[key] === 'object') {
ulimitsObject.Name = key;
ulimitsObject.Soft = service.ulimits[key].soft;
ulimitsObject.Hard = service.ulimits[key].hard;
ulimitsArray.push(ulimitsObject);
} else {
ulimitsObject.Name = key;
ulimitsObject.Soft = service.ulimits[key];
ulimitsObject.Hard = service.ulimits[key];
ulimitsArray.push(ulimitsObject);
}
}
output.Ulimits = ulimitsArray;
}
if (service.blkio_config !== undefined) {
if (service.blkio_config.weight !== undefined) {
output.BlkioWeight = service.blkio_config.weight;
}
if (service.blkio_config.weight_device !== undefined) {
let weight_device = [{}];
weight_device[0]['Path'] = service.blkio_config.weight_device[0].path;
weight_device[0]['Weight'] =
service.blkio_config.weight_device[0].weight;
output.BlkioWeightDevice = weight_device;
}
if (service.blkio_config.device_read_bps !== undefined) {
output.BlkioDeviceReadBps = convertSizeStringToByteValue(
service.blkio_config.device_read_bps
);
}
if (service.blkio_config.device_read_iops !== undefined) {
let device_read_iops = [{}];
device_read_iops[0]['Path'] =
service.blkio_config.device_read_iops[0].path;
device_read_iops[0]['Rate'] =
service.blkio_config.device_read_iops[0].rate;
output.BlkioDeviceReadIOps = device_read_iops;
}
if (service.blkio_config.device_write_bps !== undefined) {
output.BlkioDeviceWriteBps = convertSizeStringToByteValue(
service.blkio_config.device_write_bps
);
}
if (service.blkio_config.device_write_iops !== undefined) {
let device_write_iops = [{}];
device_write_iops[0]['Path'] =
service.blkio_config.device_write_iops[0].path;
device_write_iops[0]['Rate'] =
service.blkio_config.device_write_iops[0].rate;
output.BlkioDeviceWriteIOps = device_write_iops;
}
}
if (service.logging !== undefined) {
let logging = {};
logging.Type = service.logging.driver;
logging.Config = service.logging.options;
output.LogConfig = logging;
}
return output;
},
buildVolumesHostconfig: function (projectName, volumes, output, type, cwd) {
if (output['Binds'] === undefined) {
output['Binds'] = [];
}
for (var volume of volumes) {
if (typeof volume === 'string' || volume instanceof String) {
if (
volume.substring(0, 2) === './' ||
volume.substring(0, 3) === '../' ||
volume[0] === '/'
) {
const stdVolume = standardizeVolumeStr(volume, cwd);
const aux = stdVolume;
if (type == 'ro') {
aux += ':ro';
}
output['Binds'].push(aux);
} else {
var aux = projectName + '_' + volume;
if (type == 'ro') {
aux += ':ro';
}
output['Binds'].push(aux);
}
} else {
var volumestr = '';
if (volume.source && volume.target) {
if (
volume.source.substring(0, 2) === './' ||
volume.source.substring(0, 3) === '../' ||
volume.source[0] === '/'
) {
volumestr += standardizeVolumeStr(
volume.source + ':' + volume.target,
cwd
);
} else {
volumestr +=
projectName + '_' + volume.source + ':' + volume.target + ':';
}
}
if (volume.read_only || type == 'ro') {
volumestr += 'ro,';
}
if (volume.volume && volume.volume.nocopy) {
volumestr += 'nocopy,';
}
if (volume.bind && volume.bind.propagation) {
volumestr += volume.bind.propagation + ',';
}
volumestr = volumestr.slice(0, -1);
output['Binds'].push(volumestr);
}
}
},
buildVolumes: function (volumes, opts) {
if (opts['Volumes'] === undefined) {
opts['Volumes'] = {};
}
for (var volume of volumes) {
if (volume.substring(0, 2) === './' || volume[0] === '/') {
continue;
}
if (typeof volume === 'string' || volume instanceof String) {
var v = volume.split(':');
opts['Volumes'][v[1]] = {};
} else {
if (volume.target) {
opts['Volumes'][volume.target] = {};
}
}
}
},
buildEnvVars: function (service) {
var output = [];
if (service.env_file !== undefined) {
if (Array.isArray(service.env_file)) {
for (let env_file_path of service.env_file) {
this.buildEnvVarsFromFile(env_file_path, output);
}
} else {
this.buildEnvVarsFromFile(service.env_file, output);
}
}
if (service.environment !== undefined) {
if (Array.isArray(service.environment)) {
for (let environment_line of service.environment) {
output.push(environment_line);
}
} else {
var envsNames = Object.keys(service.environment);
for (var envName of envsNames) {
output.push(envName + '=' + service.environment[envName]);
}
}
}
return output;
},
buildNetworks: function (projectName, serviceName, serviceNetworks, networksToAttach, opts, toplevelNetworks) {
if (Array.isArray(serviceNetworks)) {
for (let index = 0; index < serviceNetworks.length; index++) {
let networkName = projectName + '_' + serviceNetworks[index];
// check if network is external
if (toplevelNetworks !== undefined) {
if (Object.keys(toplevelNetworks).includes(serviceNetworks[index])) {
if (toplevelNetworks[serviceNetworks[index]].external === true) {
// it is external network
networkName = toplevelNetworks[serviceNetworks[index]].name
}
}
}
let networkTemplate = {
NetworkingConfig: {
EndpointsConfig: {},
},
};
networkTemplate.NetworkingConfig.EndpointsConfig[networkName] = {};
networkTemplate.NetworkingConfig.EndpointsConfig[networkName][
'Aliases'
] = [serviceName];
if (index === 0)
opts.NetworkingConfig.EndpointsConfig =
networkTemplate.NetworkingConfig.EndpointsConfig;
networksToAttach.push(networkTemplate.NetworkingConfig.EndpointsConfig);
}
} else {
let networkNames = Object.keys(serviceNetworks);
for (let index = 0; index < networkNames.length; index++) {
let network = serviceNetworks[networkNames[index]] || {};
let networkName = projectName + '_' + serviceNetworks[index];
// check if network is external
if (toplevelNetworks !== undefined) {
if (Object.keys(toplevelNetworks).includes(networkNames[index])) {
if (toplevelNetworks[networkNames[index]].external === true) {
// it is external network
networkName = toplevelNetworks[networkNames[index]].name
}
}
}
let networkTemplate = {
NetworkingConfig: {
EndpointsConfig: {},
},
};
networkTemplate.NetworkingConfig.EndpointsConfig[networkName] = {};
networkTemplate.NetworkingConfig.EndpointsConfig[networkName][
'IPAMConfig'
] = {};
if (network.aliases !== undefined) {
networkTemplate.NetworkingConfig.EndpointsConfig[networkName][
'Aliases'
] = network.aliases;
}
if (network.ipv4_address !== undefined) {
networkTemplate.NetworkingConfig.EndpointsConfig[
networkName
].IPAMConfig['IPv4Address'] = network.ipv4_address;
}
if (network.ipv6_address !== undefined) {
networkTemplate.NetworkingConfig.EndpointsConfig[
networkName
].IPAMConfig['IPv6Address'] = network.ipv6_address;
}
if (network.link_local_ips !== undefined) {
networkTemplate.NetworkingConfig.EndpointsConfig[
networkName
].IPAMConfig['LinkLocalIPs'] = network.link_local_ips;
}
if (network.priority !== undefined) {
networkTemplate.NetworkingConfig.EndpointsConfig[
networkName
].priority = network.priority;
} else {
networkTemplate.NetworkingConfig.EndpointsConfig[
networkName
].priority = 0;
}
if (index === 0) {
opts.NetworkingConfig.EndpointsConfig =
networkTemplate.NetworkingConfig.EndpointsConfig;
}
networksToAttach.push(networkTemplate.NetworkingConfig.EndpointsConfig);
}
}
},
// TODO: OPTIMIZE!
convertSizeStringToByteValue: function (obj) {
let rate = obj[0].rate.toLowerCase();
let new_obj = [{}];
if (rate.includes('k')) {
if (rate.indexOf('k') == rate.length - 1) {
rate = rate.replace('k', '');
} else if (rate.indexOf('k') == rate.length - 2) {
rate = rate.replace('kb', '');
}
new_obj[0]['Path'] = obj[0].path;
new_obj[0]['Rate'] = rate * 1024;
return new_obj;
} else if (rate.includes('m')) {
if (rate.indexOf('m') == rate.length - 1) {
rate = rate.replace('m', '');
} else if (rate.indexOf('m') == rate.length - 2) {
rate = rate.replace('mb', '');
}
new_obj[0]['Path'] = obj[0].path;
new_obj[0]['Rate'] = rate * 1024 * 1024;
return new_obj;
} else if (rate.includes('g')) {
if (rate.indexOf('g') == rate.length - 1) {
rate = rate.replace('g', '');
} else if (rate.indexOf('g') == rate.length - 2) {
rate = rate.replace('gb', '');
}
new_obj[0]['Path'] = obj[0].path;
new_obj[0]['Rate'] = rate * 1024 * 1024 * 1024;
return new_obj;
}
},
buildEnvVarsFromFile: function (env_file_path, output) {
// Each line in an env file MUST be in `VAR=VAL` format.
let env_file = fs
.readFileSync(env_file_path, 'utf8')
.toString()
.split('\n');
for (let env_line of env_file) {
// Lines beginning with `#` MUST be ignored. Blank lines MUST also be ignored.
if (env_line != '' && env_line.indexOf('#') != 0) {
let env_line_split = env_line.split('=');
// `VAL` MAY be omitted, sin such cases the variable value is empty string. `=VAL` MAY be omitted, in such cases the variable is **unset**.
if (env_line_split[0] != '' && env_line_split[1] != '') {
output.push(env_line);
}
}
}
},
fillPortArray: function (start, end) {
return Array(end - start + 1)
.fill()
.map((_, idx) => start + idx);
},
buildDockerImage: async function (
docker,
buildPath,
obj,
dockerfile,
options
) {
options = options || {};
if (dockerfile !== null) {
obj['dockerfile'] = path.basename(dockerfile);
let streami = await docker.buildImage(
{
context: buildPath,
src: [dockerfile],
},
obj
);
if (options.verbose === true) {
streami.pipe(process.stdout);
} else {
streami.pipe(stream.PassThrough());
}
await new Promise((fulfill) => streami.once('end', fulfill));
} else {
var tarStream = tar.pack(buildPath);
let streami = await docker.buildImage(tarStream, obj);
if (options.verbose === true) {
streami.pipe(process.stdout);
} else {
streami.pipe(stream.PassThrough());
}
await new Promise((fulfill) => streami.once('end', fulfill));
}
},
};