Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contract partial based instruction generation #1101

Merged
merged 2 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 135 additions & 4 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ const sdk = fromSharedOptions();
* [.getDashboardUrl(uuid)](#balena.models.device.getDashboardUrl) ⇒ <code>String</code>
* [.getAll([options])](#balena.models.device.getAll) ⇒ <code>Promise</code>
* [.getAllByApplication(slugOrUuidOrId, [options])](#balena.models.device.getAllByApplication) ⇒ <code>Promise</code>
* [.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>
* ~~[.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>~~
* [.get(uuidOrId, [options])](#balena.models.device.get) ⇒ <code>Promise</code>
* [.getWithServiceDetails(uuidOrId, [options])](#balena.models.device.getWithServiceDetails) ⇒ <code>Promise</code>
* [.getByName(name)](#balena.models.device.getByName) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -315,6 +315,9 @@ const sdk = fromSharedOptions();
* [.getBySlugOrName(slugOrName)](#balena.models.deviceType.getBySlugOrName) ⇒ <code>Promise</code>
* [.getName(deviceTypeSlug)](#balena.models.deviceType.getName) ⇒ <code>Promise</code>
* [.getSlugByName(deviceTypeName)](#balena.models.deviceType.getSlugByName) ⇒ <code>Promise</code>
* [.getInterpolatedPartials(deviceTypeSlug)](#balena.models.deviceType.getInterpolatedPartials) ⇒ <code>Promise</code>
* [.getInstructions(deviceTypeSlug)](#balena.models.deviceType.getInstructions) ⇒ <code>Promise</code>
* [.getInstallMethod(deviceTypeSlug)](#balena.models.deviceType.getInstallMethod) ⇒ <code>Promise</code>
* [.apiKey](#balena.models.apiKey) : <code>object</code>
* [.create(name, [description])](#balena.models.apiKey.create) ⇒ <code>Promise</code>
* [.getAll([options])](#balena.models.apiKey.getAll) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -390,6 +393,9 @@ const sdk = fromSharedOptions();
* [.image](#balena.models.image) : <code>object</code>
* [.get(id, [options])](#balena.models.image.get) ⇒ <code>Promise</code>
* [.getLogs(id)](#balena.models.image.getLogs) ⇒ <code>Promise</code>
* [.creditBundle](#balena.models.creditBundle) : <code>object</code>
* [.getAllByOrg(orgId, [options])](#balena.models.creditBundle.getAllByOrg) ⇒ <code>Promise</code>
* [.create(orgId, featureId, creditsToPurchase)](#balena.models.creditBundle.create) ⇒ <code>Promise</code>
* [.billing](#balena.models.billing) : <code>object</code>
* [.getAccount(organization)](#balena.models.billing.getAccount) ⇒ <code>Promise</code>
* [.getPlan(organization)](#balena.models.billing.getPlan) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -641,7 +647,7 @@ balena.models.device.get(123).catch(function (error) {
* [.getDashboardUrl(uuid)](#balena.models.device.getDashboardUrl) ⇒ <code>String</code>
* [.getAll([options])](#balena.models.device.getAll) ⇒ <code>Promise</code>
* [.getAllByApplication(slugOrUuidOrId, [options])](#balena.models.device.getAllByApplication) ⇒ <code>Promise</code>
* [.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>
* ~~[.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>~~
* [.get(uuidOrId, [options])](#balena.models.device.get) ⇒ <code>Promise</code>
* [.getWithServiceDetails(uuidOrId, [options])](#balena.models.device.getWithServiceDetails) ⇒ <code>Promise</code>
* [.getByName(name)](#balena.models.device.getByName) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -710,6 +716,9 @@ balena.models.device.get(123).catch(function (error) {
* [.getBySlugOrName(slugOrName)](#balena.models.deviceType.getBySlugOrName) ⇒ <code>Promise</code>
* [.getName(deviceTypeSlug)](#balena.models.deviceType.getName) ⇒ <code>Promise</code>
* [.getSlugByName(deviceTypeName)](#balena.models.deviceType.getSlugByName) ⇒ <code>Promise</code>
* [.getInterpolatedPartials(deviceTypeSlug)](#balena.models.deviceType.getInterpolatedPartials) ⇒ <code>Promise</code>
* [.getInstructions(deviceTypeSlug)](#balena.models.deviceType.getInstructions) ⇒ <code>Promise</code>
* [.getInstallMethod(deviceTypeSlug)](#balena.models.deviceType.getInstallMethod) ⇒ <code>Promise</code>
* [.apiKey](#balena.models.apiKey) : <code>object</code>
* [.create(name, [description])](#balena.models.apiKey.create) ⇒ <code>Promise</code>
* [.getAll([options])](#balena.models.apiKey.getAll) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -785,6 +794,9 @@ balena.models.device.get(123).catch(function (error) {
* [.image](#balena.models.image) : <code>object</code>
* [.get(id, [options])](#balena.models.image.get) ⇒ <code>Promise</code>
* [.getLogs(id)](#balena.models.image.getLogs) ⇒ <code>Promise</code>
* [.creditBundle](#balena.models.creditBundle) : <code>object</code>
* [.getAllByOrg(orgId, [options])](#balena.models.creditBundle.getAllByOrg) ⇒ <code>Promise</code>
* [.create(orgId, featureId, creditsToPurchase)](#balena.models.creditBundle.create) ⇒ <code>Promise</code>
* [.billing](#balena.models.billing) : <code>object</code>
* [.getAccount(organization)](#balena.models.billing.getAccount) ⇒ <code>Promise</code>
* [.getPlan(organization)](#balena.models.billing.getPlan) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -2559,7 +2571,7 @@ balena.models.application.revokeSupportAccess('myorganization/myapp', function(e
* [.getDashboardUrl(uuid)](#balena.models.device.getDashboardUrl) ⇒ <code>String</code>
* [.getAll([options])](#balena.models.device.getAll) ⇒ <code>Promise</code>
* [.getAllByApplication(slugOrUuidOrId, [options])](#balena.models.device.getAllByApplication) ⇒ <code>Promise</code>
* [.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>
* ~~[.getAllByParentDevice(parentUuidOrId, [options])](#balena.models.device.getAllByParentDevice) ⇒ <code>Promise</code>~~
* [.get(uuidOrId, [options])](#balena.models.device.get) ⇒ <code>Promise</code>
* [.getWithServiceDetails(uuidOrId, [options])](#balena.models.device.getWithServiceDetails) ⇒ <code>Promise</code>
* [.getByName(name)](#balena.models.device.getByName) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -3390,7 +3402,9 @@ balena.models.device.getAllByApplication('myorganization/myapp', function(error,
```
<a name="balena.models.device.getAllByParentDevice"></a>

##### device.getAllByParentDevice(parentUuidOrId, [options]) ⇒ <code>Promise</code>
##### ~~device.getAllByParentDevice(parentUuidOrId, [options]) ⇒ <code>Promise</code>~~
***Deprecated***

**Kind**: static method of [<code>device</code>](#balena.models.device)
**Summary**: Get all devices by parent device
**Access**: public
Expand Down Expand Up @@ -5216,6 +5230,9 @@ balena.models.device.restartService('7cf02a6', 123, function(error) {
* [.getBySlugOrName(slugOrName)](#balena.models.deviceType.getBySlugOrName) ⇒ <code>Promise</code>
* [.getName(deviceTypeSlug)](#balena.models.deviceType.getName) ⇒ <code>Promise</code>
* [.getSlugByName(deviceTypeName)](#balena.models.deviceType.getSlugByName) ⇒ <code>Promise</code>
* [.getInterpolatedPartials(deviceTypeSlug)](#balena.models.deviceType.getInterpolatedPartials) ⇒ <code>Promise</code>
* [.getInstructions(deviceTypeSlug)](#balena.models.deviceType.getInstructions) ⇒ <code>Promise</code>
* [.getInstallMethod(deviceTypeSlug)](#balena.models.deviceType.getInstallMethod) ⇒ <code>Promise</code>

<a name="balena.models.deviceType.get"></a>

Expand Down Expand Up @@ -5396,6 +5413,72 @@ balena.models.deviceType.getSlugByName('Raspberry Pi', function(error, deviceTyp
// raspberry-pi
});
```
<a name="balena.models.deviceType.getInterpolatedPartials"></a>

##### deviceType.getInterpolatedPartials(deviceTypeSlug) ⇒ <code>Promise</code>
**Kind**: static method of [<code>deviceType</code>](#balena.models.deviceType)
**Summary**: Get a contract with resolved partial templates
**Access**: public
**Fulfil**: <code>Contract</code> - device type contract with resolved partials

| Param | Type | Description |
| --- | --- | --- |
| deviceTypeSlug | <code>String</code> | device type slug |

**Example**
```js
balena.models.deviceType.getInterpolatedPartials('raspberry-pi').then(function(contract) {
for (const partial in contract.partials) {
console.log(`${partial}: ${contract.partials[partial]}`);
}
// bootDevice: ["Connect power to the Raspberry Pi (v1 / Zero / Zero W)"]
});
```
<a name="balena.models.deviceType.getInstructions"></a>

##### deviceType.getInstructions(deviceTypeSlug) ⇒ <code>Promise</code>
**Kind**: static method of [<code>deviceType</code>](#balena.models.deviceType)
**Summary**: Get instructions for installing a host OS on a given device type
**Access**: public
**Fulfil**: <code>String[]</code> - step by step instructions for installing the host OS to the device

| Param | Type | Description |
| --- | --- | --- |
| deviceTypeSlug | <code>String</code> | device type slug |

**Example**
```js
balena.models.deviceType.getInstructions('raspberry-pi').then(function(instructions) {
for (let instruction of instructions.values()) {
console.log(instruction);
}
// Insert the sdcard to the host machine.
// Write the BalenaOS file you downloaded to the sdcard. We recommend using <a href="http://www.etcher.io/">Etcher</a>.
// 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.
});
```
<a name="balena.models.deviceType.getInstallMethod"></a>

##### deviceType.getInstallMethod(deviceTypeSlug) ⇒ <code>Promise</code>
**Kind**: static method of [<code>deviceType</code>](#balena.models.deviceType)
**Summary**: Get installation method on a given device type
**Access**: public
**Fulfil**: <code>String</code> - the installation method supported for the given device type slug

| Param | Type | Description |
| --- | --- | --- |
| deviceTypeSlug | <code>String</code> | device type slug |

**Example**
```js
balena.models.deviceType.getInstallMethod('raspberry-pi').then(function(method) {
console.log(method);
// externalBoot
});
```
<a name="balena.models.apiKey"></a>

#### models.apiKey : <code>object</code>
Expand Down Expand Up @@ -7308,6 +7391,54 @@ balena.models.image.getLogs(123, function(error, logs) {
console.log(logs);
});
```
<a name="balena.models.creditBundle"></a>

#### models.creditBundle : <code>object</code>
**Kind**: static namespace of [<code>models</code>](#balena.models)

* [.creditBundle](#balena.models.creditBundle) : <code>object</code>
* [.getAllByOrg(orgId, [options])](#balena.models.creditBundle.getAllByOrg) ⇒ <code>Promise</code>
* [.create(orgId, featureId, creditsToPurchase)](#balena.models.creditBundle.create) ⇒ <code>Promise</code>

<a name="balena.models.creditBundle.getAllByOrg"></a>

##### creditBundle.getAllByOrg(orgId, [options]) ⇒ <code>Promise</code>
**Kind**: static method of [<code>creditBundle</code>](#balena.models.creditBundle)
**Summary**: Get all of the credit bundles purchased by the given org
**Access**: public
**Fulfil**: <code>Object[]</code> - credit bundles

| Param | Type | Default | Description |
| --- | --- | --- | --- |
| orgId | <code>String</code> \| <code>Number</code> | | handle (string) or id (number) of the target organization. |
| [options] | <code>Object</code> | <code>{}</code> | extra pine options to use |

**Example**
```js
balena.models.creditBundle.getAllByOrg(orgId).then(function(creditBundles) {
console.log(creditBundles);
});
```
<a name="balena.models.creditBundle.create"></a>

##### creditBundle.create(orgId, featureId, creditsToPurchase) ⇒ <code>Promise</code>
**Kind**: static method of [<code>creditBundle</code>](#balena.models.creditBundle)
**Summary**: Purchase a credit bundle for the given feature and org of the given quantity
**Access**: public
**Fulfil**: <code>Object[]</code> - credit bundles

| Param | Type | Description |
| --- | --- | --- |
| orgId | <code>String</code> \| <code>Number</code> | handle (string) or id (number) of the target organization. |
| featureId | <code>String</code> \| <code>Number</code> | id (number) of the feature for which credits are being purchased. |
| creditsToPurchase | <code>String</code> \| <code>Number</code> | number of credits being purchased. |

**Example**
```js
balena.models.creditBundle.create(orgId, featureId, creditsToPurchase).then(function(creditBundle) {
console.log(creditBundle);
});
```
<a name="balena.models.billing"></a>

#### models.billing : <code>object</code>
Expand Down
76 changes: 76 additions & 0 deletions lib/models/balenaos-contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Contract } from '../types/contract';

// Hardcoded host OS contract, this should be moved to the Yocto build process with meta-balena.
// Here for initial implementatin and testing purposes
const BalenaOS: Contract = {
name: 'balenaOS',
slug: 'balenaos',
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 <a href="http://www.etcher.io/">Etcher</a>.`,
`Wait for writing of {{name}} to complete.`,
`{{#each deviceType.partials.disconnectDevice}}{{{this}}} {{/each}}`,
`{{{deviceType.partials.bootDevice}}} to boot the device.`,
],
externalFlash: [
`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 <a href="http://www.etcher.io/">Etcher</a>.`,
`Wait for writing of {{name}} to complete.`,
`Remove the {{deviceType.data.media.altBoot.[0]}} from the host machine.`,
`Insert the freshly flashed {{deviceType.data.media.altBoot.[0]}} into the {{deviceType.name}}.`,
`<strong role="alert">Warning!</strong> 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.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.defaultBoot}} to the host machine.`,
`Write the {{name}} file you downloaded to the {{deviceType.data.media.defaultBoot}}. We recommend using <a href="http://www.etcher.io/">Etcher</a>.`,
`Wait for writing of {{name}} to complete.`,
`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.`,
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mehalter the partials above should go into the existing balenaOS contract in https://github.com/balena-io/contracts/blob/master/contracts/sw.os/balenaos/contract.json.

This is already used by the OS build scripts so when added the contract will be deployed with the OS release.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, I'll get this moved over. Is this accessible through the API as well?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mehalter it's deployed with the OS release. For example if you do:

await sdk.pine.get({
    resource: 'release',
    id: nnnnn,
});

You will get something like:

belongs_to__application: {__id: 1520936, __deferred: {…}}
build_log:null
commit:"2c3a3a6c020e3ca5a2ae008f9de59643"
composition:{version: '2.1', networks: {…}, volumes: {…}, services: {…}}
contract:
"{\"name\":\"Balena OS for Raspberry Pi 4 (using 64bit OS)\",\"type\":\"sw.block\",\"description\":\"Balena OS for a Raspberry Pi 4 (using 64bit OS)\",\"provides\":[{\"type\":\"sw.os\",\"slug\":\"balena-os\"},{\"type\":\"hw.device-type\",\"slug\":\"raspberrypi4-64\"}],\"composedOf\":[\"balena-os\",\"raspberrypi4-64\"],\"version\":\"2.105.1+rev1\"}"

As you see at the moment the contract is very basic, but once you add the instructions they will appear there.

jetsonFlash: [
`Put the device in recovery mode and connect to the host computer via USB`,
`{{#if deviceType.partials.jetsonNotes}}{{#each deviceType.partials.jetsonNotes}}{{{this}}} {{/each}}{{/if}}`,
`Unzip the {{name}} image and use the Jetson Flash tool to flash the {{deviceType.name}} found at <a href="https://github.com/balena-os/jetson-flash">https://github.com/balena-os/jetson-flash</a>.`,
`Wait for writing of {{name}} to complete.`,
`{{{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.`,
],
},
},
};

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both jetsonFlash and edisonFlash are device specific and they do not belong in the OS contract. They need to be included into something like sw.device-family, and then pulled in when the specific device type contract is built.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, each of these partials are supported installation methods of the operating system and are not necessarily device specific. Any device that supports using the jetson flash tool should be allowed to get these instructions. These are just Balena OS's way of saying, "I support being installed with the jetson flash tool, and this is how. It doesn't provide any information that is specific to a device type. If that makes sense. Basically it's the BalenaOS's part in the flash process of telling the user "how to handle the file that I am giving you.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jetson is a device family, no other vendor will use Jetson tools. And the same goes for edison.

Why would a contract for the RaspberryPi need to mention other vendors like Jetson or edison? In my view this corresponds to a sw.device-family contract that is then pulled in only when building the Jetson/Edison families.

export { BalenaOS };
Loading