diff --git a/lib/models/balenaos-contract.ts b/lib/models/balenaos-contract.ts
index 32e0d8889..e0af75109 100644
--- a/lib/models/balenaos-contract.ts
+++ b/lib/models/balenaos-contract.ts
@@ -8,6 +8,7 @@ const BalenaOS: Contract = {
type: 'sw.os',
description: 'Balena OS',
partials: {
+ image: [`{{#each deviceType.partials.instructions}}{{{this}}} {{/each}}`],
internalFlash: [
`{{#each deviceType.partials.connectDevice}}{{{this}}} {{/each}}`,
`Write the {{name}} file you downloaded to the {{deviceType.name}}. We recommend using Etcher.`,
@@ -16,24 +17,24 @@ const BalenaOS: Contract = {
`{{{deviceType.partials.bootDevice}}} to boot the device.`,
],
externalFlash: [
- `Insert the {{deviceType.data.media.installation}} to the host machine.`,
- `Write the {{name}} file you downloaded to the {{deviceType.data.media.installation}}. We recommend using Etcher.`,
+ `Insert the {{deviceType.data.media.altBoot.[0]}} to the host machine.`,
+ `Write the {{name}} file you downloaded to the {{deviceType.data.media.altBoot.[0]}}. We recommend using Etcher.`,
`Wait for writing of {{name}} to complete.`,
- `Remove the {{deviceType.data.media.installation}} from the host machine.`,
- `Insert the freshly flashed {{deviceType.data.media.installation}} into the {{deviceType.name}}.`,
+ `Remove the {{deviceType.data.media.altBoot.[0]}} from the host machine.`,
+ `Insert the freshly flashed {{deviceType.data.media.altBoot.[0]}} into the {{deviceType.name}}.`,
`Warning! This will also completely erase internal storage medium, so please make a backup first.`,
`{{#each deviceType.partials.bootDeviceExternal}}{{{this}}} {{/each}}`,
`Wait for the {{deviceType.name}} to finish flashing and shutdown. {{#if deviceType.partials.flashIndicator}}Please wait until {{deviceType.partials.flashIndicator}}.{{/if}}`,
- `Remove the {{deviceType.data.media.installation}} from the {{deviceType.name}}.`,
+ `Remove the {{deviceType.data.media.altBoot.[0]}} from the {{deviceType.name}}.`,
`{{#each deviceType.partials.bootDeviceInternal}}{{{this}}} {{/each}}`,
`{{{deviceType.partials.bootDevice}}} to boot the device.`,
],
externalBoot: [
- `Insert the {{deviceType.data.media.installation}} to the host machine.`,
- `Write the {{name}} file you downloaded to the {{deviceType.data.media.installation}}. We recommend using Etcher.`,
+ `Insert the {{deviceType.data.media.defaultBoot}} to the host machine.`,
+ `Write the {{name}} file you downloaded to the {{deviceType.data.media.defaultBoot}}. We recommend using Etcher.`,
`Wait for writing of {{name}} to complete.`,
- `Remove the {{deviceType.data.media.installation}} from the host machine.`,
- `Insert the freshly flashed {{deviceType.data.media.installation}} into the {{deviceType.name}}.`,
+ `Remove the {{deviceType.data.media.defaultBoot}} from the host machine.`,
+ `Insert the freshly flashed {{deviceType.data.media.defaultBoot}} into the {{deviceType.name}}.`,
`{{{deviceType.partials.bootDevice}}} to boot the device.`,
],
jetsonFlash: [
@@ -43,10 +44,32 @@ const BalenaOS: Contract = {
`Wait for writing of {{name}} to complete.`,
`{{{deviceType.partials.bootDevice}}} to boot the device.`,
],
- custom: [
- `{{#each deviceType.partials.instructions}}{{{this}}} {{/each}}`,
- `{{{deviceType.partials.bootDevice}}} to boot the device.`,
- ],
+ edisonFlash: {
+ Linux: [
+ `{{#each deviceType.partials.Linux.flashDependencies}}{{{this}}} {{/each}}`,
+ `Unplug the {{deviceType.name}} from your system`,
+ `Unzip the downloaded {{name}} file`,
+ `{{#each deviceType.partials.Linux.flashInstructions}}{{{this}}} {{/each}}`,
+ `Plug the {{deviceType.name}} as per the instructions on your terminal.`,
+ `You can check the progress of the provisioning on your terminal.`,
+ ],
+ MacOS: [
+ `{{#each deviceType.partials.MacOS.flashDependencies}}{{{this}}} {{/each}}`,
+ `Unplug the {{deviceType.name}} from your system`,
+ `Unzip the downloaded {{name}} file`,
+ `{{#each deviceType.partials.MacOS.flashInstructions}}{{{this}}} {{/each}}`,
+ `Plug the {{deviceType.name}} as per the instructions on your terminal.`,
+ `You can check the progress of the provisioning on your terminal.`,
+ ],
+ Windows: [
+ `{{#each deviceType.partials.Windows.flashDependencies}}{{{this}}} {{/each}}`,
+ `Unplug the {{deviceType.name}} from your system`,
+ `Unzip the downloaded {{name}} file`,
+ `{{#each deviceType.partials.Windows.flashInstructions}}{{{this}}} {{/each}}`,
+ `Plug the {{deviceType.name}} as per the instructions on your terminal.`,
+ `You can check the progress of the provisioning on your terminal.`,
+ ],
+ },
},
};
diff --git a/lib/models/device-type.ts b/lib/models/device-type.ts
index 707137583..7783bb887 100644
--- a/lib/models/device-type.ts
+++ b/lib/models/device-type.ts
@@ -25,24 +25,64 @@ import cloneDeep = require('lodash/cloneDeep');
// REPLACE ONCE HOST OS CONTRACTS ARE GENERATED THROUGH YOCTO
import { BalenaOS } from './balenaos-contract';
+const traversingCompile = (partials: any, initial: any, keys: string[]) => {
+ return Object.keys(partials).reduce(
+ (interpolated: any, partialKey) => {
+ const current = partials[partialKey];
+ if (Array.isArray(current)) {
+ let location = interpolated;
+ for (const key of keys) {
+ location = location[key];
+ }
+ // if array of partials, compile the template
+ location[partialKey] = current
+ .map((partial: string) => Handlebars.compile(partial)(interpolated))
+ .filter((n) => n);
+ } else {
+ // if it's another dictionary, keep traversing
+ interpolated = traversingCompile(
+ current,
+ interpolated,
+ keys.concat([partialKey]),
+ );
+ }
+ return interpolated;
+ },
+ { ...initial },
+ );
+};
+
const interpolatedPartials = (contract: Contract, initial: any = {}) => {
const fullInitial = { ...contract, ...initial };
if (contract.partials) {
- const partials = contract.partials;
- return Object.keys(partials).reduce(
- (interpolated: any, partialKey) => {
- interpolated.partials[partialKey] = partials[partialKey].map(
- (partial: string) => Handlebars.compile(partial)(interpolated),
- );
- return interpolated;
- },
- { ...fullInitial },
- );
+ return traversingCompile(contract.partials, fullInitial, ['partials']);
} else {
return fullInitial;
}
};
+const calculateInstallMethod = (contract: Contract): string => {
+ const flashProtocol = contract.data?.flashProtocol;
+ const defaultBoot = contract.data?.media?.defaultBoot;
+ if (flashProtocol) {
+ if (flashProtocol === 'RPIBOOT') {
+ return 'internalFlash';
+ } else {
+ return flashProtocol;
+ }
+ } else if (defaultBoot) {
+ if (defaultBoot === 'internal') {
+ return 'externalFlash';
+ } else {
+ return 'externalBoot';
+ }
+ } else {
+ throw new errors.BalenaError(
+ `Unable to determine installation method for contract: ${contract.slug}`,
+ );
+ }
+};
+
const getDeviceTypeModel = function (deps: InjectedDependenciesParam) {
const { pine } = deps;
@@ -362,56 +402,37 @@ const getDeviceTypeModel = function (deps: InjectedDependenciesParam) {
* // Insert the freshly flashed sdcard into the Raspberry Pi (v1 / Zero / Zero W).
* // Connect power to the Raspberry Pi (v1 / Zero / Zero W) to boot the device.
* });
- * @example
- * balena.models.deviceType.getInstructions('raspberry-pi', baseInstructions = {
- * `Use the form on the left to configure and download {{name}} for your new {{deviceType.name}}.
- * {{#each instructions}}
- * {{{this}}}
- * {{/each}}
- * Your device should appear in your application dashboard within a few minutes. Have fun!`
- * }).then(function(instructions) {
- * for (let instruction of instructions.values()) {
- * console.log(instruction);
- * }
- * // Use the form on the left to configure and download BalenaOS for your new Raspberry Pi (v1 / Zero / Zero W).
- * // Insert the sdcard to the host machine.
- * // Write the BalenaOS file you downloaded to the sdcard. We recommend using Etcher.
- * // Wait for writing of BalenaOS to complete.
- * // Remove the sdcard from the host machine.
- * // Insert the freshly flashed sdcard into the Raspberry Pi (v1 / Zero / Zero W).
- * // Connect power to the Raspberry Pi (v1 / Zero / Zero W) to boot the device.
- * // Your device should appear in your application dashboard within a few minutes. Have fun!
- * });
*/
getInstructions: async (
deviceTypeSlug: string,
- baseInstructions?: string,
- ): Promise => {
+ ): Promise => {
const contract = (
await exports.getBySlugOrName(deviceTypeSlug, { $select: 'contract' })
).contract;
- if (contract) {
- const installMethod = contract?.data?.installation?.method;
- if (!installMethod || !contract.partials) {
- throw new Error(
- `Install method or instruction partials not defined for ${deviceTypeSlug}`,
- );
- }
- const interpolatedDeviceType = interpolatedPartials(contract);
- const interpolatedHostOS = interpolatedPartials(cloneDeep(BalenaOS), {
- deviceType: interpolatedDeviceType,
- });
+ if (!contract || !contract.partials) {
+ throw new Error(
+ `Instruction partials not defined for ${deviceTypeSlug}`,
+ );
+ }
+ const installMethod = calculateInstallMethod(contract);
+ const interpolatedDeviceType = interpolatedPartials(contract);
+ const interpolatedHostOS = interpolatedPartials(cloneDeep(BalenaOS), {
+ deviceType: interpolatedDeviceType,
+ });
- let instructions: string[] = interpolatedHostOS.partials[installMethod];
- if (baseInstructions) {
- instructions = Handlebars.compile(baseInstructions)({
- ...interpolatedHostOS,
- instructions,
- }).split('\n');
- }
- return instructions.map((s) => s.trim()).filter((s) => s);
+ return interpolatedHostOS.partials[installMethod];
+ },
+
+ getInstallMethod: async (
+ deviceTypeSlug: string,
+ ): Promise => {
+ const contract = (
+ await exports.getBySlugOrName(deviceTypeSlug, { $select: 'contract' })
+ ).contract;
+ if (contract) {
+ return calculateInstallMethod(contract);
} else {
- return [];
+ return null;
}
},
diff --git a/lib/types/contract.ts b/lib/types/contract.ts
index 4c0e371e7..4135c5963 100644
--- a/lib/types/contract.ts
+++ b/lib/types/contract.ts
@@ -1,5 +1,7 @@
import { AnyObject, Dictionary } from '../../typings/utils';
+type Partials = Dictionary;
+
export interface Contract {
slug: string;
type: string;
@@ -15,5 +17,5 @@ export interface Contract {
requires?: string[];
provides?: string[];
composedOf?: AnyObject;
- partials?: Dictionary;
+ partials?: Partials;
}