Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
33 changes: 25 additions & 8 deletions src/cache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand Down
25 changes: 15 additions & 10 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,38 @@
* 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<string, string> = {
[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) {
this.#cachedImageDir = resolve(storagePath, 'images');
}

async init(): Promise<void> {
await this.cleanupv1();
await this.cleanup();
await mkdir(this.#cachedImageDir, { recursive: true });
}

// v1 supported only 1 image, cached as `images/image`
async cleanupv1(): Promise<void> {
const imagePath = resolve(this.#cachedImageDir, 'image');
if (existsSync(imagePath)) {
await rm(imagePath);
// delete all files not listed in cachedImageNames
async cleanup(): Promise<void> {
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));
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down
25 changes: 18 additions & 7 deletions src/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
}