import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { logger } from './logger.js'; import type { DockerHost } from './classes.host.js'; export interface IDockerImageStoreConstructorOptions { /** * used for preparing images for longer term storage */ localDirPath: string; /** * a smartbucket dir for longer term storage. */ bucketDir: plugins.smartbucket.Directory; } export class DockerImageStore { public options: IDockerImageStoreConstructorOptions; constructor(dockerHost: DockerHost, optionsArg: IDockerImageStoreConstructorOptions) { this.options = optionsArg; } // Method to store tar stream public async storeImage(imageName: string, tarStream: plugins.smartstream.stream.Readable): Promise { logger.log('info', `Storing image ${imageName}...`); const uniqueProcessingId = plugins.smartunique.shortId(); const initialTarDownloadPath = plugins.path.join(this.options.localDirPath, `${uniqueProcessingId}.tar`); const extractionDir = plugins.path.join(this.options.localDirPath, uniqueProcessingId); // Create a write stream to store the tar file const writeStream = plugins.smartfile.fsStream.createWriteStream(initialTarDownloadPath); // lets wait for the write stream to finish await new Promise((resolve, reject) => { tarStream.pipe(writeStream); writeStream.on('finish', resolve); writeStream.on('error', reject); }); logger.log('info', `Image ${imageName} stored locally for processing. Extracting...`); // lets process the image const tarArchive = await plugins.smartarchive.SmartArchive.fromArchiveFile(initialTarDownloadPath); await tarArchive.exportToFs(extractionDir); logger.log('info', `Image ${imageName} extracted.`); await plugins.smartfile.fs.remove(initialTarDownloadPath); logger.log('info', `deleted original tar to save space.`); logger.log('info', `now repackaging for s3...`); const smartfileIndexJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'index.json')); const smartfileManifestJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'manifest.json')); const smartfileOciLayoutJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'oci-layout')); const smartfileRepositoriesJson = await plugins.smartfile.SmartFile.fromFilePath(plugins.path.join(extractionDir, 'repositories')); const indexJson = JSON.parse(smartfileIndexJson.contents.toString()); const manifestJson = JSON.parse(smartfileManifestJson.contents.toString()); const ociLayoutJson = JSON.parse(smartfileOciLayoutJson.contents.toString()); const repositoriesJson = JSON.parse(smartfileRepositoriesJson.contents.toString()); indexJson.manifests[0].annotations['io.containerd.image.name'] = imageName; manifestJson[0].RepoTags[0] = imageName; const repoFirstKey = Object.keys(repositoriesJson)[0]; const repoFirstValue = repositoriesJson[repoFirstKey]; repositoriesJson[imageName] = repoFirstValue; delete repositoriesJson[repoFirstKey]; smartfileIndexJson.contents = Buffer.from(JSON.stringify(indexJson, null, 2)); smartfileManifestJson.contents = Buffer.from(JSON.stringify(manifestJson, null, 2)); smartfileOciLayoutJson.contents = Buffer.from(JSON.stringify(ociLayoutJson, null, 2)); smartfileRepositoriesJson.contents = Buffer.from(JSON.stringify(repositoriesJson, null, 2)); await Promise.all([ smartfileIndexJson.write(), smartfileManifestJson.write(), smartfileOciLayoutJson.write(), smartfileRepositoriesJson.write(), ]); logger.log('info', 'repackaging archive for s3...'); const tartools = new plugins.smartarchive.TarTools(); const newTarPack = await tartools.packDirectory(extractionDir); const finalTarName = `${uniqueProcessingId}.processed.tar`; const finalTarPath = plugins.path.join(this.options.localDirPath, finalTarName); const finalWriteStream = plugins.smartfile.fsStream.createWriteStream(finalTarPath); await new Promise((resolve, reject) => { newTarPack.finalize(); newTarPack.pipe(finalWriteStream); finalWriteStream.on('finish', resolve); finalWriteStream.on('error', reject); }); logger.log('ok', `Repackaged image ${imageName} for s3.`); await plugins.smartfile.fs.remove(extractionDir); const finalTarReadStream = plugins.smartfile.fsStream.createReadStream(finalTarPath); await this.options.bucketDir.fastPutStream({ stream: finalTarReadStream, path: `${imageName}.tar`, }); await plugins.smartfile.fs.remove(finalTarPath); } public async start() { await plugins.smartfile.fs.ensureEmptyDir(this.options.localDirPath); } public async stop() {} // Method to retrieve tar stream public async getImage(imageName: string): Promise { const imagePath = plugins.path.join(this.options.localDirPath, `${imageName}.tar`); if (!(await plugins.smartfile.fs.fileExists(imagePath))) { throw new Error(`Image ${imageName} does not exist.`); } return plugins.smartfile.fsStream.createReadStream(imagePath); } }