reorder functions for no-use-before-define
This commit is contained in:
540
src/docker.js
540
src/docker.js
@@ -173,41 +173,6 @@ async function buildImage(dockerImage, sourceArchiveFilePath) {
|
||||
});
|
||||
}
|
||||
|
||||
async function downloadImage(manifest) {
|
||||
assert.strictEqual(typeof manifest, 'object');
|
||||
|
||||
debug(`downloadImage: ${manifest.dockerImage}`);
|
||||
|
||||
const image = gConnection.getImage(manifest.dockerImage);
|
||||
|
||||
const [error, result] = await safe(image.inspect());
|
||||
if (!error && result) return; // image is already present locally
|
||||
|
||||
const parsedManifestRef = parseImageRef(manifest.dockerImage);
|
||||
|
||||
await promiseRetry({ times: 10, interval: 5000, debug, retry: (pullError) => pullError.reason !== BoxError.FS_ERROR }, async () => {
|
||||
// custom (non appstore) image
|
||||
if (parsedManifestRef.registry !== null || !parsedManifestRef.fullRepositoryName.startsWith('cloudron/')) return await pullImage(manifest.dockerImage);
|
||||
|
||||
// docker hub only uses first 64 bits for ipv6 addressing. this causes many ipv6 rate limit errors
|
||||
// https://www.docker.com/blog/beta-ipv6-support-on-docker-hub-registry/ . as a hack, we try ipv4 explicity
|
||||
let upstreamRef = null, pullError = null;
|
||||
for (const registry of CLOUDRON_REGISTRIES) {
|
||||
upstreamRef = `${registry}/${manifest.dockerImage}`;
|
||||
[pullError] = await safe(pullImage(upstreamRef));
|
||||
if (!pullError) break;
|
||||
}
|
||||
|
||||
if (pullError || !upstreamRef) throw new BoxError(BoxError.DOCKER_ERROR, `Unable to pull ${manifest.dockerImage} from dockerhub or quay: ${pullError?.message}`);
|
||||
|
||||
// retag the downloaded image to not have the registry name. this prevents 'docker run' from redownloading it
|
||||
debug(`downloadImage: tagging ${upstreamRef} as ${parsedManifestRef.fullRepositoryName}:${parsedManifestRef.tag}`);
|
||||
await gConnection.getImage(upstreamRef).tag({ repo: parsedManifestRef.fullRepositoryName, tag: parsedManifestRef.tag });
|
||||
debug(`downloadImage: untagging ${upstreamRef}`);
|
||||
await deleteImage(upstreamRef);
|
||||
});
|
||||
}
|
||||
|
||||
async function getVolumeMounts(app) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
|
||||
@@ -281,8 +246,6 @@ async function getMounts(app) {
|
||||
return volumeMounts.concat(addonMounts);
|
||||
}
|
||||
|
||||
// This only returns ipv4 addresses
|
||||
// We dont bind to ipv6 interfaces, public prefix changes and container restarts wont work
|
||||
async function getAddressesForPort53() {
|
||||
const [error, deviceLinks] = await safe(fs.promises.readdir('/sys/class/net')); // https://man7.org/linux/man-pages/man5/sysfs.5.html
|
||||
if (error) return [];
|
||||
@@ -304,6 +267,276 @@ async function getAddressesForPort53() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
// This only returns ipv4 addresses
|
||||
// We dont bind to ipv6 interfaces, public prefix changes and container restarts wont work
|
||||
async function startContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error] = await safe(container.start());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable
|
||||
if (error && error.statusCode !== 304) throw new BoxError(BoxError.DOCKER_ERROR, error); // 304 means already started
|
||||
}
|
||||
|
||||
async function restartContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error] = await safe(container.restart());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Contanier ${containerId} not found`);
|
||||
if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable
|
||||
if (error && error.statusCode !== 204) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
|
||||
async function stopContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
debug(`stopContainer: stopping container ${containerId}`);
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const options = {
|
||||
t: 10 // wait for 10 seconds before killing it
|
||||
};
|
||||
|
||||
let [error] = await safe(container.stop(options));
|
||||
if (error && (error.statusCode !== 304 && error.statusCode !== 404)) throw new BoxError(BoxError.DOCKER_ERROR, 'Error stopping container:' + error.message);
|
||||
|
||||
[error] = await safe(container.wait());
|
||||
if (error && (error.statusCode !== 304 && error.statusCode !== 404)) throw new BoxError(BoxError.DOCKER_ERROR, 'Error waiting on container:' + error.message);
|
||||
}
|
||||
|
||||
async function deleteContainer(containerId) { // id can also be name
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
debug(`deleteContainer: deleting ${containerId}`);
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const removeOptions = {
|
||||
force: true, // kill container if it's running
|
||||
v: true // removes volumes associated with the container (but not host mounts)
|
||||
};
|
||||
|
||||
const [error] = await safe(container.remove(removeOptions));
|
||||
if (error && error.statusCode === 404) return;
|
||||
|
||||
if (error) {
|
||||
debug('Error removing container %s : %o', containerId, error);
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteContainers(appId, options) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const labels = [ 'appId=' + appId ];
|
||||
if (options.managedOnly) labels.push('isCloudronManaged=true');
|
||||
|
||||
const [error, containers] = await safe(gConnection.listContainers({ all: 1, filters: JSON.stringify({ label: labels }) }));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
for (const container of containers) {
|
||||
await deleteContainer(container.Id);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopContainers(appId) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
|
||||
const [error, containers] = await safe(gConnection.listContainers({ all: 1, filters: JSON.stringify({ label: [ 'appId=' + appId ] }) }));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
for (const container of containers) {
|
||||
await stopContainer(container.Id);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteImage(imageRef) {
|
||||
assert.strictEqual(typeof imageRef, 'string');
|
||||
|
||||
if (!imageRef) return;
|
||||
if (imageRef.includes('//') || imageRef.startsWith('/')) return; // a common mistake is to paste a https:// as docker image. this results in a crash at runtime in dockerode module (https://github.com/apocas/dockerode/issues/548)
|
||||
|
||||
const removeOptions = {
|
||||
force: false, // might be shared with another instance of this app
|
||||
noprune: false // delete untagged parents
|
||||
};
|
||||
|
||||
// registry v1 used to pull down all *tags*. this meant that deleting image by tag was not enough (since that
|
||||
// just removes the tag). we used to remove the image by id. this is not required anymore because aliases are
|
||||
// not created anymore after https://github.com/docker/docker/pull/10571
|
||||
debug(`deleteImage: removing ${imageRef}`);
|
||||
const [error] = await safe(gConnection.getImage(imageRef.replace(/@sha256:.*/,'')).remove(removeOptions)); // can't have the manifest id. won't remove anythin
|
||||
if (error && error.statusCode === 400) return; // invalid image format. this can happen if user installed with a bad --docker-image
|
||||
if (error && error.statusCode === 404) return; // not found
|
||||
if (error && error.statusCode === 409) return; // another container using the image
|
||||
|
||||
if (error) {
|
||||
debug(`Error removing image ${imageRef} : %o`, error);
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function inspect(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error, result] = await safe(container.inspect());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Unable to find container ${containerId}`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function downloadImage(manifest) {
|
||||
assert.strictEqual(typeof manifest, 'object');
|
||||
|
||||
debug(`downloadImage: ${manifest.dockerImage}`);
|
||||
|
||||
const image = gConnection.getImage(manifest.dockerImage);
|
||||
|
||||
const [error, result] = await safe(image.inspect());
|
||||
if (!error && result) return; // image is already present locally
|
||||
|
||||
const parsedManifestRef = parseImageRef(manifest.dockerImage);
|
||||
|
||||
await promiseRetry({ times: 10, interval: 5000, debug, retry: (pullError) => pullError.reason !== BoxError.FS_ERROR }, async () => {
|
||||
// custom (non appstore) image
|
||||
if (parsedManifestRef.registry !== null || !parsedManifestRef.fullRepositoryName.startsWith('cloudron/')) return await pullImage(manifest.dockerImage);
|
||||
|
||||
// docker hub only uses first 64 bits for ipv6 addressing. this causes many ipv6 rate limit errors
|
||||
// https://www.docker.com/blog/beta-ipv6-support-on-docker-hub-registry/ . as a hack, we try ipv4 explicity
|
||||
let upstreamRef = null, pullError = null;
|
||||
for (const registry of CLOUDRON_REGISTRIES) {
|
||||
upstreamRef = `${registry}/${manifest.dockerImage}`;
|
||||
[pullError] = await safe(pullImage(upstreamRef));
|
||||
if (!pullError) break;
|
||||
}
|
||||
|
||||
if (pullError || !upstreamRef) throw new BoxError(BoxError.DOCKER_ERROR, `Unable to pull ${manifest.dockerImage} from dockerhub or quay: ${pullError?.message}`);
|
||||
|
||||
// retag the downloaded image to not have the registry name. this prevents 'docker run' from redownloading it
|
||||
debug(`downloadImage: tagging ${upstreamRef} as ${parsedManifestRef.fullRepositoryName}:${parsedManifestRef.tag}`);
|
||||
await gConnection.getImage(upstreamRef).tag({ repo: parsedManifestRef.fullRepositoryName, tag: parsedManifestRef.tag });
|
||||
debug(`downloadImage: untagging ${upstreamRef}`);
|
||||
await deleteImage(upstreamRef);
|
||||
});
|
||||
}
|
||||
|
||||
async function getContainerIp(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
if (constants.TEST) return '127.0.5.5';
|
||||
|
||||
const result = await inspect(containerId);
|
||||
|
||||
const ip = safe.query(result, 'NetworkSettings.Networks.cloudron.IPAddress', null);
|
||||
if (!ip) throw new BoxError(BoxError.DOCKER_ERROR, 'Error getting container IP');
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
async function createExec(containerId, options) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
const [error, exec] = await safe(container.exec(options));
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error && error.statusCode === 409) throw new BoxError(BoxError.BAD_STATE, error.message); // container restarting/not running
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return exec.id;
|
||||
}
|
||||
|
||||
async function getExec(execId) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error, result] = await safe(exec.inspect());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Unable to find exec container ${execId}`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return { exitCode: result.ExitCode, running: result.Running };
|
||||
}
|
||||
|
||||
async function startExec(execId, options) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error, stream] = await safe(exec.start(options)); /* in hijacked mode, stream is a net.socket */
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
return stream;
|
||||
}
|
||||
|
||||
async function resizeExec(execId, options) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error] = await safe(exec.resize(options)); // { h, w }
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
|
||||
async function getEvents(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const [error, stream] = await safe(gConnection.getEvents(options));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
return stream;
|
||||
}
|
||||
|
||||
async function getStats(containerId, options) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error, result] = await safe(container.stats({ stream: !!options.stream }));
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function info() {
|
||||
const [error, result] = await safe(gConnection.info());
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, `Error connecting to docker: ${error.message}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function df(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const [error, result] = await safe(gConnection.df(options));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, `Error connecting to docker: ${error.message}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function update(name, memory) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof memory, 'number');
|
||||
|
||||
// scale back db containers, if possible. this is retried because updating memory constraints can fail
|
||||
// with failed to write to memory.memsw.limit_in_bytes: write /sys/fs/cgroup/memory/docker/xx/memory.memsw.limit_in_bytes: device or resource busy
|
||||
for (let times = 0; times < 10; times++) {
|
||||
const [error] = await safe(shell.spawn('docker', ['update', '--memory', memory, '--memory-swap', '-1', name], { encoding: 'utf8' }));
|
||||
if (!error) return;
|
||||
await timers.setTimeout(60 * 1000);
|
||||
}
|
||||
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, 'Unable to update container');
|
||||
}
|
||||
|
||||
async function createSubcontainer(app, name, cmd, options) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
@@ -486,239 +719,6 @@ async function createContainer(app) {
|
||||
return await createSubcontainer(app, app.id /* name */, null /* cmd */, { } /* options */);
|
||||
}
|
||||
|
||||
async function startContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error] = await safe(container.start());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable
|
||||
if (error && error.statusCode !== 304) throw new BoxError(BoxError.DOCKER_ERROR, error); // 304 means already started
|
||||
}
|
||||
|
||||
async function restartContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error] = await safe(container.restart());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Contanier ${containerId} not found`);
|
||||
if (error && error.statusCode === 400) throw new BoxError(BoxError.BAD_FIELD, error); // e.g start.sh is not executable
|
||||
if (error && error.statusCode !== 204) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
|
||||
async function stopContainer(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
debug(`stopContainer: stopping container ${containerId}`);
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const options = {
|
||||
t: 10 // wait for 10 seconds before killing it
|
||||
};
|
||||
|
||||
let [error] = await safe(container.stop(options));
|
||||
if (error && (error.statusCode !== 304 && error.statusCode !== 404)) throw new BoxError(BoxError.DOCKER_ERROR, 'Error stopping container:' + error.message);
|
||||
|
||||
[error] = await safe(container.wait());
|
||||
if (error && (error.statusCode !== 304 && error.statusCode !== 404)) throw new BoxError(BoxError.DOCKER_ERROR, 'Error waiting on container:' + error.message);
|
||||
}
|
||||
|
||||
async function deleteContainer(containerId) { // id can also be name
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
debug(`deleteContainer: deleting ${containerId}`);
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const removeOptions = {
|
||||
force: true, // kill container if it's running
|
||||
v: true // removes volumes associated with the container (but not host mounts)
|
||||
};
|
||||
|
||||
const [error] = await safe(container.remove(removeOptions));
|
||||
if (error && error.statusCode === 404) return;
|
||||
|
||||
if (error) {
|
||||
debug('Error removing container %s : %o', containerId, error);
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteContainers(appId, options) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const labels = [ 'appId=' + appId ];
|
||||
if (options.managedOnly) labels.push('isCloudronManaged=true');
|
||||
|
||||
const [error, containers] = await safe(gConnection.listContainers({ all: 1, filters: JSON.stringify({ label: labels }) }));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
for (const container of containers) {
|
||||
await deleteContainer(container.Id);
|
||||
}
|
||||
}
|
||||
|
||||
async function stopContainers(appId) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
|
||||
const [error, containers] = await safe(gConnection.listContainers({ all: 1, filters: JSON.stringify({ label: [ 'appId=' + appId ] }) }));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
for (const container of containers) {
|
||||
await stopContainer(container.Id);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteImage(imageRef) {
|
||||
assert.strictEqual(typeof imageRef, 'string');
|
||||
|
||||
if (!imageRef) return;
|
||||
if (imageRef.includes('//') || imageRef.startsWith('/')) return; // a common mistake is to paste a https:// as docker image. this results in a crash at runtime in dockerode module (https://github.com/apocas/dockerode/issues/548)
|
||||
|
||||
const removeOptions = {
|
||||
force: false, // might be shared with another instance of this app
|
||||
noprune: false // delete untagged parents
|
||||
};
|
||||
|
||||
// registry v1 used to pull down all *tags*. this meant that deleting image by tag was not enough (since that
|
||||
// just removes the tag). we used to remove the image by id. this is not required anymore because aliases are
|
||||
// not created anymore after https://github.com/docker/docker/pull/10571
|
||||
debug(`deleteImage: removing ${imageRef}`);
|
||||
const [error] = await safe(gConnection.getImage(imageRef.replace(/@sha256:.*/,'')).remove(removeOptions)); // can't have the manifest id. won't remove anythin
|
||||
if (error && error.statusCode === 400) return; // invalid image format. this can happen if user installed with a bad --docker-image
|
||||
if (error && error.statusCode === 404) return; // not found
|
||||
if (error && error.statusCode === 409) return; // another container using the image
|
||||
|
||||
if (error) {
|
||||
debug(`Error removing image ${imageRef} : %o`, error);
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
}
|
||||
|
||||
async function inspect(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error, result] = await safe(container.inspect());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Unable to find container ${containerId}`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function getContainerIp(containerId) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
|
||||
if (constants.TEST) return '127.0.5.5';
|
||||
|
||||
const result = await inspect(containerId);
|
||||
|
||||
const ip = safe.query(result, 'NetworkSettings.Networks.cloudron.IPAddress', null);
|
||||
if (!ip) throw new BoxError(BoxError.DOCKER_ERROR, 'Error getting container IP');
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
async function createExec(containerId, options) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
const [error, exec] = await safe(container.exec(options));
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error && error.statusCode === 409) throw new BoxError(BoxError.BAD_STATE, error.message); // container restarting/not running
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return exec.id;
|
||||
}
|
||||
|
||||
async function startExec(execId, options) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error, stream] = await safe(exec.start(options)); /* in hijacked mode, stream is a net.socket */
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
return stream;
|
||||
}
|
||||
|
||||
async function getExec(execId) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error, result] = await safe(exec.inspect());
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Unable to find exec container ${execId}`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return { exitCode: result.ExitCode, running: result.Running };
|
||||
}
|
||||
|
||||
async function resizeExec(execId, options) {
|
||||
assert.strictEqual(typeof execId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const exec = gConnection.getExec(execId);
|
||||
const [error] = await safe(exec.resize(options)); // { h, w }
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Exec container ${execId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
}
|
||||
|
||||
async function getEvents(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const [error, stream] = await safe(gConnection.getEvents(options));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
return stream;
|
||||
}
|
||||
|
||||
async function getStats(containerId, options) {
|
||||
assert.strictEqual(typeof containerId, 'string');
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const container = gConnection.getContainer(containerId);
|
||||
|
||||
const [error, result] = await safe(container.stats({ stream: !!options.stream }));
|
||||
if (error && error.statusCode === 404) throw new BoxError(BoxError.NOT_FOUND, `Container ${containerId} not found`);
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, error);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function info() {
|
||||
const [error, result] = await safe(gConnection.info());
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, `Error connecting to docker: ${error.message}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function df(options) {
|
||||
assert.strictEqual(typeof options, 'object');
|
||||
|
||||
const [error, result] = await safe(gConnection.df(options));
|
||||
if (error) throw new BoxError(BoxError.DOCKER_ERROR, `Error connecting to docker: ${error.message}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function update(name, memory) {
|
||||
assert.strictEqual(typeof name, 'string');
|
||||
assert.strictEqual(typeof memory, 'number');
|
||||
|
||||
// scale back db containers, if possible. this is retried because updating memory constraints can fail
|
||||
// with failed to write to memory.memsw.limit_in_bytes: write /sys/fs/cgroup/memory/docker/xx/memory.memsw.limit_in_bytes: device or resource busy
|
||||
for (let times = 0; times < 10; times++) {
|
||||
const [error] = await safe(shell.spawn('docker', ['update', '--memory', memory, '--memory-swap', '-1', name], { encoding: 'utf8' }));
|
||||
if (!error) return;
|
||||
await timers.setTimeout(60 * 1000);
|
||||
}
|
||||
|
||||
throw new BoxError(BoxError.DOCKER_ERROR, 'Unable to update container');
|
||||
}
|
||||
|
||||
export default {
|
||||
ping,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user