feat: add baseos raw image builds
This commit is contained in:
@@ -9,6 +9,7 @@ import * as plugins from './plugins.js';
|
|||||||
import type {
|
import type {
|
||||||
IBaseOsImageArtifactResult,
|
IBaseOsImageArtifactResult,
|
||||||
IBaseOsImageJob,
|
IBaseOsImageJob,
|
||||||
|
TBaseOsImageKind,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
|
|
||||||
export interface IBaseOsImageBuilderOptions {
|
export interface IBaseOsImageBuilderOptions {
|
||||||
@@ -23,22 +24,22 @@ export class BaseOsImageBuilder {
|
|||||||
artifact: IBaseOsImageArtifactResult;
|
artifact: IBaseOsImageArtifactResult;
|
||||||
logs: string[];
|
logs: string[];
|
||||||
}> {
|
}> {
|
||||||
if (jobArg.architecture === 'rpi') {
|
|
||||||
throw new Error('Raspberry Pi image builds require a raw-image preset and are not supported by the current isocreator ISO pipeline yet');
|
|
||||||
}
|
|
||||||
|
|
||||||
const logs: string[] = [];
|
const logs: string[] = [];
|
||||||
|
const imageKind = this.getImageKind(jobArg);
|
||||||
const jobDir = path.join(this.options.workdir, jobArg.id);
|
const jobDir = path.join(this.options.workdir, jobArg.id);
|
||||||
const outputDir = path.join(jobDir, 'output');
|
const outputDir = path.join(jobDir, 'output');
|
||||||
await fsp.rm(jobDir, { recursive: true, force: true });
|
await fsp.rm(jobDir, { recursive: true, force: true });
|
||||||
await fsp.mkdir(outputDir, { recursive: true });
|
await fsp.mkdir(outputDir, { recursive: true });
|
||||||
|
|
||||||
const filename = jobArg.architecture === 'amd64' ? 'baseos.iso' : 'baseos-arm64.iso';
|
const filename = this.getArtifactFilename(jobArg, imageKind);
|
||||||
const outputPath = path.join(outputDir, filename);
|
const outputPath = path.join(outputDir, filename);
|
||||||
const configPath = path.join(jobDir, 'isocreator.config.json');
|
const configPath = path.join(jobDir, 'isocreator.config.json');
|
||||||
await fsp.writeFile(configPath, `${JSON.stringify(this.createIsoCreatorConfig(jobArg, outputDir, filename), null, 2)}\n`);
|
const isoCreatorConfig = imageKind === 'balena-raw'
|
||||||
|
? this.createRawImageConfig(jobArg, outputDir, filename)
|
||||||
|
: this.createIsoCreatorConfig(jobArg, outputDir, filename);
|
||||||
|
await fsp.writeFile(configPath, `${JSON.stringify(isoCreatorConfig, null, 2)}\n`);
|
||||||
|
|
||||||
logs.push(`Starting isocreator for ${jobArg.architecture}`);
|
logs.push(`Starting isocreator for ${jobArg.architecture} ${imageKind}`);
|
||||||
await this.runIsoCreator(configPath, logs);
|
await this.runIsoCreator(configPath, logs);
|
||||||
|
|
||||||
const stat = await fsp.stat(outputPath);
|
const stat = await fsp.stat(outputPath);
|
||||||
@@ -51,7 +52,7 @@ export class BaseOsImageBuilder {
|
|||||||
bucketName: jobArg.s3Descriptor.bucketName,
|
bucketName: jobArg.s3Descriptor.bucketName,
|
||||||
key: jobArg.artifactKey,
|
key: jobArg.artifactKey,
|
||||||
filename,
|
filename,
|
||||||
contentType: 'application/x-iso9660-image',
|
contentType: this.getContentType(filename, imageKind),
|
||||||
size: stat.size,
|
size: stat.size,
|
||||||
sha256,
|
sha256,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
@@ -66,6 +67,7 @@ export class BaseOsImageBuilder {
|
|||||||
const envFile = this.createBaseOsEnvFile(jobArg);
|
const envFile = this.createBaseOsEnvFile(jobArg);
|
||||||
return {
|
return {
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
|
imageKind: 'iso',
|
||||||
...(jobArg.sourceImageUrl
|
...(jobArg.sourceImageUrl
|
||||||
? {
|
? {
|
||||||
source: {
|
source: {
|
||||||
@@ -136,6 +138,85 @@ export class BaseOsImageBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createRawImageConfig(jobArg: IBaseOsImageJob, outputDirArg: string, filenameArg: string) {
|
||||||
|
if (!jobArg.sourceImageUrl) {
|
||||||
|
throw new Error('sourceImageUrl is required for balena-raw BaseOS image builds');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: '1.0',
|
||||||
|
imageKind: 'raw-image',
|
||||||
|
source: {
|
||||||
|
type: 'url',
|
||||||
|
url: jobArg.sourceImageUrl,
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: filenameArg,
|
||||||
|
path: outputDirArg,
|
||||||
|
},
|
||||||
|
...(jobArg.wifi?.ssid
|
||||||
|
? {
|
||||||
|
network: {
|
||||||
|
wifi: jobArg.wifi,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
raw_image: {
|
||||||
|
sourceFormat: 'auto',
|
||||||
|
bootPartition: '/dev/sda1',
|
||||||
|
outputCompression: filenameArg.endsWith('.xz') ? 'xz' : 'none',
|
||||||
|
},
|
||||||
|
balena_os: {
|
||||||
|
hostname: jobArg.hostname || `baseos-${jobArg.id.slice(0, 8)}`,
|
||||||
|
sshPublicKeys: jobArg.sshPublicKey ? [jobArg.sshPublicKey] : [],
|
||||||
|
configJson: {
|
||||||
|
serveZone: {
|
||||||
|
baseos: {
|
||||||
|
buildId: jobArg.id,
|
||||||
|
cloudlyUrl: jobArg.cloudlyUrl,
|
||||||
|
provisioningToken: jobArg.provisioningToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
baseOsEnv: {
|
||||||
|
BASEOS_CLOUDLY_URL: jobArg.cloudlyUrl,
|
||||||
|
BASEOS_JOIN_TOKEN: jobArg.provisioningToken,
|
||||||
|
BASEOS_STATE_PATH: '/data/baseos/state.json',
|
||||||
|
BASEOS_HEARTBEAT_INTERVAL_MS: '60000',
|
||||||
|
SERVEZONE_RUNTIME: 'baseos',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getImageKind(jobArg: IBaseOsImageJob): TBaseOsImageKind {
|
||||||
|
if (jobArg.imageKind) {
|
||||||
|
return jobArg.imageKind;
|
||||||
|
}
|
||||||
|
if (jobArg.architecture === 'rpi') {
|
||||||
|
return 'balena-raw';
|
||||||
|
}
|
||||||
|
if (jobArg.sourceImageUrl && /\.(img|img\.xz|zip)(\?|$)/i.test(jobArg.sourceImageUrl)) {
|
||||||
|
return 'balena-raw';
|
||||||
|
}
|
||||||
|
return 'ubuntu-iso';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getArtifactFilename(jobArg: IBaseOsImageJob, imageKindArg: TBaseOsImageKind) {
|
||||||
|
const architectureSuffix = jobArg.architecture === 'amd64' ? '' : `-${jobArg.architecture}`;
|
||||||
|
if (imageKindArg === 'balena-raw') {
|
||||||
|
return `baseos${architectureSuffix}.img.xz`;
|
||||||
|
}
|
||||||
|
return `baseos${architectureSuffix}.iso`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getContentType(filenameArg: string, imageKindArg: TBaseOsImageKind) {
|
||||||
|
if (imageKindArg === 'ubuntu-iso') {
|
||||||
|
return 'application/x-iso9660-image';
|
||||||
|
}
|
||||||
|
return filenameArg.endsWith('.xz') ? 'application/x-xz' : 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
private createBaseOsEnvFile(jobArg: IBaseOsImageJob) {
|
private createBaseOsEnvFile(jobArg: IBaseOsImageJob) {
|
||||||
return [
|
return [
|
||||||
`BASEOS_CLOUDLY_URL=${this.escapeEnvValue(jobArg.cloudlyUrl)}`,
|
`BASEOS_CLOUDLY_URL=${this.escapeEnvValue(jobArg.cloudlyUrl)}`,
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ export class CoreBuildServer {
|
|||||||
return {
|
return {
|
||||||
workerId: this.options.workerId,
|
workerId: this.options.workerId,
|
||||||
supportedBuildTypes: ['baseos-image'],
|
supportedBuildTypes: ['baseos-image'],
|
||||||
supportedArchitectures: ['amd64', 'arm64'],
|
supportedArchitectures: ['amd64', 'arm64', 'rpi'],
|
||||||
|
supportedImageKinds: ['ubuntu-iso', 'balena-raw'],
|
||||||
cpuCores: os.cpus().length,
|
cpuCores: os.cpus().length,
|
||||||
memoryGb: Math.round(os.totalmem() / 1024 / 1024 / 1024),
|
memoryGb: Math.round(os.totalmem() / 1024 / 1024 / 1024),
|
||||||
workdir: this.options.workdir,
|
workdir: this.options.workdir,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Readable } from 'node:stream';
|
import type { Readable } from 'node:stream';
|
||||||
|
|
||||||
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
|
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
|
||||||
|
export type TBaseOsImageKind = 'ubuntu-iso' | 'balena-raw';
|
||||||
|
|
||||||
export interface IS3Descriptor {
|
export interface IS3Descriptor {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
@@ -15,6 +16,7 @@ export interface IS3Descriptor {
|
|||||||
export interface IBaseOsImageJob {
|
export interface IBaseOsImageJob {
|
||||||
id: string;
|
id: string;
|
||||||
architecture: TBaseOsImageArchitecture;
|
architecture: TBaseOsImageArchitecture;
|
||||||
|
imageKind?: TBaseOsImageKind;
|
||||||
cloudlyUrl: string;
|
cloudlyUrl: string;
|
||||||
provisioningToken: string;
|
provisioningToken: string;
|
||||||
sourceImageUrl?: string;
|
sourceImageUrl?: string;
|
||||||
@@ -55,6 +57,7 @@ export interface ICoreBuildCapabilities {
|
|||||||
workerId: string;
|
workerId: string;
|
||||||
supportedBuildTypes: string[];
|
supportedBuildTypes: string[];
|
||||||
supportedArchitectures: TBaseOsImageArchitecture[];
|
supportedArchitectures: TBaseOsImageArchitecture[];
|
||||||
|
supportedImageKinds: TBaseOsImageKind[];
|
||||||
cpuCores: number;
|
cpuCores: number;
|
||||||
memoryGb?: number;
|
memoryGb?: number;
|
||||||
workdir: string;
|
workdir: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user