feat: add baseos source presets
This commit is contained in:
@@ -15,8 +15,29 @@ import {
|
||||
type IBaseOsImageBuildPublic,
|
||||
type TBaseOsImageArchitecture,
|
||||
type TBaseOsImageKind,
|
||||
type TBaseOsImageSourcePreset,
|
||||
} from './classes.baseosimagebuild.js';
|
||||
|
||||
interface IBalenaSourcePreset {
|
||||
preset: TBaseOsImageSourcePreset;
|
||||
architecture: TBaseOsImageArchitecture;
|
||||
}
|
||||
|
||||
const balenaSourcePresets: IBalenaSourcePreset[] = [
|
||||
{
|
||||
preset: 'balena-generic-amd64',
|
||||
architecture: 'amd64',
|
||||
},
|
||||
{
|
||||
preset: 'balena-generic-aarch64',
|
||||
architecture: 'arm64',
|
||||
},
|
||||
{
|
||||
preset: 'balena-raspberrypi4-64',
|
||||
architecture: 'rpi',
|
||||
},
|
||||
];
|
||||
|
||||
interface IBaseOsRegisterRequest {
|
||||
joinToken?: string;
|
||||
nodeToken?: string;
|
||||
@@ -28,6 +49,7 @@ interface IBaseOsRegisterResponse {
|
||||
nodeToken?: string;
|
||||
accepted: boolean;
|
||||
message?: string;
|
||||
desiredState?: IBaseOsDesiredState;
|
||||
}
|
||||
|
||||
interface IBaseOsHeartbeatRequest {
|
||||
@@ -51,11 +73,25 @@ interface IRequestGetBaseOsNodes {
|
||||
};
|
||||
}
|
||||
|
||||
interface IRequestSetBaseOsNodeDesiredState {
|
||||
method: 'setBaseOsNodeDesiredState';
|
||||
request: {
|
||||
identity: plugins.servezoneInterfaces.data.IIdentity;
|
||||
nodeId: string;
|
||||
desiredState: IBaseOsDesiredState;
|
||||
};
|
||||
response: {
|
||||
node: IBaseOsNodePublic;
|
||||
};
|
||||
}
|
||||
|
||||
interface IBaseOsImageBuildRequest {
|
||||
architecture: TBaseOsImageArchitecture;
|
||||
imageKind?: TBaseOsImageKind;
|
||||
cloudlyUrl?: string;
|
||||
sourceImageUrl?: string;
|
||||
sourceImagePreset?: TBaseOsImageSourcePreset;
|
||||
balenaOsVersion?: string;
|
||||
ubuntuVersion?: string;
|
||||
hostname?: string;
|
||||
wifi?: {
|
||||
@@ -122,6 +158,7 @@ interface ICoreBuildCapabilitiesResponse {
|
||||
supportedBuildTypes: string[];
|
||||
supportedArchitectures: TBaseOsImageArchitecture[];
|
||||
supportedImageKinds?: TBaseOsImageKind[];
|
||||
supportedSourcePresets?: TBaseOsImageSourcePreset[];
|
||||
}
|
||||
|
||||
interface ICoreBuildWorkerSetting {
|
||||
@@ -180,6 +217,24 @@ export class CloudlyBaseOsManager {
|
||||
),
|
||||
);
|
||||
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<IRequestSetBaseOsNodeDesiredState>(
|
||||
'setBaseOsNodeDesiredState',
|
||||
async (requestDataArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(
|
||||
{ identity: requestDataArg.identity },
|
||||
[this.cloudlyRef.authManager.adminIdentityGuard],
|
||||
);
|
||||
return {
|
||||
node: (await this.setNodeDesiredState(
|
||||
requestDataArg.nodeId,
|
||||
requestDataArg.desiredState,
|
||||
)).toPublicNode(),
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<IRequestGetBaseOsImageBuilds>(
|
||||
'getBaseOsImageBuilds',
|
||||
@@ -335,6 +390,7 @@ export class CloudlyBaseOsManager {
|
||||
return {
|
||||
accepted: true,
|
||||
nodeId: existingNode.id,
|
||||
desiredState: existingNode.data.desiredState || {},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -347,6 +403,7 @@ export class CloudlyBaseOsManager {
|
||||
accepted: true,
|
||||
nodeId: node.id,
|
||||
nodeToken,
|
||||
desiredState: node.data.desiredState || {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -371,6 +428,7 @@ export class CloudlyBaseOsManager {
|
||||
accepted: true,
|
||||
nodeId: node.id,
|
||||
nodeToken,
|
||||
desiredState: node.data.desiredState || {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -410,6 +468,23 @@ export class CloudlyBaseOsManager {
|
||||
return nodes.map((nodeArg) => nodeArg.toPublicNode());
|
||||
}
|
||||
|
||||
public async setNodeDesiredState(nodeIdArg: string, desiredStateArg: IBaseOsDesiredState) {
|
||||
const node = await this.CBaseOsNode.getInstance({ id: nodeIdArg });
|
||||
if (!node) {
|
||||
throw new plugins.typedrequest.TypedResponseError(`BaseOS node ${nodeIdArg} not found`);
|
||||
}
|
||||
node.data = {
|
||||
...node.data,
|
||||
desiredState: {
|
||||
...desiredStateArg,
|
||||
updatedAt: desiredStateArg.updatedAt || Date.now(),
|
||||
},
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
await node.save();
|
||||
return node;
|
||||
}
|
||||
|
||||
public async createImageBuild(buildRequestArg: IBaseOsImageBuildRequest) {
|
||||
const s3Descriptor = this.cloudlyRef.config.data.s3Descriptor;
|
||||
if (!s3Descriptor?.bucketName) {
|
||||
@@ -417,10 +492,14 @@ export class CloudlyBaseOsManager {
|
||||
}
|
||||
|
||||
const imageKind = this.getImageKind(buildRequestArg);
|
||||
if (imageKind === 'balena-raw' && !buildRequestArg.sourceImageUrl) {
|
||||
throw new plugins.typedrequest.TypedResponseError('sourceImageUrl is required for balena-raw BaseOS image builds');
|
||||
if (buildRequestArg.architecture === 'rpi' && imageKind === 'ubuntu-iso') {
|
||||
throw new plugins.typedrequest.TypedResponseError('Raspberry Pi BaseOS images require balena-raw image builds');
|
||||
}
|
||||
const worker = await this.selectCoreBuildWorker(buildRequestArg.architecture, imageKind);
|
||||
const sourceImagePreset = this.getSourceImagePreset(buildRequestArg, imageKind);
|
||||
const balenaOsVersion = imageKind === 'balena-raw' && !buildRequestArg.sourceImageUrl
|
||||
? buildRequestArg.balenaOsVersion?.trim() || 'latest'
|
||||
: undefined;
|
||||
const worker = await this.selectCoreBuildWorker(buildRequestArg.architecture, imageKind, sourceImagePreset);
|
||||
|
||||
const now = Date.now();
|
||||
const buildId = await this.CBaseOsImageBuild.getNewId();
|
||||
@@ -435,6 +514,8 @@ export class CloudlyBaseOsManager {
|
||||
imageKind,
|
||||
cloudlyUrl: buildRequestArg.cloudlyUrl || this.getPublicCloudlyUrl(),
|
||||
sourceImageUrl: buildRequestArg.sourceImageUrl,
|
||||
sourceImagePreset,
|
||||
balenaOsVersion,
|
||||
ubuntuVersion: buildRequestArg.ubuntuVersion || '24.04',
|
||||
hostname: buildRequestArg.hostname,
|
||||
wifiSsid: buildRequestArg.wifi?.ssid,
|
||||
@@ -596,6 +677,8 @@ export class CloudlyBaseOsManager {
|
||||
cloudlyUrl: buildArg.data.cloudlyUrl,
|
||||
provisioningToken: provisioningTokenArg,
|
||||
sourceImageUrl: buildArg.data.sourceImageUrl,
|
||||
sourceImagePreset: buildArg.data.sourceImagePreset,
|
||||
balenaOsVersion: buildArg.data.balenaOsVersion,
|
||||
ubuntuVersion: buildArg.data.ubuntuVersion,
|
||||
hostname: buildArg.data.hostname,
|
||||
wifi: buildArg.data.wifiSsid
|
||||
@@ -632,6 +715,7 @@ export class CloudlyBaseOsManager {
|
||||
private async selectCoreBuildWorker(
|
||||
architectureArg: TBaseOsImageArchitecture,
|
||||
imageKindArg: TBaseOsImageKind,
|
||||
sourceImagePresetArg?: TBaseOsImageSourcePreset,
|
||||
): Promise<ISelectedCoreBuildWorker> {
|
||||
const workers = await this.getConfiguredCoreBuildWorkers();
|
||||
if (workers.length === 0) {
|
||||
@@ -656,6 +740,10 @@ export class CloudlyBaseOsManager {
|
||||
rejectionReasons.push(`${workerLabel}: missing ${imageKindArg} support`);
|
||||
continue;
|
||||
}
|
||||
if (sourceImagePresetArg && !capabilities.supportedSourcePresets?.includes(sourceImagePresetArg)) {
|
||||
rejectionReasons.push(`${workerLabel}: missing ${sourceImagePresetArg} source preset support`);
|
||||
continue;
|
||||
}
|
||||
return {
|
||||
...worker,
|
||||
capabilities,
|
||||
@@ -738,12 +826,58 @@ export class CloudlyBaseOsManager {
|
||||
if (buildRequestArg.architecture === 'rpi') {
|
||||
return 'balena-raw';
|
||||
}
|
||||
if (buildRequestArg.sourceImageUrl && /\.(img|img\.xz|zip)(\?|$)/i.test(buildRequestArg.sourceImageUrl)) {
|
||||
if (buildRequestArg.sourceImagePreset || buildRequestArg.balenaOsVersion) {
|
||||
return 'balena-raw';
|
||||
}
|
||||
if (buildRequestArg.sourceImageUrl && this.isRawImageUrl(buildRequestArg.sourceImageUrl)) {
|
||||
return 'balena-raw';
|
||||
}
|
||||
return 'ubuntu-iso';
|
||||
}
|
||||
|
||||
private getSourceImagePreset(
|
||||
buildRequestArg: IBaseOsImageBuildRequest,
|
||||
imageKindArg: TBaseOsImageKind,
|
||||
) {
|
||||
if (imageKindArg === 'ubuntu-iso') {
|
||||
if (buildRequestArg.sourceImagePreset || buildRequestArg.balenaOsVersion) {
|
||||
throw new plugins.typedrequest.TypedResponseError('balenaOS source presets only apply to balena-raw builds');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (buildRequestArg.sourceImageUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const preset = buildRequestArg.sourceImagePreset
|
||||
? balenaSourcePresets.find((presetArg) => presetArg.preset === buildRequestArg.sourceImagePreset)
|
||||
: balenaSourcePresets.find((presetArg) => presetArg.architecture === buildRequestArg.architecture);
|
||||
if (!preset) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
`No balenaOS source preset is available for ${buildRequestArg.architecture}`,
|
||||
);
|
||||
}
|
||||
if (preset.architecture !== buildRequestArg.architecture) {
|
||||
throw new plugins.typedrequest.TypedResponseError(
|
||||
`${preset.preset} is only valid for ${preset.architecture} BaseOS images`,
|
||||
);
|
||||
}
|
||||
return preset.preset;
|
||||
}
|
||||
|
||||
private isRawImageUrl(sourceImageUrlArg: string) {
|
||||
if (/\.(img|img\.xz|zip)(\?|$)/i.test(sourceImageUrlArg)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const sourceUrl = new URL(sourceImageUrlArg);
|
||||
return sourceUrl.searchParams.get('fileType') === '.zip';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private getArtifactFilename(
|
||||
architectureArg: TBaseOsImageArchitecture,
|
||||
imageKindArg: TBaseOsImageKind,
|
||||
|
||||
Reference in New Issue
Block a user