mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-04-21 17:47:22 +00:00
feat: add airgap binaries embedding the Podman images (#1120)
- on mac, it embeds the qemu FCOS binaries - on Windows, it embeds the WSL binary When initializing a new podman machine using airgap binaries it'll use the embedded files and not grab it from internet There is also an optional way to provide a custom image in the create menu Change-Id: I0b250ae2e018ff56eda1d2170fd91ab8cfc52eae Signed-off-by: Florent Benoit <fbenoit@redhat.com> Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
parent
3c4fbb3fb6
commit
f6245907a5
5 changed files with 223 additions and 7 deletions
|
|
@ -26,6 +26,15 @@ if (process.env.VITE_APP_VERSION === undefined) {
|
|||
}`;
|
||||
}
|
||||
|
||||
let macosArches = ['x64', 'arm64', 'universal'];
|
||||
let artifactNameSuffix = '';
|
||||
if (process.env.AIRGAP_DOWNLOAD) {
|
||||
artifactNameSuffix = '-airgap';
|
||||
// Create only one universal build for airgap mode
|
||||
macosArches = ['universal'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
|
|
@ -65,10 +74,10 @@ const config = {
|
|||
},
|
||||
files: ['packages/**/dist/**', 'extensions/**/builtin/*.cdix/**'],
|
||||
portable: {
|
||||
artifactName: 'podman-desktop-${version}.${ext}',
|
||||
artifactName: `podman-desktop${artifactNameSuffix}-\${version}.\${ext}`,
|
||||
},
|
||||
nsis: {
|
||||
artifactName: 'podman-desktop-${version}-setup.${ext}',
|
||||
artifactName: `podman-desktop${artifactNameSuffix}-\${version}-setup.\${ext}`,
|
||||
},
|
||||
win: {
|
||||
target: ['portable', 'nsis'],
|
||||
|
|
@ -110,12 +119,12 @@ const config = {
|
|||
},
|
||||
afterSign: 'electron-builder-notarize',
|
||||
mac: {
|
||||
artifactName: 'podman-desktop-${version}-${arch}.${ext}',
|
||||
artifactName: `podman-desktop${artifactNameSuffix}-\${version}-\${arch}.\${ext}`,
|
||||
hardenedRuntime: true,
|
||||
entitlements: './node_modules/electron-builder-notarize/entitlements.mac.inherit.plist',
|
||||
target: {
|
||||
target: 'default',
|
||||
arch: ['x64', 'arm64', 'universal'],
|
||||
arch: macosArches,
|
||||
},
|
||||
},
|
||||
dmg: {
|
||||
|
|
@ -142,6 +151,14 @@ const config = {
|
|||
},*/
|
||||
};
|
||||
|
||||
// do not publish auto-update files for airgap mode
|
||||
if (process.env.AIRGAP_DOWNLOAD) {
|
||||
config.publish = {
|
||||
publishAutoUpdate: false,
|
||||
provider: 'github'
|
||||
};
|
||||
}
|
||||
|
||||
const azureCodeSign = filePath => {
|
||||
if (!process.env.AZURE_KEY_VAULT_URL) {
|
||||
console.log('Skipping code signing, no environment variables set for that.');
|
||||
|
|
|
|||
13
.github/workflows/release.yaml
vendored
13
.github/workflows/release.yaml
vendored
|
|
@ -119,12 +119,19 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.PODMAN_DESKTOP_BOT_TOKEN }}
|
||||
|
||||
build:
|
||||
name: Build / ${{ matrix.os }}
|
||||
name: Build / ${{ matrix.os }} ${{ matrix.airgap == 'true' && '(Air Gap)' || '' }}
|
||||
needs: tag
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-2022, ubuntu-20.04, macos-11]
|
||||
include:
|
||||
- os: "windows-2022"
|
||||
- os: "windows-2022"
|
||||
airgap: "true"
|
||||
- os: "macos-11"
|
||||
- os: "macos-11"
|
||||
airgap: "true"
|
||||
- os: "ubuntu-20.04"
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -206,6 +213,8 @@ jobs:
|
|||
|
||||
- name: Run Build
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
AIRGAP_DOWNLOAD: ${{ matrix.airgap == 'true' && '1' || '' }}
|
||||
run: yarn compile:next
|
||||
|
||||
release:
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@
|
|||
"default": 100,
|
||||
"scope": "ContainerProviderConnectionFactory",
|
||||
"description": "Disk size (GB)"
|
||||
},
|
||||
"podman.factory.machine.image-path": {
|
||||
"type": "string",
|
||||
"format": "file",
|
||||
"scope": "ContainerProviderConnectionFactory",
|
||||
"description": "Image Path (Optional)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as https from 'node:https';
|
||||
import { Octokit } from 'octokit';
|
||||
import type { OctokitOptions } from '@octokit/core/dist-types/types';
|
||||
import * as hasha from 'hasha';
|
||||
|
|
@ -29,6 +30,8 @@ const platform = process.platform;
|
|||
|
||||
const MAX_DOWNLOAD_ATTEMPT = 3;
|
||||
let downloadAttempt = 0;
|
||||
let downloadAttemptFcos = 0;
|
||||
const downloadAttemptFedora = 0;
|
||||
|
||||
const octokitOptions: OctokitOptions = {};
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
|
|
@ -130,6 +133,155 @@ async function downloadAndCheckSha(tagVersion: string, fileName: string, artifac
|
|||
}
|
||||
}
|
||||
|
||||
// grab the JSON from the given URL
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async function httpsJsonRequest(url): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, res => {
|
||||
let body = '';
|
||||
|
||||
res.on('data', chunk => {
|
||||
body += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(body);
|
||||
resolve(json);
|
||||
} catch (error) {
|
||||
reject(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
res.on('error', error => {
|
||||
reject(error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// download the file from the given URL and store the content in destFile
|
||||
async function httpsDownloadFile(url, destFile): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.get(url, async res => {
|
||||
// handle url redirect
|
||||
if (res.statusCode === 302) {
|
||||
await httpsDownloadFile(res.headers.location, destFile);
|
||||
resolve();
|
||||
}
|
||||
|
||||
res.on('data', data => {
|
||||
fs.appendFileSync(destFile, data);
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
res.on('error', error => {
|
||||
reject(error.message);
|
||||
});
|
||||
});
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
// For Windows binaries, grab the latest release from GitHub repository
|
||||
async function downloadFedoraImage(repo: string): Promise<void> {
|
||||
if (downloadAttemptFedora >= MAX_DOWNLOAD_ATTEMPT) {
|
||||
console.error('Max download attempt reached, exiting...');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// download the file and check the sha256
|
||||
|
||||
const destDir = path.resolve(__dirname, '..', 'assets');
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir);
|
||||
}
|
||||
|
||||
// now, grab the files
|
||||
|
||||
const release = await octokit.request('GET /repos/{owner}/{repo}/releases/latest', {
|
||||
owner: 'containers',
|
||||
repo,
|
||||
});
|
||||
|
||||
const artifactRelease = release.data.assets.find(asset => asset.name === 'rootfs.tar.xz');
|
||||
|
||||
if (!artifactRelease) {
|
||||
throw new Error(`Can't find assets to download and verify for podman image from repository ${repo}`);
|
||||
}
|
||||
|
||||
const arch = 'x64';
|
||||
const filename = `podman-image-${arch}.tar.xz`;
|
||||
const destFile = path.resolve(destDir, filename);
|
||||
if (!fs.existsSync(destFile)) {
|
||||
// download the file from diskLocation
|
||||
console.log(`Downloading Podman package from ${artifactRelease.browser_download_url}`);
|
||||
await httpsDownloadFile(artifactRelease.browser_download_url, destFile);
|
||||
console.log(`Downloaded to ${destFile}`);
|
||||
} else {
|
||||
console.log(`Podman image ${filename} already downloaded.`);
|
||||
}
|
||||
}
|
||||
|
||||
// For macOS, grab the qemu image from Fedora CoreOS
|
||||
async function downloadFCosImage(arch: string): Promise<void> {
|
||||
if (downloadAttemptFcos >= MAX_DOWNLOAD_ATTEMPT) {
|
||||
console.error('Max download attempt reached, exiting...');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// download the JSON of testing Fedora CoreOS images at https://builds.coreos.fedoraproject.org/streams/testing.json
|
||||
const data = await httpsJsonRequest('https://builds.coreos.fedoraproject.org/streams/testing.json');
|
||||
|
||||
// get the file to download
|
||||
const qemuArch = arch === 'x64' ? 'x86_64' : 'aarch64';
|
||||
const qemuData = data['architectures'][qemuArch]['artifacts']['qemu'];
|
||||
|
||||
// get disk object
|
||||
const disk = qemuData.formats['qcow2.xz'].disk;
|
||||
|
||||
// get the disk location
|
||||
const diskLocation = disk.location;
|
||||
|
||||
// get the sha2556
|
||||
const sha256 = disk.sha256;
|
||||
|
||||
// download the file and check the sha256
|
||||
|
||||
const destDir = path.resolve(__dirname, '..', 'assets');
|
||||
if (!fs.existsSync(destDir)) {
|
||||
fs.mkdirSync(destDir);
|
||||
}
|
||||
|
||||
const filename = `podman-image-${arch}.qcow2.xz`;
|
||||
const destFile = path.resolve(destDir, filename);
|
||||
if (!fs.existsSync(destFile)) {
|
||||
// download the file from diskLocation
|
||||
console.log(`Downloading Podman package from ${diskLocation}`);
|
||||
await httpsDownloadFile(diskLocation, destFile);
|
||||
console.log(`Downloaded to ${destFile}`);
|
||||
} else {
|
||||
console.log(`Podman image ${filename} already downloaded.`);
|
||||
}
|
||||
|
||||
if (!(await checkFileSha(destFile, sha256))) {
|
||||
console.warn(`Checksum for downloaded ${destFile} is not matching, downloading again...`);
|
||||
fs.rmSync(destFile);
|
||||
downloadAttemptFcos++;
|
||||
// call the loop again
|
||||
downloadFCosImage(arch);
|
||||
} else {
|
||||
console.log(`Checksum for ${filename} matched.`);
|
||||
}
|
||||
}
|
||||
|
||||
let tagVersion: string, dlName: string;
|
||||
|
||||
if (platform === 'win32') {
|
||||
|
|
@ -138,6 +290,11 @@ if (platform === 'win32') {
|
|||
// eslint-disable-next-line prefer-const
|
||||
dlName = tools.platform.win32.fileName;
|
||||
downloadAndCheckSha(tagVersion, dlName, `podman-${tools.version}-setup.exe`);
|
||||
|
||||
// download the fedora image
|
||||
if (process.env.AIRGAP_DOWNLOAD) {
|
||||
downloadFedoraImage('podman-wsl-fedora');
|
||||
}
|
||||
} else if (platform === 'darwin') {
|
||||
tagVersion = tools.platform.darwin.version;
|
||||
dlName = tools.platform.darwin.arch.x64.fileName;
|
||||
|
|
@ -145,4 +302,11 @@ if (platform === 'win32') {
|
|||
|
||||
dlName = tools.platform.darwin.arch.arm64.fileName;
|
||||
downloadAndCheckSha(tagVersion, dlName, 'podman-installer-macos-aarch64.pkg');
|
||||
|
||||
// download the current testing image if AIRGAP_DOWNLOAD env variable is set
|
||||
if (process.env.AIRGAP_DOWNLOAD) {
|
||||
// download the fedora core os images for both arches
|
||||
downloadFCosImage('x64');
|
||||
downloadFCosImage('arm64');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import * as fs from 'node:fs';
|
|||
import { spawn } from 'node:child_process';
|
||||
import { RegistrySetup } from './registry-setup';
|
||||
|
||||
import { isLinux, isMac, isWindows } from './util';
|
||||
import { getAssetsFolder, isLinux, isMac, isWindows } from './util';
|
||||
import { PodmanInstall } from './podman-install';
|
||||
import type { InstalledPodman } from './podman-cli';
|
||||
import { execPromise, getPodmanCli, getPodmanInstallation } from './podman-cli';
|
||||
|
|
@ -425,6 +425,26 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
|
|||
parameters.push(params['podman.factory.machine.diskSize']);
|
||||
}
|
||||
|
||||
// disk size
|
||||
if (params['podman.factory.machine.image-path']) {
|
||||
parameters.push('--image-path');
|
||||
parameters.push(params['podman.factory.machine.image-path']);
|
||||
} else if (isMac || isWindows) {
|
||||
// check if we have an embedded asset for the image path for macOS or Windows
|
||||
let suffix = '';
|
||||
if (isWindows) {
|
||||
suffix = `-${process.arch}.tar.xz`;
|
||||
} else if (isMac) {
|
||||
suffix = `-${process.arch}.qcow2.xz`;
|
||||
}
|
||||
const assetImagePath = path.resolve(getAssetsFolder(), `podman-image${suffix}`);
|
||||
// check if the file exists and if it does, use it
|
||||
if (fs.existsSync(assetImagePath)) {
|
||||
parameters.push('--image-path');
|
||||
parameters.push(assetImagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// name at the end
|
||||
if (params['podman.factory.machine.name']) {
|
||||
parameters.push(params['podman.factory.machine.name']);
|
||||
|
|
|
|||
Loading…
Reference in a new issue