UNPKG

@apiclient.xyz/docker

Version:

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

198 lines 15.9 kB
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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