Update balena-sdk to 14.x

Update balena-sdk from 13.6.0 to 14.8.0

Change-type: patch
This commit is contained in:
Pagan Gazzard 2020-07-31 15:35:20 +01:00 committed by Balena CI
parent bf22d9eaa8
commit e1c42405a1
36 changed files with 390 additions and 434 deletions

View File

@ -86,6 +86,7 @@ export default class AppCreateCmd extends Command {
application = await balena.models.application.create({
name: params.name,
deviceType,
organization: (await balena.auth.whoami())!,
});
} catch (err) {
// BalenaRequestError: Request error: Unique key constraint violated
@ -97,7 +98,7 @@ export default class AppCreateCmd extends Command {
throw err;
}
console.info(
`Application created: ${application.slug} (${application.device_type}, id ${application.id})`,
`Application created: ${application.slug} (${deviceType}, id ${application.id})`,
);
}
}

View File

@ -20,6 +20,7 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import { Release } from 'balena-sdk';
interface FlagsDef {
help: void;
@ -57,10 +58,22 @@ export default class AppCmd extends Command {
public async run() {
const { args: params } = this.parse<FlagsDef, ArgsDef>(AppCmd);
const application = await getBalenaSdk().models.application.get(
const application = (await getBalenaSdk().models.application.get(
tryAsInteger(params.name),
);
{
$expand: {
is_for__device_type: { $select: 'slug' },
should_be_running__release: { $select: 'commit' },
},
},
)) as ApplicationWithDeviceType & {
should_be_running__release: [Release?];
};
// @ts-expect-error
application.device_type = application.is_for__device_type[0].slug;
// @ts-expect-error
application.commit = application.should_be_running__release[0]?.commit;
console.log(
getVisuals().table.vertical(application, [
`$${application.app_name}$`,

View File

@ -16,13 +16,12 @@
*/
import { flags } from '@oclif/command';
import type { Application } from 'balena-sdk';
import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, getVisuals, stripIndent } from '../utils/lazy';
import { isV12 } from '../utils/version';
interface ExtendedApplication extends Application {
interface ExtendedApplication extends ApplicationWithDeviceType {
device_count?: number;
online_devices?: number;
}
@ -64,12 +63,13 @@ export default class AppsCmd extends Command {
const balena = getBalenaSdk();
// Get applications
const applications: ExtendedApplication[] = await balena.models.application.getAll(
{
$select: ['id', 'app_name', 'slug', 'device_type'],
$expand: { owns__device: { $select: 'is_online' } },
const applications = (await balena.models.application.getAll({
$select: ['id', 'app_name', 'slug'],
$expand: {
is_for__device_type: { $select: 'slug' },
owns__device: { $select: 'is_online' },
},
);
})) as ExtendedApplication[];
const _ = await import('lodash');
// Add extended properties
@ -78,6 +78,8 @@ export default class AppsCmd extends Command {
application.online_devices = _.sumBy(application.owns__device, (d) =>
d.is_online === true ? 1 : 0,
);
// @ts-expect-error
application.device_type = application.is_for__device_type[0].slug;
});
// Display

View File

@ -189,7 +189,7 @@ ${dockerignoreHelp}
const { getAppWithArch } = await import('../utils/helpers');
const app = await getAppWithArch(opts.application);
opts.arch = app.arch;
opts.deviceType = app.device_type;
opts.deviceType = app.is_for__device_type[0].slug;
return app;
}
}

View File

@ -19,7 +19,7 @@ import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getBalenaSdk, getCliForm, stripIndent } from '../../utils/lazy';
import type { Device, Application, PineDeferred } from 'balena-sdk';
import type { Application, PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
@ -130,27 +130,34 @@ export default class ConfigGenerateCmd extends Command {
await this.validateOptions(options);
let deviceType = options.deviceType;
// Get device | application
let resource;
if (options.device != null) {
const { tryAsInteger } = await import('../../utils/validation');
resource = (await balena.models.device.get(
tryAsInteger(options.device),
)) as Device & { belongs_to__application: PineDeferred };
resource = (await balena.models.device.get(tryAsInteger(options.device), {
$expand: {
is_of__device_type: { $select: 'slug' },
},
})) as DeviceWithDeviceType & { belongs_to__application: PineDeferred };
deviceType = deviceType || resource.is_of__device_type[0].slug;
} else {
resource = await balena.models.application.get(options.application!);
resource = (await balena.models.application.get(options.application!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
deviceType = deviceType || resource.is_for__device_type[0].slug;
}
const deviceType = options.deviceType || resource.device_type;
const deviceManifest = await balena.models.device.getManifestBySlug(
deviceType,
deviceType!,
);
// Check compatibility if application and deviceType provided
if (options.application && options.deviceType) {
const appDeviceManifest = await balena.models.device.getManifestBySlug(
resource.device_type,
deviceType!,
);
const helpers = await import('../../utils/helpers');

View File

@ -22,11 +22,12 @@ import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import type { Application, Device } from 'balena-sdk';
import type { Application, Release } from 'balena-sdk';
interface ExtendedDevice extends Device {
interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string;
application_name?: string;
device_type?: string;
commit?: string;
last_seen?: string;
}
@ -70,25 +71,23 @@ export default class DeviceCmd extends Command {
const balena = getBalenaSdk();
const device: ExtendedDevice = await balena.models.device.get(params.uuid, {
const device = (await balena.models.device.get(params.uuid, {
$select: [
'device_name',
'id',
'device_type',
'overall_status',
'is_online',
'ip_address',
'mac_address',
'last_connectivity_event',
'uuid',
'is_on__commit',
'supervisor_version',
'is_web_accessible',
'note',
'os_version',
],
...expandForAppName,
});
})) as ExtendedDevice;
device.status = device.overall_status;
device.dashboard_url = balena.models.device.getDashboardUrl(device.uuid);
@ -98,8 +97,9 @@ export default class DeviceCmd extends Command {
? belongsToApplication[0].app_name
: 'N/a';
device.commit = device.is_on__commit;
device.last_seen = device.last_connectivity_event;
device.device_type = device.is_of__device_type[0].slug;
device.commit = (device.is_running__release as Release[])[0].commit;
device.last_seen = device.last_connectivity_event ?? undefined;
console.log(
getVisuals().table.vertical(device, [

View File

@ -95,10 +95,17 @@ export default class DeviceInitCmd extends Command {
delete options.app;
// Get application and
const application = await balena.models.application.get(
const application = (await balena.models.application.get(
options['application'] ||
(await (await import('../../utils/patterns')).selectApplication()),
);
{
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
},
)) as ApplicationWithDeviceType;
// Register new device
const deviceUuid = balena.models.device.generateUniqueKey();
@ -111,13 +118,14 @@ export default class DeviceInitCmd extends Command {
try {
logger.logDebug(`Downloading OS image...`);
const osVersion = options['os-version'] || 'default';
await downloadOSImage(application.device_type, tmpPath, osVersion);
const deviceType = application.is_for__device_type[0].slug;
await downloadOSImage(deviceType, tmpPath, osVersion);
logger.logDebug(`Configuring OS image...`);
await this.configureOsImage(tmpPath, device.uuid, options);
logger.logDebug(`Writing OS image...`);
await this.writeOsImage(tmpPath, application.device_type, options);
await this.writeOsImage(tmpPath, deviceType, options);
} catch (e) {
// Remove device in failed cases
try {

View File

@ -17,7 +17,7 @@
import { flags } from '@oclif/command';
import type { IArg } from '@oclif/parser/lib/args';
import type { Application, Device } from 'balena-sdk';
import type { Application } from 'balena-sdk';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
@ -25,7 +25,7 @@ import { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import { ExpectedError } from '../../errors';
interface ExtendedDevice extends Device {
interface ExtendedDevice extends DeviceWithDeviceType {
application_name?: string;
}
@ -85,10 +85,15 @@ export default class DeviceMoveCmd extends Command {
options.application = options.application || options.app;
delete options.app;
const devices: ExtendedDevice[] = await Promise.all(
const devices = await Promise.all(
params.uuid
.split(',')
.map((uuid) => balena.models.device.get(uuid, expandForAppName)),
.map(
(uuid) =>
balena.models.device.get(uuid, expandForAppName) as Promise<
ExtendedDevice
>,
),
);
for (const device of devices) {
@ -106,7 +111,9 @@ export default class DeviceMoveCmd extends Command {
const [deviceDeviceTypes, deviceTypes] = await Promise.all([
Promise.all(
devices.map((device) =>
balena.models.device.getManifestBySlug(device.device_type),
balena.models.device.getManifestBySlug(
device.is_of__device_type[0].slug,
),
),
),
balena.models.config.getDeviceTypes(),
@ -127,8 +134,10 @@ export default class DeviceMoveCmd extends Command {
const patterns = await import('../../utils/patterns');
try {
application = await patterns.selectApplication(
(app: Application) =>
compatibleDeviceTypes.some((dt) => dt.slug === app.device_type) &&
(app) =>
compatibleDeviceTypes.some(
(dt) => dt.slug === app.is_for__device_type[0].slug,
) &&
// @ts-ignore using the extended device object prop
devices.some((device) => device.application_name !== app.app_name),
true,

View File

@ -81,12 +81,17 @@ export default class DeviceOsUpdateCmd extends Command {
// Get device info
const {
uuid,
device_type,
is_of__device_type,
os_version,
os_variant,
} = await sdk.models.device.get(params.uuid, {
$select: ['uuid', 'device_type', 'os_version', 'os_variant'],
});
} = (await sdk.models.device.get(params.uuid, {
$select: ['uuid', 'os_version', 'os_variant'],
$expand: {
is_of__device_type: {
$select: 'slug',
},
},
})) as DeviceWithDeviceType;
// Get current device OS version
const currentOsVersion = sdk.models.device.getOsVersion({
@ -101,7 +106,7 @@ export default class DeviceOsUpdateCmd extends Command {
// Get supported OS update versions
const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions(
device_type,
is_of__device_type[0].slug,
currentOsVersion,
);
if (hupVersionInfo.versions.length === 0) {

View File

@ -21,9 +21,9 @@ import * as cf from '../../utils/common-flags';
import { expandForAppName } from '../../utils/helpers';
import { getBalenaSdk, getVisuals, stripIndent } from '../../utils/lazy';
import { tryAsInteger } from '../../utils/validation';
import type { Device, Application } from 'balena-sdk';
import type { Application } from 'balena-sdk';
interface ExtendedDevice extends Device {
interface ExtendedDevice extends DeviceWithDeviceType {
dashboard_url?: string;
application_name?: string;
}
@ -80,15 +80,17 @@ export default class DevicesCmd extends Command {
options.application = options.application || options.app;
delete options.app;
let devices: ExtendedDevice[];
let devices;
if (options.application != null) {
devices = await balena.models.device.getAllByApplication(
devices = (await balena.models.device.getAllByApplication(
tryAsInteger(options.application),
expandForAppName,
);
)) as ExtendedDevice[];
} else {
devices = await balena.models.device.getAll(expandForAppName);
devices = (await balena.models.device.getAll(
expandForAppName,
)) as ExtendedDevice[];
}
devices = devices.map(function (device) {
@ -100,6 +102,9 @@ export default class DevicesCmd extends Command {
: 'N/a';
device.uuid = device.uuid.slice(0, 7);
// @ts-ignore
device.device_type = device.is_of__device_type[0].slug;
return device;
});

View File

@ -76,7 +76,9 @@ export default class DevicesSupportedCmd extends Command {
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DevicesSupportedCmd);
let deviceTypes: Array<Partial<SDK.DeviceType>> = await getBalenaSdk()
let deviceTypes: Array<Partial<
SDK.DeviceTypeJson.DeviceType
>> = await getBalenaSdk()
.models.config.getDeviceTypes()
.map((d) => {
if (d.aliases && d.aliases.length) {
@ -100,7 +102,7 @@ export default class DevicesSupportedCmd extends Command {
: ['slug', 'aliases', 'arch', 'name'];
deviceTypes = _.sortBy(
deviceTypes.map((d) => {
const picked = _.pick<Partial<SDK.DeviceType>>(d, fields);
const picked = _.pick(d, fields);
// 'BETA' renamed to 'NEW'
picked.state = picked.state === 'BETA' ? 'NEW' : picked.state;
return picked;

View File

@ -317,7 +317,7 @@ async function getAppVars(
appVars.push(...vars);
}
if (!options.config && (options.service || options.all)) {
const pineOpts: SDK.PineOptionsFor<SDK.ServiceEnvironmentVariable> = {
const pineOpts: SDK.PineOptions<SDK.ServiceEnvironmentVariable> = {
$expand: {
service: {},
},
@ -359,7 +359,7 @@ async function getDeviceVars(
deviceVars.push(...deviceConfigVars);
} else {
if (options.service || options.all) {
const pineOpts: SDK.PineOptionsFor<SDK.DeviceServiceEnvironmentVariable> = {
const pineOpts: SDK.PineOptions<SDK.DeviceServiceEnvironmentVariable> = {
$expand: {
service_install: {
$expand: 'installs__service',

View File

@ -20,7 +20,7 @@ import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getCliForm, stripIndent } from '../../utils/lazy';
import * as _ from 'lodash';
import type { DeviceType } from 'balena-sdk';
import type { DeviceTypeJson } from 'balena-sdk';
interface FlagsDef {
advanced: boolean;
@ -104,7 +104,7 @@ export default class OsBuildConfigCmd extends Command {
}
async buildConfigForDeviceType(
deviceTypeManifest: DeviceType,
deviceTypeManifest: DeviceTypeJson.DeviceType,
advanced: boolean,
) {
if (advanced == null) {

View File

@ -51,10 +51,6 @@ interface ArgsDef {
image: string;
}
interface DeferredDevice extends BalenaSdk.Device {
belongs_to__application: BalenaSdk.PineDeferred;
}
interface Answers {
appUpdatePollInterval: number; // in minutes
deviceType: string; // e.g. "raspberrypi3"
@ -192,18 +188,29 @@ export default class OsConfigureCmd extends Command {
'../../utils/config'
);
const helpers = await import('../../utils/helpers');
let app: BalenaSdk.Application | undefined;
let device: BalenaSdk.Device | undefined;
let app: ApplicationWithDeviceType | undefined;
let device;
let deviceTypeSlug: string;
const balena = getBalenaSdk();
if (options.device) {
device = await balena.models['device'].get(options.device);
deviceTypeSlug = device.device_type;
device = (await balena.models.device.get(options.device, {
$expand: {
is_of__device_type: { $select: 'slug' },
},
})) as DeviceWithDeviceType & {
belongs_to__application: BalenaSdk.PineDeferred;
};
deviceTypeSlug = device.is_of__device_type[0].slug;
} else {
app = await balena.models['application'].get(options.application!);
app = (await balena.models.application.get(options.application!, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
await checkDeviceTypeCompatibility(balena, options, app);
deviceTypeSlug = options['device-type'] || app.device_type;
deviceTypeSlug =
options['device-type'] || app.is_for__device_type[0].slug;
}
const deviceTypeManifest = await helpers.getManifest(
@ -232,7 +239,7 @@ export default class OsConfigureCmd extends Command {
if (_.isEmpty(configJson)) {
if (device) {
configJson = await generateDeviceConfig(
device as DeferredDevice,
device,
options['device-api-key'],
answers,
);
@ -335,7 +342,7 @@ async function validateOptions(options: FlagsDef) {
*/
async function getOsVersionFromImage(
imagePath: string,
deviceTypeManifest: BalenaSdk.DeviceType,
deviceTypeManifest: BalenaSdk.DeviceTypeJson.DeviceType,
devInit: typeof import('balena-device-init'),
): Promise<string> {
const osVersion = await devInit.getImageOsVersion(
@ -361,11 +368,11 @@ async function getOsVersionFromImage(
async function checkDeviceTypeCompatibility(
sdk: BalenaSdk.BalenaSDK,
options: FlagsDef,
app: BalenaSdk.Application,
app: ApplicationWithDeviceType,
) {
if (options['device-type']) {
const [appDeviceType, optionDeviceType] = await Promise.all([
sdk.models.device.getManifestBySlug(app.device_type),
sdk.models.device.getManifestBySlug(app.is_for__device_type[0].slug),
sdk.models.device.getManifestBySlug(options['device-type']),
]);
const helpers = await import('../../utils/helpers');
@ -392,7 +399,7 @@ async function checkDeviceTypeCompatibility(
* The questions are extracted from the given deviceType "manifest".
*/
async function askQuestionsForDeviceType(
deviceType: BalenaSdk.DeviceType,
deviceType: BalenaSdk.DeviceTypeJson.DeviceType,
options: FlagsDef,
configJson?: import('../../utils/config').ImgConfig,
): Promise<Answers> {
@ -460,14 +467,17 @@ async function askQuestionsForDeviceType(
* [ 'network', 'wifiSsid', 'wifiKey', 'appUpdatePollInterval' ]
*/
function getQuestionNames(
deviceType: BalenaSdk.DeviceType,
deviceType: BalenaSdk.DeviceTypeJson.DeviceType,
): Array<keyof Answers> {
const questionNames: string[] = _.chain(deviceType.options)
.flatMap(
(group: BalenaSdk.DeviceTypeOptions) =>
(group: BalenaSdk.DeviceTypeJson.DeviceTypeOptions) =>
(group.isGroup && group.options) || [],
)
.map((groupOption: BalenaSdk.DeviceTypeOptionsGroup) => groupOption.name)
.map(
(groupOption: BalenaSdk.DeviceTypeJson.DeviceTypeOptionsGroup) =>
groupOption.name,
)
.filter()
.value();
return questionNames as Array<keyof Answers>;

View File

@ -20,7 +20,7 @@ import Command from '../command';
import * as cf from '../utils/common-flags';
import { getBalenaSdk, stripIndent } from '../utils/lazy';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
import type { BalenaSDK } from 'balena-sdk';
import type { BalenaSDK, Application, Organization } from 'balena-sdk';
import { ExpectedError, instanceOf } from '../errors';
enum BuildTarget {
@ -362,17 +362,21 @@ export default class PushCmd extends Command {
async getAppOwner(sdk: BalenaSDK, appName: string) {
const _ = await import('lodash');
const applications = await sdk.models.application.getAll({
const applications = (await sdk.models.application.getAll({
$expand: {
user: {
$select: ['username'],
organization: {
$select: ['handle'],
},
},
$filter: {
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
},
$select: ['id'],
});
})) as Array<
Application & {
organization: [Organization];
}
>;
if (applications == null || applications.length === 0) {
throw new ExpectedError(
@ -385,7 +389,7 @@ export default class PushCmd extends Command {
}
if (applications.length === 1) {
return _.get(applications, '[0].user[0].username');
return applications[0].organization[0].handle;
}
// If we got more than one application with the same name it means that the
@ -393,7 +397,7 @@ export default class PushCmd extends Command {
// present a list to the user which shows the fully qualified application
// name (user/appname) and allows them to select
const entries = _.map(applications, (app) => {
const username = _.get(app, 'user[0].username');
const username = app.organization[0].handle;
return {
name: `${username}/${appName}`,
extra: username,

View File

@ -94,7 +94,7 @@ const deployProject = function (docker, logger, composeOpts, opts) {
project.name,
compositionToBuild,
opts.app.arch,
opts.app.device_type,
opts.app.is_for__device_type[0].slug,
opts.buildEmulated,
opts.buildOpts,
composeOpts.inlineLogs,

View File

@ -39,6 +39,9 @@ const applicationExpandOptions = {
status: 'success',
},
},
should_be_running__release: {
$select: 'commit',
},
};
let allDeviceTypes;
@ -69,11 +72,18 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
const balena = getBalenaSdk();
return getDeviceTypesWithSameArch(deviceType).then((deviceTypes) => {
/** @type {import('balena-sdk').PineOptionsFor<import('balena-sdk').Application>} */
/** @type {import('balena-sdk').PineOptions<ApplicationWithDeviceType & { should_be_running__release: [import('balena-sdk').Release?] }>} */
const options = {
$filter: {
device_type: {
$in: deviceTypes,
is_for__device_type: {
$any: {
$alias: 'dt',
$expr: {
dt: {
slug: { $in: deviceTypes },
},
},
},
},
owns__release: {
$any: {
@ -87,13 +97,7 @@ const getApplicationsWithSuccessfulBuilds = function (deviceType) {
},
},
$expand: applicationExpandOptions,
$select: [
'id',
'app_name',
'device_type',
'commit',
'should_track_latest_release',
],
$select: ['id', 'app_name', 'should_track_latest_release'],
$orderby: 'app_name asc',
};
return balena.pine.get({
@ -209,9 +213,10 @@ Would you like to disable automatic updates for this application now?\
/**
* @param {import('balena-sdk').BalenaSDK} balenaSdk
* @param {string | number} appId
* @returns {Promise<import('balena-sdk').Application>}
* @returns {Promise<import('balena-sdk').Application & { should_be_running__release: [import('balena-sdk').Release?] }>}
*/
async function getAppWithReleases(balenaSdk, appId) {
// @ts-ignore
return balenaSdk.models.application.get(appId, {
$expand: applicationExpandOptions,
});
@ -229,10 +234,12 @@ async function prepareAndPreload(preloader, balenaSdk, options) {
/** @type {string} commit hash or the strings 'latest' or 'current' */
let commit;
const appCommit = application.should_be_running__release[0]?.commit;
// Use the commit given as --commit or show an interactive commit selection menu
if (options.commit) {
if (isCurrent(options.commit)) {
if (!application.commit) {
if (!appCommit) {
throw new Error(
`Unexpected empty commit hash for app ID "${application.id}"`,
);
@ -257,7 +264,7 @@ async function prepareAndPreload(preloader, balenaSdk, options) {
await preloader.setAppIdAndCommit(
application.id,
isCurrent(commit) ? application.commit : commit,
isCurrent(commit) ? appCommit : commit,
);
// Propose to disable automatic app updates if the commit is not the current release

View File

@ -92,7 +92,7 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize(
$expand: selectAppFields
? { belongs_to__application: { $select: selectAppFields } }
: 'belongs_to__application',
} as SDK.PineOptionsFor<SDK.Device>;
} as SDK.PineOptions<SDK.Device>;
if (selectDeviceFields) {
pineOpts.$select = selectDeviceFields as any;
}

View File

@ -102,7 +102,7 @@ export async function generateApplicationConfig(
}
export function generateDeviceConfig(
device: BalenaSdk.Device & {
device: DeviceWithDeviceType & {
belongs_to__application: BalenaSdk.PineDeferred;
},
deviceApiKey: string | true | undefined,
@ -113,7 +113,7 @@ export function generateDeviceConfig(
.then(async (application) => {
const baseConfigOpts = {
...options,
deviceType: device.device_type,
deviceType: device.is_of__device_type[0].slug,
};
const config = await generateBaseConfig(application, baseConfigOpts);

View File

@ -21,7 +21,7 @@ import * as _ from 'lodash';
import * as os from 'os';
import type * as ShellEscape from 'shell-escape';
import type { Device, PineOptionsFor } from 'balena-sdk';
import type { Device, PineOptions } from 'balena-sdk';
import { ExpectedError } from '../errors';
import { getBalenaSdk, getChalk, getVisuals } from './lazy';
import { promisify } from 'util';
@ -121,7 +121,7 @@ export function runCommand<T>(commandArgs: string[]): Promise<T> {
export async function getManifest(
image: string,
deviceType: string,
): Promise<BalenaSdk.DeviceType> {
): Promise<BalenaSdk.DeviceTypeJson.DeviceType> {
const init = await import('balena-device-init');
const manifest = await init.getImageManifest(image);
if (manifest != null) {
@ -131,8 +131,8 @@ export async function getManifest(
}
export const areDeviceTypesCompatible = (
appDeviceType: BalenaSdk.DeviceType,
osDeviceType: BalenaSdk.DeviceType,
appDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
osDeviceType: BalenaSdk.DeviceTypeJson.DeviceType,
) =>
getBalenaSdk().models.os.isArchitectureCompatibleWith(
osDeviceType.arch,
@ -166,13 +166,13 @@ export async function osProgressHandler(step: InitializeEmitter) {
export function getAppWithArch(
applicationName: string,
): Promise<BalenaSdk.Application & { arch: string }> {
): Promise<ApplicationWithDeviceType & { arch: string }> {
return Promise.all([
getApplication(applicationName),
getBalenaSdk().models.config.getDeviceTypes(),
]).then(function ([app, deviceTypes]) {
const config = _.find<BalenaSdk.DeviceType>(deviceTypes, {
slug: app.device_type,
const config = _.find<BalenaSdk.DeviceTypeJson.DeviceType>(deviceTypes, {
slug: app.is_for__device_type[0].slug,
});
if (!config) {
@ -183,16 +183,21 @@ export function getAppWithArch(
});
}
function getApplication(applicationName: string) {
function getApplication(
applicationName: string,
): Promise<ApplicationWithDeviceType> {
// Check for an app of the form `user/application`, and send
// that off to a special handler (before importing any modules)
const match = applicationName.split('/');
const extraOptions: BalenaSdk.PineOptionsFor<BalenaSdk.Application> = {
const extraOptions: BalenaSdk.PineOptions<BalenaSdk.Application> = {
$expand: {
application_type: {
$select: ['name', 'slug', 'supports_multicontainer', 'is_legacy'],
},
is_for__device_type: {
$select: 'slug',
},
},
};
@ -487,6 +492,10 @@ export function getProxyConfig(): ProxyConfig | undefined {
}
}
export const expandForAppName: PineOptionsFor<Device> = {
$expand: { belongs_to__application: { $select: 'app_name' } },
export const expandForAppName: PineOptions<Device> = {
$expand: {
belongs_to__application: { $select: 'app_name' },
is_of__device_type: { $select: 'slug' },
is_running__release: { $select: 'commit' },
},
};

View File

@ -168,7 +168,7 @@ export async function confirm(
}
export function selectApplication(
filter?: (app: BalenaSdk.Application) => boolean,
filter?: (app: ApplicationWithDeviceType) => boolean,
errorOnEmptySelection = false,
) {
const balena = getBalenaSdk();
@ -179,7 +179,7 @@ export function selectApplication(
throw new ExpectedError("You don't have any applications");
}
return balena.models.application.getAll();
return balena.models.application.getAll() as Promise<ApplicationWithDeviceType[]>;
})
.filter(filter || _.constant(true))
.then((applications) => {
@ -190,7 +190,7 @@ export function selectApplication(
message: 'Select an application',
type: 'list',
choices: _.map(applications, (application) => ({
name: `${application.app_name} (${application.device_type})`,
name: `${application.app_name} (${application.is_for__device_type[0].slug})`,
value: application.app_name,
})),
});
@ -207,14 +207,20 @@ export function selectOrCreateApplication() {
return Promise.resolve(undefined);
}
return balena.models.application.getAll().then((applications) => {
const appOptions = _.map<
BalenaSdk.Application,
{ name: string; value: string | null }
>(applications, (application) => ({
name: `${application.app_name} (${application.device_type})`,
value: application.app_name,
}));
return (balena.models.application.getAll({
$expand: {
is_for__device_type: {
$select: 'slug',
},
},
}) as Promise<ApplicationWithDeviceType[]>).then((applications) => {
const appOptions: Array<{ name: string; value: string | null }> = _.map(
applications,
(application) => ({
name: `${application.app_name} (${application.is_for__device_type[0].slug})`,
value: application.app_name,
}),
);
appOptions.unshift({
name: 'Create a new application',

View File

@ -40,10 +40,12 @@ export async function join(
logger.logDebug('Determining application...');
const app = await getOrSelectApplication(sdk, deviceType, appName);
logger.logDebug(`Using application: ${app.app_name} (${app.device_type})`);
if (app.device_type !== deviceType) {
logger.logDebug(
`Using application: ${app.app_name} (${app.is_for__device_type[0].slug})`,
);
if (app.is_for__device_type[0].slug !== deviceType) {
logger.logDebug(`Forcing device type to: ${deviceType}`);
app.device_type = deviceType;
app.is_for__device_type[0].slug = deviceType;
}
logger.logDebug('Determining device OS version...');
@ -186,7 +188,9 @@ async function getOrSelectLocalDevice(deviceIp?: string): Promise<string> {
return ip;
}
async function selectAppFromList(applications: BalenaSdk.Application[]) {
async function selectAppFromList(
applications: ApplicationWithDeviceType[],
): Promise<ApplicationWithDeviceType> {
const _ = await import('lodash');
const { selectFromList } = await import('../utils/patterns');
@ -204,7 +208,7 @@ async function getOrSelectApplication(
sdk: BalenaSdk.BalenaSDK,
deviceType: string,
appName?: string,
): Promise<BalenaSdk.Application> {
): Promise<ApplicationWithDeviceType> {
const _ = await import('lodash');
const allDeviceTypes = await sdk.models.config.getDeviceTypes();
@ -229,7 +233,11 @@ async function getOrSelectApplication(
return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType);
}
const options: BalenaSdk.PineOptionsFor<BalenaSdk.Application> = {};
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
$expand: {
is_for__device_type: { $select: 'slug' },
},
};
// Check for an app of the form `user/application` and update the API query.
let name: string;
@ -245,7 +253,9 @@ async function getOrSelectApplication(
name = appName;
}
const applications = await sdk.models.application.getAll(options);
const applications = (await sdk.models.application.getAll(
options,
)) as ApplicationWithDeviceType[];
if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({
@ -264,7 +274,7 @@ async function getOrSelectApplication(
// We've found at least one app with the given name.
// Filter out apps for non-matching device types and see what we're left with.
const validApplications = applications.filter((app) =>
_.includes(compatibleDeviceTypes, app.device_type),
_.includes(compatibleDeviceTypes, app.is_for__device_type[0].slug),
);
if (validApplications.length === 0) {
@ -285,13 +295,19 @@ async function createOrSelectAppOrExit(
sdk: BalenaSdk.BalenaSDK,
compatibleDeviceTypes: string[],
deviceType: string,
) {
const options = {
$filter: { device_type: { $in: compatibleDeviceTypes } },
};
): Promise<ApplicationWithDeviceType> {
// No application specified, show a list to select one.
const applications = await sdk.models.application.getAll(options);
const applications = (await sdk.models.application.getAll({
$expand: { is_for__device_type: { $select: 'slug' } },
$filter: {
is_for__device_type: {
$any: {
$alias: 'dt',
$expr: { dt: { slug: { $in: compatibleDeviceTypes } } },
},
},
},
})) as ApplicationWithDeviceType[];
if (applications.length === 0) {
const shouldCreateApp = await getCliForm().ask({
@ -314,14 +330,13 @@ async function createApplication(
sdk: BalenaSdk.BalenaSDK,
deviceType: string,
name?: string,
): Promise<BalenaSdk.Application> {
): Promise<ApplicationWithDeviceType> {
const validation = await import('./validation');
let username = await sdk.auth.whoami();
const username = await sdk.auth.whoami();
if (!username) {
throw new sdk.errors.BalenaNotLoggedIn();
}
username = username.toLowerCase();
const applicationName = await new Promise<string>(async (resolve, reject) => {
while (true) {
@ -337,7 +352,7 @@ async function createApplication(
await sdk.models.application.get(appName, {
$filter: {
$or: [
{ slug: { $startswith: `${username}/` } },
{ slug: { $startswith: `${username!.toLowerCase()}/` } },
{ $not: { slug: { $contains: '/' } } },
],
},
@ -357,20 +372,28 @@ async function createApplication(
}
});
return sdk.models.application.create({
const app = await sdk.models.application.create({
name: applicationName,
deviceType,
organization: username,
});
return (await sdk.models.application.get(app.id, {
$expand: {
is_for__device_type: { $select: 'slug' },
},
})) as ApplicationWithDeviceType;
}
async function generateApplicationConfig(
sdk: BalenaSdk.BalenaSDK,
app: BalenaSdk.Application,
app: ApplicationWithDeviceType,
options: { version: string },
) {
const { generateApplicationConfig: configGen } = await import('./config');
const manifest = await sdk.models.device.getManifestBySlug(app.device_type);
const manifest = await sdk.models.device.getManifestBySlug(
app.is_for__device_type[0].slug,
);
const opts =
manifest.options &&
manifest.options.filter((opt) => opt.name !== 'network');

165
npm-shrinkwrap.json generated
View File

@ -2243,44 +2243,6 @@
"resin-image-fs": "^5.0.9"
},
"dependencies": {
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
},
"dependencies": {
"balena-errors": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==",
"requires": {
"tslib": "^1.11.1",
"typed-error": "^3.0.0"
}
}
}
},
"balena-semver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2354,44 +2316,6 @@
"rimraf": "^3.0.2"
},
"dependencies": {
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
},
"dependencies": {
"balena-errors": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==",
"requires": {
"tslib": "^1.11.1",
"typed-error": "^3.0.0"
}
}
}
},
"balena-semver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2474,34 +2398,6 @@
"typed-error": "^3.0.0"
}
},
"balena-sdk": {
"version": "14.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.8.0.tgz",
"integrity": "sha512-GptB/Ju8BtE1E9NODHioScdF4HW8svcDhfdMHPrDjMFWbs38KOoTgiKogDjLlvEjoAg5FIvz8DaUeH27EtZ1Rg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2",
"tslib": "^2.0.0"
}
},
"balena-semver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -2638,19 +2534,20 @@
}
},
"balena-sdk": {
"version": "13.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-13.8.0.tgz",
"integrity": "sha512-EhaXZq7kNCewhp0oSMeeTslvRIhvaV0QxlS7u3SWmdMoDjH1gdg01sG2I3b20DkN8oDAIoNkaD14eo2eTf0C8w==",
"version": "14.8.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.8.0.tgz",
"integrity": "sha512-GptB/Ju8BtE1E9NODHioScdF4HW8svcDhfdMHPrDjMFWbs38KOoTgiKogDjLlvEjoAg5FIvz8DaUeH27EtZ1Rg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.0.1",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
@ -2660,15 +2557,16 @@
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
"semver": "^7.3.2",
"tslib": "^2.0.0"
},
"dependencies": {
"balena-errors": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.0.tgz",
"integrity": "sha512-w5Zje97Gl0veNKpAhH4OQ3R7ACt0MYNUBfb37o1/Yq7EeeB7HsVFsvXNhZwBn8DIBvJamxZWSIbqjw53GKjUzQ==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.4.1.tgz",
"integrity": "sha512-912lPp1LyBjkpxRg6m/EpOCssqMhgkzyYbrKwtT2uRvixm89WOlJrj5sPkxnbPnp5IoMNaoRONxFt1jtiQf50Q==",
"requires": {
"tslib": "^1.11.1",
"tslib": "^2.0.0",
"typed-error": "^3.0.0"
}
},
@ -2689,9 +2587,9 @@
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
}
}
},
@ -2794,33 +2692,6 @@
}
}
},
"balena-sdk": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-14.1.0.tgz",
"integrity": "sha512-J/T1F3zQI6GChK0zZp5YKdNhBSynx3VaXk7tGcGykNnLuN4ZVeApHPZ7vVLUuyN/hvckax55iKSPO5JSEYA0sg==",
"requires": {
"@balena/es-version": "^1.0.0",
"@types/bluebird": "^3.5.30",
"@types/lodash": "^4.14.150",
"@types/memoizee": "^0.4.3",
"@types/node": "^10.17.20",
"abortcontroller-polyfill": "^1.4.0",
"balena-auth": "^3.1.0",
"balena-errors": "^4.4.0",
"balena-hup-action-utils": "~4.0.1",
"balena-pine": "^11.2.0",
"balena-register-device": "^6.1.1",
"balena-request": "^10.0.9",
"balena-semver": "^2.3.0",
"balena-settings-client": "^4.0.4",
"bluebird": "^3.7.2",
"lodash": "^4.17.15",
"memoizee": "^0.4.14",
"moment": "~2.24.0 || ^2.25.1",
"ndjson": "^1.5.0",
"semver": "^7.3.2"
}
},
"balena-semver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/balena-semver/-/balena-semver-2.3.0.tgz",
@ -12604,9 +12475,9 @@
}
},
"ref-napi": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-2.0.3.tgz",
"integrity": "sha512-zsAhPhh9gUlk0pP5iR9nhvwFeC/E9G1X0cdH/qQRTwx3VDgVi40Aflq/EdbobcVg++RNaMxZsbaQV+/E2u57LQ==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-2.0.4.tgz",
"integrity": "sha512-Ees+O+abgS4skRDU/RyICk9gbSaVjYCN1FXivAlcxdj/yW9N+GFKbB9fYACa+lgBjjqjSGHw5SkoP5GU0anwwQ==",
"optional": true,
"requires": {
"debug": "^4.1.1",

View File

@ -26,6 +26,7 @@
"pkg": {
"scripts": [
"build/**/*.js",
"node_modules/balena-sdk/es2018/index.js",
"node_modules/balena-sync/build/capitano/*.js",
"node_modules/balena-sync/build/sync/*.js",
"node_modules/pinejs-client-request/node_modules/pinejs-client-core/es2018/index.js",
@ -192,7 +193,7 @@
"balena-image-manager": "^7.0.1",
"balena-preload": "^10.2.0",
"balena-release": "^3.0.0",
"balena-sdk": "^13.6.0",
"balena-sdk": "^14.8.0",
"balena-semver": "^2.2.0",
"balena-settings-client": "^4.0.5",
"balena-settings-storage": "^6.0.0",

View File

@ -35,26 +35,6 @@ export class BalenaAPIMock extends NockMock {
notFound = false,
optional = false,
persist = false,
} = {}) {
const interceptor = this.optGet(/^\/v5\/application($|[(?])/, {
optional,
persist,
});
if (notFound) {
interceptor.reply(200, { d: [] });
} else {
interceptor.replyWithFile(
200,
path.join(apiResponsePath, 'application-GET-v5-expanded-app-type.json'),
jHeader,
);
}
}
public expectGetApplicationV6({
notFound = false,
optional = false,
persist = false,
} = {}) {
const interceptor = this.optGet(/^\/v6\/application($|[(?])/, {
optional,
@ -99,10 +79,10 @@ export class BalenaAPIMock extends NockMock {
}
public expectGetMyApplication(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/my_application($|[(?])/, opts).reply(
this.optGet(/^\/v6\/my_application($|[(?])/, opts).reply(
200,
JSON.parse(`{"d": [{
"user": [{ "username": "bob", "__metadata": {} }],
"organization": [{ "handle": "bob", "__metadata": {} }],
"id": 1301645,
"__metadata": { "uri": "/resin/my_application(@id)?@id=1301645" }}]}
`),
@ -116,13 +96,16 @@ export class BalenaAPIMock extends NockMock {
}
public expectGetRelease(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/release($|[(?])/, opts).replyWithFile(
this.optGet(/^\/v6\/release($|[(?])/, opts).replyWithFile(
200,
path.join(apiResponsePath, 'release-GET-v5.json'),
path.join(apiResponsePath, 'release-GET-v6.json'),
jHeader,
);
}
/**
* Mocks balena-release call
*/
public expectPatchRelease({
replyBody = 'OK',
statusCode = 200,
@ -136,6 +119,9 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectPostRelease(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/release($|[(?])/, opts).replyWithFile(
200,
@ -144,6 +130,9 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectPatchImage({
replyBody = 'OK',
statusCode = 200,
@ -157,6 +146,9 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectPostImage(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/image($|[(?])/, opts).replyWithFile(
201,
@ -165,6 +157,9 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectPostImageLabel(opts: ScopeOpts = {}) {
this.optPost(/^\/v6\/image_label($|[(?])/, opts).replyWithFile(
201,
@ -173,6 +168,9 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectPostImageIsPartOfRelease(opts: ScopeOpts = {}) {
this.optPost(
/^\/v6\/image__is_part_of__release($|[(?])/,
@ -350,23 +348,10 @@ export class BalenaAPIMock extends NockMock {
);
}
/**
* Mocks balena-release call
*/
public expectGetUser(opts: ScopeOpts = {}) {
this.optGet(/^\/v5\/user/, opts).reply(200, {
d: [
{
id: 99999,
actor: 1234567,
username: 'gh_user',
created_at: '2018-08-19T13:55:04.485Z',
__metadata: {
uri: '/resin/user(@id)?@id=43699',
},
},
],
});
}
public expectGetUserV6(opts: ScopeOpts = {}) {
this.optGet(/^\/v6\/user/, opts).reply(200, {
d: [
{

View File

@ -80,10 +80,9 @@ describe('balena deploy', function () {
api.expectGetMixpanel({ optional: true });
api.expectGetDeviceTypes();
api.expectGetApplication();
api.expectGetApplicationV6();
api.expectPostRelease();
api.expectGetRelease();
api.expectGetUserV6();
api.expectGetUser();
api.expectGetService({ serviceName: 'main' });
api.expectPostService409();
api.expectGetAuth();

View File

@ -74,7 +74,7 @@ describe('balena device', function () {
it('should list device details for provided uuid', async () => {
api.scope
.get(
/^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
)
.replyWithFile(200, path.join(apiResponsePath, 'device.json'), {
'Content-Type': 'application/json',
@ -96,7 +96,7 @@ describe('balena device', function () {
// e.g. When user has a device associated with app that user is no longer a collaborator of.
api.scope
.get(
/^\/v5\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
/^\/v6\/device\?.+&\$expand=belongs_to__application\(\$select=app_name\)/,
)
.replyWithFile(
200,

View File

@ -75,7 +75,7 @@ describe('balena devices', function () {
it('should list devices from own and collaborator apps', async () => {
api.scope
.get(
'/v5/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name)',
'/v6/device?$orderby=device_name%20asc&$expand=belongs_to__application($select=app_name),is_of__device_type($select=slug),is_running__release($select=commit)',
)
.replyWithFile(200, path.join(apiResponsePath, 'devices.json'), {
'Content-Type': 'application/json',

View File

@ -1,35 +0,0 @@
{
"d": [
{
"application_type": [
{
"name": "Starter",
"slug": "microservices-starter",
"supports_multicontainer": true,
"is_legacy": false,
"__metadata": {}
}
],
"id": 1301645,
"user": {
"__deferred": {
"uri": "/resin/user(43699)"
},
"__id": 43699
},
"depends_on__application": null,
"actor": 3423895,
"app_name": "testApp",
"slug": "gh_user/testApp",
"commit": "96eec431d57e6976d3a756df33fde7e2",
"device_type": "raspberrypi3",
"should_track_latest_release": true,
"is_accessible_by_support_until__date": null,
"is_public": false,
"is_host": false,
"__metadata": {
"uri": "/resin/application(@id)?@id=1301645"
}
}
]
}

View File

@ -21,8 +21,16 @@
"actor": 3423895,
"app_name": "testApp",
"slug": "gh_user/testApp",
"commit": "96eec431d57e6976d3a756df33fde7e2",
"device_type": "raspberrypi3",
"should_be__running_release": [
{
"commit": "96eec431d57e6976d3a756df33fde7e2"
}
],
"is_for__device_type": [
{
"slug": "raspberrypi3"
}
],
"should_track_latest_release": true,
"is_accessible_by_support_until__date": null,
"is_public": false,

View File

@ -1,26 +1,25 @@
{
"d": [
{
"belongs_to__application": [
],
"id": 1747415,
"is_managed_by__device": null,
"device_name": "sparkling-wood",
"device_type": "raspberrypi4-64",
"uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804",
"note": null,
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"mac_address": null,
"os_version": "balenaOS 2.44.0+rev3",
"supervisor_version": "10.3.7",
"is_web_accessible": false,
"overall_status": "idle",
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}
}
]
"d": [
{
"belongs_to__application": [],
"id": 1747415,
"is_managed_by__device": null,
"device_name": "sparkling-wood",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2",
"is_running__release": [{ "commit": "18756d3386c25a044db66b89e0409804" }],
"note": null,
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"mac_address": null,
"os_version": "balenaOS 2.44.0+rev3",
"supervisor_version": "10.3.7",
"is_web_accessible": false,
"overall_status": "idle",
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}
}
]
}

View File

@ -1,30 +1,30 @@
{
"d": [
{
"belongs_to__application": [
"d": [
{
"app_name": "test app",
"__metadata": {}
"belongs_to__application": [
{
"app_name": "test app",
"__metadata": {}
}
],
"id": 1747415,
"is_managed_by__device": null,
"device_name": "sparkling-wood",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2",
"is_running__release": [{ "commit": "18756d3386c25a044db66b89e0409804" }],
"note": null,
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"mac_address": null,
"os_version": "balenaOS 2.44.0+rev3",
"supervisor_version": "10.3.7",
"is_web_accessible": false,
"overall_status": "offline",
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}
}
],
"id": 1747415,
"is_managed_by__device": null,
"device_name": "sparkling-wood",
"device_type": "raspberrypi4-64",
"uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804",
"note": null,
"is_online": false,
"last_connectivity_event": "2019-11-23T00:26:35.074Z",
"ip_address": "192.168.0.112",
"mac_address": null,
"os_version": "balenaOS 2.44.0+rev3",
"supervisor_version": "10.3.7",
"is_web_accessible": false,
"overall_status": "offline",
"__metadata": {
"uri": "/resin/device(@id)?@id=1747415"
}
}
]
]
}

View File

@ -16,11 +16,14 @@
},
"is_managed_by__device": null,
"actor": 4180757,
"should_be_running__release": null,
"device_name": "sparkling-wood",
"device_type": "raspberrypi4-64",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f2",
"is_on__commit": "18756d3386c25a044db66b89e0409804",
"is_running__release": [
{
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null,
"local_id": null,
"status": "Idle",
@ -61,8 +64,7 @@
}
},
{
"belongs_to__application": [
],
"belongs_to__application": [],
"id": 1747416,
"belongs_to__user": {
"__deferred": {
@ -72,11 +74,14 @@
},
"is_managed_by__device": null,
"actor": 4180757,
"should_be_running__release": null,
"device_name": "dashing-spruce",
"device_type": "raspberrypi4-64",
"is_of__device_type": [{ "slug": "raspberrypi4-64" }],
"uuid": "fda508c8583011b8466c26abdd5159f3",
"is_on__commit": "18756d3386c25a044db66b89e0409804",
"is_running__release": [
{
"commit": "18756d3386c25a044db66b89e0409804"
}
],
"note": null,
"local_id": null,
"status": "Idle",

View File

@ -16,7 +16,7 @@
*/
declare module 'balena-device-init' {
import { DeviceType } from 'balena-sdk';
import { DeviceTypeJson } from 'balena-sdk';
import * as Bluebird from 'bluebird';
import { EventEmitter } from 'events';
@ -81,21 +81,23 @@ declare module 'balena-device-init' {
export function configure(
image: string,
manifest: DeviceType,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
config: {},
options?: {},
): Bluebird<InitializeEmitter>;
export function initialize(
image: string,
manifest: DeviceType,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
config: {},
): Bluebird<InitializeEmitter>;
export function getImageOsVersion(
image: string,
manifest: DeviceType,
manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType,
): Bluebird<string | null>;
export function getImageManifest(image: string): Bluebird<DeviceType | null>;
export function getImageManifest(
image: string,
): Bluebird<BalenaSdk.DeviceTypeJson.DeviceType.DeviceType | null>;
}

10
typings/global.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import { Application, DeviceType, Device } from 'balena-sdk';
declare global {
type ApplicationWithDeviceType = Application & {
is_for__device_type: [DeviceType];
};
type DeviceWithDeviceType = Device & {
is_of__device_type: [DeviceType];
};
}