UNPKG

@apiclient.xyz/docker

Version:

Provides easy communication with Docker remote API from Node.js, with TypeScript support.

258 lines 20.8 kB
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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