import { tap, expect } from '@git.zone/tstest/tapbundle'; import { CloudlyRegistryManager } from '../ts/manager.registry/classes.registrymanager.js'; const digest = (fillArg: string): string => `sha256:${fillArg.repeat(64)}`; class FakeRegistryStorage { public objects = new Map(); public async getObject(pathArg: string): Promise { return this.objects.get(pathArg) || null; } public async putObject(pathArg: string, dataArg: Buffer): Promise { this.objects.set(pathArg, dataArg); } public async deleteObject(pathArg: string): Promise { this.objects.delete(pathArg); } public async objectExists(pathArg: string): Promise { return this.objects.has(pathArg); } public async listObjects(prefixArg: string): Promise { return Array.from(this.objects.keys()).filter((pathArg) => pathArg.startsWith(prefixArg)); } public async getOciManifest(repositoryArg: string, digestArg: string): Promise { return this.getObject(`oci/manifests/${repositoryArg}/${digestArg.slice('sha256:'.length)}`); } } const putJson = (storageArg: FakeRegistryStorage, pathArg: string, dataArg: unknown): void => { storageArg.objects.set(pathArg, Buffer.from(JSON.stringify(dataArg))); }; tap.test('should delete Cloudly service-owned OCI repository without deleting shared blobs', async () => { const storage = new FakeRegistryStorage(); const manager = Object.create(CloudlyRegistryManager.prototype) as any; const service = { id: 'service-1', data: { registryTarget: { repository: 'workloads/ghost-service-1' }, }, }; manager.started = true; manager.smartRegistry = { getStorage: () => storage }; manager.recordedTagDigests = new Map([ ['workloads/ghost-service-1:latest', digest('a')], ]); manager.cloudlyRef = { serviceManager: { CService: { getInstances: async () => [service] }, }, }; const targetDigest = digest('a'); const otherDigest = digest('b'); const sharedLayerDigest = digest('c'); const targetOnlyLayerDigest = digest('d'); const otherOnlyLayerDigest = digest('e'); putJson(storage, 'oci/tags/workloads/ghost-service-1/tags.json', { latest: targetDigest }); putJson(storage, 'oci/tags/workloads/other-service/tags.json', { latest: otherDigest }); putJson(storage, `oci/manifests/workloads/ghost-service-1/${targetDigest.slice('sha256:'.length)}`, { layers: [{ digest: sharedLayerDigest }, { digest: targetOnlyLayerDigest }], }); putJson(storage, `oci/manifests/workloads/other-service/${otherDigest.slice('sha256:'.length)}`, { layers: [{ digest: sharedLayerDigest }, { digest: otherOnlyLayerDigest }], }); for (const blobDigest of [sharedLayerDigest, targetOnlyLayerDigest, otherOnlyLayerDigest]) { storage.objects.set(`oci/blobs/sha256/${blobDigest.slice('sha256:'.length)}`, Buffer.from(blobDigest)); } await manager.deleteServiceRepository(service); expect(storage.objects.has('oci/tags/workloads/ghost-service-1/tags.json')).toBeFalse(); expect(storage.objects.has(`oci/manifests/workloads/ghost-service-1/${targetDigest.slice('sha256:'.length)}`)).toBeFalse(); expect(storage.objects.has(`oci/blobs/sha256/${targetOnlyLayerDigest.slice('sha256:'.length)}`)).toBeFalse(); expect(storage.objects.has(`oci/blobs/sha256/${sharedLayerDigest.slice('sha256:'.length)}`)).toBeTrue(); expect(storage.objects.has(`oci/blobs/sha256/${otherOnlyLayerDigest.slice('sha256:'.length)}`)).toBeTrue(); expect(manager.recordedTagDigests.has('workloads/ghost-service-1:latest')).toBeFalse(); }); export default tap.start();