From e1c42405a175f724af9bcd4600b36893d8c7e4cf Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Fri, 31 Jul 2020 15:35:20 +0100 Subject: [PATCH] Update balena-sdk to 14.x Update balena-sdk from 13.6.0 to 14.8.0 Change-type: patch --- lib/actions-oclif/app/create.ts | 3 +- lib/actions-oclif/app/index.ts | 17 +- lib/actions-oclif/apps.ts | 16 +- lib/actions-oclif/build.ts | 2 +- lib/actions-oclif/config/generate.ts | 25 ++- lib/actions-oclif/device/index.ts | 16 +- lib/actions-oclif/device/init.ts | 16 +- lib/actions-oclif/device/move.ts | 23 ++- lib/actions-oclif/device/os-update.ts | 15 +- lib/actions-oclif/devices/index.ts | 17 +- lib/actions-oclif/devices/supported.ts | 6 +- lib/actions-oclif/envs.ts | 4 +- lib/actions-oclif/os/build-config.ts | 4 +- lib/actions-oclif/os/configure.ts | 46 +++-- lib/actions-oclif/push.ts | 18 +- lib/actions/deploy.js | 2 +- lib/actions/preload.js | 33 ++-- lib/utils/cloud.ts | 2 +- lib/utils/config.ts | 4 +- lib/utils/helpers.ts | 31 ++-- lib/utils/patterns.ts | 28 +-- lib/utils/promote.ts | 65 ++++--- npm-shrinkwrap.json | 165 ++---------------- package.json | 3 +- tests/balena-api-mock.ts | 65 +++---- tests/commands/deploy.spec.ts | 3 +- tests/commands/device/device.spec.ts | 4 +- tests/commands/device/devices.spec.ts | 2 +- .../application-GET-v5-expanded-app-type.json | 35 ---- .../application-GET-v6-expanded-app-type.json | 12 +- .../api-response/device-missing-app.json | 47 +++-- tests/test-data/api-response/device.json | 52 +++--- tests/test-data/api-response/devices.json | 21 ++- ...elease-GET-v5.json => release-GET-v6.json} | 0 typings/balena-device-init/index.d.ts | 12 +- typings/global.d.ts | 10 ++ 36 files changed, 390 insertions(+), 434 deletions(-) delete mode 100644 tests/test-data/api-response/application-GET-v5-expanded-app-type.json rename tests/test-data/api-response/{release-GET-v5.json => release-GET-v6.json} (100%) create mode 100644 typings/global.d.ts diff --git a/lib/actions-oclif/app/create.ts b/lib/actions-oclif/app/create.ts index beda6cba..5cbec64a 100644 --- a/lib/actions-oclif/app/create.ts +++ b/lib/actions-oclif/app/create.ts @@ -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})`, ); } } diff --git a/lib/actions-oclif/app/index.ts b/lib/actions-oclif/app/index.ts index 37764967..ffbb1167 100644 --- a/lib/actions-oclif/app/index.ts +++ b/lib/actions-oclif/app/index.ts @@ -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(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}$`, diff --git a/lib/actions-oclif/apps.ts b/lib/actions-oclif/apps.ts index 6991fd2d..49a8822e 100644 --- a/lib/actions-oclif/apps.ts +++ b/lib/actions-oclif/apps.ts @@ -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 diff --git a/lib/actions-oclif/build.ts b/lib/actions-oclif/build.ts index 430c0a88..65a2f0b3 100644 --- a/lib/actions-oclif/build.ts +++ b/lib/actions-oclif/build.ts @@ -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; } } diff --git a/lib/actions-oclif/config/generate.ts b/lib/actions-oclif/config/generate.ts index 38b37f6d..381b7214 100644 --- a/lib/actions-oclif/config/generate.ts +++ b/lib/actions-oclif/config/generate.ts @@ -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'); diff --git a/lib/actions-oclif/device/index.ts b/lib/actions-oclif/device/index.ts index 8395837a..5814f5a7 100644 --- a/lib/actions-oclif/device/index.ts +++ b/lib/actions-oclif/device/index.ts @@ -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, [ diff --git a/lib/actions-oclif/device/init.ts b/lib/actions-oclif/device/init.ts index c8fc7c3f..791d3419 100644 --- a/lib/actions-oclif/device/init.ts +++ b/lib/actions-oclif/device/init.ts @@ -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 { diff --git a/lib/actions-oclif/device/move.ts b/lib/actions-oclif/device/move.ts index 51101cfd..a67ceeb9 100644 --- a/lib/actions-oclif/device/move.ts +++ b/lib/actions-oclif/device/move.ts @@ -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, diff --git a/lib/actions-oclif/device/os-update.ts b/lib/actions-oclif/device/os-update.ts index a28f2dc6..fd6601cd 100644 --- a/lib/actions-oclif/device/os-update.ts +++ b/lib/actions-oclif/device/os-update.ts @@ -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) { diff --git a/lib/actions-oclif/devices/index.ts b/lib/actions-oclif/devices/index.ts index 8502ded7..a7265d5a 100644 --- a/lib/actions-oclif/devices/index.ts +++ b/lib/actions-oclif/devices/index.ts @@ -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; }); diff --git a/lib/actions-oclif/devices/supported.ts b/lib/actions-oclif/devices/supported.ts index 4b964a5e..60e03390 100644 --- a/lib/actions-oclif/devices/supported.ts +++ b/lib/actions-oclif/devices/supported.ts @@ -76,7 +76,9 @@ export default class DevicesSupportedCmd extends Command { public async run() { const { flags: options } = this.parse(DevicesSupportedCmd); - let deviceTypes: Array> = await getBalenaSdk() + let deviceTypes: Array> = 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>(d, fields); + const picked = _.pick(d, fields); // 'BETA' renamed to 'NEW' picked.state = picked.state === 'BETA' ? 'NEW' : picked.state; return picked; diff --git a/lib/actions-oclif/envs.ts b/lib/actions-oclif/envs.ts index d32a4926..85eb2e9a 100644 --- a/lib/actions-oclif/envs.ts +++ b/lib/actions-oclif/envs.ts @@ -317,7 +317,7 @@ async function getAppVars( appVars.push(...vars); } if (!options.config && (options.service || options.all)) { - const pineOpts: SDK.PineOptionsFor = { + const pineOpts: SDK.PineOptions = { $expand: { service: {}, }, @@ -359,7 +359,7 @@ async function getDeviceVars( deviceVars.push(...deviceConfigVars); } else { if (options.service || options.all) { - const pineOpts: SDK.PineOptionsFor = { + const pineOpts: SDK.PineOptions = { $expand: { service_install: { $expand: 'installs__service', diff --git a/lib/actions-oclif/os/build-config.ts b/lib/actions-oclif/os/build-config.ts index 2779ab89..2aa9129b 100644 --- a/lib/actions-oclif/os/build-config.ts +++ b/lib/actions-oclif/os/build-config.ts @@ -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) { diff --git a/lib/actions-oclif/os/configure.ts b/lib/actions-oclif/os/configure.ts index 557775d9..ad3e3884 100644 --- a/lib/actions-oclif/os/configure.ts +++ b/lib/actions-oclif/os/configure.ts @@ -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 { 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 { @@ -460,14 +467,17 @@ async function askQuestionsForDeviceType( * [ 'network', 'wifiSsid', 'wifiKey', 'appUpdatePollInterval' ] */ function getQuestionNames( - deviceType: BalenaSdk.DeviceType, + deviceType: BalenaSdk.DeviceTypeJson.DeviceType, ): Array { 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; diff --git a/lib/actions-oclif/push.ts b/lib/actions-oclif/push.ts index c150de09..f6236e7b 100644 --- a/lib/actions-oclif/push.ts +++ b/lib/actions-oclif/push.ts @@ -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, diff --git a/lib/actions/deploy.js b/lib/actions/deploy.js index 3f73ec6f..d25e551c 100644 --- a/lib/actions/deploy.js +++ b/lib/actions/deploy.js @@ -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, diff --git a/lib/actions/preload.js b/lib/actions/preload.js index fb84034b..405b0d11 100644 --- a/lib/actions/preload.js +++ b/lib/actions/preload.js @@ -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} */ + /** @type {import('balena-sdk').PineOptions} */ 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} + * @returns {Promise} */ 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 diff --git a/lib/utils/cloud.ts b/lib/utils/cloud.ts index 27d5c830..1fc9a45d 100644 --- a/lib/utils/cloud.ts +++ b/lib/utils/cloud.ts @@ -92,7 +92,7 @@ export const getDeviceAndMaybeAppFromUUID = _.memoize( $expand: selectAppFields ? { belongs_to__application: { $select: selectAppFields } } : 'belongs_to__application', - } as SDK.PineOptionsFor; + } as SDK.PineOptions; if (selectDeviceFields) { pineOpts.$select = selectDeviceFields as any; } diff --git a/lib/utils/config.ts b/lib/utils/config.ts index 3ed82565..dce8d74d 100644 --- a/lib/utils/config.ts +++ b/lib/utils/config.ts @@ -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); diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 00cee500..c6362080 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -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(commandArgs: string[]): Promise { export async function getManifest( image: string, deviceType: string, -): Promise { +): Promise { 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 { +): Promise { return Promise.all([ getApplication(applicationName), getBalenaSdk().models.config.getDeviceTypes(), ]).then(function ([app, deviceTypes]) { - const config = _.find(deviceTypes, { - slug: app.device_type, + const config = _.find(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 { // 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 = { + const extraOptions: BalenaSdk.PineOptions = { $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 = { - $expand: { belongs_to__application: { $select: 'app_name' } }, +export const expandForAppName: PineOptions = { + $expand: { + belongs_to__application: { $select: 'app_name' }, + is_of__device_type: { $select: 'slug' }, + is_running__release: { $select: 'commit' }, + }, }; diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index 627842c6..dc3d2b24 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -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; }) .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).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', diff --git a/lib/utils/promote.ts b/lib/utils/promote.ts index 7e92e048..7ec954bf 100644 --- a/lib/utils/promote.ts +++ b/lib/utils/promote.ts @@ -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 { return ip; } -async function selectAppFromList(applications: BalenaSdk.Application[]) { +async function selectAppFromList( + applications: ApplicationWithDeviceType[], +): Promise { 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 { +): Promise { 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 = {}; + const options: BalenaSdk.PineOptions = { + $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 { // 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 { +): Promise { 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(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'); diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2d56bdc2..6ffccfad 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -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", diff --git a/package.json b/package.json index b0235860..e54d256a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tests/balena-api-mock.ts b/tests/balena-api-mock.ts index 46450da8..488f9228 100644 --- a/tests/balena-api-mock.ts +++ b/tests/balena-api-mock.ts @@ -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: [ { diff --git a/tests/commands/deploy.spec.ts b/tests/commands/deploy.spec.ts index a7b038d9..1b997460 100644 --- a/tests/commands/deploy.spec.ts +++ b/tests/commands/deploy.spec.ts @@ -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(); diff --git a/tests/commands/device/device.spec.ts b/tests/commands/device/device.spec.ts index 18217a97..c97d0e6e 100644 --- a/tests/commands/device/device.spec.ts +++ b/tests/commands/device/device.spec.ts @@ -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, diff --git a/tests/commands/device/devices.spec.ts b/tests/commands/device/devices.spec.ts index c8627a17..20cb7925 100644 --- a/tests/commands/device/devices.spec.ts +++ b/tests/commands/device/devices.spec.ts @@ -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', diff --git a/tests/test-data/api-response/application-GET-v5-expanded-app-type.json b/tests/test-data/api-response/application-GET-v5-expanded-app-type.json deleted file mode 100644 index 67fa9312..00000000 --- a/tests/test-data/api-response/application-GET-v5-expanded-app-type.json +++ /dev/null @@ -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" - } - } - ] -} diff --git a/tests/test-data/api-response/application-GET-v6-expanded-app-type.json b/tests/test-data/api-response/application-GET-v6-expanded-app-type.json index 67fa9312..be6b3ab6 100644 --- a/tests/test-data/api-response/application-GET-v6-expanded-app-type.json +++ b/tests/test-data/api-response/application-GET-v6-expanded-app-type.json @@ -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, diff --git a/tests/test-data/api-response/device-missing-app.json b/tests/test-data/api-response/device-missing-app.json index e8754509..e5b98836 100644 --- a/tests/test-data/api-response/device-missing-app.json +++ b/tests/test-data/api-response/device-missing-app.json @@ -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" + } + } + ] } diff --git a/tests/test-data/api-response/device.json b/tests/test-data/api-response/device.json index 818c0264..3a854491 100644 --- a/tests/test-data/api-response/device.json +++ b/tests/test-data/api-response/device.json @@ -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" - } - } - ] + ] } diff --git a/tests/test-data/api-response/devices.json b/tests/test-data/api-response/devices.json index 0c9d4116..79b0439f 100644 --- a/tests/test-data/api-response/devices.json +++ b/tests/test-data/api-response/devices.json @@ -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", diff --git a/tests/test-data/api-response/release-GET-v5.json b/tests/test-data/api-response/release-GET-v6.json similarity index 100% rename from tests/test-data/api-response/release-GET-v5.json rename to tests/test-data/api-response/release-GET-v6.json diff --git a/typings/balena-device-init/index.d.ts b/typings/balena-device-init/index.d.ts index e7bffde4..f70c16be 100644 --- a/typings/balena-device-init/index.d.ts +++ b/typings/balena-device-init/index.d.ts @@ -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; export function initialize( image: string, - manifest: DeviceType, + manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType, config: {}, ): Bluebird; export function getImageOsVersion( image: string, - manifest: DeviceType, + manifest: BalenaSdk.DeviceTypeJson.DeviceType.DeviceType, ): Bluebird; - export function getImageManifest(image: string): Bluebird; + export function getImageManifest( + image: string, + ): Bluebird; } diff --git a/typings/global.d.ts b/typings/global.d.ts new file mode 100644 index 00000000..5be7c315 --- /dev/null +++ b/typings/global.d.ts @@ -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]; + }; +}