mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-05-24 10:18:53 +00:00
chore: add scripts to download podman 5 machines
For Windows download from another repository on GitHub containers/podman-machine-wsl-os and for macOS, fetch OCI images from quay.io/v2/podman/machine-os related to https://github.com/containers/podman-desktop/issues/6360 Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
parent
d424c913d5
commit
d85c142cdd
3 changed files with 491 additions and 26 deletions
|
|
@ -23,8 +23,7 @@ import * as podman4JSON from '../src/podman4.json';
|
|||
import * as podman5JSON from '../src/podman5.json';
|
||||
|
||||
const podman4Download = new PodmanDownload(podman4JSON, true);
|
||||
podman4Download.downloadBinaries();
|
||||
await podman4Download.downloadBinaries();
|
||||
|
||||
// do not fetch for airgap mode now
|
||||
const podman5Download = new PodmanDownload(podman5JSON, false);
|
||||
podman5Download.downloadBinaries();
|
||||
const podman5Download = new PodmanDownload(podman5JSON, true);
|
||||
await podman5Download.downloadBinaries();
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { afterEach } from 'node:test';
|
|||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import {
|
||||
DownloadAndCheck,
|
||||
Podman5DownloadMachineOS,
|
||||
PodmanDownload,
|
||||
PodmanDownloadFcosImage,
|
||||
PodmanDownloadFedoraImage,
|
||||
|
|
@ -27,8 +28,13 @@ import {
|
|||
} from './podman-download';
|
||||
import * as podman4JSON from '../src/podman4.json';
|
||||
import nock from 'nock';
|
||||
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
||||
import { WriteStream, appendFileSync, createWriteStream, existsSync, mkdirSync } from 'node:fs';
|
||||
import { Octokit } from 'octokit';
|
||||
import { PassThrough, Readable, Writable } from 'node:stream';
|
||||
import { WritableStream, WritableStreamDefaultWriter } from 'stream/web';
|
||||
import path from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import exp from 'node:constants';
|
||||
|
||||
const mockedPodman4 = {
|
||||
version: '4.5.0',
|
||||
|
|
@ -256,6 +262,8 @@ test('downloadAndCheckSha', async () => {
|
|||
} as unknown as ShaCheck;
|
||||
// mock GitHub requests
|
||||
|
||||
vi.mocked(shaCheck.checkFile).mockResolvedValue(true);
|
||||
|
||||
const response = {
|
||||
name: 'vFakeVersion',
|
||||
assets: [
|
||||
|
|
@ -312,3 +320,230 @@ test('downloadAndCheckSha', async () => {
|
|||
// check the sha
|
||||
expect(shaCheck.checkFile).toHaveBeenCalledWith(expect.stringContaining('podman-fake-binary'), 'fake-sha');
|
||||
});
|
||||
|
||||
describe('Podman5DownloadMachineOS', () => {
|
||||
const shaCheck = {
|
||||
checkFile: vi.fn(),
|
||||
} as unknown as ShaCheck;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(shaCheck.checkFile).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
class TestPodman5DownloadMachineOS extends Podman5DownloadMachineOS {
|
||||
public pipe(
|
||||
title: string,
|
||||
total: number,
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
writableStream: globalThis.WritableStream<Uint8Array>,
|
||||
): Promise<void> {
|
||||
return super.pipe(title, total, stream, writableStream);
|
||||
}
|
||||
}
|
||||
|
||||
test('download all the files and perform checks', async () => {
|
||||
// spy Writable.toWeb
|
||||
vi.spyOn(Writable, 'toWeb').mockResolvedValue({} as unknown as WritableStream);
|
||||
|
||||
// mock manifests
|
||||
const rootManifest = {
|
||||
schemaVersion: 2,
|
||||
mediaType: 'application/vnd.oci.image.index.v1+json',
|
||||
manifests: [
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:123amd64',
|
||||
size: 481,
|
||||
annotations: {
|
||||
disktype: 'applehv',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'x86_64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:456arm64',
|
||||
size: 482,
|
||||
annotations: {
|
||||
disktype: 'applehv',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'aarch64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:ee45494db66e33525f50835af65c4099db4db7a066b1da9a85fba7e88f95f594',
|
||||
size: 481,
|
||||
annotations: {
|
||||
disktype: 'hyperv',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'x86_64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:56bbdde7b2dc8714a0397f37ae1c8ac1353ed3e4de1b09c1db791d6fa5bc56fa',
|
||||
size: 482,
|
||||
annotations: {
|
||||
disktype: 'hyperv',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'aarch64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:c25ce5ba618f870f88c418c7aa0f176af4a7b32ed39aa328a8e27eae5a497e11',
|
||||
size: 480,
|
||||
annotations: {
|
||||
disktype: 'qemu',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'x86_64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:9a9285bd1a01e5b4c5b27467025cc5da281e250913432a9f92cf8fe1668fec19',
|
||||
size: 481,
|
||||
annotations: {
|
||||
disktype: 'qemu',
|
||||
},
|
||||
platform: {
|
||||
architecture: 'aarch64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:34f21a8b9b8b9ff13fc348f8eba72fa92e74e52caa9984a78b68a7f5822641c7',
|
||||
size: 11003,
|
||||
platform: {
|
||||
architecture: 'aarch64',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
{
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
digest: 'sha256:c7bbce32d96c44b0db6e05a3f78fa4cb11f578c7d18cec49d23a7d6b40843a05',
|
||||
size: 11009,
|
||||
platform: {
|
||||
architecture: 'x86_644',
|
||||
os: 'linux',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
nock('https://quay.io').get('/v2/podman/machine-os/manifests/1.0-fake').reply(200, rootManifest);
|
||||
|
||||
// fake digest for amd64
|
||||
nock('https://quay.io')
|
||||
.get('/v2/podman/machine-os/manifests/sha256:123amd64')
|
||||
.reply(200, {
|
||||
schemaVersion: 2,
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
config: {
|
||||
mediaType: 'application/vnd.oci.empty.v1+json',
|
||||
digest: 'sha256:1234',
|
||||
size: 2,
|
||||
data: 'e30=',
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
mediaType: 'application/zstd',
|
||||
digest: 'sha256:zstfakeamd64digest',
|
||||
size: 1233263850,
|
||||
annotations: {
|
||||
'org.opencontainers.image.title': 'podman-machine-daily.amd64.applehv.raw.zst',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// fake digest for arm64
|
||||
nock('https://quay.io')
|
||||
.get('/v2/podman/machine-os/manifests/sha256:456arm64')
|
||||
.reply(200, {
|
||||
schemaVersion: 2,
|
||||
mediaType: 'application/vnd.oci.image.manifest.v1+json',
|
||||
config: {
|
||||
mediaType: 'application/vnd.oci.empty.v1+json',
|
||||
digest: 'sha256:1234',
|
||||
size: 2,
|
||||
data: 'e30=',
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
mediaType: 'application/zstd',
|
||||
digest: 'sha256:zstfakearm64digest',
|
||||
size: 1233263850,
|
||||
annotations: {
|
||||
'org.opencontainers.image.title': 'podman-machine-daily.aarch64.applehv.raw.zst',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// now do the digests for blobs
|
||||
nock('https://quay.io')
|
||||
.get('/v2/podman/machine-os/blobs/sha256:zstfakeamd64digest')
|
||||
.reply(200, 'fake-amd64-content');
|
||||
|
||||
const zstdArchiveFakeContent = 'blablabla-ARM64\n';
|
||||
nock('https://quay.io')
|
||||
.get('/v2/podman/machine-os/blobs/sha256:zstfakearm64digest')
|
||||
.reply(200, zstdArchiveFakeContent, {
|
||||
'content-type': 'application/octet-stream',
|
||||
'content-length': `${zstdArchiveFakeContent.length}`,
|
||||
'content-disposition': 'attachment; filename=binary.zip',
|
||||
});
|
||||
|
||||
const fakeContent = 'blablabla-ARM64\n';
|
||||
|
||||
const processArm64File = (): Buffer => {
|
||||
return Buffer.from(fakeContent);
|
||||
};
|
||||
|
||||
nock('https://quay.io')
|
||||
.get('/v2/podman/machine-os/blobs/sha256:zstfakearm64digest')
|
||||
.reply(200, processArm64File(), {
|
||||
'content-type': 'application/octet-stream',
|
||||
'content-length': `${fakeContent.length}`,
|
||||
'content-disposition': 'attachment; filename=foo.raw.std',
|
||||
});
|
||||
|
||||
const podman5DownloadMachineOS = new TestPodman5DownloadMachineOS('1.0-fake', shaCheck, '/fake-directory');
|
||||
|
||||
vi.spyOn(podman5DownloadMachineOS, 'pipe').mockResolvedValue();
|
||||
|
||||
await podman5DownloadMachineOS.download();
|
||||
});
|
||||
|
||||
test('check pipe method', async () => {
|
||||
const podman5DownloadMachineOS = new TestPodman5DownloadMachineOS('1.0-fake', shaCheck, '/fake-directory');
|
||||
|
||||
const myStream = Readable.from('Hello, World!');
|
||||
|
||||
const readableStream = Readable.toWeb(myStream) as ReadableStream<Uint8Array>;
|
||||
|
||||
const writeMock = vi.fn();
|
||||
const writableStream = new WritableStream({
|
||||
write: writeMock,
|
||||
});
|
||||
|
||||
await podman5DownloadMachineOS.pipe('fake-title', 100, readableStream, writableStream);
|
||||
|
||||
// check we wrote the content
|
||||
expect(writeMock).toHaveBeenCalledWith('Hello, World!', expect.anything());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { Octokit } from 'octokit';
|
|||
import type { OctokitOptions } from '@octokit/core/dist-types/types';
|
||||
import { hashFile } from 'hasha';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Writable } from 'node:stream';
|
||||
|
||||
// to make this file a module
|
||||
export class PodmanDownload {
|
||||
|
|
@ -31,6 +32,8 @@ export class PodmanDownload {
|
|||
#downloadAndCheck: DownloadAndCheck;
|
||||
#podmanDownloadFcosImage: PodmanDownloadFcosImage;
|
||||
#podmanDownloadFedoraImage: PodmanDownloadFedoraImage;
|
||||
#podman5DownloadFedoraImage: Podman5DownloadFedoraImage | undefined;
|
||||
#podman5DownloadMachineOS: Podman5DownloadMachineOS | undefined;
|
||||
|
||||
#shaCheck: ShaCheck;
|
||||
|
||||
|
|
@ -121,6 +124,23 @@ export class PodmanDownload {
|
|||
if (!fs.existsSync(this.#assetsFolder)) {
|
||||
fs.mkdirSync(this.#assetsFolder);
|
||||
}
|
||||
|
||||
if (podmanJSON.version.startsWith('5.')) {
|
||||
// grab only first 2 digits from the version
|
||||
const majorMinorVersion = podmanJSON.version.split('.').slice(0, 2).join('.');
|
||||
|
||||
this.#podman5DownloadFedoraImage = new Podman5DownloadFedoraImage(
|
||||
majorMinorVersion,
|
||||
this.#octokit,
|
||||
this.#downloadAndCheck,
|
||||
);
|
||||
|
||||
this.#podman5DownloadMachineOS = new Podman5DownloadMachineOS(
|
||||
majorMinorVersion,
|
||||
this.#shaCheck,
|
||||
this.#assetsFolder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected getPodmanDownloadFcosImage(): PodmanDownloadFcosImage {
|
||||
|
|
@ -141,7 +161,7 @@ export class PodmanDownload {
|
|||
async downloadBinaries(): Promise<void> {
|
||||
// fetch from GitHub releases
|
||||
for (const artifact of this.#artifactsToDownload) {
|
||||
this.#downloadAndCheck.downloadAndCheckSha(artifact.version, artifact.downloadName, artifact.artifactName);
|
||||
await this.#downloadAndCheck.downloadAndCheckSha(artifact.version, artifact.downloadName, artifact.artifactName);
|
||||
}
|
||||
|
||||
// fetch optional binaries in case of AirGap
|
||||
|
|
@ -155,13 +175,19 @@ export class PodmanDownload {
|
|||
|
||||
if (this.#platform === 'win32') {
|
||||
// download the fedora image
|
||||
this.#podmanDownloadFedoraImage.download('podman-wsl-fedora', 'x64');
|
||||
this.#podmanDownloadFedoraImage.download('podman-wsl-fedora-arm', 'arm64');
|
||||
|
||||
await this.#podman5DownloadFedoraImage?.download('x64');
|
||||
await this.#podman5DownloadFedoraImage?.download('arm64');
|
||||
|
||||
await this.#podmanDownloadFedoraImage.download('podman-wsl-fedora', 'x64');
|
||||
await this.#podmanDownloadFedoraImage.download('podman-wsl-fedora-arm', 'arm64');
|
||||
} else if (this.#platform === 'darwin') {
|
||||
// download the fedora core os images for both arches
|
||||
|
||||
await this.#podmanDownloadFcosImage.download('x64');
|
||||
await this.#podmanDownloadFcosImage.download('arm64');
|
||||
|
||||
// download the podman 5 machines OS
|
||||
await this.#podman5DownloadMachineOS?.download();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -278,21 +304,21 @@ export class PodmanDownloadFcosImage {
|
|||
const destFile = path.resolve(this.#assetsFolder, filename);
|
||||
if (!fs.existsSync(destFile)) {
|
||||
// download the file from diskLocation
|
||||
console.log(`Downloading Podman package from ${diskLocation}`);
|
||||
console.log(`⚡️ Downloading Podman package from ${diskLocation}`);
|
||||
await this.httpsDownloader.downloadFile(diskLocation, destFile);
|
||||
console.log(`Downloaded to ${destFile}`);
|
||||
console.log(`📔 Downloaded to ${destFile}`);
|
||||
} else {
|
||||
console.log(`Podman image ${filename} already downloaded.`);
|
||||
console.log(`⏭️ Skipping podman image (already downloaded to ${filename})`);
|
||||
}
|
||||
|
||||
if (!(await this.#shaCheck.checkFile(destFile, sha256))) {
|
||||
console.warn(`Checksum for downloaded ${destFile} is not matching, downloading again...`);
|
||||
console.warn(`❌ Invalid checksum for downloaded ${destFile} is not matching, downloading again...`);
|
||||
fs.rmSync(destFile);
|
||||
this.#downloadAttempt++;
|
||||
// call the loop again
|
||||
this.download(arch);
|
||||
await this.download(arch);
|
||||
} else {
|
||||
console.log(`Checksum for ${filename} matched.`);
|
||||
console.log(`✅ Valid checksum for ${filename}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -338,11 +364,11 @@ export class PodmanDownloadFedoraImage {
|
|||
const destFile = path.resolve(this.#assetsFolder, filename);
|
||||
if (!fs.existsSync(destFile)) {
|
||||
// download the file from diskLocation
|
||||
console.log(`Downloading Podman package from ${artifactRelease.browser_download_url}`);
|
||||
console.log(`⚡️ Downloading Podman package from ${artifactRelease.browser_download_url}`);
|
||||
await this.#httpsDownloader.downloadFile(artifactRelease.browser_download_url, destFile);
|
||||
console.log(`Downloaded to ${destFile}`);
|
||||
console.log(`📔 Downloaded to ${destFile}`);
|
||||
} else {
|
||||
console.log(`Podman image ${filename} already downloaded.`);
|
||||
console.log(`⏭️ Skipping Windows podman image for ${arch} (already downloaded to ${filename})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -423,7 +449,7 @@ export class DownloadAndCheck {
|
|||
|
||||
const destFile = path.resolve(this.#assetsFolder, fileName);
|
||||
if (!fs.existsSync(destFile)) {
|
||||
console.log(`Downloading artifact from ${artifactRelease.browser_download_url}`);
|
||||
console.log(`⚡️ Downloading artifact from ${artifactRelease.browser_download_url}`);
|
||||
// await downloadFile(url, destFile);
|
||||
const artifactAsset = await this.#octokit.rest.repos.getReleaseAsset({
|
||||
asset_id: artifactRelease.id,
|
||||
|
|
@ -435,22 +461,227 @@ export class DownloadAndCheck {
|
|||
});
|
||||
|
||||
fs.appendFileSync(destFile, Buffer.from(artifactAsset.data as unknown as ArrayBuffer));
|
||||
console.log(`Downloaded to ${destFile}`);
|
||||
console.log(`📔 Downloaded to ${destFile}`);
|
||||
} else {
|
||||
console.log(`Artifact ${artifactRelease.browser_download_url} already downloaded.`);
|
||||
console.log(`⏭️ Skipping ${artifactName} (already downloaded)`);
|
||||
}
|
||||
|
||||
console.log(`Verifying ${fileName}...`);
|
||||
|
||||
if (!(await this.#shaCheck.checkFile(destFile, msiSha))) {
|
||||
console.warn(`Checksum for downloaded ${destFile} does not match, downloading again...`);
|
||||
console.warn(`❌ Invalid checksum for ${fileName} downloading again...`);
|
||||
fs.rmSync(destFile);
|
||||
this.#downloadAttempt++;
|
||||
this.downloadAndCheckSha(tagVersion, fileName, artifactName);
|
||||
await this.downloadAndCheckSha(tagVersion, fileName, artifactName);
|
||||
} else {
|
||||
console.log(`Checksum for ${fileName} is matching.`);
|
||||
console.log(`✅ Valid checksum for ${fileName}`);
|
||||
}
|
||||
|
||||
this.#downloadAttempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class Podman5DownloadFedoraImage {
|
||||
readonly MAX_DOWNLOAD_ATTEMPT = 3;
|
||||
#downloadAttempt = 0;
|
||||
#octokit: Octokit;
|
||||
#version: string;
|
||||
|
||||
#downloadAndCheck: DownloadAndCheck;
|
||||
|
||||
constructor(
|
||||
readonly version: string,
|
||||
readonly octokit: Octokit,
|
||||
readonly downloadAndCheck: DownloadAndCheck,
|
||||
) {
|
||||
this.#version = version;
|
||||
this.#octokit = octokit;
|
||||
this.#downloadAndCheck = downloadAndCheck;
|
||||
}
|
||||
|
||||
// For Windows binaries, grab the latest release from GitHub repository
|
||||
async download(arch: string): Promise<void> {
|
||||
if (this.#downloadAttempt >= this.MAX_DOWNLOAD_ATTEMPT) {
|
||||
console.error('Max download attempt reached, exiting...');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const owner = 'containers';
|
||||
const repo = 'podman-machine-wsl-os';
|
||||
|
||||
// now, grab the files
|
||||
const release = await this.#octokit.request('GET /repos/{owner}/{repo}/releases/latest', {
|
||||
owner,
|
||||
repo,
|
||||
});
|
||||
|
||||
let artifactArch;
|
||||
if (arch === 'x64') {
|
||||
artifactArch = 'amd64';
|
||||
} else if (arch === 'arm64') {
|
||||
artifactArch = 'arm64';
|
||||
}
|
||||
|
||||
const artifactName = `${this.#version}-rootfs-${artifactArch}.tar.zst`;
|
||||
const filename = `podman-image-${arch}.tar.zst`;
|
||||
const artifactRelease = release.data.assets.find(asset => asset.name === artifactName);
|
||||
|
||||
if (!artifactRelease) {
|
||||
throw new Error(
|
||||
`Can't find asset with name ${artifactName} to download and verify for podman image from repository ${repo}`,
|
||||
);
|
||||
}
|
||||
|
||||
// tag version
|
||||
const tagVersion = release.data.tag_name;
|
||||
await this.#downloadAndCheck.downloadAndCheckSha(tagVersion, filename, artifactName, owner, repo);
|
||||
}
|
||||
}
|
||||
|
||||
export class Podman5DownloadMachineOS {
|
||||
#version: string;
|
||||
#shaCheck: ShaCheck;
|
||||
#assetsFolder: string;
|
||||
|
||||
constructor(
|
||||
readonly version: string,
|
||||
readonly shaCheck: ShaCheck,
|
||||
readonly assetsFolder: string,
|
||||
) {
|
||||
this.#version = version;
|
||||
this.#shaCheck = shaCheck;
|
||||
this.#assetsFolder = assetsFolder;
|
||||
}
|
||||
|
||||
async getManifest(manifestUrl: string): Promise<any> {
|
||||
const response = await fetch(manifestUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'docker-distribution-api-version': 'registry/2.0',
|
||||
Accept: 'application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json',
|
||||
},
|
||||
});
|
||||
return response.json();
|
||||
}
|
||||
|
||||
protected async pipe(
|
||||
title: string,
|
||||
total: number,
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
writableStream: WritableStream<Uint8Array>,
|
||||
) {
|
||||
let loaded = 0;
|
||||
|
||||
var progress = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
loaded += chunk.length;
|
||||
|
||||
// 20 chars = 100%
|
||||
const i = Math.floor((loaded / total) * 20);
|
||||
const dots = '.'.repeat(i);
|
||||
const left = 20 - i;
|
||||
const empty = ' '.repeat(left);
|
||||
|
||||
process.stdout.write(`\r⚡️ Downloading ${title} [${dots}${empty}] ${i * 5}%`);
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
});
|
||||
|
||||
await stream.pipeThrough(progress).pipeTo(writableStream);
|
||||
}
|
||||
|
||||
async downloadZstdFromManifest(
|
||||
title: string,
|
||||
filename: string,
|
||||
layer: { digest: string; size: number },
|
||||
): Promise<void> {
|
||||
const blobURL = `https://quay.io/v2/podman/machine-os/blobs/${layer.digest}`;
|
||||
|
||||
const blobResponse = await fetch(blobURL);
|
||||
const total = layer.size;
|
||||
const outputFile = path.resolve(this.#assetsFolder, filename);
|
||||
// digest is using the format : sha256:checksum
|
||||
// extract the checksum
|
||||
const checksum = layer.digest.split(':')[1];
|
||||
|
||||
// check if the file exists and has the expected checksum
|
||||
if (fs.existsSync(outputFile)) {
|
||||
// check now the checksum
|
||||
const valid = await this.#shaCheck.checkFile(outputFile, checksum);
|
||||
if (valid) {
|
||||
console.log(`⏭️ Skipping ${title} (already downloaded to ${filename})`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const writer = fs.createWriteStream(outputFile);
|
||||
const writableStream = Writable.toWeb(writer);
|
||||
|
||||
if (!blobResponse.body) {
|
||||
throw new Error(`❌ Cannot get blob for ${title}`);
|
||||
}
|
||||
|
||||
await this.pipe(title, total, blobResponse.body, writableStream);
|
||||
|
||||
process.stdout.write(`\r📔 ${title} downloaded to ${filename}\n`);
|
||||
|
||||
// verify the checksum
|
||||
const valid = await this.#shaCheck.checkFile(outputFile, checksum);
|
||||
if (valid) {
|
||||
console.log(`✅ Valid checksum for ${filename}`);
|
||||
} else {
|
||||
throw new Error(`❌ Invalid checksum for ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
// For macOS, need to grab images from quay.io/podman/machine-os repository
|
||||
async download(): Promise<void> {
|
||||
const manifestUrl = `https://quay.io/v2/podman/machine-os/manifests/${this.#version}`;
|
||||
|
||||
// get first level of manifests
|
||||
const rootManifest = await this.getManifest(manifestUrl);
|
||||
|
||||
if (rootManifest.errors) {
|
||||
console.error(`❌ Cannot get manifest for ${manifestUrl}`, rootManifest.errors);
|
||||
throw new Error(`❌ Cannot get manifest for ${manifestUrl}`);
|
||||
}
|
||||
|
||||
const manifests = rootManifest.manifests;
|
||||
|
||||
// grab applehv as annotations / disktype
|
||||
const keepManifests = manifests.filter(manifest => {
|
||||
const annotations = manifest.annotations;
|
||||
return annotations && annotations.disktype === 'applehv';
|
||||
});
|
||||
|
||||
// should have aarch64 for arm64 and x86_64 for x64
|
||||
const amd64Manifest = keepManifests.find(
|
||||
manifest => manifest.platform.architecture === 'x86_64' && manifest.platform.os === 'linux',
|
||||
);
|
||||
const arm64Manifest = keepManifests.find(
|
||||
manifest => manifest.platform.architecture === 'aarch64' && manifest.platform.os === 'linux',
|
||||
);
|
||||
|
||||
if (!amd64Manifest || !arm64Manifest) {
|
||||
throw new Error('❌ Cannot find amd64 or arm64 manifest');
|
||||
}
|
||||
|
||||
// now get the zstd entry from the arch manifest
|
||||
const amd64ZstdManifest = await this.getManifest(
|
||||
`https://quay.io/v2/podman/machine-os/manifests/${amd64Manifest.digest}`,
|
||||
);
|
||||
const arm64ZstdManifest = await this.getManifest(
|
||||
`https://quay.io/v2/podman/machine-os/manifests/${arm64Manifest.digest}`,
|
||||
);
|
||||
|
||||
// download the zstd layers
|
||||
await this.downloadZstdFromManifest(
|
||||
`${manifestUrl} for arm64`,
|
||||
'podman-image-arm64.zst',
|
||||
arm64ZstdManifest.layers[0],
|
||||
);
|
||||
await this.downloadZstdFromManifest(
|
||||
`${manifestUrl} for amd64`,
|
||||
'podman-image-amd64.zst',
|
||||
amd64ZstdManifest.layers[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue