@apiclient.xyz/docker
Version:
Provides easy communication with Docker remote API from Node.js, with TypeScript support.
198 lines • 15.9 kB
JavaScript
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './classes.host.js';
import { logger } from './logger.js';
/**
* represents a docker image on the remote docker host
*/
export class DockerImage {
// STATIC
static async getImages(dockerHost) {
const images = [];
const response = await dockerHost.request('GET', '/images/json');
for (const imageObject of response.body) {
images.push(new DockerImage(dockerHost, imageObject));
}
return images;
}
static async getImageByName(dockerHost, imageNameArg) {
const images = await this.getImages(dockerHost);
const result = images.find((image) => {
if (image.RepoTags) {
return image.RepoTags.includes(imageNameArg);
}
else {
return false;
}
});
return result;
}
static async createFromRegistry(dockerHostArg, optionsArg) {
// lets create a sanatized imageUrlObject
const imageUrlObject = {
imageUrl: optionsArg.creationObject.imageUrl,
imageTag: optionsArg.creationObject.imageTag,
imageOriginTag: null,
};
if (imageUrlObject.imageUrl.includes(':')) {
const imageUrl = imageUrlObject.imageUrl.split(':')[0];
const imageTag = imageUrlObject.imageUrl.split(':')[1];
if (imageUrlObject.imageTag) {
throw new Error(`imageUrl ${imageUrlObject.imageUrl} can't be tagged with ${imageUrlObject.imageTag} because it is already tagged with ${imageTag}`);
}
else {
imageUrlObject.imageUrl = imageUrl;
imageUrlObject.imageTag = imageTag;
}
}
else if (!imageUrlObject.imageTag) {
imageUrlObject.imageTag = 'latest';
}
imageUrlObject.imageOriginTag = `${imageUrlObject.imageUrl}:${imageUrlObject.imageTag}`;
// lets actually create the image
const response = await dockerHostArg.request('POST', `/images/create?fromImage=${encodeURIComponent(imageUrlObject.imageUrl)}&tag=${encodeURIComponent(imageUrlObject.imageTag)}`);
if (response.statusCode < 300) {
logger.log('info', `Successfully pulled image ${imageUrlObject.imageUrl} from the registry`);
const image = await DockerImage.getImageByName(dockerHostArg, imageUrlObject.imageOriginTag);
return image;
}
else {
logger.log('error', `Failed at the attempt of creating a new image`);
}
}
/**
*
* @param dockerHostArg
* @param tarStreamArg
*/
static async createFromTarStream(dockerHostArg, optionsArg) {
// Start the request for importing an image
const response = await dockerHostArg.requestStreaming('POST', '/images/load', optionsArg.tarStream);
/**
* Docker typically returns lines like:
* {"stream":"Loaded image: myrepo/myimage:latest"}
*
* So we will collect those lines and parse out the final image name.
*/
let rawOutput = '';
response.on('data', (chunk) => {
rawOutput += chunk.toString();
});
// Wrap the end event in a Promise for easier async/await usage
await new Promise((resolve, reject) => {
response.on('end', () => {
resolve();
});
response.on('error', (err) => {
reject(err);
});
});
// Attempt to parse each line to find something like "Loaded image: ..."
let loadedImageTag;
const lines = rawOutput.trim().split('\n').filter(Boolean);
for (const line of lines) {
try {
const jsonLine = JSON.parse(line);
if (jsonLine.stream &&
(jsonLine.stream.startsWith('Loaded image:') ||
jsonLine.stream.startsWith('Loaded image ID:'))) {
// Examples:
// "Loaded image: your-image:latest"
// "Loaded image ID: sha256:...."
loadedImageTag = jsonLine.stream
.replace('Loaded image: ', '')
.replace('Loaded image ID: ', '')
.trim();
}
}
catch {
// not valid JSON, ignore
}
}
if (!loadedImageTag) {
throw new Error(`Could not parse the loaded image info from Docker response.\nResponse was:\n${rawOutput}`);
}
// Now try to look up that image by the "loadedImageTag".
// Depending on Docker’s response, it might be something like:
// "myrepo/myimage:latest" OR "sha256:someHash..."
// If Docker gave you an ID (e.g. "sha256:..."), you may need a separate
// DockerImage.getImageById method; or if you prefer, you can treat it as a name.
const newlyImportedImage = await DockerImage.getImageByName(dockerHostArg, loadedImageTag);
if (!newlyImportedImage) {
throw new Error(`Image load succeeded, but no local reference found for "${loadedImageTag}".`);
}
logger.log('info', `Successfully imported image "${loadedImageTag}".`);
return newlyImportedImage;
}
static async tagImageByIdOrName(dockerHost, idOrNameArg, newTagArg) {
const response = await dockerHost.request('POST', `/images/${encodeURIComponent(idOrNameArg)}/${encodeURIComponent(newTagArg)}`);
}
static async buildImage(dockerHostArg, dockerImageTag) {
// TODO: implement building an image
}
constructor(dockerHostArg, dockerImageObjectArg) {
this.dockerHost = dockerHostArg;
Object.keys(dockerImageObjectArg).forEach((keyArg) => {
this[keyArg] = dockerImageObjectArg[keyArg];
});
}
/**
* tag an image
* @param newTag
*/
async tagImage(newTag) {
throw new Error('.tagImage is not yet implemented');
}
/**
* pulls the latest version from the registry
*/
async pullLatestImageFromRegistry() {
const updatedImage = await DockerImage.createFromRegistry(this.dockerHost, {
creationObject: {
imageUrl: this.RepoTags[0],
},
});
Object.assign(this, updatedImage);
// TODO: Compare image digists before and after
return true;
}
// get stuff
async getVersion() {
if (this.Labels && this.Labels.version) {
return this.Labels.version;
}
else {
return '0.0.0';
}
}
/**
* exports an image to a tar ball
*/
async exportToTarStream() {
logger.log('info', `Exporting image ${this.RepoTags[0]} to tar stream.`);
const response = await this.dockerHost.requestStreaming('GET', `/images/${encodeURIComponent(this.RepoTags[0])}/get`);
let counter = 0;
const webduplexStream = new plugins.smartstream.SmartDuplex({
writeFunction: async (chunk, tools) => {
if (counter % 1000 === 0)
console.log(`Got chunk: ${counter}`);
counter++;
return chunk;
}
});
response.on('data', (chunk) => {
if (!webduplexStream.write(chunk)) {
response.pause();
webduplexStream.once('drain', () => {
response.resume();
});
}
;
});
response.on('end', () => {
webduplexStream.end();
});
return webduplexStream;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pbWFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuaW1hZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLFVBQVUsTUFBTSx1QkFBdUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0MsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUVyQzs7R0FFRztBQUNILE1BQU0sT0FBTyxXQUFXO0lBQ3RCLFNBQVM7SUFDRixNQUFNLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxVQUFzQjtRQUNsRCxNQUFNLE1BQU0sR0FBa0IsRUFBRSxDQUFDO1FBQ2pDLE1BQU0sUUFBUSxHQUFHLE1BQU0sVUFBVSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsY0FBYyxDQUFDLENBQUM7UUFDakUsS0FBSyxNQUFNLFdBQVcsSUFBSSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDeEMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLFdBQVcsQ0FBQyxVQUFVLEVBQUUsV0FBVyxDQUFDLENBQUMsQ0FBQztRQUN4RCxDQUFDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVNLE1BQU0sQ0FBQyxLQUFLLENBQUMsY0FBYyxDQUFDLFVBQXNCLEVBQUUsWUFBb0I7UUFDN0UsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ2hELE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRTtZQUNuQyxJQUFJLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDbkIsT0FBTyxLQUFLLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUMvQyxDQUFD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