f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
202 lines
7.3 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
}
|