@apiclient.xyz/docker
Version:
Provides easy communication with Docker remote API from Node.js, with TypeScript support.
258 lines • 20.8 kB
JavaScript
import * as plugins from './plugins.js';
import * as interfaces from './interfaces/index.js';
import { DockerHost } from './classes.host.js';
import { DockerResource } from './classes.base.js';
import { logger } from './logger.js';
/**
* represents a docker image on the remote docker host
*/
export class DockerImage extends DockerResource {
// STATIC (Internal - prefixed with _ to indicate internal use)
/**
* Internal: Get all images
* Public API: Use dockerHost.listImages() instead
*/
static async _list(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;
}
/**
* Internal: Get image by name
* Public API: Use dockerHost.getImageByName(name) instead
*/
static async _fromName(dockerHost, imageNameArg) {
const images = await this._list(dockerHost);
const result = images.find((image) => {
if (image.RepoTags) {
return image.RepoTags.includes(imageNameArg);
}
else {
return false;
}
});
return result;
}
/**
* Internal: Create image from registry
* Public API: Use dockerHost.createImageFromRegistry(descriptor) instead
*/
static async _createFromRegistry(dockerHostArg, optionsArg) {
// lets create a sanatized imageUrlObject
const imageUrlObject = {
imageUrl: optionsArg.creationObject.imageUrl,
imageTag: optionsArg.creationObject.imageTag ?? '',
imageOriginTag: '',
};
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._fromName(dockerHostArg, imageUrlObject.imageOriginTag);
if (!image) {
throw new Error(`Image ${imageUrlObject.imageOriginTag} not found after pull`);
}
return image;
}
else {
// Pull failed — check if the image already exists locally
const existingImage = await DockerImage._fromName(dockerHostArg, imageUrlObject.imageOriginTag);
if (existingImage) {
logger.log('warn', `Pull failed for ${imageUrlObject.imageUrl}, using locally cached image`);
return existingImage;
}
throw new Error(`Failed to pull image ${imageUrlObject.imageOriginTag} and no local copy exists`);
}
}
/**
* Internal: Create image from tar stream
* Public API: Use dockerHost.createImageFromTarStream(stream, descriptor) instead
*/
static async _createFromTarStream(dockerHostArg, optionsArg) {
// Start the request for importing an image
const response = await dockerHostArg.requestStreaming('POST', '/images/load', optionsArg.tarStream);
// requestStreaming now returns Node.js stream
const nodeStream = response;
/**
* 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 = '';
nodeStream.on('data', (chunk) => {
rawOutput += chunk.toString();
});
// Wrap the end event in a Promise for easier async/await usage
await new Promise((resolve, reject) => {
nodeStream.on('end', () => {
resolve();
});
nodeStream.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._fromName(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)}`);
}
/**
* Internal: Build image from Dockerfile
* Public API: Use dockerHost.buildImage(tag) instead
*/
static async _build(dockerHostArg, dockerImageTag) {
// TODO: implement building an image
}
constructor(dockerHostArg, dockerImageObjectArg) {
super(dockerHostArg);
Object.keys(dockerImageObjectArg).forEach((keyArg) => {
this[keyArg] = dockerImageObjectArg[keyArg];
});
}
// INSTANCE METHODS
/**
* Refreshes this image's state from the Docker daemon
*/
async refresh() {
if (!this.RepoTags || this.RepoTags.length === 0) {
throw new Error('Cannot refresh image without RepoTags');
}
const updated = await DockerImage._fromName(this.dockerHost, this.RepoTags[0]);
if (updated) {
Object.assign(this, updated);
}
}
/**
* 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;
}
/**
* Removes this image from the Docker daemon
*/
async remove(options) {
const queryParams = new URLSearchParams();
if (options?.force)
queryParams.append('force', '1');
if (options?.noprune)
queryParams.append('noprune', '1');
const queryString = queryParams.toString();
const response = await this.dockerHost.request('DELETE', `/images/${encodeURIComponent(this.Id)}${queryString ? '?' + queryString : ''}`);
if (response.statusCode >= 300) {
throw new Error(`Failed to remove image: ${response.statusCode}`);
}
}
// 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`);
// requestStreaming now returns Node.js stream
const nodeStream = response;
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;
},
});
nodeStream.on('data', (chunk) => {
if (!webduplexStream.write(chunk)) {
nodeStream.pause();
webduplexStream.once('drain', () => {
nodeStream.resume();
});
}
});
nodeStream.on('end', () => {
webduplexStream.end();
});
nodeStream.on('error', (error) => {
logger.log('error', `Error during image export: ${error.message}`);
webduplexStream.destroy(error);
});
return webduplexStream;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pbWFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuaW1hZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxLQUFLLFVBQVUsTUFBTSx1QkFBdUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0MsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFckM7O0dBRUc7QUFDSCxNQUFNLE9BQU8sV0FBWSxTQUFRLGNBQWM7SUFDN0MsK0RBQStEO0lBRS9EOzs7T0FHRztJQUNJLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFVBQXNCO1FBQzlDLE1BQU0sTUFBTSxHQUFrQixFQUFFLENBQUM7UUFDakMsTUFBTSxRQUFRLEdBQUcsTUFBTSxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxjQUFjLENBQUMsQ0FBQztRQUNqRSxLQUFLLE1BQU0sV0FBVyxJQUFJLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN4QyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksV0FBVyxDQUFDLFVBQVUsRUFBRSxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBQ3hELENBQUM7UUFDRCxPQUFPLE1BQU0sQ0FBQztJQUNoQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksTUFBTSxDQUFDLEtBQUssQ0FBQyxTQUFTLENBQzNCLFVBQXNCLEVBQ3RCLFlBQW9CO1FBRXBCLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUM1QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDbkMsSUFBSSxLQUFLLENBQUMs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