diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index fe2ec52dc..cc851bd8e 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -213,8 +213,7 @@ const sdk = fromSharedOptions(); * [.remove(slugOrUuidOrIdOrIds)](#balena.models.application.remove) ⇒ Promise * [.rename(slugOrUuidOrId, newName)](#balena.models.application.rename) ⇒ Promise * [.restart(slugOrUuidOrId)](#balena.models.application.restart) ⇒ Promise - * ~~[.generateApiKey(slugOrUuidOrId)](#balena.models.application.generateApiKey) ⇒ Promise~~ - * [.generateProvisioningKey(slugOrUuidOrId, [keyName], [keyDescription], [keyExpiryDate])](#balena.models.application.generateProvisioningKey) ⇒ Promise + * [.generateProvisioningKey(generateProvisioningKeyParams)](#balena.models.application.generateProvisioningKey) ⇒ Promise * [.purge(appId)](#balena.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#balena.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#balena.models.application.reboot) ⇒ Promise @@ -302,13 +301,9 @@ const sdk = fromSharedOptions(); * [.trackApplicationRelease(uuidOrIdOrArray)](#balena.models.device.trackApplicationRelease) ⇒ Promise * [.setSupervisorRelease(uuidOrIdOrArray, supervisorVersionOrId)](#balena.models.device.setSupervisorRelease) ⇒ Promise * [.startOsUpdate(uuidOrUuids, targetOsVersion, [options])](#balena.models.device.startOsUpdate) ⇒ Promise - * ~~[.getOsUpdateStatus(uuid)](#balena.models.device.getOsUpdateStatus) ⇒ Promise~~ * [.ping(uuidOrId)](#balena.models.device.ping) ⇒ Promise - * ~~[.getApplicationInfo(uuidOrId)](#balena.models.device.getApplicationInfo) ⇒ Promise~~ * [.identify(uuidOrId)](#balena.models.device.identify) ⇒ Promise * [.restartApplication(uuidOrId)](#balena.models.device.restartApplication) ⇒ Promise - * ~~[.startApplication(uuidOrId)](#balena.models.device.startApplication) ⇒ Promise~~ - * ~~[.stopApplication(uuidOrId)](#balena.models.device.stopApplication) ⇒ Promise~~ * [.reboot(uuidOrId, [options])](#balena.models.device.reboot) ⇒ Promise * [.shutdown(uuidOrId, [options])](#balena.models.device.shutdown) ⇒ Promise * [.purge(uuidOrId)](#balena.models.device.purge) ⇒ Promise @@ -328,7 +323,7 @@ const sdk = fromSharedOptions(); * [.getInstructions(deviceTypeSlugOrContract)](#balena.models.deviceType.getInstructions) ⇒ Promise * [.getInstallMethod(deviceTypeSlug)](#balena.models.deviceType.getInstallMethod) ⇒ Promise * [.apiKey](#balena.models.apiKey) : object - * [.create(name, [description])](#balena.models.apiKey.create) ⇒ Promise + * [.create(createApiKeyParams)](#balena.models.apiKey.create) ⇒ Promise * [.getAll([options])](#balena.models.apiKey.getAll) ⇒ Promise * [.getAllNamedUserApiKeys([options])](#balena.models.apiKey.getAllNamedUserApiKeys) ⇒ Promise * [.getProvisioningApiKeysByApplication(slugOrUuidOrId, [options])](#balena.models.apiKey.getProvisioningApiKeysByApplication) ⇒ Promise @@ -345,7 +340,6 @@ const sdk = fromSharedOptions(); * [.get(membershipId, [options])](#balena.models.organization.membership.get) ⇒ Promise * [.getAllByOrganization(handleOrId, [options])](#balena.models.organization.membership.getAllByOrganization) ⇒ Promise * [.getAllByUser(usernameOrId, [options])](#balena.models.organization.membership.getAllByUser) ⇒ Promise - * ~~[.create(options)](#balena.models.organization.membership.create) ⇒ Promise~~ * [.changeRole(idOrUniqueKey, roleName)](#balena.models.organization.membership.changeRole) ⇒ Promise * [.remove(id)](#balena.models.organization.membership.remove) ⇒ Promise * [.invite](#balena.models.organization.invite) : object @@ -363,7 +357,6 @@ const sdk = fromSharedOptions(); * [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ Promise * [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ Promise * [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ Promise - * [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ Promise * [.download(options)](#balena.models.os.download) ⇒ Promise * [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ Promise * [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ Promise @@ -622,8 +615,7 @@ balena.models.device.get(123).catch(function (error) { * [.remove(slugOrUuidOrIdOrIds)](#balena.models.application.remove) ⇒ Promise * [.rename(slugOrUuidOrId, newName)](#balena.models.application.rename) ⇒ Promise * [.restart(slugOrUuidOrId)](#balena.models.application.restart) ⇒ Promise - * ~~[.generateApiKey(slugOrUuidOrId)](#balena.models.application.generateApiKey) ⇒ Promise~~ - * [.generateProvisioningKey(slugOrUuidOrId, [keyName], [keyDescription], [keyExpiryDate])](#balena.models.application.generateProvisioningKey) ⇒ Promise + * [.generateProvisioningKey(generateProvisioningKeyParams)](#balena.models.application.generateProvisioningKey) ⇒ Promise * [.purge(appId)](#balena.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#balena.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#balena.models.application.reboot) ⇒ Promise @@ -711,13 +703,9 @@ balena.models.device.get(123).catch(function (error) { * [.trackApplicationRelease(uuidOrIdOrArray)](#balena.models.device.trackApplicationRelease) ⇒ Promise * [.setSupervisorRelease(uuidOrIdOrArray, supervisorVersionOrId)](#balena.models.device.setSupervisorRelease) ⇒ Promise * [.startOsUpdate(uuidOrUuids, targetOsVersion, [options])](#balena.models.device.startOsUpdate) ⇒ Promise - * ~~[.getOsUpdateStatus(uuid)](#balena.models.device.getOsUpdateStatus) ⇒ Promise~~ * [.ping(uuidOrId)](#balena.models.device.ping) ⇒ Promise - * ~~[.getApplicationInfo(uuidOrId)](#balena.models.device.getApplicationInfo) ⇒ Promise~~ * [.identify(uuidOrId)](#balena.models.device.identify) ⇒ Promise * [.restartApplication(uuidOrId)](#balena.models.device.restartApplication) ⇒ Promise - * ~~[.startApplication(uuidOrId)](#balena.models.device.startApplication) ⇒ Promise~~ - * ~~[.stopApplication(uuidOrId)](#balena.models.device.stopApplication) ⇒ Promise~~ * [.reboot(uuidOrId, [options])](#balena.models.device.reboot) ⇒ Promise * [.shutdown(uuidOrId, [options])](#balena.models.device.shutdown) ⇒ Promise * [.purge(uuidOrId)](#balena.models.device.purge) ⇒ Promise @@ -737,7 +725,7 @@ balena.models.device.get(123).catch(function (error) { * [.getInstructions(deviceTypeSlugOrContract)](#balena.models.deviceType.getInstructions) ⇒ Promise * [.getInstallMethod(deviceTypeSlug)](#balena.models.deviceType.getInstallMethod) ⇒ Promise * [.apiKey](#balena.models.apiKey) : object - * [.create(name, [description])](#balena.models.apiKey.create) ⇒ Promise + * [.create(createApiKeyParams)](#balena.models.apiKey.create) ⇒ Promise * [.getAll([options])](#balena.models.apiKey.getAll) ⇒ Promise * [.getAllNamedUserApiKeys([options])](#balena.models.apiKey.getAllNamedUserApiKeys) ⇒ Promise * [.getProvisioningApiKeysByApplication(slugOrUuidOrId, [options])](#balena.models.apiKey.getProvisioningApiKeysByApplication) ⇒ Promise @@ -754,7 +742,6 @@ balena.models.device.get(123).catch(function (error) { * [.get(membershipId, [options])](#balena.models.organization.membership.get) ⇒ Promise * [.getAllByOrganization(handleOrId, [options])](#balena.models.organization.membership.getAllByOrganization) ⇒ Promise * [.getAllByUser(usernameOrId, [options])](#balena.models.organization.membership.getAllByUser) ⇒ Promise - * ~~[.create(options)](#balena.models.organization.membership.create) ⇒ Promise~~ * [.changeRole(idOrUniqueKey, roleName)](#balena.models.organization.membership.changeRole) ⇒ Promise * [.remove(id)](#balena.models.organization.membership.remove) ⇒ Promise * [.invite](#balena.models.organization.invite) : object @@ -772,7 +759,6 @@ balena.models.device.get(123).catch(function (error) { * [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ Promise * [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ Promise * [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ Promise - * [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ Promise * [.download(options)](#balena.models.os.download) ⇒ Promise * [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ Promise * [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ Promise @@ -879,8 +865,7 @@ balena.models.device.get(123).catch(function (error) { * [.remove(slugOrUuidOrIdOrIds)](#balena.models.application.remove) ⇒ Promise * [.rename(slugOrUuidOrId, newName)](#balena.models.application.rename) ⇒ Promise * [.restart(slugOrUuidOrId)](#balena.models.application.restart) ⇒ Promise - * ~~[.generateApiKey(slugOrUuidOrId)](#balena.models.application.generateApiKey) ⇒ Promise~~ - * [.generateProvisioningKey(slugOrUuidOrId, [keyName], [keyDescription], [keyExpiryDate])](#balena.models.application.generateProvisioningKey) ⇒ Promise + * [.generateProvisioningKey(generateProvisioningKeyParams)](#balena.models.application.generateProvisioningKey) ⇒ Promise * [.purge(appId)](#balena.models.application.purge) ⇒ Promise * [.shutdown(appId, [options])](#balena.models.application.shutdown) ⇒ Promise * [.reboot(appId, [options])](#balena.models.application.reboot) ⇒ Promise @@ -1387,7 +1372,7 @@ balena.models.application.membership.getAllByUser(123).then(function(memberships ###### membership.create(options) ⇒ Promise -This method adds a user to an application by their username. +This method adds a user to an application by their username if they are a member of the organization. **Kind**: static method of [membership](#balena.models.application.membership) **Summary**: Creates a new membership for an application @@ -1889,39 +1874,9 @@ balena.models.application.restart('myorganization/myapp'); ```js balena.models.application.restart(123); ``` - - -##### ~~application.generateApiKey(slugOrUuidOrId) ⇒ Promise~~ -***Deprecated*** - -Generally you shouldn't use this method: if you're provisioning a recent BalenaOS -version (2.4.0+) then generateProvisioningKey should work just as well, but -be more secure. - -**Kind**: static method of [application](#balena.models.application) -**Summary**: Generate an API key for a specific application -**Access**: public -**Fulfil**: String - api key - -| Param | Type | Description | -| --- | --- | --- | -| slugOrUuidOrId | String \| Number | application slug (string), uuid (string) or id (number) | - -**Example** -```js -balena.models.application.generateApiKey('myorganization/myapp').then(function(apiKey) { - console.log(apiKey); -}); -``` -**Example** -```js -balena.models.application.generateApiKey(123).then(function(apiKey) { - console.log(apiKey); -}); -``` -##### application.generateProvisioningKey(slugOrUuidOrId, [keyName], [keyDescription], [keyExpiryDate]) ⇒ Promise +##### application.generateProvisioningKey(generateProvisioningKeyParams) ⇒ Promise **Kind**: static method of [application](#balena.models.application) **Summary**: Generate a device provisioning key for a specific application **Access**: public @@ -1929,26 +1884,27 @@ balena.models.application.generateApiKey(123).then(function(apiKey) { | Param | Type | Description | | --- | --- | --- | -| slugOrUuidOrId | String \| Number | application slug (string), uuid (string) or id (number) | -| [keyName] | String | Provisioning key name | -| [keyDescription] | String | Description for provisioning key | -| [keyExpiryDate] | String | Expiry Date for provisioning key | +| generateProvisioningKeyParams | Object | an object containing the parameters for the provisioning key generation | +| generateProvisioningKeyParams.slugOrUuidOrId | String \| Number | application slug (string), uuid (string) or id (number) | +| generateProvisioningKeyParams.keyExpiryDate | String | Expiry Date for provisioning key | +| [generateProvisioningKeyParams.keyName] | String | Provisioning key name | +| [generateProvisioningKeyParams.keyDescription] | String | Description for provisioning key | **Example** ```js -balena.models.application.generateProvisioningKey('myorganization/myapp').then(function(key) { +balena.models.application.generateProvisioningKey({slugOrUuidOrId: 'myorganization/myapp', keyExpiryDate: '2030-10-12'}).then(function(key) { console.log(key); }); ``` **Example** ```js -balena.models.application.generateProvisioningKey(123).then(function(key) { +balena.models.application.generateProvisioningKey({slugOrUuidOrId: 123, keyExpiryDate: '2030-10-12'}).then(function(key) { console.log(key); }); ``` **Example** ```js -balena.models.application.generateProvisioningKey(123, 'api key name', 'api key long description', '2030-01-01T00:00:00Z').then(function(key) { +balena.models.application.generateProvisioningKey({slugOrUuidOrId: 123, keyExpiryDate: '2030-10-12', keyName: 'api key name', keyDescription: 'api key long description'}).then(function(key) { console.log(key); }); ``` @@ -2288,13 +2244,9 @@ balena.models.application.revokeSupportAccess(123); * [.trackApplicationRelease(uuidOrIdOrArray)](#balena.models.device.trackApplicationRelease) ⇒ Promise * [.setSupervisorRelease(uuidOrIdOrArray, supervisorVersionOrId)](#balena.models.device.setSupervisorRelease) ⇒ Promise * [.startOsUpdate(uuidOrUuids, targetOsVersion, [options])](#balena.models.device.startOsUpdate) ⇒ Promise - * ~~[.getOsUpdateStatus(uuid)](#balena.models.device.getOsUpdateStatus) ⇒ Promise~~ * [.ping(uuidOrId)](#balena.models.device.ping) ⇒ Promise - * ~~[.getApplicationInfo(uuidOrId)](#balena.models.device.getApplicationInfo) ⇒ Promise~~ * [.identify(uuidOrId)](#balena.models.device.identify) ⇒ Promise * [.restartApplication(uuidOrId)](#balena.models.device.restartApplication) ⇒ Promise - * ~~[.startApplication(uuidOrId)](#balena.models.device.startApplication) ⇒ Promise~~ - * ~~[.stopApplication(uuidOrId)](#balena.models.device.stopApplication) ⇒ Promise~~ * [.reboot(uuidOrId, [options])](#balena.models.device.reboot) ⇒ Promise * [.shutdown(uuidOrId, [options])](#balena.models.device.shutdown) ⇒ Promise * [.purge(uuidOrId)](#balena.models.device.purge) ⇒ Promise @@ -4032,7 +3984,7 @@ balena.models.device.setSupervisorRelease(123, '11.4.14').then(function() { | uuidOrUuids | String \| Array.<String> | full device uuid or array of full uuids | | targetOsVersion | String | semver-compatible version for the target device Unsupported (unpublished) version will result in rejection. The version **must** be the exact version number, a "prod" variant and greater than the one running on the device. To resolve the semver-compatible range use `balena.model.os.getMaxSatisfyingVersion`. | | [options] | Object | options | -| [options.runDetached] | Boolean | run the update in detached mode. Default behaviour is runDetached=false but is DEPRECATED and will be removed in a future release. Use runDetached=true for more reliable updates. | +| [options.runDetached] | Boolean | run the update in detached mode. True by default | **Example** ```js @@ -4040,26 +3992,6 @@ balena.models.device.startOsUpdate('7cf02a687b74206f92cb455969cf8e98', '2.29.2+r console.log(result.status); }); ``` - - -##### ~~device.getOsUpdateStatus(uuid) ⇒ Promise~~ -***Deprecated*** - -**Kind**: static method of [device](#balena.models.device) -**Summary**: Get the OS update status of a device. This will no longer return a useful status for runDetached=true updates. -**Access**: public -**Fulfil**: Object - action response - -| Param | Type | Description | -| --- | --- | --- | -| uuid | String | full device uuid | - -**Example** -```js -balena.models.device.getOsUpdateStatus('7cf02a687b74206f92cb455969cf8e98').then(function(status) { - console.log(result.status); -}); -``` ##### device.ping(uuidOrId) ⇒ Promise @@ -4081,34 +4013,6 @@ balena.models.device.ping('7cf02a6'); ```js balena.models.device.ping(123); ``` - - -##### ~~device.getApplicationInfo(uuidOrId) ⇒ Promise~~ -***Deprecated*** - -This is not supported on multicontainer devices, and will be removed in a future major release - -**Kind**: static method of [device](#balena.models.device) -**Summary**: Get application container information -**Access**: public -**Fulfil**: Object - application info - -| Param | Type | Description | -| --- | --- | --- | -| uuidOrId | String \| Number | device uuid (string) or id (number) | - -**Example** -```js -balena.models.device.getApplicationInfo('7cf02a6').then(function(appInfo) { - console.log(appInfo); -}); -``` -**Example** -```js -balena.models.device.getApplicationInfo(123).then(function(appInfo) { - console.log(appInfo); -}); -``` ##### device.identify(uuidOrId) ⇒ Promise @@ -4151,62 +4055,6 @@ balena.models.device.restartApplication('7cf02a6'); ```js balena.models.device.restartApplication(123); ``` - - -##### ~~device.startApplication(uuidOrId) ⇒ Promise~~ -***Deprecated*** - -This is not supported on multicontainer devices, and will be removed in a future major release - -**Kind**: static method of [device](#balena.models.device) -**Summary**: Start application on device -**Access**: public -**Fulfil**: String - application container id - -| Param | Type | Description | -| --- | --- | --- | -| uuidOrId | String \| Number | device uuid (string) or id (number) | - -**Example** -```js -balena.models.device.startApplication('7cf02a6').then(function(containerId) { - console.log(containerId); -}); -``` -**Example** -```js -balena.models.device.startApplication(123).then(function(containerId) { - console.log(containerId); -}); -``` - - -##### ~~device.stopApplication(uuidOrId) ⇒ Promise~~ -***Deprecated*** - -This is not supported on multicontainer devices, and will be removed in a future major release - -**Kind**: static method of [device](#balena.models.device) -**Summary**: Stop application on device -**Access**: public -**Fulfil**: String - application container id - -| Param | Type | Description | -| --- | --- | --- | -| uuidOrId | String \| Number | device uuid (string) or id (number) | - -**Example** -```js -balena.models.device.stopApplication('7cf02a6').then(function(containerId) { - console.log(containerId); -}); -``` -**Example** -```js -balena.models.device.stopApplication(123).then(function(containerId) { - console.log(containerId); -}); -``` ##### device.reboot(uuidOrId, [options]) ⇒ Promise @@ -4613,7 +4461,7 @@ balena.models.deviceType.getInstallMethod('raspberry-pi').then(function(method) **Kind**: static namespace of [models](#balena.models) * [.apiKey](#balena.models.apiKey) : object - * [.create(name, [description])](#balena.models.apiKey.create) ⇒ Promise + * [.create(createApiKeyParams)](#balena.models.apiKey.create) ⇒ Promise * [.getAll([options])](#balena.models.apiKey.getAll) ⇒ Promise * [.getAllNamedUserApiKeys([options])](#balena.models.apiKey.getAllNamedUserApiKeys) ⇒ Promise * [.getProvisioningApiKeysByApplication(slugOrUuidOrId, [options])](#balena.models.apiKey.getProvisioningApiKeysByApplication) ⇒ Promise @@ -4623,7 +4471,7 @@ balena.models.deviceType.getInstallMethod('raspberry-pi').then(function(method) -##### apiKey.create(name, [description]) ⇒ Promise +##### apiKey.create(createApiKeyParams) ⇒ Promise This method registers a new api key for the current user with the name given. **Kind**: static method of [apiKey](#balena.models.apiKey) @@ -4633,18 +4481,20 @@ This method registers a new api key for the current user with the name given. | Param | Type | Default | Description | | --- | --- | --- | --- | -| name | String | | the API key name | -| [description] | String | | the API key description | +| createApiKeyParams | Object | | an object containing the parameters for the creation of an API key | +| createApiKeyParams.name | String | | the API key name | +| createApiKeyParams.expiryDate | String | | the API key expiry date | +| [createApiKeyParams.description] | String | | the API key description | **Example** ```js -balena.models.apiKey.create(apiKeyName).then(function(apiKey) { +balena.models.apiKey.create({name: apiKeyName, expiryDate: 2030-10-12}).then(function(apiKey) { console.log(apiKey); }); ``` **Example** ```js -balena.models.apiKey.create(apiKeyName, apiKeyDescription).then(function(apiKey) { +balena.models.apiKey.create({name: apiKeyName, expiryDate: 2030-10-12, description: apiKeyDescription}).then(function(apiKey) { console.log(apiKey); }); ``` @@ -4856,7 +4706,6 @@ balena.models.key.create('Main', 'ssh-rsa AAAAB....').then(function(key) { * [.get(membershipId, [options])](#balena.models.organization.membership.get) ⇒ Promise * [.getAllByOrganization(handleOrId, [options])](#balena.models.organization.membership.getAllByOrganization) ⇒ Promise * [.getAllByUser(usernameOrId, [options])](#balena.models.organization.membership.getAllByUser) ⇒ Promise - * ~~[.create(options)](#balena.models.organization.membership.create) ⇒ Promise~~ * [.changeRole(idOrUniqueKey, roleName)](#balena.models.organization.membership.changeRole) ⇒ Promise * [.remove(id)](#balena.models.organization.membership.remove) ⇒ Promise * [.invite](#balena.models.organization.invite) : object @@ -4879,7 +4728,6 @@ balena.models.key.create('Main', 'ssh-rsa AAAAB....').then(function(key) { * [.get(membershipId, [options])](#balena.models.organization.membership.get) ⇒ Promise * [.getAllByOrganization(handleOrId, [options])](#balena.models.organization.membership.getAllByOrganization) ⇒ Promise * [.getAllByUser(usernameOrId, [options])](#balena.models.organization.membership.getAllByUser) ⇒ Promise - * ~~[.create(options)](#balena.models.organization.membership.create) ⇒ Promise~~ * [.changeRole(idOrUniqueKey, roleName)](#balena.models.organization.membership.changeRole) ⇒ Promise * [.remove(id)](#balena.models.organization.membership.remove) ⇒ Promise @@ -4958,32 +4806,6 @@ balena.models.organization.membership.getAllByUser(123).then(function(membership console.log(memberships); }); ``` - - -###### ~~membership.create(options) ⇒ Promise~~ -***use balena.models.organization.invite.create instead*** - -This method adds a user to an organization by their usename. -WARNING: This method is deprecated, use balena.models.organization.invite.create instead. - -**Kind**: static method of [membership](#balena.models.organization.membership) -**Summary**: Creates a new membership for an organization -**Access**: public -**Fulfil**: Object - organization membership - -| Param | Type | Default | Description | -| --- | --- | --- | --- | -| options | Object | | membership creation parameters | -| options.organization | String \| Number | | organization handle (string), or id (number) | -| options.username | String | | the username of the balena user that will become a member | -| [options.roleName] | String | "member" | the role name to be granted to the membership | - -**Example** -```js -balena.models.organization.membership.create({ organization: "myorg", username: "user123", roleName: "member" }).then(function(membership) { - console.log(membership); -}); -``` ###### membership.changeRole(idOrUniqueKey, roleName) ⇒ Promise @@ -5267,7 +5089,6 @@ balena.models.organization.remove(123); * [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ Promise * [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ Promise * [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ Promise - * [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ Promise * [.download(options)](#balena.models.os.download) ⇒ Promise * [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ Promise * [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ Promise @@ -5366,29 +5187,6 @@ balena.models.os.getMaxSatisfyingVersion('raspberry-pi', '^2.11.0').then(functio console.log(version); }); ``` - - -##### os.getLastModified(deviceType, [version]) ⇒ Promise -**Kind**: static method of [os](#balena.models.os) -**Summary**: Get the OS image last modified date -**Access**: public -**Fulfil**: Date - last modified date - -| Param | Type | Description | -| --- | --- | --- | -| deviceType | String | device type slug | -| [version] | String | semver-compatible version or 'latest', defaults to 'latest'. Unsupported (unpublished) version will result in rejection. The version **must** be the exact version number. To resolve the semver-compatible range use `balena.model.os.getMaxSatisfyingVersion`. | - -**Example** -```js -balena.models.os.getLastModified('raspberry-pi').then(function(date) { - console.log('The raspberry-pi image was last modified in ' + date); -}); - -balena.models.os.getLastModified('raspberrypi3', '2.0.0').then(function(date) { - console.log('The raspberry-pi image was last modified in ' + date); -}); -``` ##### os.download(options) ⇒ Promise diff --git a/package.json b/package.json index 95dff00d7..22429301e 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "author": "Balena Ltd. ", "license": "Apache-2.0", "engines": { - "node": ">=18.0" + "node": "^20.12.0 || >= 22.0.0" }, "devDependencies": { "@balena/env-parsing": "^1.2.0", @@ -119,7 +119,7 @@ "dependencies": { "@balena/es-version": "^1.0.0", "@types/json-schema": "^7.0.9", - "@types/node": "^18.19.50", + "@types/node": "^20.17.8", "abortcontroller-polyfill": "^1.7.1", "balena-auth": "^6.0.1", "balena-errors": "^4.9.0", diff --git a/src/models/api-key.ts b/src/models/api-key.ts index 56a9eb33b..036f0703a 100644 --- a/src/models/api-key.ts +++ b/src/models/api-key.ts @@ -38,40 +38,49 @@ const getApiKeysModel = function ( * * @description This method registers a new api key for the current user with the name given. * - * @param {String} name - the API key name - * @param {String} [description=null] - the API key description + * @param {Object} createApiKeyParams - an object containing the parameters for the creation of an API key + * @param {String} createApiKeyParams.name - the API key name + * @param {String} createApiKeyParams.expiryDate - the API key expiry date + * @param {String} [createApiKeyParams.description=null] - the API key description * * @fulfil {String} - API key * @returns {Promise} * * @example - * balena.models.apiKey.create(apiKeyName).then(function(apiKey) { + * balena.models.apiKey.create({name: apiKeyName, expiryDate: 2030-10-12}).then(function(apiKey) { * console.log(apiKey); * }); * * @example - * balena.models.apiKey.create(apiKeyName, apiKeyDescription).then(function(apiKey) { + * balena.models.apiKey.create({name: apiKeyName, expiryDate: 2030-10-12, description: apiKeyDescription}).then(function(apiKey) { * console.log(apiKey); * }); */ - async create( - name: string, - description: string | null = null, - expiryDate: string | null = null, - ): Promise { + async create({ + name, + expiryDate, + description = null, + }: { + name: string; + expiryDate: string | null; + description?: string | null; + }): Promise { + if (typeof expiryDate === 'undefined') { + throw new Error( + 'An expiry date must be provided as either an ISO string or null', + ); + } const apiKeyBody: { name: string; + expiryDate: string | null; description?: string | null; - expiryDate?: string | null; } = { name, + expiryDate, }; if (typeof description === 'string' && !!description) { apiKeyBody.description = description; } - if (typeof expiryDate === 'string' && !!expiryDate) { - apiKeyBody.expiryDate = expiryDate; - } try { const { body } = await request.send({ method: 'POST', diff --git a/src/models/application-membership.ts b/src/models/application-membership.ts index 39eaeb9f0..e791127ea 100644 --- a/src/models/application-membership.ts +++ b/src/models/application-membership.ts @@ -225,7 +225,7 @@ const getApplicationMembershipModel = function ( * @function * @memberof balena.models.application.membership * - * @description This method adds a user to an application by their username. + * @description This method adds a user to an application by their username if they are a member of the organization. * * @param {Object} options - membership creation parameters * @param {String|Number} options.application - application handle (string), or id (number) @@ -248,9 +248,23 @@ const getApplicationMembershipModel = function ( PinePostResult > { const [{ id }, roleId] = await Promise.all([ - getApplication(application, { $select: 'id' }), + getApplication(application, { + $select: 'id', + $expand: { + organization: { + $select: 'id', + $filter: { organization_membership: { user: { username } } }, + }, + }, + }), roleName ? getRoleId(roleName) : undefined, ]); + // If the user does not have an organization membership, they cannot be added to an application + if (!id) { + throw new Error( + 'It is necessary that each user (Auth) that is member of an application that has an organization, is member of the organization', + ); + } type ApplicationMembershipBase = Omit; type ApplicationMembershipPostBody = ApplicationMembershipBase & { username: string; diff --git a/src/models/application.ts b/src/models/application.ts index cf5faa009..6c3f4c825 100644 --- a/src/models/application.ts +++ b/src/models/application.ts @@ -37,14 +37,7 @@ import * as url from 'url'; import once from 'lodash/once'; import * as errors from 'balena-errors'; -import { - isId, - isNoApplicationForKeyResponse, - isNotFoundResponse, - mergePineOptions, - treatAsMissingApplication, - withSupervisorLockedError, -} from '../util'; +import { isId, mergePineOptions, withSupervisorLockedError } from '../util'; import { getCurrentServiceDetailsPineExpand, @@ -732,18 +725,15 @@ const getApplicationModel = function ( slugOrUuidOrIdOrIds: string | number | number[], ): Promise => { if (typeof slugOrUuidOrIdOrIds === 'string') { - try { - const applicationId = await getId(slugOrUuidOrIdOrIds); - await pine.delete({ - resource: 'application', - id: applicationId, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrIdOrIds, err); - } - throw err; - } + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrIdOrIds, { + $select: 'id', + }) + ).id; + await pine.delete({ + resource: 'application', + id: applicationId, + }); return; } await batchApplicationOperation()({ @@ -783,21 +773,18 @@ const getApplicationModel = function ( slugOrUuidOrId: string | number, newAppName: string, ): Promise => { - try { - const applicationId = await getId(slugOrUuidOrId); - await pine.patch({ - resource: 'application', - id: applicationId, - body: { - app_name: newAppName, - }, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; + await pine.patch({ + resource: 'application', + id: applicationId, + body: { + app_name: newAppName, + }, + }); }, /** @@ -818,62 +805,19 @@ const getApplicationModel = function ( */ restart: (slugOrUuidOrId: string | number): Promise => withSupervisorLockedError(async () => { - try { - const applicationId = await getId(slugOrUuidOrId); + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; - await request.send({ - method: 'POST', - url: `/application/${applicationId}/restart`, - baseUrl: apiUrl, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + await request.send({ + method: 'POST', + url: `/application/${applicationId}/restart`, + baseUrl: apiUrl, + }); }), - /** - * @summary Generate an API key for a specific application - * @name generateApiKey - * @public - * @function - * @memberof balena.models.application - * @deprecated - * @description - * Generally you shouldn't use this method: if you're provisioning a recent BalenaOS - * version (2.4.0+) then generateProvisioningKey should work just as well, but - * be more secure. - * - * @param {String|Number} slugOrUuidOrId - application slug (string), uuid (string) or id (number) - * @fulfil {String} - api key - * @returns {Promise} - * - * @example - * balena.models.application.generateApiKey('myorganization/myapp').then(function(apiKey) { - * console.log(apiKey); - * }); - * - * @example - * balena.models.application.generateApiKey(123).then(function(apiKey) { - * console.log(apiKey); - * }); - */ - generateApiKey: async ( - slugOrUuidOrId: string | number, - ): Promise => { - // Do a full get, not just getId, because the actual api endpoint doesn't fail if the id - // doesn't exist. TODO: Can use getId once https://github.com/balena-io/balena-api/issues/110 is resolved - const { id } = await exports.get(slugOrUuidOrId, { $select: 'id' }); - const { body } = await request.send({ - method: 'POST', - url: `/application/${id}/generate-api-key`, - baseUrl: apiUrl, - }); - return body; - }, - /** * @summary Generate a device provisioning key for a specific application * @name generateProvisioningKey @@ -881,56 +825,59 @@ const getApplicationModel = function ( * @function * @memberof balena.models.application * - * @param {String|Number} slugOrUuidOrId - application slug (string), uuid (string) or id (number) - * @param {String} [keyName] - Provisioning key name - * @param {String} [keyDescription] - Description for provisioning key - * @param {String} [keyExpiryDate] - Expiry Date for provisioning key + * @param {Object} generateProvisioningKeyParams - an object containing the parameters for the provisioning key generation + * @param {String|Number} generateProvisioningKeyParams.slugOrUuidOrId - application slug (string), uuid (string) or id (number) + * @param {String} generateProvisioningKeyParams.keyExpiryDate - Expiry Date for provisioning key + * @param {String} [generateProvisioningKeyParams.keyName] - Provisioning key name + * @param {String} [generateProvisioningKeyParams.keyDescription] - Description for provisioning key * @fulfil {String} - device provisioning key * @returns {Promise} * * @example - * balena.models.application.generateProvisioningKey('myorganization/myapp').then(function(key) { + * balena.models.application.generateProvisioningKey({slugOrUuidOrId: 'myorganization/myapp', keyExpiryDate: '2030-10-12'}).then(function(key) { * console.log(key); * }); * * @example - * balena.models.application.generateProvisioningKey(123).then(function(key) { + * balena.models.application.generateProvisioningKey({slugOrUuidOrId: 123, keyExpiryDate: '2030-10-12'}).then(function(key) { * console.log(key); * }); * * @example - * balena.models.application.generateProvisioningKey(123, 'api key name', 'api key long description', '2030-01-01T00:00:00Z').then(function(key) { + * balena.models.application.generateProvisioningKey({slugOrUuidOrId: 123, keyExpiryDate: '2030-10-12', keyName: 'api key name', keyDescription: 'api key long description'}).then(function(key) { * console.log(key); * }); */ - generateProvisioningKey: async ( - slugOrUuidOrId: string | number, - keyName?: string, - keyDescription?: string, - keyExpiryDate?: string, - ): Promise => { - try { - const applicationId = await getId(slugOrUuidOrId); - const { body } = await request.send({ - method: 'POST', - url: '/api-key/v1/', - baseUrl: apiUrl, - body: { - actorType: 'application', - actorTypeId: applicationId, - roles: ['provisioning-api-key'], - name: keyName, - description: keyDescription, - expiryDate: keyExpiryDate, - }, - }); - return body; - } catch (err) { - if (isNoApplicationForKeyResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + generateProvisioningKey: async ({ + slugOrUuidOrId, + keyExpiryDate, + keyName, + keyDescription, + }: { + slugOrUuidOrId: string | number; + keyExpiryDate: string | null; + keyName?: string; + keyDescription?: string; + }): Promise => { + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; + const { body } = await request.send({ + method: 'POST', + url: '/api-key/v2/', + baseUrl: apiUrl, + body: { + actorType: 'application', + actorTypeId: applicationId, + roles: ['provisioning-api-key'], + name: keyName, + description: keyDescription, + expiryDate: keyExpiryDate, + }, + }); + return body; }, /** @@ -1358,19 +1305,16 @@ const getApplicationModel = function ( ); } - try { - const applicationId = await getId(slugOrUuidOrId); - await pine.patch({ - resource: 'application', - id: applicationId, - body: { is_accessible_by_support_until__date: expiryTimestamp }, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; + await pine.patch({ + resource: 'application', + id: applicationId, + body: { is_accessible_by_support_until__date: expiryTimestamp }, + }); }, /** @@ -1392,19 +1336,16 @@ const getApplicationModel = function ( revokeSupportAccess: async ( slugOrUuidOrId: string | number, ): Promise => { - try { - const applicationId = await getId(slugOrUuidOrId); - await pine.patch({ - resource: 'application', - id: applicationId, - body: { is_accessible_by_support_until__date: null }, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; + await pine.patch({ + resource: 'application', + id: applicationId, + body: { is_accessible_by_support_until__date: null }, + }); }, /** diff --git a/src/models/config.ts b/src/models/config.ts index d1a1fd650..83e1f1391 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import * as errors from 'balena-errors'; -import type { JSONSchema6 } from 'json-schema'; +import type { JSONSchema7 } from 'json-schema'; import type { InjectedDependenciesParam, InjectedOptionsParam } from '..'; import type * as DeviceTypeJson from '../types/device-type-json'; @@ -25,11 +25,6 @@ export interface Config { deviceUrlsBase: string; adminUrl: string; gitServerUrl: string; - /** @deprecated */ - pubnub?: { - subscribe_key: string; - publish_key: string; - }; ga?: GaConfig; mixpanelToken?: string; intercomAppId?: string; @@ -44,7 +39,7 @@ export interface Config { supportedSocialProviders: string[]; } -export type ConfigVarSchema = JSONSchema6 & { +export type ConfigVarSchema = JSONSchema7 & { will_reboot?: boolean; warning?: string; }; @@ -74,8 +69,13 @@ const getConfigModel = function ( const { request } = deps; const { apiUrl } = opts; + // TODO: Drop when `instructions` is no longer returned by the `/config` and `/device-types/v1` endpoints const normalizeDeviceTypes = ( - deviceTypes: DeviceTypeJson.DeviceType[], + deviceTypes: Array< + DeviceTypeJson.DeviceType & { + instructions?: string[] | DeviceTypeJson.DeviceTypeInstructions; + } + >, ): DeviceTypeJson.DeviceType[] => deviceTypes.map(function (deviceType) { // Remove the device-type.json instructions to enforce diff --git a/src/models/device.supervisor-api.partial.ts b/src/models/device.supervisor-api.partial.ts index a98805d15..0cb01a6ca 100644 --- a/src/models/device.supervisor-api.partial.ts +++ b/src/models/device.supervisor-api.partial.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as bSemver from 'balena-semver'; import type { InjectedOptionsParam, InjectedDependenciesParam, @@ -23,11 +22,7 @@ import type { } from '..'; import type { Device } from '../types/models'; -import { - isNotFoundResponse, - treatAsMissingDevice, - withSupervisorLockedError, -} from '../util'; +import { withSupervisorLockedError } from '../util'; import { ensureVersionCompatibility } from '../util/device-os-version'; @@ -69,9 +64,6 @@ export const getSupervisorApiHelper = function ( } = deps; const { apiUrl } = opts; - const getId = (uuidOrId: string | number) => - sdkInstance.models.device._getId(uuidOrId); - const exports = { /** * @summary Ping a device @@ -114,68 +106,6 @@ export const getSupervisorApiHelper = function ( }); }, - /** - * @summary Get application container information - * @name getApplicationInfo - * @public - * @function - * @memberof balena.models.device - * - * @deprecated - * @description - * This is not supported on multicontainer devices, and will be removed in a future major release - * - * @param {String|Number} uuidOrId - device uuid (string) or id (number) - * @fulfil {Object} - application info - * @returns {Promise} - * - * @example - * balena.models.device.getApplicationInfo('7cf02a6').then(function(appInfo) { - * console.log(appInfo); - * }); - * - * @example - * balena.models.device.getApplicationInfo(123).then(function(appInfo) { - * console.log(appInfo); - * }); - */ - getApplicationInfo: async ( - uuidOrId: string | number, - ): Promise<{ - appId: string; - commit: string; - containerId: string; - env: { [key: string]: string | number }; - imageId: string; - }> => { - const deviceOptions = { - $select: ['id', 'supervisor_version'], - $expand: { belongs_to__application: { $select: 'id' } }, - } satisfies PineOptions; - - const device = (await sdkInstance.models.device.get( - uuidOrId, - deviceOptions, - )) as PineTypedResult; - ensureVersionCompatibility( - device.supervisor_version, - MIN_SUPERVISOR_APPS_API, - 'supervisor', - ); - const appId = device.belongs_to__application[0].id; - const { body } = await request.send({ - method: 'POST', - url: `/supervisor/v1/apps/${appId}`, - baseUrl: apiUrl, - body: { - deviceId: device.id, - appId, - method: 'GET', - }, - }); - return body; - }, - /** * @summary Identify device * @name identify @@ -228,133 +158,6 @@ export const getSupervisorApiHelper = function ( * balena.models.device.restartApplication(123); */ restartApplication: (uuidOrId: string | number): Promise => - withSupervisorLockedError(async () => { - try { - const deviceOptions = { - $select: ['id', 'supervisor_version'], - $expand: { belongs_to__application: { $select: 'id' } }, - } satisfies PineOptions; - const device = (await sdkInstance.models.device.get( - uuidOrId, - deviceOptions, - )) as PineTypedResult; - // TODO: Drop this once we drop support for ResinOS v2.11.0. - if ( - !bSemver.valid(device.supervisor_version) || - bSemver.lt(device.supervisor_version, '7.0.0') - ) { - return ( - await request.send({ - method: 'POST', - url: `/device/${device.id}/restart`, - baseUrl: apiUrl, - timeout: CONTAINER_ACTION_ENDPOINT_TIMEOUT, - }) - ).body; - } - - const appId = device.belongs_to__application[0].id; - const { body } = await request.send({ - method: 'POST', - url: `/supervisor/v1/restart`, - baseUrl: apiUrl, - body: { - deviceId: device.id, - appId, - data: { - appId, - }, - }, - timeout: CONTAINER_ACTION_ENDPOINT_TIMEOUT, - }); - return body; - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingDevice(uuidOrId, err); - } - throw err; - } - }), - - /** - * @summary Start application on device - * @name startApplication - * @public - * @function - * @memberof balena.models.device - * - * @deprecated - * @description - * This is not supported on multicontainer devices, and will be removed in a future major release - * - * @param {String|Number} uuidOrId - device uuid (string) or id (number) - * @fulfil {String} - application container id - * @returns {Promise} - * - * @example - * balena.models.device.startApplication('7cf02a6').then(function(containerId) { - * console.log(containerId); - * }); - * - * @example - * balena.models.device.startApplication(123).then(function(containerId) { - * console.log(containerId); - * }); - */ - startApplication: async (uuidOrId: string | number): Promise => { - const deviceOptions = { - $select: ['id', 'supervisor_version'], - $expand: { belongs_to__application: { $select: 'id' } }, - } satisfies PineOptions; - const device = (await sdkInstance.models.device.get( - uuidOrId, - deviceOptions, - )) as PineTypedResult; - ensureVersionCompatibility( - device.supervisor_version, - MIN_SUPERVISOR_APPS_API, - 'supervisor', - ); - const appId = device.belongs_to__application[0].id; - const { body } = await request.send({ - method: 'POST', - url: `/supervisor/v1/apps/${appId}/start`, - baseUrl: apiUrl, - body: { - deviceId: device.id, - appId, - }, - timeout: CONTAINER_ACTION_ENDPOINT_TIMEOUT, - }); - return body.containerId; - }, - - /** - * @summary Stop application on device - * @name stopApplication - * @public - * @function - * @memberof balena.models.device - * - * @deprecated - * @description - * This is not supported on multicontainer devices, and will be removed in a future major release - * - * @param {String|Number} uuidOrId - device uuid (string) or id (number) - * @fulfil {String} - application container id - * @returns {Promise} - * - * @example - * balena.models.device.stopApplication('7cf02a6').then(function(containerId) { - * console.log(containerId); - * }); - * - * @example - * balena.models.device.stopApplication(123).then(function(containerId) { - * console.log(containerId); - * }); - */ - stopApplication: (uuidOrId: string | number): Promise => withSupervisorLockedError(async () => { const deviceOptions = { $select: ['id', 'supervisor_version'], @@ -364,23 +167,22 @@ export const getSupervisorApiHelper = function ( uuidOrId, deviceOptions, )) as PineTypedResult; - ensureVersionCompatibility( - device.supervisor_version, - MIN_SUPERVISOR_APPS_API, - 'supervisor', - ); + const appId = device.belongs_to__application[0].id; const { body } = await request.send({ method: 'POST', - url: `/supervisor/v1/apps/${appId}/stop`, + url: `/supervisor/v1/restart`, baseUrl: apiUrl, body: { deviceId: device.id, appId, + data: { + appId, + }, }, timeout: CONTAINER_ACTION_ENDPOINT_TIMEOUT, }); - return body.containerId; + return body; }), /** @@ -410,26 +212,21 @@ export const getSupervisorApiHelper = function ( options = {}; } - try { - const deviceId = await getId(uuidOrId); - const { body } = await request.send({ - method: 'POST', - url: '/supervisor/v1/reboot', - baseUrl: apiUrl, - body: { - deviceId, - data: { - force: Boolean(options?.force), - }, + const deviceId = ( + await sdkInstance.models.device.get(uuidOrId, { $select: 'id' }) + ).id; + const { body } = await request.send({ + method: 'POST', + url: '/supervisor/v1/reboot', + baseUrl: apiUrl, + body: { + deviceId, + data: { + force: Boolean(options?.force), }, - }); - return body; - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingDevice(uuidOrId, err); - } - throw err; - } + }, + }); + return body; }), /** diff --git a/src/models/device.ts b/src/models/device.ts index 4ea9789ce..f27a12587 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -46,10 +46,8 @@ import memoizee from 'memoizee'; import { isId, - isNoDeviceForKeyResponse, isFullUuid, mergePineOptions, - treatAsMissingDevice, limitedMap, groupByMap, } from '../util'; @@ -222,17 +220,6 @@ const getDeviceModel = function ( return (await sdkInstance.models.config.getAll()).deviceUrlsBase; }); - // Internal method for uuid/id disambiguation - // Note that this throws an exception for missing uuids, but not missing ids - const getId = async (uuidOrId: string | number) => { - if (isId(uuidOrId)) { - return uuidOrId; - } else { - const { id } = await exports.get(uuidOrId, { $select: 'id' }); - return id; - } - }; - const getAppliedConfigVariableValue = async ( uuidOrId: string | number, name: string, @@ -345,7 +332,7 @@ const getDeviceModel = function ( async function startOsUpdate( uuidOrUuids: string | string[], targetOsVersion: string, - options: { runDetached?: boolean } = { runDetached: false }, + options: { runDetached?: boolean } = { runDetached: true }, ): Promise> { if (!targetOsVersion) { throw new errors.BalenaInvalidParameterError( @@ -433,7 +420,6 @@ const getDeviceModel = function ( } const exports = { - _getId: getId, OverallStatus, /** * @summary Get Dashboard URL for a specific device @@ -1385,9 +1371,12 @@ const getDeviceModel = function ( const [{ id: userId }, apiKey, application, deviceType] = await Promise.all([ sdkInstance.auth.getUserInfo(), - sdkInstance.models.application.generateProvisioningKey( - applicationSlugOrUuidOrId, - ), + sdkInstance.models.application.generateProvisioningKey({ + slugOrUuidOrId: applicationSlugOrUuidOrId, + // Use 10 minute expiry date as we will immediately use the provisioning key to create a device and then not need it + keyExpiryDate: new Date(Date.now() + 1000 * 60 * 10).toISOString(), + keyDescription: 'Created by SDK to register a device', + }), sdkInstance.models.application.get( applicationSlugOrUuidOrId, applicationOptions, @@ -1456,25 +1445,20 @@ const getDeviceModel = function ( keyDescription?: string, keyExpiryDate?: string, ): Promise => { - try { - const deviceId = await getId(uuidOrId); - const { body } = await request.send({ - method: 'POST', - url: `/api-key/device/${deviceId}/device-key`, - baseUrl: apiUrl, - body: { - name: keyName, - description: keyDescription, - expiryDate: keyExpiryDate, - }, - }); - return body; - } catch (err) { - if (isNoDeviceForKeyResponse(err)) { - treatAsMissingDevice(uuidOrId, err); - } - throw err; - } + const deviceId = ( + await sdkInstance.models.device.get(uuidOrId, { $select: 'id' }) + ).id; + const { body } = await request.send({ + method: 'POST', + url: `/api-key/device/${deviceId}/device-key`, + baseUrl: apiUrl, + body: { + name: keyName, + description: keyDescription, + expiryDate: keyExpiryDate, + }, + }); + return body; }, /** @@ -2271,9 +2255,7 @@ const getDeviceModel = function ( * The version **must** be the exact version number, a "prod" variant and greater than the one running on the device. * To resolve the semver-compatible range use `balena.model.os.getMaxSatisfyingVersion`. * @param {Object} [options] - options - * @param {Boolean} [options.runDetached] - run the update in detached mode. - * Default behaviour is runDetached=false but is DEPRECATED and will be removed in a future release. Use runDetached=true - * for more reliable updates. + * @param {Boolean} [options.runDetached] - run the update in detached mode. True by default * @fulfil {Object} - action response * @returns {Promise} * @@ -2284,41 +2266,6 @@ const getDeviceModel = function ( */ startOsUpdate, - /** - * @deprecated - * @summary Get the OS update status of a device. This will no longer return a useful status for runDetached=true updates. - * @name getOsUpdateStatus - * @public - * @function - * @memberof balena.models.device - * - * @param {String} uuid - full device uuid - * @fulfil {Object} - action response - * @returns {Promise} - * - * @example - * balena.models.device.getOsUpdateStatus('7cf02a687b74206f92cb455969cf8e98').then(function(status) { - * console.log(result.status); - * }); - */ - getOsUpdateStatus: async (uuid: string): Promise => { - try { - const osUpdateHelper = await getOsUpdateHelper(); - return await osUpdateHelper.getOsUpdateStatus(uuid); - } catch (err) { - if (err.statusCode !== 400) { - throw err; - } - - // as an attempt to reduce the requests for this method - // check whether the device exists only when the request rejects - // so that it's rejected with the appropriate BalenaDeviceNotFound error - await exports.get(uuid, { $select: 'id' }); - // if the device exists, then re-throw the original error - throw err; - } - }, - /** * @namespace balena.models.device.tags * @memberof balena.models.device diff --git a/src/models/image.ts b/src/models/image.ts index 5776e0df4..dd4196354 100644 --- a/src/models/image.ts +++ b/src/models/image.ts @@ -16,7 +16,6 @@ limitations under the License. import * as errors from 'balena-errors'; import type { Image, PineOptions, InjectedDependenciesParam } from '..'; -import { mergePineOptions } from '../util'; const getImageModel = function (deps: InjectedDependenciesParam) { const { pine } = deps; @@ -39,33 +38,10 @@ const getImageModel = function (deps: InjectedDependenciesParam) { * }); */ async get(id: number, options: PineOptions = {}): Promise { - const baseOptions = { - $select: [ - // Select all the interesting fields *except* build_log - // (which can be very large) - 'id', - 'content_hash', - 'dockerfile', - 'project_type', - 'status', - 'error_message', - 'image_size', - 'created_at', - 'push_timestamp', - 'start_timestamp', - 'end_timestamp', - ], - } satisfies PineOptions; const image = await pine.get({ resource: 'image', id, - options: mergePineOptions( - baseOptions, - options, - // TODO: Mark the build_log as explicitRead in the next API model version - // so that we can remove this & the explicit property selection from here. - true, - ), + options, }); if (image == null) { throw new errors.BalenaImageNotFound(id); diff --git a/src/models/organization-membership.ts b/src/models/organization-membership.ts index aa9267f30..c588a3dac 100644 --- a/src/models/organization-membership.ts +++ b/src/models/organization-membership.ts @@ -22,9 +22,7 @@ import type { OrganizationMembershipRoles, OrganizationMembershipTag, PineOptions, - PineSubmitBody, InjectedDependenciesParam, - PinePostResult, } from '..'; import { mergePineOptions } from '../util'; @@ -241,58 +239,6 @@ const getOrganizationMembershipModel = function ( }); }, - /** - * @summary Creates a new membership for an organization - * @name create - * @public - * @function - * @memberof balena.models.organization.membership - * - * @deprecated use balena.models.organization.invite.create instead - * @description This method adds a user to an organization by their usename. - * WARNING: This method is deprecated, use balena.models.organization.invite.create instead. - * - * @param {Object} options - membership creation parameters - * @param {String|Number} options.organization - organization handle (string), or id (number) - * @param {String} options.username - the username of the balena user that will become a member - * @param {String} [options.roleName="member"] - the role name to be granted to the membership - * - * @fulfil {Object} - organization membership - * @returns {Promise} - * - * @example - * balena.models.organization.membership.create({ organization: "myorg", username: "user123", roleName: "member" }).then(function(membership) { - * console.log(membership); - * }); - */ - async create({ - organization, - username, - roleName, - }: OrganizationMembershipCreationOptions): Promise< - PinePostResult - > { - const [{ id }, roleId] = await Promise.all([ - getOrganization(organization, { $select: 'id' }), - roleName ? getRoleId(roleName) : undefined, - ]); - type OrganizationMembershipBase = Omit; - type OrganizationMembershipPostBody = OrganizationMembershipBase & { - username: string; - }; - const body: PineSubmitBody = { - username, - is_member_of__organization: id, - }; - if (roleName) { - body.organization_membership_role = roleId; - } - return (await pine.post({ - resource: RESOURCE, - body, - })) as PinePostResult; - }, - /** * @summary Changes the role of an organization member * @name changeRole diff --git a/src/models/organization.ts b/src/models/organization.ts index 269bedb9d..12bcbe2fa 100644 --- a/src/models/organization.ts +++ b/src/models/organization.ts @@ -18,18 +18,13 @@ import * as errors from 'balena-errors'; import type * as BalenaSdk from '..'; import type { InjectedDependenciesParam, InjectedOptionsParam } from '..'; -import { - isId, - isNotFoundResponse, - mergePineOptions, - treatAsMissingOrganization, -} from '../util'; +import { isId, mergePineOptions } from '../util'; const getOrganizationModel = function ( deps: InjectedDependenciesParam, opts: InjectedOptionsParam, ) { - const { pine } = deps; + const { pine, sdkInstance } = deps; /* eslint-disable @typescript-eslint/no-require-imports */ const membershipModel = ( @@ -41,11 +36,6 @@ const getOrganizationModel = function ( ).default(deps, opts, (...args: Parameters) => get(...args)); /* eslint-enable @typescript-eslint/no-require-imports */ - const getId = async (handleOrId: string | number) => { - const { id } = await get(handleOrId, { $select: 'id' }); - return id; - }; - /** * @summary Creates a new organization * @name create @@ -186,18 +176,13 @@ const getOrganizationModel = function ( * balena.models.organization.remove(123); */ const remove = async function (handleOrId: string | number): Promise { - try { - const id = await getId(handleOrId); - await pine.delete({ - resource: 'organization', - id, - }); - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingOrganization(handleOrId, err); - } - throw err; - } + const id = ( + await sdkInstance.models.organization.get(handleOrId, { $select: 'id' }) + ).id; + await pine.delete({ + resource: 'organization', + id, + }); }; return { diff --git a/src/models/os.ts b/src/models/os.ts index 580f6530b..bf8444228 100644 --- a/src/models/os.ts +++ b/src/models/os.ts @@ -20,7 +20,6 @@ import once from 'lodash/once'; import { isNotFoundResponse, onlyIf, - treatAsMissingApplication, mergePineOptionsTyped, type ExtendedPineTypedResult, } from '../util'; @@ -38,6 +37,7 @@ import type { PineTypedResult, } from '..'; import { getAuthDependentMemoize } from '../util/cache'; +import { BalenaReleaseNotFound } from 'balena-errors'; const RELEASE_POLICY_TAG_NAME = 'release-policy'; const ESR_NEXT_TAG_NAME = 'esr-next'; @@ -76,8 +76,6 @@ export interface OsVersion basedOnVersion?: string; osType: string; line?: OsLines; - /** @deprecated */ - isRecommended?: boolean; } export interface ImgConfigOptions { @@ -325,22 +323,6 @@ const getOsModel = function ( // transform version sets Object.keys(osVersionsByDeviceType).forEach((deviceType) => { osVersionsByDeviceType[deviceType].sort(sortVersions); - - // TODO: Drop in next major - // Note: the recommended version settings might come from the server in the future, for now we just set it to the latest version for each os type. - const recommendedPerOsType: Dictionary = {}; - osVersionsByDeviceType[deviceType].forEach((version) => { - if (!recommendedPerOsType[version.osType]) { - if ( - version.variant !== 'dev' && - !version.known_issue_list && - !bSemver.prerelease(version.raw_version) - ) { - version.isRecommended = true; - recommendedPerOsType[version.osType] = true; - } - } - }); }); return osVersionsByDeviceType; @@ -544,19 +526,14 @@ const getOsModel = function ( */ const _getMaxSatisfyingVersion = function ( versionOrRange: string, - osVersions: Array>, + osVersions: Array>, ) { - if (versionOrRange === 'recommended') { - return osVersions.find((v) => v.isRecommended)?.raw_version; - } - if (versionOrRange === 'latest') { return osVersions[0]?.raw_version; } if (versionOrRange === 'default') { - return (osVersions.find((v) => v.isRecommended) ?? osVersions[0]) - ?.raw_version; + return osVersions[0]?.raw_version; } const versions = osVersions.map((v) => v.raw_version); @@ -643,56 +620,6 @@ const getOsModel = function ( return _getMaxSatisfyingVersion(versionOrRange, osVersions) ?? null; }; - /** - * @summary Get the OS image last modified date - * @name getLastModified - * @public - * @function - * @memberof balena.models.os - * - * @param {String} deviceType - device type slug - * @param {String} [version] - semver-compatible version or 'latest', defaults to 'latest'. - * Unsupported (unpublished) version will result in rejection. - * The version **must** be the exact version number. - * To resolve the semver-compatible range use `balena.model.os.getMaxSatisfyingVersion`. - * @fulfil {Date} - last modified date - * @returns {Promise} - * - * @example - * balena.models.os.getLastModified('raspberry-pi').then(function(date) { - * console.log('The raspberry-pi image was last modified in ' + date); - * }); - * - * balena.models.os.getLastModified('raspberrypi3', '2.0.0').then(function(date) { - * console.log('The raspberry-pi image was last modified in ' + date); - * }); - */ - const getLastModified = async function ( - deviceType: string, - version = 'latest', - ): Promise { - try { - deviceType = await _getNormalizedDeviceTypeSlug(deviceType); - version = normalizeVersion(version); - const response = await request.send({ - method: 'HEAD', - url: '/download', - qs: { - deviceType, - version, - }, - baseUrl: apiUrl, - }); - // TODO: Drop the ! on the next major - return new Date(response.headers.get('last-modified')!); - } catch (err) { - if (isNotFoundResponse(err)) { - throw new Error('No such version for the device type'); - } - throw err; - } - }; - /** * @summary Download an OS image * @name download @@ -729,11 +656,15 @@ const getOsModel = function ( try { const slug = await _getNormalizedDeviceTypeSlug(deviceType); if (version === 'latest') { - const versions = (await getAvailableOsVersions(slug)).filter( + const foundVersion = (await getAvailableOsVersions(slug)).find( (v) => v.osType === OsTypes.DEFAULT, ); - version = (versions.find((v) => v.isRecommended) ?? versions[0]) - ?.raw_version; + if (!foundVersion) { + throw new BalenaReleaseNotFound( + 'No version available for this device type', + ); + } + version = foundVersion.raw_version; } else { version = normalizeVersion(version); } @@ -809,26 +740,22 @@ const getOsModel = function ( options.network = options.network ?? 'ethernet'; - try { - const applicationId = - await sdkInstance.models.application._getId(slugOrUuidOrId); - - const { body } = await request.send({ - method: 'POST', - url: '/download-config', - baseUrl: apiUrl, - body: { - ...options, - appId: applicationId, - }, - }); - return body; - } catch (err) { - if (isNotFoundResponse(err)) { - treatAsMissingApplication(slugOrUuidOrId, err); - } - throw err; - } + const applicationId = ( + await sdkInstance.models.application.get(slugOrUuidOrId, { + $select: 'id', + }) + ).id; + + const { body } = await request.send({ + method: 'POST', + url: '/download-config', + baseUrl: apiUrl, + body: { + ...options, + appId: applicationId, + }, + }); + return body; }; /** @@ -1125,7 +1052,6 @@ const getOsModel = function ( getAvailableOsVersions, getMaxSatisfyingVersion, getDownloadSize, - getLastModified, download, getConfig, isSupportedOsUpdate, diff --git a/src/types/device-type-json.ts b/src/types/device-type-json.ts index 89e9f2c3e..1ec90f078 100644 --- a/src/types/device-type-json.ts +++ b/src/types/device-type-json.ts @@ -14,8 +14,6 @@ export interface DeviceType { isDependent?: boolean; imageDownloadAlerts?: DeviceTypeDownloadAlert[]; - /** @deprecated Use the balena.models.deviceType.getInstructions() */ - instructions?: string[] | DeviceTypeInstructions; gettingStartedLink?: string | DeviceTypeGettingStartedLink; stateInstructions?: { [key: string]: string[] }; options?: DeviceTypeOptions[]; @@ -25,8 +23,6 @@ export interface DeviceType { command: string; }>; }; - /** @deprecated Use the DeviceType.contract.data.led */ - supportsBlink?: boolean; yocto: { fstype?: string; deployArtifact: string; @@ -40,8 +36,6 @@ export interface DeviceType { }; /** Holds the latest balenaOS version */ buildId?: string; - /** @deprecated Use the logo field from the models.deviceType.get() method. */ - logoUrl?: string; } export interface DeviceTypeDownloadAlert { diff --git a/src/types/jwt.ts b/src/types/jwt.ts index 57dcabf70..1b81b4d79 100644 --- a/src/types/jwt.ts +++ b/src/types/jwt.ts @@ -5,45 +5,4 @@ export interface JWTUser { actualUser?: number; twoFactorRequired?: boolean; permissions?: string[]; - - /** @deprecated Use the user resource */ - created_at?: string; - /** @deprecated Use the user resource */ - username?: string; - /** @deprecated Use the actualUser field */ - loginAs?: boolean; - /** @deprecated */ - features?: string[]; - - /** @deprecated Use the user_profile resource */ - first_name?: string; - /** @deprecated Use the user_profile resource */ - last_name?: string; - /** @deprecated Use the user_profile resource */ - email?: string; - /** @deprecated Use the user_profile resource */ - account_type?: string; - /** @deprecated Use the user_profile resource */ - company?: string; - /** @deprecated Use the user_profile resource */ - has_disabled_newsletter?: boolean; - /** @deprecated Use the user_profile resource */ - hasPasswordSet?: boolean; - /** @deprecated Use the user_profile resource */ - must_be_verified?: boolean; - /** @deprecated Use the user_profile resource */ - is_verified?: boolean; - - /** @deprecated */ - intercomUserName?: string; - /** @deprecated */ - intercomUserHash?: string; - - /** @deprecated Use the social_service_account resource */ - social_service_account?: SocialServiceAccount[]; -} - -interface SocialServiceAccount { - provider: string; - display_name: string; } diff --git a/src/types/models.ts b/src/types/models.ts index 6c88e4316..a300c039a 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -284,8 +284,6 @@ export interface ApplicationType { name: string; slug: string; description: string | null; - /** @deprecated */ - supports_gateway_mode: boolean; supports_multicontainer: boolean; supports_web_url: boolean; is_legacy: boolean; @@ -356,8 +354,6 @@ export interface Release { update_timestamp: string; end_timestamp: string | null; phase: 'next' | 'current' | 'sunset' | 'end-of-life' | null; - /** @deprecated */ - release_version: string | null; semver: string; semver_major: number; semver_minor: number; @@ -379,8 +375,6 @@ export interface Release { is_created_by__user: OptionalNavigationResource; belongs_to__application: NavigationResource; - /** @deprecated Prefer using the Term Form "release_image" property */ - contains__image?: ReverseNavigationResource; release_image?: ReverseNavigationResource; should_be_running_on__application?: ReverseNavigationResource; is_running_on__device?: ReverseNavigationResource; @@ -603,8 +597,6 @@ export interface ImageInstall { status: string; install_date: string; - /** @deprecated Use `installs__image` instead. */ - image: NavigationResource; installs__image: NavigationResource; device: NavigationResource; is_provided_by__release: NavigationResource; diff --git a/src/util/device-actions/os-update/index.ts b/src/util/device-actions/os-update/index.ts index 844e74fb0..11e29b2df 100644 --- a/src/util/device-actions/os-update/index.ts +++ b/src/util/device-actions/os-update/index.ts @@ -43,16 +43,7 @@ export const getOsUpdateHelper = function ( }); }; - const getOsUpdateStatus = (uuid: string) => { - return deviceActionsService.getActionStatus({ - uuid, - // TODO: this is an assumption recorded here: https://github.com/resin-io/resin-proxy/issues/51#issuecomment-274087973 - actionId: OS_UPDATE_ACTION_NAME, - }); - }; - return { startOsUpdate, - getOsUpdateStatus, }; }; diff --git a/src/util/device-service-details.ts b/src/util/device-service-details.ts index 9bdc55d1c..b2b28ffc8 100644 --- a/src/util/device-service-details.ts +++ b/src/util/device-service-details.ts @@ -41,7 +41,7 @@ export const getCurrentServiceDetailsPineExpand = (expandRelease: boolean) => { }, }, $expand: { - image: { + installs__image: { $select: ['id'], $expand: { is_a_build_of__service: { @@ -68,7 +68,7 @@ interface WithServiceName { function getSingleInstallSummary( rawData: ImageInstall, ): CurrentService & WithServiceName { - const image = (rawData.image as Image[])[0]; + const image = (rawData.installs__image as Image[])[0]; const service = (image.is_a_build_of__service as Service[])[0]; let releaseInfo: { @@ -90,7 +90,6 @@ function getSingleInstallSummary( const result: CurrentService & Partial> & - Partial> & WithServiceName = { ...rawData, service_id: service.id, @@ -100,8 +99,6 @@ function getSingleInstallSummary( ...releaseInfo, }; - // prefer over omit for performance reasons - delete result.image; if ('installs__image' in result) { delete result.installs__image; } diff --git a/src/util/index.ts b/src/util/index.ts index e4f9e6d33..d4b391bbf 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -52,38 +52,6 @@ export const isUnauthorizedResponse = (err: Error) => export const isNotFoundResponse = (err: Error) => isBalenaRequestErrorResponseWithCode(err, 404); -export const isNoDeviceForKeyResponse = (err: Error) => - isBalenaRequestErrorResponseWithCode(err, 500) && - err.body === 'No device found to associate with the api key'; - -export const isNoApplicationForKeyResponse = (err: Error) => - isBalenaRequestErrorResponseWithCode(err, 500) && - err.body === 'No application found to associate with the api key'; - -export const treatAsMissingOrganization = ( - handleOrId: string | number, - err: Error, -) => { - const replacementErr = new errors.BalenaOrganizationNotFound(handleOrId); - replacementErr.stack = err.stack ?? ''; - throw replacementErr; -}; - -export const treatAsMissingApplication = ( - slugOrUuidOrId: string | number, - err: Error, -) => { - const replacementErr = new errors.BalenaApplicationNotFound(slugOrUuidOrId); - replacementErr.stack = err.stack ?? ''; - throw replacementErr; -}; - -export const treatAsMissingDevice = (uuidOrId: string | number, err: Error) => { - const replacementErr = new errors.BalenaDeviceNotFound(uuidOrId); - replacementErr.stack = err.stack ?? ''; - throw replacementErr; -}; - // TODO: Make it so that it also infers the extras param export function mergePineOptionsTyped< R extends object, @@ -99,15 +67,6 @@ export type ExtendedPineTypedResult< > = TBaseResult & IfDefined>; -const knownPineOptionKeys = new Set([ - '$top', - '$skip', - '$select', - '$expand', - '$filter', - '$orderby', -]); - const passthroughPineOptionKeys = ['$top', '$skip', '$orderby'] as const; // Merging two sets of pine options sensibly is more complicated than it sounds. @@ -122,33 +81,19 @@ const passthroughPineOptionKeys = ['$top', '$skip', '$orderby'] as const; export function mergePineOptions< R extends object, TDefault extends Pine.ODataOptions, ->( - defaults: TDefault, - extras: Pine.ODataOptions | undefined, - replace$selects?: boolean, -): TDefault; +>(defaults: TDefault, extras: Pine.ODataOptions | undefined): TDefault; export function mergePineOptions( defaults: Pine.ODataOptions, extras: Pine.ODataOptions | undefined, - replace$selects?: boolean, ): Pine.ODataOptions; export function mergePineOptions( defaults: Pine.ODataOptions, extras: Pine.ODataOptions | undefined, - replace$selects?: boolean, ): Pine.ODataOptions { if (!extras) { return defaults; } - // TOOD: Consider dropping in the next major - const unknownPineOption = Object.keys(extras).find( - (key) => !knownPineOptionKeys.has(key), - ); - if (unknownPineOption != null) { - throw new Error(`Unknown pine option: ${unknownPineOption}`); - } - const result = { ...defaults }; if (extras.$select != null) { @@ -160,9 +105,7 @@ export function mergePineOptions( (extras.$select as '*') : [extras.$select]; - if (replace$selects) { - result.$select = extraSelect; - } else if (extraSelect === '*') { + if (extraSelect === '*') { result.$select = '*'; } else { result.$select = [ @@ -193,11 +136,7 @@ export function mergePineOptions( } if (extras.$expand != null) { - result.$expand = mergeExpandOptions( - defaults.$expand, - extras.$expand, - replace$selects, - ); + result.$expand = mergeExpandOptions(defaults.$expand, extras.$expand); } return result; @@ -206,7 +145,6 @@ export function mergePineOptions( const mergeExpandOptions = ( defaultExpand: Pine.Expand | undefined, extraExpand: Pine.Expand | undefined, - replace$selects?: boolean, ): Pine.Expand | undefined => { if (defaultExpand == null) { return extraExpand; @@ -222,7 +160,6 @@ const mergeExpandOptions = ( $defaultExpand[expandKey] = mergePineOptions( $defaultExpand[expandKey] ?? {}, $extraExpand[expandKey], - replace$selects, ); } @@ -257,21 +194,6 @@ const convertExpandToObject = ( ); } - // Check the options in this object are the ones we know how to merge - for (const expandKey of Object.keys(expandOption) as Array< - keyof typeof expandOption - >) { - const expandRelationshipOptions = expandOption[expandKey]; - - // TOOD: Consider dropping in the next major - const unknownPineOption = Object.keys(expandRelationshipOptions ?? {}).find( - (key) => !knownPineOptionKeys.has(key), - ); - if (unknownPineOption != null) { - throw new Error(`Unknown pine expand options: ${unknownPineOption}`); - } - } - if (cloneIfNeeded) { return { ...expandOption }; } diff --git a/tests/integration/auth.spec.ts b/tests/integration/auth.spec.ts index a4a46ed58..7c58f0012 100644 --- a/tests/integration/auth.spec.ts +++ b/tests/integration/auth.spec.ts @@ -83,9 +83,10 @@ describe('SDK authentication', function () { it('should be able to login with an API Key', async () => { const token = await balena.auth.authenticate(credentials); await balena.auth.loginWithToken(token); - const apiKey = await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey`, - ); + const apiKey = await balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }); await balena.auth.logout(); await balena.auth.loginWithToken(apiKey); expect(await balena.auth.getToken()).to.be.a('string'); diff --git a/tests/integration/balena.spec.ts b/tests/integration/balena.spec.ts index 941b0fb47..050c95031 100644 --- a/tests/integration/balena.spec.ts +++ b/tests/integration/balena.spec.ts @@ -162,7 +162,9 @@ describe('Balena SDK', function () { }, }); - const promise = balena.models.device.reboot(999999); + const promise = balena.request.send({ + url: 'example.com', + }); return expect(promise).to.be.rejected.then(() => expect(called).to.equal( @@ -370,10 +372,11 @@ describe('Balena SDK', function () { givenLoggedInUser(before); before(async function () { - const testApiKey = await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey`, - 'apiKeyDescription', - ); + const testApiKey = await balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + description: 'apiKeyDescription', + }); this.testApiKey = testApiKey; expect(this.testApiKey).to.be.a('string'); await balena.auth.logout(); diff --git a/tests/integration/models/api-key.spec.ts b/tests/integration/models/api-key.spec.ts index a8d77fd63..eef5c324c 100644 --- a/tests/integration/models/api-key.spec.ts +++ b/tests/integration/models/api-key.spec.ts @@ -18,40 +18,34 @@ describe('API Key model', function () { parallel('', function () { it('should be able to create a new api key', async function () { - const key = await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey`, - ); - expect(key).to.be.a('string'); - }); - - it('should be able to create a new api key with description', async function () { - const key = await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey2`, - 'apiKeyDescription', - ); - expect(key).to.be.a('string'); - }); - - it('should be able to create a new api key with expiry-date', async function () { - const tomorrowDate = new Date(Date.now() + 86400000).toISOString(); // one day in future - const key = await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKeyWithExpiry`, - 'apiKeyDescription', - tomorrowDate, - ); + const tomorrowDate = new Date( + Date.now() + 1000 * 60 * 60 * 24, + ).toISOString(); + const key = await balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey`, + expiryDate: tomorrowDate, + }); expect(key).to.be.a('string'); - const userKeys = await balena.models.apiKey.getAllNamedUserApiKeys(); expect(userKeys).to.be.an('array'); const userKeyWithExpiry = userKeys.filter( - (elem) => elem.name === `${TEST_KEY_NAME_PREFIX}_apiKeyWithExpiry`, + (elem) => elem.name === `${TEST_KEY_NAME_PREFIX}_apiKey`, ); expect(userKeyWithExpiry).to.not.be.empty; expect(userKeyWithExpiry[0]) .to.have.property('expiry_date') .to.be.equal(tomorrowDate); }); + + it('should be able to create a new api key with description', async function () { + const key = await balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey2`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + description: 'apiKeyDescription', + }); + expect(key).to.be.a('string'); + }); }); }); @@ -74,11 +68,15 @@ describe('API Key model', function () { describe('given two named api keys', function () { before(() => Promise.all([ - balena.models.apiKey.create(`${TEST_KEY_NAME_PREFIX}_apiKey1`), - balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey2`, - 'apiKey2Description', - ), + balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey1`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }), + balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey2`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + description: 'apiKey2Description', + }), ]), ); @@ -122,11 +120,15 @@ describe('API Key model', function () { describe('given two named api keys', function () { before(() => Promise.all([ - balena.models.apiKey.create(`${TEST_KEY_NAME_PREFIX}_apiKey1`), - balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKey2`, - 'apiKey2Description', - ), + balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey1`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }), + balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKey2`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + description: 'apiKey2Description', + }), ]), ); @@ -169,18 +171,20 @@ describe('API Key model', function () { ] as const; before(async function () { - await balena.models.apiKey.create( - `${TEST_KEY_NAME_PREFIX}_apiKeyToBeUpdated`, - 'apiKeyDescriptionToBeUpdated', - ); + await balena.models.apiKey.create({ + name: `${TEST_KEY_NAME_PREFIX}_apiKeyToBeUpdated`, + expiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + description: 'apiKeyDescriptionToBeUpdated', + }); const [apiKey] = await balena.models.apiKey.getAll({ $filter: { name: `${TEST_KEY_NAME_PREFIX}_apiKeyToBeUpdated` }, }); ctx.namedUserApiKey = apiKey; - await balena.models.application.generateProvisioningKey( - this.application.id, - ); + await balena.models.application.generateProvisioningKey({ + slugOrUuidOrId: this.application.id, + keyExpiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }); await balena.models.device.generateDeviceKey(this.device.id); }); diff --git a/tests/integration/models/application.spec.ts b/tests/integration/models/application.spec.ts index 23b96eea9..2e0a51343 100644 --- a/tests/integration/models/application.spec.ts +++ b/tests/integration/models/application.spec.ts @@ -593,35 +593,6 @@ describe('Application Model', function () { }); }); - describe('balena.models.application.generateApiKey()', function () { - applicationRetrievalFields.forEach((prop) => { - it(`should be able to generate an API key by ${prop}`, async function () { - const apiKey = await balena.models.application.generateApiKey( - this.application[prop], - ); - - expect(apiKey).to.be.a('string'); - expect(apiKey).to.have.length(32); - }); - }); - - it('should be rejected if the application slug does not exist', function () { - const promise = balena.models.application.generateApiKey( - `${this.initialOrg.handle}/helloworldapp`, - ); - return expect(promise).to.be.rejectedWith( - `Application not found: ${this.initialOrg.handle}/helloworldapp`, - ); - }); - - it('should be rejected if the application id does not exist', function () { - const promise = balena.models.application.generateApiKey(999999); - return expect(promise).to.be.rejectedWith( - 'Application not found: 999999', - ); - }); - }); - describe('balena.models.application.generateProvisioningKey()', function () { const getProvisioningKeys = async function (appNameOrSlug, options?) { const provisioningKeys = @@ -638,7 +609,12 @@ describe('Application Model', function () { applicationRetrievalFields.forEach((prop) => { it(`should be able to generate a provisioning key by ${prop}`, function () { return balena.models.application - .generateProvisioningKey(this.application[prop]) + .generateProvisioningKey({ + slugOrUuidOrId: this.application[prop], + keyExpiryDate: new Date( + Date.now() + 1000 * 60 * 60, + ).toISOString(), + }) .then(function (key) { expect(_.isString(key)).to.be.true; return expect(key).to.have.length(32); @@ -653,8 +629,13 @@ describe('Application Model', function () { ); const key = await balena.models.application.generateProvisioningKey( - this.application[prop], - `key_${prop}`, + { + slugOrUuidOrId: this.application[prop], + keyExpiryDate: new Date( + Date.now() + 1000 * 60 * 60, + ).toISOString(), + keyName: `key_${prop}`, + }, ); expect(key).to.be.a('string'); @@ -682,9 +663,14 @@ describe('Application Model', function () { ); const key = await balena.models.application.generateProvisioningKey( - this.application[prop], - `key_${prop}`, - `Provisioning key generated with name key_${prop}`, + { + slugOrUuidOrId: this.application[prop], + keyExpiryDate: new Date( + Date.now() + 1000 * 60 * 60, + ).toISOString(), + keyName: `key_${prop}`, + keyDescription: `Provisioning key generated with name key_${prop}`, + }, ); expect(key).to.be.a('string'); @@ -711,11 +697,16 @@ describe('Application Model', function () { this.application[prop], ); + const oneHourDate = new Date( + Date.now() + 1000 * 60 * 60, + ).toISOString(); const key = await balena.models.application.generateProvisioningKey( - this.application[prop], - `key_${prop}`, - `Provisioning key generated with name key_${prop}`, - '2030-01-01', + { + slugOrUuidOrId: this.application[prop], + keyExpiryDate: oneHourDate, + keyName: `key_${prop}`, + keyDescription: `Provisioning key generated with name key_${prop}`, + }, ); expect(key).to.be.a('string'); @@ -734,22 +725,25 @@ describe('Application Model', function () { expect(provisionKeys[0]).to.have.property('expiry_date'); expect(provisionKeys[0]) .to.have.property('expiry_date') - .to.be.equal('2030-01-01T00:00:00.000Z'); + .to.be.equal(oneHourDate); }); }); it('should be rejected if the application slug does not exist', function () { - const promise = balena.models.application.generateProvisioningKey( - `${this.initialOrg.handle}/helloworldapp`, - ); + const promise = balena.models.application.generateProvisioningKey({ + slugOrUuidOrId: `${this.initialOrg.handle}/helloworldapp`, + keyExpiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }); return expect(promise).to.be.rejectedWith( `Application not found: ${this.initialOrg.handle}/helloworldapp`, ); }); it('should be rejected if the application id does not exist', function () { - const promise = - balena.models.application.generateProvisioningKey(999999); + const promise = balena.models.application.generateProvisioningKey({ + slugOrUuidOrId: 999999, + keyExpiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }); return expect(promise).to.be.rejectedWith( 'Application not found: 999999', ); @@ -1536,7 +1530,6 @@ describe('Application Model', function () { expect(imageInstall) .to.have.property('download_progress') .that.is.oneOf([50, null]); - expect(imageInstall).to.have.property('image').that.has.length(1); if (expectCommit) { expect(imageInstall) .to.have.property('is_provided_by__release') diff --git a/tests/integration/models/device.spec.ts b/tests/integration/models/device.spec.ts index 0986b1c4f..6c2f20794 100644 --- a/tests/integration/models/device.spec.ts +++ b/tests/integration/models/device.spec.ts @@ -1548,26 +1548,6 @@ describe('Device Model', function () { }); }); - describe('balena.models.device.getOsUpdateStatus()', function () { - givenADevice(before); - - it('should be able to get the current OS update status', async function () { - const status = await balena.models.device.getOsUpdateStatus( - this.device.uuid, - ); - return expect(status).to.deep.match({ - status: 'idle', - }); - }); - - it('should be rejected if the device does not exist', function () { - const promise = balena.models.device.getOsUpdateStatus('asdfghjkl'); - return expect(promise).to.be.rejectedWith( - 'Device not found: asdfghjkl', - ); - }); - }); - ['single uuid', 'array of uuids'].forEach((paramType) => { describe(`balena.models.device.startOsUpdate() called with ${paramType}`, function () { givenADevice(before); @@ -2368,7 +2348,6 @@ describe('Device Model', function () { expect(imageInstall) .to.have.property('download_progress') .that.is.oneOf([50, null]); - expect(imageInstall).to.have.property('image').that.has.length(1); expect(imageInstall) .to.have.property('is_provided_by__release') .that.has.length(1); diff --git a/tests/integration/models/organization-membership.spec.ts b/tests/integration/models/organization-membership.spec.ts index 9557985e2..5525af814 100644 --- a/tests/integration/models/organization-membership.spec.ts +++ b/tests/integration/models/organization-membership.spec.ts @@ -7,7 +7,6 @@ import { credentials, givenInitialOrganization, givenLoggedInUser, - organizationRetrievalFields, TEST_ORGANIZATION_NAME, } from '../setup'; import type * as BalenaSdk from '../../..'; @@ -180,105 +179,11 @@ describe('Organization Membership Model', function () { }); }); - describe('balena.models.organization.membership.create()', function () { - before(function () { - ctx = this; - }); - - parallel('[read operations]', function () { - it(`should not be able to add a new member to the organization usign a wrong role name`, async function () { - const promise = balena.models.organization.membership.create({ - organization: ctx.organization.id, - username: credentials.member.username, - // @ts-expect-error invalid value - roleName: 'unknown role', - }); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaOrganizationMembershipRoleNotFound', - ); - }); - - const randomOrdInfo = { - id: Math.floor(Date.now() / 1000), - handle: `random_sdk_test_org_handle_${Math.floor(Date.now() / 1000)}`, - }; - - organizationRetrievalFields.forEach((field) => { - it(`should not be able to add a new member when using an not existing organization ${field}`, async function () { - const promise = balena.models.organization.membership.create({ - organization: randomOrdInfo[field], - username: credentials.member.username, - roleName: 'member', - }); - await expect(promise).to.be.rejected.and.eventually.have.property( - 'code', - 'BalenaOrganizationNotFound', - ); - }); - }); - }); - - describe('[mutating operations]', function () { - let membership: - | BalenaSdk.PinePostResult - | undefined; - afterEach(async function () { - await balena.models.organization.membership.remove(membership!.id); - }); - organizationRetrievalFields.forEach(function (field) { - it(`should be able to add a new member to the organization by ${field}`, async function () { - membership = await balena.models.organization.membership.create({ - organization: this.organization[field], - username: credentials.member.username, - }); - - expect(membership) - .to.be.an('object') - .that.has.nested.property('organization_membership_role.__id') - .that.equals(this.orgMemberRole.id); - }); - }); - - it(`should be able to add a new member to the organization without providing a role`, async function () { - membership = await balena.models.organization.membership.create({ - organization: this.organization.id, - username: credentials.member.username, - }); - - expect(membership) - .to.be.an('object') - .that.has.nested.property('organization_membership_role.__id') - .that.equals(this.orgMemberRole.id); - }); - - (['member', 'administrator'] as const).forEach(function (roleName) { - it(`should be able to add a new member to the organization with a given role [${roleName}]`, async function () { - membership = await balena.models.organization.membership.create({ - organization: this.organization.id, - username: credentials.member.username, - roleName, - }); - - expect(membership) - .to.be.an('object') - .that.has.nested.property('organization_membership_role.__id') - .that.equals(this.orgRoleMap[roleName].id); - }); - }); - }); - }); - - describe('given a member organization membership [contained scenario]', function () { + // TODO: re-add this test in the future, we need to add a second user that is a member of the organization from the start + describe.skip('given a member organization membership [contained scenario]', function () { let membership: | BalenaSdk.PinePostResult | undefined; - beforeEach(async function () { - membership = await balena.models.organization.membership.create({ - organization: this.organization.id, - username: credentials.member.username, - }); - }); describe('balena.models.organization.membership.remove()', function () { keyAlternatives.forEach(([title, keyGetter]) => { @@ -297,7 +202,8 @@ describe('Organization Membership Model', function () { }); }); - describe('given an organization with an administrator organization membership [contained scenario]', function () { + // TODO: re-add this test in the future, we need to add a second user that is a member of the organization from the start + describe.skip('given an organization with an administrator organization membership [contained scenario]', function () { const testOrg1Name = `${TEST_ORGANIZATION_NAME}_org_member_tests_${Date.now()}`; let testOrg: BalenaSdk.PinePostResult | undefined; let membership: @@ -308,11 +214,6 @@ describe('Organization Membership Model', function () { testOrg = await balena.models.organization.create({ name: testOrg1Name, }); - membership = await balena.models.organization.membership.create({ - organization: testOrg.id, - username: credentials.member.username, - roleName: 'administrator', - }); }); after(async function () { diff --git a/tests/integration/models/organizationInvite.spec.ts b/tests/integration/models/organizationInvite.spec.ts index 331c3d3aa..e1dcc1bee 100644 --- a/tests/integration/models/organizationInvite.spec.ts +++ b/tests/integration/models/organizationInvite.spec.ts @@ -1,8 +1,16 @@ import { expect } from 'chai'; import parallel from 'mocha.parallel'; -import { balena, givenInitialOrganization, givenLoggedInUser } from '../setup'; -import { timeSuite } from '../../util'; -import { assertDeepMatchAndLength } from '../../util'; +import { + balena, + givenInitialOrganization, + givenLoggedInUser, + credentials, + organizationRetrievalFields, +} from '../setup'; +import { timeSuite, assertDeepMatchAndLength } from '../../util'; +import type * as BalenaSdk from '../../..'; +// eslint-disable-next-line no-restricted-imports +import * as _ from 'lodash'; const TEST_EMAIL = 'user.test@example.org'; const TEST_MESSAGE = 'Hey!, Join my org on balenaCloud'; const TEST_ROLE = 'member'; @@ -119,6 +127,96 @@ describe('Organization Invite Model', function () { }); }); }); + + parallel('[read operations]', function () { + const randomOrdInfo = { + id: Math.floor(Date.now() / 1000), + handle: `random_sdk_test_org_handle_${Math.floor(Date.now() / 1000)}`, + }; + + for (const field of organizationRetrievalFields) { + it(`should not be able to invite a new member when using an not existing organization ${field}`, async function () { + const promise = balena.models.organization.invite.create( + randomOrdInfo[field], + { + invitee: credentials.member.email, + roleName: 'member', + }, + ); + await expect(promise).to.be.rejected.and.eventually.have.property( + 'code', + 'BalenaOrganizationNotFound', + ); + }); + } + }); + + describe('[mutating operations]', function () { + let membership: + | BalenaSdk.PinePostResult + | undefined; + before(async function () { + const roles = await balena.pine.get({ + resource: 'organization_membership_role', + options: { $select: ['id', 'name'] }, + }); + this.orgRoleMap = _.keyBy(roles, 'name'); + roles.forEach((role) => { + expect(role).to.be.an('object'); + expect(role).to.have.property('id').that.is.a('number'); + }); + this.orgMemberRole = this.orgRoleMap['member']; + }); + afterEach(async function () { + await balena.models.organization.invite.revoke(membership!.id); + }); + for (const field of organizationRetrievalFields) { + it(`should be able to invite a new member to the organization by ${field}`, async function () { + membership = await balena.models.organization.invite.create( + this.organization[field], + { + invitee: credentials.member.email, + }, + ); + + expect(membership) + .to.be.an('object') + .that.has.nested.property('organization_membership_role.__id') + .that.equals(this.orgMemberRole.id); + }); + } + + it(`should be able to invite a new member to the organization without providing a role`, async function () { + membership = await balena.models.organization.invite.create( + this.organization.id, + { + invitee: credentials.member.email, + }, + ); + + expect(membership) + .to.be.an('object') + .that.has.nested.property('organization_membership_role.__id') + .that.equals(this.orgMemberRole.id); + }); + + (['member', 'administrator'] as const).forEach(function (roleName) { + it(`should be able to invite a new member to the organization with a given role [${roleName}]`, async function () { + membership = await balena.models.organization.invite.create( + this.organization.id, + { + invitee: credentials.member.email, + roleName, + }, + ); + + expect(membership) + .to.be.an('object') + .that.has.nested.property('organization_membership_role.__id') + .that.equals(this.orgRoleMap[roleName].id); + }); + }); + }); }); describe('given a single organization invite [contained scenario]', function () { diff --git a/tests/integration/models/os.spec.ts b/tests/integration/models/os.spec.ts index 74959d9de..0eb382ce1 100644 --- a/tests/integration/models/os.spec.ts +++ b/tests/integration/models/os.spec.ts @@ -488,12 +488,6 @@ describe('OS model', function () { ); }); - it("should support 'recommended'", () => { - expect( - _getMaxSatisfyingVersion('recommended', defaultOsVersions), - ).to.equal('2.85.2+rev3.prod'); - }); - it("should support 'default'", () => { expect(_getMaxSatisfyingVersion('default', defaultOsVersions)).to.equal( '2.85.2+rev3.prod', @@ -712,47 +706,6 @@ describe('OS model', function () { }); }); - describe('balena.models.os.getLastModified()', function () { - parallel('given a valid device slug', function () { - it('should eventually be a valid Date instance', async function () { - const lastModified = - await balena.models.os.getLastModified('raspberry-pi'); - expect(lastModified).to.be.an.instanceof(Date); - }); - - it('should eventually be a valid Date instance if passing a device type alias', async function () { - const lastModified = - await balena.models.os.getLastModified('raspberrypi'); - expect(lastModified).to.be.an.instanceof(Date); - }); - - it('should be able to query for a specific version', async function () { - const lastModified = await balena.models.os.getLastModified( - 'raspberrypi', - '1.26.1', - ); - expect(lastModified).to.be.an.instanceof(Date); - }); - - it('should be able to query for a version containing a plus', async function () { - const lastModified = await balena.models.os.getLastModified( - 'raspberrypi', - '2.0.6+rev3.prod', - ); - expect(lastModified).to.be.an.instanceof(Date); - }); - }); - - describe('given an invalid device slug', () => { - it('should be rejected with an error message', async function () { - const promise = balena.models.os.getLastModified('foo-bar-baz'); - await expect(promise).to.be.rejectedWith( - 'Invalid device type: foo-bar-baz', - ); - }); - }); - }); - describe('balena.models.os.download()', function () { if (IS_BROWSER) { return; diff --git a/tests/integration/setup.ts b/tests/integration/setup.ts index 3adf07d87..219017aef 100644 --- a/tests/integration/setup.ts +++ b/tests/integration/setup.ts @@ -361,9 +361,10 @@ export function givenLoggedInWithAnApplicationApiKey( givenAnApplication(beforeFn); beforeFn(async function () { - const key = await balena.models.application.generateProvisioningKey( - this.application.slug, - ); + const key = await balena.models.application.generateProvisioningKey({ + slugOrUuidOrId: this.application.slug, + keyExpiryDate: new Date(Date.now() + 1000 * 60 * 60).toISOString(), + }); await balena.auth.logout(); await balena.auth.loginWithToken(key); }); diff --git a/tests/util.spec.ts b/tests/util.spec.ts index 6b04e464e..1371901e9 100644 --- a/tests/util.spec.ts +++ b/tests/util.spec.ts @@ -124,18 +124,6 @@ describe('Pine option merging', function () { }); }); - it('overrides $select params for expand options for the same relationship, if present', function () { - const result = mergePineOptions( - { $expand: { device: { $select: ['id'] } } }, - { $expand: { device: { $select: ['name'] } } }, - true, - ); - - return expect(result).to.deep.equal({ - $expand: { device: { $select: ['name'] } }, - }); - }); - it('adds $filter params for expand options, if present', function () { const result = mergePineOptions( { $expand: 'device' }, @@ -240,13 +228,6 @@ describe('Pine option merging', function () { }); }); - it('rejects any unknown extra options', () => { - // @ts-expect-error b/c it's not typed - expect(() => mergePineOptions({}, { unknownKey: 'value' })).to.throw( - 'Unknown pine option: unknownKey', - ); - }); - it('ignores any unknown default options', () => { // @ts-expect-error b/c it's not typed expect(() => mergePineOptions({ unknownKey: 'value' }, {})).not.to.throw(); diff --git a/typing_tests/pine-options.ts b/typing_tests/pine-options.ts index 1a41585c4..0735cd93c 100644 --- a/typing_tests/pine-options.ts +++ b/typing_tests/pine-options.ts @@ -586,7 +586,6 @@ export const appOptionsEValid20: BalenaSdk.PineOptions = type ReleaseExpandablePropsExpectation = | 'is_created_by__user' | 'belongs_to__application' - | 'contains__image' | 'release_image' | 'should_be_running_on__application' | 'should_operate__device'