feat: add baseos source presets
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/cloudly",
|
"name": "@serve.zone/cloudly",
|
||||||
"version": "5.3.0",
|
"version": "5.4.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
|
"description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import * as plugins from '../plugins.js';
|
|||||||
|
|
||||||
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
|
export type TBaseOsImageArchitecture = 'amd64' | 'arm64' | 'rpi';
|
||||||
export type TBaseOsImageKind = 'ubuntu-iso' | 'balena-raw';
|
export type TBaseOsImageKind = 'ubuntu-iso' | 'balena-raw';
|
||||||
|
export type TBaseOsImageSourcePreset =
|
||||||
|
| 'balena-generic-amd64'
|
||||||
|
| 'balena-generic-aarch64'
|
||||||
|
| 'balena-raspberrypi4-64';
|
||||||
export type TBaseOsImageBuildStatus = 'queued' | 'building' | 'ready' | 'failed' | 'cancelled';
|
export type TBaseOsImageBuildStatus = 'queued' | 'building' | 'ready' | 'failed' | 'cancelled';
|
||||||
|
|
||||||
export interface IBaseOsImageArtifact {
|
export interface IBaseOsImageArtifact {
|
||||||
@@ -22,6 +26,8 @@ export interface IBaseOsImageBuildPublic {
|
|||||||
imageKind?: TBaseOsImageKind;
|
imageKind?: TBaseOsImageKind;
|
||||||
cloudlyUrl: string;
|
cloudlyUrl: string;
|
||||||
sourceImageUrl?: string;
|
sourceImageUrl?: string;
|
||||||
|
sourceImagePreset?: TBaseOsImageSourcePreset;
|
||||||
|
balenaOsVersion?: string;
|
||||||
ubuntuVersion?: string;
|
ubuntuVersion?: string;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
wifiSsid?: string;
|
wifiSsid?: string;
|
||||||
|
|||||||
@@ -15,8 +15,29 @@ import {
|
|||||||
type IBaseOsImageBuildPublic,
|
type IBaseOsImageBuildPublic,
|
||||||
type TBaseOsImageArchitecture,
|
type TBaseOsImageArchitecture,
|
||||||
type TBaseOsImageKind,
|
type TBaseOsImageKind,
|
||||||
|
type TBaseOsImageSourcePreset,
|
||||||
} from './classes.baseosimagebuild.js';
|
} 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 {
|
interface IBaseOsRegisterRequest {
|
||||||
joinToken?: string;
|
joinToken?: string;
|
||||||
nodeToken?: string;
|
nodeToken?: string;
|
||||||
@@ -28,6 +49,7 @@ interface IBaseOsRegisterResponse {
|
|||||||
nodeToken?: string;
|
nodeToken?: string;
|
||||||
accepted: boolean;
|
accepted: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
desiredState?: IBaseOsDesiredState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IBaseOsHeartbeatRequest {
|
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 {
|
interface IBaseOsImageBuildRequest {
|
||||||
architecture: TBaseOsImageArchitecture;
|
architecture: TBaseOsImageArchitecture;
|
||||||
imageKind?: TBaseOsImageKind;
|
imageKind?: TBaseOsImageKind;
|
||||||
cloudlyUrl?: string;
|
cloudlyUrl?: string;
|
||||||
sourceImageUrl?: string;
|
sourceImageUrl?: string;
|
||||||
|
sourceImagePreset?: TBaseOsImageSourcePreset;
|
||||||
|
balenaOsVersion?: string;
|
||||||
ubuntuVersion?: string;
|
ubuntuVersion?: string;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
wifi?: {
|
wifi?: {
|
||||||
@@ -122,6 +158,7 @@ interface ICoreBuildCapabilitiesResponse {
|
|||||||
supportedBuildTypes: string[];
|
supportedBuildTypes: string[];
|
||||||
supportedArchitectures: TBaseOsImageArchitecture[];
|
supportedArchitectures: TBaseOsImageArchitecture[];
|
||||||
supportedImageKinds?: TBaseOsImageKind[];
|
supportedImageKinds?: TBaseOsImageKind[];
|
||||||
|
supportedSourcePresets?: TBaseOsImageSourcePreset[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICoreBuildWorkerSetting {
|
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(
|
this.typedRouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<IRequestGetBaseOsImageBuilds>(
|
new plugins.typedrequest.TypedHandler<IRequestGetBaseOsImageBuilds>(
|
||||||
'getBaseOsImageBuilds',
|
'getBaseOsImageBuilds',
|
||||||
@@ -335,6 +390,7 @@ export class CloudlyBaseOsManager {
|
|||||||
return {
|
return {
|
||||||
accepted: true,
|
accepted: true,
|
||||||
nodeId: existingNode.id,
|
nodeId: existingNode.id,
|
||||||
|
desiredState: existingNode.data.desiredState || {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,6 +403,7 @@ export class CloudlyBaseOsManager {
|
|||||||
accepted: true,
|
accepted: true,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
nodeToken,
|
nodeToken,
|
||||||
|
desiredState: node.data.desiredState || {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,6 +428,7 @@ export class CloudlyBaseOsManager {
|
|||||||
accepted: true,
|
accepted: true,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
nodeToken,
|
nodeToken,
|
||||||
|
desiredState: node.data.desiredState || {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,6 +468,23 @@ export class CloudlyBaseOsManager {
|
|||||||
return nodes.map((nodeArg) => nodeArg.toPublicNode());
|
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) {
|
public async createImageBuild(buildRequestArg: IBaseOsImageBuildRequest) {
|
||||||
const s3Descriptor = this.cloudlyRef.config.data.s3Descriptor;
|
const s3Descriptor = this.cloudlyRef.config.data.s3Descriptor;
|
||||||
if (!s3Descriptor?.bucketName) {
|
if (!s3Descriptor?.bucketName) {
|
||||||
@@ -417,10 +492,14 @@ export class CloudlyBaseOsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imageKind = this.getImageKind(buildRequestArg);
|
const imageKind = this.getImageKind(buildRequestArg);
|
||||||
if (imageKind === 'balena-raw' && !buildRequestArg.sourceImageUrl) {
|
if (buildRequestArg.architecture === 'rpi' && imageKind === 'ubuntu-iso') {
|
||||||
throw new plugins.typedrequest.TypedResponseError('sourceImageUrl is required for balena-raw BaseOS image builds');
|
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 now = Date.now();
|
||||||
const buildId = await this.CBaseOsImageBuild.getNewId();
|
const buildId = await this.CBaseOsImageBuild.getNewId();
|
||||||
@@ -435,6 +514,8 @@ export class CloudlyBaseOsManager {
|
|||||||
imageKind,
|
imageKind,
|
||||||
cloudlyUrl: buildRequestArg.cloudlyUrl || this.getPublicCloudlyUrl(),
|
cloudlyUrl: buildRequestArg.cloudlyUrl || this.getPublicCloudlyUrl(),
|
||||||
sourceImageUrl: buildRequestArg.sourceImageUrl,
|
sourceImageUrl: buildRequestArg.sourceImageUrl,
|
||||||
|
sourceImagePreset,
|
||||||
|
balenaOsVersion,
|
||||||
ubuntuVersion: buildRequestArg.ubuntuVersion || '24.04',
|
ubuntuVersion: buildRequestArg.ubuntuVersion || '24.04',
|
||||||
hostname: buildRequestArg.hostname,
|
hostname: buildRequestArg.hostname,
|
||||||
wifiSsid: buildRequestArg.wifi?.ssid,
|
wifiSsid: buildRequestArg.wifi?.ssid,
|
||||||
@@ -596,6 +677,8 @@ export class CloudlyBaseOsManager {
|
|||||||
cloudlyUrl: buildArg.data.cloudlyUrl,
|
cloudlyUrl: buildArg.data.cloudlyUrl,
|
||||||
provisioningToken: provisioningTokenArg,
|
provisioningToken: provisioningTokenArg,
|
||||||
sourceImageUrl: buildArg.data.sourceImageUrl,
|
sourceImageUrl: buildArg.data.sourceImageUrl,
|
||||||
|
sourceImagePreset: buildArg.data.sourceImagePreset,
|
||||||
|
balenaOsVersion: buildArg.data.balenaOsVersion,
|
||||||
ubuntuVersion: buildArg.data.ubuntuVersion,
|
ubuntuVersion: buildArg.data.ubuntuVersion,
|
||||||
hostname: buildArg.data.hostname,
|
hostname: buildArg.data.hostname,
|
||||||
wifi: buildArg.data.wifiSsid
|
wifi: buildArg.data.wifiSsid
|
||||||
@@ -632,6 +715,7 @@ export class CloudlyBaseOsManager {
|
|||||||
private async selectCoreBuildWorker(
|
private async selectCoreBuildWorker(
|
||||||
architectureArg: TBaseOsImageArchitecture,
|
architectureArg: TBaseOsImageArchitecture,
|
||||||
imageKindArg: TBaseOsImageKind,
|
imageKindArg: TBaseOsImageKind,
|
||||||
|
sourceImagePresetArg?: TBaseOsImageSourcePreset,
|
||||||
): Promise<ISelectedCoreBuildWorker> {
|
): Promise<ISelectedCoreBuildWorker> {
|
||||||
const workers = await this.getConfiguredCoreBuildWorkers();
|
const workers = await this.getConfiguredCoreBuildWorkers();
|
||||||
if (workers.length === 0) {
|
if (workers.length === 0) {
|
||||||
@@ -656,6 +740,10 @@ export class CloudlyBaseOsManager {
|
|||||||
rejectionReasons.push(`${workerLabel}: missing ${imageKindArg} support`);
|
rejectionReasons.push(`${workerLabel}: missing ${imageKindArg} support`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (sourceImagePresetArg && !capabilities.supportedSourcePresets?.includes(sourceImagePresetArg)) {
|
||||||
|
rejectionReasons.push(`${workerLabel}: missing ${sourceImagePresetArg} source preset support`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...worker,
|
...worker,
|
||||||
capabilities,
|
capabilities,
|
||||||
@@ -738,12 +826,58 @@ export class CloudlyBaseOsManager {
|
|||||||
if (buildRequestArg.architecture === 'rpi') {
|
if (buildRequestArg.architecture === 'rpi') {
|
||||||
return 'balena-raw';
|
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 'balena-raw';
|
||||||
}
|
}
|
||||||
return 'ubuntu-iso';
|
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(
|
private getArtifactFilename(
|
||||||
architectureArg: TBaseOsImageArchitecture,
|
architectureArg: TBaseOsImageArchitecture,
|
||||||
imageKindArg: TBaseOsImageKind,
|
imageKindArg: TBaseOsImageKind,
|
||||||
|
|||||||
@@ -12,6 +12,16 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
type TBaseOsImageBuild = any;
|
type TBaseOsImageBuild = any;
|
||||||
|
type TBaseOsImageSourcePreset =
|
||||||
|
| 'balena-generic-amd64'
|
||||||
|
| 'balena-generic-aarch64'
|
||||||
|
| 'balena-raspberrypi4-64';
|
||||||
|
|
||||||
|
const sourcePresetArchitectures: Record<TBaseOsImageSourcePreset, string> = {
|
||||||
|
'balena-generic-amd64': 'amd64',
|
||||||
|
'balena-generic-aarch64': 'arm64',
|
||||||
|
'balena-raspberrypi4-64': 'rpi',
|
||||||
|
};
|
||||||
|
|
||||||
@customElement('cloudly-view-baseos')
|
@customElement('cloudly-view-baseos')
|
||||||
export class CloudlyViewBaseOs extends DeesElement {
|
export class CloudlyViewBaseOs extends DeesElement {
|
||||||
@@ -115,10 +125,24 @@ export class CloudlyViewBaseOs extends DeesElement {
|
|||||||
></dees-input-dropdown>
|
></dees-input-dropdown>
|
||||||
<dees-input-text .key=${'cloudlyUrl'} .label=${'Cloudly URL'} .value=${window.location.origin} .required=${true}></dees-input-text>
|
<dees-input-text .key=${'cloudlyUrl'} .label=${'Cloudly URL'} .value=${window.location.origin} .required=${true}></dees-input-text>
|
||||||
<dees-input-text .key=${'hostname'} .label=${'Hostname'} .value=${'baseos-node'} .required=${false}></dees-input-text>
|
<dees-input-text .key=${'hostname'} .label=${'Hostname'} .value=${'baseos-node'} .required=${false}></dees-input-text>
|
||||||
|
<dees-input-dropdown
|
||||||
|
.key=${'sourceImagePreset'}
|
||||||
|
.label=${'balenaOS Source Preset'}
|
||||||
|
.selectedOption=${'auto'}
|
||||||
|
.options=${[
|
||||||
|
{ key: 'auto', option: 'Auto by architecture', payload: null },
|
||||||
|
{ key: 'balena-generic-amd64', option: 'Generic x86_64 (GPT)', payload: null },
|
||||||
|
{ key: 'balena-generic-aarch64', option: 'Generic AARCH64', payload: null },
|
||||||
|
{ key: 'balena-raspberrypi4-64', option: 'Raspberry Pi 4 64-bit', payload: null },
|
||||||
|
{ key: 'custom-url', option: 'Custom source URL', payload: null },
|
||||||
|
]}
|
||||||
|
.description=${'Used for balenaOS raw images when no custom source URL is provided.'}
|
||||||
|
></dees-input-dropdown>
|
||||||
|
<dees-input-text .key=${'balenaOsVersion'} .label=${'balenaOS Version'} .value=${'latest'} .description=${'Use latest, or an explicit balenaOS raw_version such as 2026.1.0.'} .required=${false}></dees-input-text>
|
||||||
<dees-input-text .key=${'wifiSsid'} .label=${'WiFi SSID'} .required=${false}></dees-input-text>
|
<dees-input-text .key=${'wifiSsid'} .label=${'WiFi SSID'} .required=${false}></dees-input-text>
|
||||||
<dees-input-text .key=${'wifiPassword'} .label=${'WiFi Password'} .isPasswordBool=${true} .required=${false}></dees-input-text>
|
<dees-input-text .key=${'wifiPassword'} .label=${'WiFi Password'} .isPasswordBool=${true} .required=${false}></dees-input-text>
|
||||||
<dees-input-textarea .key=${'sshPublicKey'} .label=${'SSH Public Key'} .required=${false}></dees-input-textarea>
|
<dees-input-textarea .key=${'sshPublicKey'} .label=${'SSH Public Key'} .required=${false}></dees-input-textarea>
|
||||||
<dees-input-text .key=${'sourceImageUrl'} .label=${'Source Image URL'} .description=${'Required for balenaOS raw images (.img, .img.xz, or .zip). Optional for Ubuntu ISO builds.'} .required=${false}></dees-input-text>
|
<dees-input-text .key=${'sourceImageUrl'} .label=${'Custom Source Image URL'} .description=${'Optional override for balenaOS raw images (.img, .img.xz, .zip, or balena download URL with fileType=.zip).'} .required=${false}></dees-input-text>
|
||||||
<dees-form-submit .text=${this.isLoading ? 'Creating...' : 'Create BaseOS Image'} .disabled=${this.isLoading}></dees-form-submit>
|
<dees-form-submit .text=${this.isLoading ? 'Creating...' : 'Create BaseOS Image'} .disabled=${this.isLoading}></dees-form-submit>
|
||||||
</dees-form>
|
</dees-form>
|
||||||
</dees-panel>
|
</dees-panel>
|
||||||
@@ -139,6 +163,7 @@ export class CloudlyViewBaseOs extends DeesElement {
|
|||||||
<div>
|
<div>
|
||||||
<strong>${data.hostname || buildArg.id}</strong>
|
<strong>${data.hostname || buildArg.id}</strong>
|
||||||
<div class="meta">${data.imageKind || 'ubuntu-iso'} · ${data.architecture} · ${data.cloudlyUrl}</div>
|
<div class="meta">${data.imageKind || 'ubuntu-iso'} · ${data.architecture} · ${data.cloudlyUrl}</div>
|
||||||
|
${data.sourceImagePreset ? html`<div class="meta">${data.sourceImagePreset} · balenaOS ${data.balenaOsVersion || 'latest'}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<dees-badge .text=${data.status} .type=${data.status === 'ready' ? 'success' : data.status === 'failed' ? 'error' : 'info'}></dees-badge>
|
<dees-badge .text=${data.status} .type=${data.status === 'ready' ? 'success' : data.status === 'failed' ? 'error' : 'info'}></dees-badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,15 +188,34 @@ export class CloudlyViewBaseOs extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createBuild(formDataArg: any) {
|
private async createBuild(formDataArg: any) {
|
||||||
this.isLoading = true;
|
|
||||||
try {
|
try {
|
||||||
|
const architecture = formDataArg.architecture || 'amd64';
|
||||||
|
const imageKind = formDataArg.imageKind || 'balena-raw';
|
||||||
|
const sourceImageUrl = formDataArg.sourceImageUrl?.trim() || undefined;
|
||||||
|
const selectedSourceImagePreset = formDataArg.sourceImagePreset || 'auto';
|
||||||
|
const sourceImagePreset = this.getSourceImagePreset(selectedSourceImagePreset, imageKind, sourceImageUrl);
|
||||||
|
const balenaOsVersion = imageKind === 'balena-raw' && !sourceImageUrl
|
||||||
|
? formDataArg.balenaOsVersion?.trim() || 'latest'
|
||||||
|
: undefined;
|
||||||
|
this.validateBuildForm({
|
||||||
|
architecture,
|
||||||
|
imageKind,
|
||||||
|
selectedSourceImagePreset,
|
||||||
|
sourceImagePreset,
|
||||||
|
sourceImageUrl,
|
||||||
|
wifiSsid: formDataArg.wifiSsid,
|
||||||
|
wifiPassword: formDataArg.wifiPassword,
|
||||||
|
});
|
||||||
|
this.isLoading = true;
|
||||||
const response = await this.fireBaseOsRequest('createBaseOsImageBuild', {
|
const response = await this.fireBaseOsRequest('createBaseOsImageBuild', {
|
||||||
build: {
|
build: {
|
||||||
architecture: formDataArg.architecture || 'amd64',
|
architecture,
|
||||||
imageKind: formDataArg.imageKind || undefined,
|
imageKind,
|
||||||
cloudlyUrl: formDataArg.cloudlyUrl || window.location.origin,
|
cloudlyUrl: formDataArg.cloudlyUrl || window.location.origin,
|
||||||
hostname: formDataArg.hostname || undefined,
|
hostname: formDataArg.hostname || undefined,
|
||||||
sourceImageUrl: formDataArg.sourceImageUrl || undefined,
|
sourceImageUrl,
|
||||||
|
sourceImagePreset,
|
||||||
|
balenaOsVersion,
|
||||||
wifi: formDataArg.wifiSsid
|
wifi: formDataArg.wifiSsid
|
||||||
? {
|
? {
|
||||||
ssid: formDataArg.wifiSsid,
|
ssid: formDataArg.wifiSsid,
|
||||||
@@ -190,6 +234,50 @@ export class CloudlyViewBaseOs extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSourceImagePreset(
|
||||||
|
sourceImagePresetArg: string | undefined,
|
||||||
|
imageKindArg: string,
|
||||||
|
sourceImageUrlArg?: string,
|
||||||
|
) {
|
||||||
|
if (imageKindArg !== 'balena-raw' || sourceImageUrlArg || sourceImagePresetArg === 'auto') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (sourceImagePresetArg === 'custom-url') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return sourceImagePresetArg as TBaseOsImageSourcePreset | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateBuildForm(optionsArg: {
|
||||||
|
architecture: string;
|
||||||
|
imageKind: string;
|
||||||
|
selectedSourceImagePreset?: string;
|
||||||
|
sourceImagePreset?: TBaseOsImageSourcePreset;
|
||||||
|
sourceImageUrl?: string;
|
||||||
|
wifiSsid?: string;
|
||||||
|
wifiPassword?: string;
|
||||||
|
}) {
|
||||||
|
if (optionsArg.architecture === 'rpi' && optionsArg.imageKind === 'ubuntu-iso') {
|
||||||
|
throw new Error('Raspberry Pi BaseOS images require the balenaOS raw image type.');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
optionsArg.imageKind === 'balena-raw'
|
||||||
|
&& optionsArg.selectedSourceImagePreset === 'custom-url'
|
||||||
|
&& !optionsArg.sourceImageUrl
|
||||||
|
) {
|
||||||
|
throw new Error('A custom source image URL is required when the custom source preset is selected.');
|
||||||
|
}
|
||||||
|
if (optionsArg.imageKind === 'balena-raw' && optionsArg.sourceImagePreset) {
|
||||||
|
const expectedArchitecture = sourcePresetArchitectures[optionsArg.sourceImagePreset];
|
||||||
|
if (expectedArchitecture !== optionsArg.architecture) {
|
||||||
|
throw new Error(`${optionsArg.sourceImagePreset} is only valid for ${expectedArchitecture} images.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (optionsArg.wifiPassword && !optionsArg.wifiSsid) {
|
||||||
|
throw new Error('A WiFi SSID is required when a WiFi password is set.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async downloadBuild(buildIdArg: string) {
|
private async downloadBuild(buildIdArg: string) {
|
||||||
const response = await this.fireBaseOsRequest('createBaseOsImageDownloadUrl', {
|
const response = await this.fireBaseOsRequest('createBaseOsImageDownloadUrl', {
|
||||||
buildId: buildIdArg,
|
buildId: buildIdArg,
|
||||||
|
|||||||
Reference in New Issue
Block a user