diff --git a/package.json b/package.json index 0f1a324..61f5c63 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ }, "macadam.factory.machine.image": { "type": "string", - "enum": ["RHEL 10", "local image on disk"], + "enum": ["RHEL 10.0", "RHEL 9.6", "local image on disk"], "scope": "VmProviderConnectionFactory", - "default": "RHEL 10", + "default": "RHEL 10.0", "description": "Image to download" }, "macadam.factory.machine.image-path": { diff --git a/src/cache.spec.ts b/src/cache.spec.ts index 70d2452..0a82870 100644 --- a/src/cache.spec.ts +++ b/src/cache.spec.ts @@ -16,7 +16,7 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import * as fs from 'node:fs'; +import type { Dirent } from 'node:fs'; import * as fsPromises from 'node:fs/promises'; import { resolve } from 'node:path'; @@ -27,15 +27,32 @@ import { ImageCache } from './cache'; vi.mock('node:fs'); vi.mock('node:fs/promises'); -test('images/image file is deleted during init if it exists', async () => { - const cache = new ImageCache(resolve('/', 'path', 'to', 'cache')); +test('images/image and images/rhel9_5 are deleted during init if they exist, rhel9_ยง is not deleted', async () => { + const cachePath = resolve('/', 'path', 'to', 'cache'); + const cache = new ImageCache(cachePath); - vi.mocked(fs.existsSync).mockImplementation((path: fs.PathLike): boolean => { - return path === resolve('/', 'path', 'to', 'cache', 'images', 'image'); - }); + vi.mocked(fsPromises.readdir).mockResolvedValue([ + { + name: 'image', + parentPath: resolve(cachePath, 'images'), + isFile: () => true, + } as Dirent, + { + name: 'rhel9_5', + parentPath: resolve(cachePath, 'images'), + isFile: () => true, + } as Dirent, + { + name: 'rhel9_6', + parentPath: resolve(cachePath, 'images'), + isFile: () => true, + } as Dirent, + ]); await cache.init(); expect(fsPromises.rm).toHaveBeenCalledWith(resolve('/', 'path', 'to', 'cache', 'images', 'image')); + expect(fsPromises.rm).toHaveBeenCalledWith(resolve('/', 'path', 'to', 'cache', 'images', 'rhel9_5')); + expect(fsPromises.rm).not.toHaveBeenCalledWith(resolve('/', 'path', 'to', 'cache', 'images', 'rhel9_6')); }); test('images directory is created', async () => { @@ -49,8 +66,8 @@ test('images directory is created', async () => { test('getPath returns path for known image', async () => { const cache = new ImageCache(resolve('/', 'path', 'to', 'cache')); await cache.init(); - const path = cache.getPath('RHEL 10'); - expect(path).toBe(resolve('/', 'path', 'to', 'cache', 'images', 'rhel10')); + const path = cache.getPath('RHEL 10.0'); + expect(path).toBe(resolve('/', 'path', 'to', 'cache', 'images', 'rhel10_0')); }); test('getPath raises an exception for an unknown image', async () => { diff --git a/src/cache.ts b/src/cache.ts index 1e3ef08..9788042 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -16,17 +16,17 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import { existsSync } from 'node:fs'; -import { mkdir, rm } from 'node:fs/promises'; +import { mkdir, readdir, rm } from 'node:fs/promises'; import { resolve } from 'node:path'; -import { MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10 } from './constants'; +import { MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9, MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10 } from './constants'; export class ImageCache { #cachedImageDir: string; #cachedImageNames: Record = { - [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10]: 'rhel10', + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10]: 'rhel10_0', + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9]: 'rhel9_6', }; constructor(storagePath: string) { @@ -34,15 +34,20 @@ export class ImageCache { } async init(): Promise { - await this.cleanupv1(); + await this.cleanup(); await mkdir(this.#cachedImageDir, { recursive: true }); } - // v1 supported only 1 image, cached as `images/image` - async cleanupv1(): Promise { - const imagePath = resolve(this.#cachedImageDir, 'image'); - if (existsSync(imagePath)) { - await rm(imagePath); + // delete all files not listed in cachedImageNames + async cleanup(): Promise { + const possibleValues = Object.values(this.#cachedImageNames); + const files = await readdir(this.#cachedImageDir, { recursive: false, withFileTypes: true }); + for (const file of files) { + if (!file.isFile() || possibleValues.includes(file.name)) { + continue; + } + console.log('deleting unused image from cache', resolve(file.parentPath, file.name)); + await rm(resolve(file.parentPath, file.name)); } } diff --git a/src/constants.ts b/src/constants.ts index e4a7cdd..b2a372d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -18,6 +18,9 @@ export const MACADAM_IMAGE_PROPERTY_KEY = 'macadam.factory.machine.image'; export const MACADAM_IMAGE_PROPERTY_VALUE_LOCAL = 'local image on disk'; -export const MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10 = 'RHEL 10'; + +// Must match enumed value for macadam.factory.machine.image in package.json +export const MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10 = 'RHEL 10.0'; +export const MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9 = 'RHEL 9.6'; export const MACADAM_LOCAL_IMAGE_KEY = 'macadam.localImage'; diff --git a/src/extension.ts b/src/extension.ts index 319f5b5..bd05a15 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -504,7 +504,7 @@ async function createVM( logger?.log(`Using image cached in ${cachedImagePath}\n`); } else { const client = await initAuthentication(); - const imageSha = getImageSha(provider); + const imageSha = getImageSha(provider, image); logger?.log('Downloading image, please wait...\n'); await pullImageFromRedHatRegistry(client, imageSha, cachedImagePath, logger, token); logger?.log(`Image downloaded\n`); diff --git a/src/images.ts b/src/images.ts index d2d3559..066b030 100644 --- a/src/images.ts +++ b/src/images.ts @@ -18,22 +18,33 @@ import { arch } from 'node:os'; -const IMAGES: { [provider: string]: string } = { - applehv: 'd32f05024821400932b3653647567abcd34dc03b4243d9a8ae5a11ecd11b1544', - wsl: 'dccb2abb166981fa7598e948f9cab029aa1775219f8249fd54760ddd0f2b8910', - linux_native_x64: '73473542ff4622524ae200ffabd4064bb2a7ff39ac40a012443a43d429d60019', +import { MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9, MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10 } from './constants'; + +const IMAGES: { [provider: string]: { [version: string]: string } } = { + applehv: { + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10]: 'd32f05024821400932b3653647567abcd34dc03b4243d9a8ae5a11ecd11b1544', + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9]: '73c93ad15404a93b2d5bb3b01474e2827e03eb74316132c1067519e4cb598652', + }, + wsl: { + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10]: 'dccb2abb166981fa7598e948f9cab029aa1775219f8249fd54760ddd0f2b8910', + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9]: 'dca8920b81a0664788a098b20328ee98ee6f822f1a1106ee85e9146c50ae6b98', + }, + linux_native_x64: { + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_10]: '73473542ff4622524ae200ffabd4064bb2a7ff39ac40a012443a43d429d60019', + [MACADAM_IMAGE_PROPERTY_VALUE_RHEL_9]: '49443696e7f2410e5647aa16f15eb7b7b08610fea92cdf3eba44ab9ec9ff899f', + }, }; -export function getImageSha(provider?: string): string { +export function getImageSha(provider: string | undefined, version: string): string { if (!provider) { if (arch() === 'x64') { - return IMAGES['linux_native_x64']; + return IMAGES['linux_native_x64'][version]; } else { throw new Error('linux non-x64 is not supported'); } } if (provider in IMAGES) { - return IMAGES[provider]; + return IMAGES[provider][version]; } throw new Error(`provider ${provider} is not supported`); }