Files
cloudly/ts/manager.image/classes.imagemanager.ts
T
jkunz f40ef6b7c0 chore: update cloudly dependency stack
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
2026-05-08 13:56:20 +00:00

202 lines
7.3 KiB
TypeScript

import type { Cloudly } from '../classes.cloudly.js';
import * as plugins from '../plugins.js';
import * as paths from '../paths.js';
import { Image } from './classes.image.js';
export class ImageManager {
cloudlyRef: Cloudly;
public typedrouter = new plugins.typedrequest.TypedRouter();
public smartbucketInstance!: plugins.smartbucket.SmartBucket;
public imageDir!: plugins.smartbucket.Directory;
public dockerImageStore!: plugins.docker.DockerImageStore;
get db() {
return this.cloudlyRef.mongodbConnector.smartdataDb;
}
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
constructor(cloudlyRefArg: Cloudly) {
this.cloudlyRef = cloudlyRefArg;
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
'createImage',
async (reqArg, toolsArg) => {
await toolsArg!.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.create({
name: reqArg.name,
description: reqArg.description,
versions: [],
});
return {
image: await image.createSavableObject(),
};
},
),
);
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
new plugins.typedrequest.TypedHandler('getImage', async (reqArg, toolsArg) => {
await toolsArg!.passGuards([this.cloudlyRef.authManager.adminOrClusterIdentityGuard], reqArg);
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
return {
image: await image.createSavableObject(),
};
}),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
'deleteImage',
async (reqArg, toolsArg) => {
await toolsArg!.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
await image.delete();
return {};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
'getAllImages',
async (requestArg, toolsArg) => {
await toolsArg!.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
const images = await this.CImage.getInstances({});
return {
images: await Promise.all(
images.map((image) => {
return image.createSavableObject();
}),
),
};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
'pushImageVersion',
async (reqArg, toolsArg) => {
await plugins.smartguard.passGuardsOrReject(reqArg, [
this.cloudlyRef.authManager.validIdentityGuard,
]);
const refImage = await this.CImage.getInstance({
id: reqArg.imageId,
});
if (!refImage) {
throw new plugins.typedrequest.TypedResponseError('Image not found');
}
const imageVersion = reqArg.versionString;
if (!imageVersion) {
throw new plugins.typedrequest.TypedResponseError('versionString is required');
}
console.log(
`got request to push image version ${imageVersion} for image ${refImage.data.name}`,
);
const storagePath = await refImage.getStoragePath(imageVersion);
refImage.data.versions = [
...refImage.data.versions.filter((version) => version.versionString !== imageVersion),
{
versionString: imageVersion,
source: 'upload',
storagePath,
size: 0,
createdAt: Date.now(),
},
];
await refImage.save();
const imagePushStream = reqArg.imageStream;
(async () => {
const smartWebDuplex = new plugins.smartstream.webstream.WebDuplexStream<
Uint8Array,
Uint8Array
>({
writeFunction: async (chunkArg, toolsArg) => {
console.log(chunkArg);
return chunkArg;
},
});
imagePushStream.writeToWebstream(smartWebDuplex.writable);
await this.dockerImageStore.storeImage(
storagePath,
plugins.smartstream.SmartDuplex.fromWebReadableStream(smartWebDuplex.readable),
);
})().catch((error) => {
console.error(`failed to store image ${refImage.id}:${imageVersion}`, error);
});
return {
allowed: true,
};
},
),
);
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
'pullImageVersion',
async (reqArg) => {
const image = await this.CImage.getInstance({
id: reqArg.imageId,
});
const imageVersion = image.data.versions.find(
(version) => version.versionString === reqArg.versionString,
);
if (!imageVersion) {
throw new plugins.typedrequest.TypedResponseError('Image version not found');
}
const readable = await this.imageDir.fastGetStream(
{
path: imageVersion.storagePath || await image.getStoragePath(reqArg.versionString),
},
'webstream',
);
const imageVirtualStream = new plugins.typedrequest.VirtualStream();
(async () => {
await imageVirtualStream.readFromWebstream(readable);
})().catch((error) => {
console.error(`failed to stream image ${image.id}:${reqArg.versionString}`, error);
});
return {
imageStream: imageVirtualStream,
};
},
),
);
}
public async start() {
// lets setup s3
const s3Descriptor =
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor') as plugins.tsclass.storage.IS3Descriptor;
console.log(this.cloudlyRef.config.data.s3Descriptor);
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
this.cloudlyRef.config.data.s3Descriptor!,
);
const bucketName = s3Descriptor.bucketName!;
const bucket = await this.smartbucketInstance.bucketExists(bucketName)
? await this.smartbucketInstance.getBucketByName(bucketName)
: await this.smartbucketInstance.createBucket(bucketName);
await bucket.fastPut({ path: 'images/00init', contents: 'init', overwrite: true });
this.imageDir = await bucket.getDirectoryFromPath({
path: '/images',
});
// lets setup dockerstore
await plugins.fsPromises.mkdir(paths.dockerImageStoreDir, { recursive: true });
this.dockerImageStore = new plugins.docker.DockerImageStore({
localDirPath: paths.dockerImageStoreDir,
bucketDir: this.imageDir,
});
}
}